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

201 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-07-06 10:39 +0000

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 ] 

28 

29import abc 

30import numpy as np 

31import astropy.units as u 

32import pandas 

33import astropy.table 

34 

35import lsst.pex.config as pexConfig 

36import lsst.pipe.base as pipeBase 

37 

38 

39class BaseSourceSelectorConfig(pexConfig.Config): 

40 pass 

41 

42 

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

44 """Base class for source selectors 

45 

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

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

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

49 identify if the source was selected. 

50 

51 Register all source selectors with the sourceSelectorRegistry using: 

52 sourceSelectorRegistry.register(name, class) 

53 

54 Attributes 

55 ---------- 

56 usesMatches : `bool` 

57 A boolean variable specify if the inherited source selector uses 

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

59 argument to ``run()``. 

60 """ 

61 

62 ConfigClass = BaseSourceSelectorConfig 

63 _DefaultName = "sourceSelector" 

64 usesMatches = False 

65 

66 def __init__(self, **kwargs): 

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

68 

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

70 """Select sources and return them. 

71 

72 The input catalog must be contiguous in memory. 

73 

74 Parameters 

75 ---------- 

76 sourceCat : Various table formats 

77 Catalog of sources to select from. Can be 

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

79 `astropy.table.Table`, 

80 sourceSelectedField : `str` or None 

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

82 If set, will modify sourceCat in-place. 

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

84 List of matches to use for source selection. 

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

86 If not, it is ignored. 

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

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

89 

90 Returns 

91 ------- 

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

93 The struct contains the following data: 

94 

95 ``sourceCat`` 

96 The catalog of sources that were selected. 

97 (may not be memory-contiguous) 

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

99 or `astropy.table.Table`) 

100 ``selected`` 

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

102 sourceCat. 

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

104 

105 Raises 

106 ------ 

107 RuntimeError 

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

109 """ 

110 if hasattr(sourceCat, 'isContiguous'): 

111 # Check for continuity on afwTable catalogs 

112 if not sourceCat.isContiguous(): 

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

114 

115 result = self.selectSources(sourceCat=sourceCat, 

116 exposure=exposure, 

117 matches=matches) 

118 

119 if sourceSelectedField is not None: 

120 sourceCat[sourceSelectedField] = result.selected 

121 

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

123 selected=result.selected) 

124 

125 @abc.abstractmethod 

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

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

128 

129 Parameters 

130 ---------- 

131 sourceCat : Various table formats 

132 Catalog of sources to select from. Supports 

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

134 or `astropy.table.Table` 

135 This catalog must be contiguous in memory. 

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

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

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

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

140 

141 Returns 

142 ------- 

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

144 The struct contains the following data: 

145 

146 ``selected`` 

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

148 sourceCat. 

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

150 """ 

151 raise NotImplementedError("BaseSourceSelectorTask is abstract") 

152 

153 

154sourceSelectorRegistry = pexConfig.makeRegistry( 

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

156 "BaseSourceSelectorTask)", 

157) 

158 

159 

160class BaseLimit(pexConfig.Config): 

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

162 

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

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

165 in the catalog that match the configured limit. 

166 

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

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

169 subclass. 

170 """ 

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

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

173 

174 def apply(self, values): 

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

176 

177 Subclasses should calculate the array of values and then 

178 return the result of calling this method. 

179 

180 Parameters 

181 ---------- 

182 values : `numpy.ndarray` 

183 Array of values to which to apply limits. 

184 

185 Returns 

186 ------- 

187 selected : `numpy.ndarray` 

188 Boolean array indicating for each source whether it is selected 

189 (True means selected). 

190 """ 

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

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

193 if self.minimum is not None: 

194 selected &= values > self.minimum 

195 if self.maximum is not None: 

196 selected &= values < self.maximum 

197 return selected 

198 

199 

200class ColorLimit(BaseLimit): 

201 """Select sources using a color limit 

202 

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

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

205 in the catalog that match the configured limit. 

206 

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

208 two components of the color, which is: 

209 

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

211 """ 

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

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

214 

215 def apply(self, catalog): 

216 """Apply the color limit to a catalog 

217 

218 Parameters 

219 ---------- 

220 catalog : Various table formats 

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

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

223 or `astropy.table.Table` 

224 

225 Returns 

226 ------- 

227 selected : `numpy.ndarray` 

228 Boolean array indicating for each source whether it is selected 

229 (True means selected). 

230 """ 

231 primary = _getFieldFromCatalog(catalog, self.primary) 

232 secondary = _getFieldFromCatalog(catalog, self.secondary) 

233 

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

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

236 color = primary - secondary 

237 return BaseLimit.apply(self, color) 

238 

239 

240class FluxLimit(BaseLimit): 

241 """Select sources using a flux limit 

242 

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

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

245 in the catalog that match the configured limit. 

