Coverage for python/lsst/meas/algorithms/astrometrySourceSelector.py: 38%

58 statements  

« prev     ^ index     » next       coverage.py v6.4.1, created at 2022-06-07 01:50 -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"""Select sources that are useful for astrometry. 

24 

25Such sources have good signal-to-noise, are well centroided, not blended, 

26and not flagged with a handful of "bad" flags. 

27""" 

28 

29__all__ = ["AstrometrySourceSelectorConfig", "AstrometrySourceSelectorTask"] 

30 

31import numpy as np 

32 

33import lsst.pex.config as pexConfig 

34from .sourceSelector import BaseSourceSelectorConfig, BaseSourceSelectorTask, sourceSelectorRegistry 

35from lsst.pipe.base import Struct 

36from functools import reduce 

37 

38 

39class AstrometrySourceSelectorConfig(BaseSourceSelectorConfig): 

40 badFlags = pexConfig.ListField( 

41 doc="List of flags which cause a source to be rejected as bad", 

42 dtype=str, 

43 default=[ 

44 "base_PixelFlags_flag_edge", 

45 "base_PixelFlags_flag_interpolatedCenter", 

46 "base_PixelFlags_flag_saturatedCenter", 

47 "base_PixelFlags_flag_crCenter", 

48 "base_PixelFlags_flag_bad", 

49 ], 

50 ) 

51 sourceFluxType = pexConfig.Field( 

52 doc="Type of source flux; typically one of Ap or Psf", 

53 dtype=str, 

54 default="Ap", 

55 ) 

56 minSnr = pexConfig.Field( 

57 dtype=float, 

58 doc="Minimum allowed signal-to-noise ratio for sources used for matching " 

59 "(in the flux specified by sourceFluxType); <= 0 for no limit", 

60 default=10, 

61 ) 

62 

63 

64@pexConfig.registerConfigurable("astrometry", sourceSelectorRegistry) 

65class AstrometrySourceSelectorTask(BaseSourceSelectorTask): 

66 """Select sources that are useful for astrometry. 

67 

68 Good astrometry sources have high signal/noise, are non-blended, and 

69 did not have certain "bad" flags set during source extraction. They need not 

70 be PSF sources, just have reliable centroids. 

71 """ 

72 ConfigClass = AstrometrySourceSelectorConfig 

73 

74 def __init__(self, *args, **kwargs): 

75 BaseSourceSelectorTask.__init__(self, *args, **kwargs) 

76 

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

78 """Return a selection of sources that are useful for astrometry. 

79 

80 Parameters: 

81 ----------- 

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

83 Catalog of sources to select from. 

84 This catalog must be contiguous in memory. 

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

86 Ignored in this SourceSelector. 

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

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

89 

90 Return 

91 ------ 

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

93 The struct contains the following data: 

94 

95 - selected : `array` of `bool`` 

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

97 sourceCat. 

98 """ 

99 self._getSchemaKeys(sourceCat.schema) 

100 

101 bad = reduce(lambda x, y: np.logical_or(x, sourceCat.get(y)), self.config.badFlags, False) 

102 good = self._isGood(sourceCat) 

103 return Struct(selected=good & ~bad) 

104 

105 def _getSchemaKeys(self, schema): 

106 """Extract and save the necessary keys from schema with asKey. 

107 """ 

108 self.parentKey = schema["parent"].asKey() 

109 self.nChildKey = schema["deblend_nChild"].asKey() 

110 self.centroidXKey = schema["slot_Centroid_x"].asKey() 

111 self.centroidYKey = schema["slot_Centroid_y"].asKey() 

112 self.centroidXErrKey = schema["slot_Centroid_xErr"].asKey() 

113 self.centroidYErrKey = schema["slot_Centroid_yErr"].asKey() 

114 self.centroidFlagKey = schema["slot_Centroid_flag"].asKey() 

115 

116 self.edgeKey = schema["base_PixelFlags_flag_edge"].asKey() 

117 self.interpolatedCenterKey = schema["base_PixelFlags_flag_interpolatedCenter"].asKey() 

118 self.saturatedKey = schema["base_PixelFlags_flag_saturated"].asKey() 

119 

120 fluxPrefix = "slot_%sFlux_" % (self.config.sourceFluxType,) 

121 self.instFluxKey = schema[fluxPrefix + "instFlux"].asKey() 

122 self.fluxFlagKey = schema[fluxPrefix + "flag"].asKey() 

123 self.instFluxErrKey = schema[fluxPrefix + "instFluxErr"].asKey() 

124 

125 def _isMultiple(self, sourceCat): 

126 """Return True for each source that is likely multiple sources. 

127 """ 

128 test = (sourceCat.get(self.parentKey) != 0) | (sourceCat.get(self.nChildKey) != 0) 

129 # have to currently manage footprints on a source-by-source basis. 

130 for i, cat in enumerate(sourceCat): 

131 footprint = cat.getFootprint() 

132 test[i] |= (footprint is not None) and (len(footprint.getPeaks()) > 1) 

133 return test 

134 

135 def _hasCentroid(self, sourceCat): 

136 """Return True for each source that has a valid centroid 

137 """ 

138 def checkNonfiniteCentroid(): 

139 """Return True for sources with non-finite centroids. 

140 """ 

141 return ~np.isfinite(sourceCat.get(self.centroidXKey)) | \ 

142 ~np.isfinite(sourceCat.get(self.centroidYKey)) 

143 assert ~checkNonfiniteCentroid().any(), \ 

144 "Centroids not finite for %d unflagged sources." % (checkNonfiniteCentroid().sum()) 

145 return np.isfinite(sourceCat.get(self.centroidXErrKey)) \ 

146 & np.isfinite(sourceCat.get(self.centroidYErrKey)) \ 

147 & ~sourceCat.get(self.centroidFlagKey) 

148 

149 def _goodSN(self, sourceCat): 

150 """Return True for each source that has Signal/Noise > config.minSnr. 

151 """ 

152 if self.config.minSnr <= 0: 

153 return True 

154 else: 

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

156 return sourceCat.get(self.instFluxKey)/sourceCat.get(self.instFluxErrKey) > self.config.minSnr 

157 

158 def _isUsable(self, sourceCat): 

159 """Return True for each source that is usable for matching, even if it may 

160 have a poor centroid. 

161 

162 For a source to be usable it must: 

163 - have a valid centroid 

164 - not be deblended 

165 - have a valid flux (of the type specified in this object's constructor) 

166 - have adequate signal-to-noise 

167 """ 

168 

169 return self._hasCentroid(sourceCat) \ 

170 & ~self._isMultiple(sourceCat) \ 

171 & self._goodSN(sourceCat) \ 

172 & ~sourceCat.get(self.fluxFlagKey) 

173 

174 def _isGood(self, sourceCat): 

175 """Return True for each source that is usable for matching and likely has a 

176 good centroid. 

177 

178 The additional tests for a good centroid, beyond isUsable, are: 

179 - not interpolated in the center 

180 - not saturated 

181 - not near the edge 

182 """ 

183 

184 return self._isUsable(sourceCat) \ 

185 & ~sourceCat.get(self.saturatedKey) \ 

186 & ~sourceCat.get(self.interpolatedCenterKey) \ 

187 & ~sourceCat.get(self.edgeKey) 

188 

189 def _isBadFlagged(self, source): 

190 """Return True if any of config.badFlags are set for this source. 

191 """ 

192 return any(source.get(flag) for flag in self.config.badFlags)