Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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 

29import numpy as np 

30 

31import lsst.pex.config as pexConfig 

32from .sourceSelector import BaseSourceSelectorConfig, BaseSourceSelectorTask, sourceSelectorRegistry 

33from lsst.pipe.base import Struct 

34from functools import reduce 

35 

36 

37class AstrometrySourceSelectorConfig(BaseSourceSelectorConfig): 

38 badFlags = pexConfig.ListField( 

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

40 dtype=str, 

41 default=[ 

42 "base_PixelFlags_flag_edge", 

43 "base_PixelFlags_flag_interpolatedCenter", 

44 "base_PixelFlags_flag_saturatedCenter", 

45 "base_PixelFlags_flag_crCenter", 

46 "base_PixelFlags_flag_bad", 

47 ], 

48 ) 

49 sourceFluxType = pexConfig.Field( 

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

51 dtype=str, 

52 default="Ap", 

53 ) 

54 minSnr = pexConfig.Field( 

55 dtype=float, 

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

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

58 default=10, 

59 ) 

60 

61 

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

63class AstrometrySourceSelectorTask(BaseSourceSelectorTask): 

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

65 

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

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

68 be PSF sources, just have reliable centroids. 

69 """ 

70 ConfigClass = AstrometrySourceSelectorConfig 

71 

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

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

74 

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

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

77 

78 Parameters: 

79 ----------- 

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

81 Catalog of sources to select from. 

82 This catalog must be contiguous in memory. 

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

84 Ignored in this SourceSelector. 

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

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

87 

88 Return 

89 ------ 

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

91 The struct contains the following data: 

92 

93 - selected : `array` of `bool`` 

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

95 sourceCat. 

96 """ 

97 self._getSchemaKeys(sourceCat.schema) 

98 

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

100 good = self._isGood(sourceCat) 

101 return Struct(selected=good & ~bad) 

102 

103 def _getSchemaKeys(self, schema): 

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

105 """ 

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

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

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

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

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

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

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

113 

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

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

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

117 

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

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

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

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

122 

123 def _isMultiple(self, sourceCat): 

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

125 """ 

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

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

128 for i, cat in enumerate(sourceCat): 

129 footprint = cat.getFootprint() 

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

131 return test 

132 

133 def _hasCentroid(self, sourceCat): 

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

135 """ 

136 def checkNonfiniteCentroid(): 

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

138 """ 

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

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

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

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

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

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

145 & ~sourceCat.get(self.centroidFlagKey) 

146 

147 def _goodSN(self, sourceCat): 

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

149 """ 

150 if self.config.minSnr <= 0: 

151 return True 

152 else: 

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

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

155 

156 def _isUsable(self, sourceCat): 

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

158 have a poor centroid. 

159 

160 For a source to be usable it must: 

161 - have a valid centroid 

162 - not be deblended 

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

164 - have adequate signal-to-noise 

165 """ 

166 

167 return self._hasCentroid(sourceCat) \ 

168 & ~self._isMultiple(sourceCat) \ 

169 & self._goodSN(sourceCat) \ 

170 & ~sourceCat.get(self.fluxFlagKey) 

171 

172 def _isGood(self, sourceCat): 

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

174 good centroid. 

175 

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

177 - not interpolated in the center 

178 - not saturated 

179 - not near the edge 

180 """ 

181 

182 return self._isUsable(sourceCat) \ 

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

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

185 & ~sourceCat.get(self.edgeKey) 

186 

187 def _isBadFlagged(self, source): 

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

189 """ 

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