246 """ 

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

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

249 

250 def apply(self, catalog): 

251 """Apply the flux limits to a catalog 

252 

253 Parameters 

254 ---------- 

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

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

257 

258 Returns 

259 ------- 

260 selected : `numpy.ndarray` 

261 Boolean array indicating for each source whether it is selected 

262 (True means selected). 

263 """ 

264 flagField = self.fluxField + "_flag" 

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

266 flux = _getFieldFromCatalog(catalog, self.fluxField) 

267 

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

269 return selected 

270 

271 

272class MagnitudeLimit(BaseLimit): 

273 """Select sources using a magnitude limit 

274 

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

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

277 intended for reference catalogs rather than catalogs extracted from 

278 science images. 

279 

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

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

282 in the catalog that match the configured limit. 

283 """ 

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

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

286 

287 def apply(self, catalog): 

288 """Apply the magnitude limits to a catalog 

289 

290 Parameters 

291 ---------- 

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

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

294 

295 Returns 

296 ------- 

297 selected : `numpy.ndarray` 

298 Boolean array indicating for each source whether it is selected 

299 (True means selected). 

300 """ 

301 flagField = self.fluxField + "_flag" 

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

303 flux = _getFieldFromCatalog(catalog, self.fluxField) 

304 

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

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

307 return selected 

308 

309 

310class SignalToNoiseLimit(BaseLimit): 

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

312 

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

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

315 in the catalog that match the configured limit. 

316 """ 

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

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

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

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

321 

322 def apply(self, catalog): 

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

324 

325 Parameters 

326 ---------- 

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

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

329 

330 Returns 

331 ------- 

332 selected : `numpy.ndarray` 

333 Boolean array indicating for each source whether it is selected 

334 (True means selected). 

335 """ 

336 flagField = self.fluxField + "_flag" 

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

338 flux = _getFieldFromCatalog(catalog, self.fluxField) 

339 err = _getFieldFromCatalog(catalog, self.errField) 

340 

341 signalToNoise = flux/err 

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

343 return selected 

344 

345 

346class MagnitudeErrorLimit(BaseLimit): 

347 """Select sources using a magnitude error limit 

348 

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

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

351 you only have a magnitude. 

352 

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

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

355 in the catalog that match the configured limit. 

356 """ 

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

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

359 

360 def apply(self, catalog): 

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

362 

363 Parameters 

364 ---------- 

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

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

367 

368 Returns 

369 ------- 

370 selected : `numpy.ndarray` 

371 Boolean array indicating for each source whether it is selected 

372 (True means selected). 

373 """ 

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

375 

376 

377class RequireFlags(pexConfig.Config): 

378 """Select sources using flags 

379 

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

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

382 in the catalog that match the configured limit. 

383 """ 

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

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

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

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

388 

389 def apply(self, catalog): 

390 """Apply the flag requirements to a catalog 

391 

392 Returns whether the source is selected. 

393 

394 Parameters 

395 ---------- 

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

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

398 

399 Returns 

400 ------- 

401 selected : `numpy.ndarray` 

402 Boolean array indicating for each source whether it is selected 

403 (True means selected). 

404 """ 

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

406 for flag in self.good: 

407 selected &= catalog[flag] 

408 for flag in self.bad: 

409 selected &= ~catalog[flag] 

410 return selected 

411 

412 

413class RequireUnresolved(BaseLimit): 

414 """Select sources using star/galaxy separation 

415 

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

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

418 in the catalog that match the configured limit. 

419 """ 

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

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

422 

423 def setDefaults(self): 

424 """Set default 

425 

426 ``base_ClassificationExtendedness_value < 0.5`` means unresolved. 

427 """ 

428 self.maximum = 0.5 

429 

430 def apply(self, catalog): 

431 """Apply the flag requirements to a catalog 

432 

433 Returns whether the source is selected. 

434 

435 Parameters 

436 ---------- 

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

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

439 

440 Returns 

441 ------- 

442 selected : `numpy.ndarray` 

443 Boolean array indicating for each source whether it is selected 

444 (True means selected). 

445 """ 

446 value = catalog[self.name] 

447 return BaseLimit.apply(self, value) 

448 

449 

450class RequireIsolated(pexConfig.Config): 

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

452 

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

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

455 

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

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

458 """ 

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

460 doc="Name of column for parent") 

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

462 doc="Name of column for nChild") 

463 

464 def apply(self, catalog): 

465 """Apply the isolation requirements to a catalog 

466 

467 Returns whether the source is selected. 

468 

469 Parameters 

470 ---------- 

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

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

473 

474 Returns 

475 ------- 

476 selected : `numpy.ndarray` 

477 Boolean array indicating for each source whether it is selected 

478 (True means selected). 

479 """ 

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

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

482 return selected 

483 

484 

485class RequireFiniteRaDec(pexConfig.Config): 

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

487 

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

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

490 

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

492 entries are not numpy.isfinite(). 

493 """ 

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

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

496 

497 def apply(self, catalog): 

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

499 

500 Returns whether the sources were selected. 

501 

502 Parameters 

503 ---------- 

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

505 or `astropy.table.Table` 

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

507 

508 Returns 

509 ------- 

510 selected : `numpy.ndarray` 

511 Boolean array indicating for each source whether it is selected 

512 (True means selected). 

513 """ 

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

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

516 return selected 

517 

518 

519class RequirePrimary(pexConfig.Config): 

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

521 

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

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

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

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

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

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

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

529 """ 

530 primaryColName = pexConfig.Field( 

531 dtype=str, 

532 default="detect_isPrimary", 

533 doc="Name of primary flag column", 

534 ) 

535 

536 def apply(self, catalog): 

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

538 

539 Returns whether the sources were selected. 

540 

541 Parameters 

542 ---------- 

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

544 or `astropy.table.Table` 

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

546 

547 Returns 

548 ------- 

549 selected : `numpy.ndarray` 

550 Boolean array indicating for each source whether it is selected 

551 (True means selected). 

552 """ 

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

554 

555 return selected 

556 

557 

558class ScienceSourceSelectorConfig(pexConfig.Config): 

559 """Configuration for selecting science sources""" 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

574 requireFiniteRaDec = pexConfig.ConfigField(dtype=RequireFiniteRaDec, 

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

576 requirePrimary = pexConfig.ConfigField(dtype=RequirePrimary, 

577 doc="Primary source criteria to apply") 

578 

579 def setDefaults(self): 

580 pexConfig.Config.setDefaults(self) 

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

582 self.signalToNoise.fluxField = "base_PsfFlux_instFlux" 

583 self.signalToNoise.errField = "base_PsfFlux_instFluxErr" 

584 

585 

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

587class ScienceSourceSelectorTask(BaseSourceSelectorTask): 

588 """Science source selector 

589 

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

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

592 

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

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

595 """ 

596 ConfigClass = ScienceSourceSelectorConfig 

597 

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

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

600 

601 Parameters 

602 ---------- 

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

604 Catalog of sources to select from. 

605 This catalog must be contiguous in memory. 

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

607 Ignored in this SourceSelector. 

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

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

610 

611 Returns 

612 ------- 

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

614 The struct contains the following data: 

615 

616 ``selected`` 

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

618 sourceCat. 

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

620 """ 

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

622 if self.config.doFluxLimit: 

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

624 if self.config.doFlags: 

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

626 if self.config.doUnresolved: 

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

628 if self.config.doSignalToNoise: 

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

630 if self.config.doIsolated: 

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

632 if self.config.doRequireFiniteRaDec: 

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

634 if self.config.doRequirePrimary: 

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

636 

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

638 

639 return pipeBase.Struct(selected=selected) 

640 

641 

642class ReferenceSourceSelectorConfig(pexConfig.Config): 

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

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

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

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

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

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

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

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

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

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

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

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

655 

656 

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

658class ReferenceSourceSelectorTask(BaseSourceSelectorTask): 

659 """Reference source selector 

660 

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

662 magnitude limit, flag requirements and color limits. 

663 """ 

664 ConfigClass = ReferenceSourceSelectorConfig 

665 

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

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

668 

669 Parameters 

670 ---------- 

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

672 Catalog of sources to select from. 

673 This catalog must be contiguous in memory. 

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

675 Ignored in this SourceSelector. 

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

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

678 

679 Returns 

680 ------- 

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

682 The struct contains the following data: 

683 

684 ``selected`` 

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

686 sourceCat. 

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

688 """ 

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

690 if self.config.doMagLimit: 

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

692 if self.config.doFlags: 

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

694 if self.config.doUnresolved: 

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

696 if self.config.doSignalToNoise: 

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

698 if self.config.doMagError: 

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

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

701 selected &= limit.apply(sourceCat) 

702 

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

704 

705 return pipeBase.Struct(selected=selected) 

706 

707 

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

709 """ 

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

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

712 

713 Parameters 

714 ---------- 

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

716 or `astropy.table.Table` 

717 Catalog of sources to extract field array 

718 field : `str` 

719 Name of field 

720 isFlag : `bool`, optional 

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

722 of False. 

723 

724 Returns 

725 ------- 

726 array : `np.ndarray` 

727 Array of field values from the catalog. 

728 """ 

729 found = False 

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

731 if field in catalog.columns: 

732 found = True 

733 # Sequences must be converted to numpy arrays 

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

735 else: 

736 if field in catalog.schema: 

737 found = True 

738 arr = catalog[field] 

739 

740 if isFlag and not found: 

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

742 elif not found: 

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

744 

745 return arr