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# Copyright 2008-2016 AURA/LSST. 

4# 

5# This product includes software developed by the 

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

7# 

8# This program is free software: you can redistribute it and/or modify 

9# it under the terms of the GNU General Public License as published by 

10# the Free Software Foundation, either version 3 of the License, or 

11# (at your option) any later version. 

12# 

13# This program is distributed in the hope that it will be useful, 

14# but WITHOUT ANY WARRANTY; without even the implied warranty of 

15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

16# GNU General Public License for more details. 

17# 

18# You should have received a copy of the LSST License Statement and 

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

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

21# 

22 

23__all__ = ['RefMatchConfig', 'RefMatchTask'] 

24 

25import astropy.time 

26 

27import lsst.geom 

28from lsst.daf.base import DateTime 

29import lsst.afw.math as afwMath 

30import lsst.pex.config as pexConfig 

31import lsst.pipe.base as pipeBase 

32from lsst.meas.algorithms import ReferenceSourceSelectorTask 

33from lsst.meas.algorithms.sourceSelector import sourceSelectorRegistry 

34from .matchPessimisticB import MatchPessimisticBTask 

35from .display import displayAstrometry 

36from . import makeMatchStatistics 

37 

38 

39class RefMatchConfig(pexConfig.Config): 

40 matcher = pexConfig.ConfigurableField( 

41 target=MatchPessimisticBTask, 

42 doc="reference object/source matcher", 

43 ) 

44 matchDistanceSigma = pexConfig.RangeField( 

45 doc="the maximum match distance is set to " 

46 " mean_match_distance + matchDistanceSigma*std_dev_match_distance; " 

47 "ignored if not fitting a WCS", 

48 dtype=float, 

49 default=2, 

50 min=0, 

51 ) 

52 sourceSelector = sourceSelectorRegistry.makeField( 

53 doc="How to select sources for cross-matching.", 

54 default="science", 

55 ) 

56 referenceSelector = pexConfig.ConfigurableField( 

57 target=ReferenceSourceSelectorTask, 

58 doc="How to select reference objects for cross-matching." 

59 ) 

60 sourceFluxType = pexConfig.Field( 

61 dtype=str, 

62 doc="Source flux type to use in source selection.", 

63 default='Calib' 

64 ) 

65 

66 def setDefaults(self): 

67 self.sourceSelector.name = "science" 

68 self.sourceSelector['science'].fluxLimit.fluxField = \ 

69 'slot_%sFlux_instFlux' % (self.sourceFluxType) 

70 self.sourceSelector['science'].signalToNoise.fluxField = \ 

71 'slot_%sFlux_instFlux' % (self.sourceFluxType) 

72 self.sourceSelector['science'].signalToNoise.errField = \ 

73 'slot_%sFlux_instFluxErr' % (self.sourceFluxType) 

74 

75 

76class RefMatchTask(pipeBase.Task): 

77 """Match an input source catalog with objects from a reference catalog. 

78 

79 Parameters 

80 ---------- 

81 refObjLoader : `lsst.meas.algorithms.ReferenceLoader` 

82 A reference object loader object 

83 **kwargs 

84 additional keyword arguments for pipe_base `lsst.pipe.base.Task` 

85 """ 

86 ConfigClass = RefMatchConfig 

87 _DefaultName = "calibrationBaseClass" 

88 

89 def __init__(self, refObjLoader, **kwargs): 

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

91 if refObjLoader: 

92 self.refObjLoader = refObjLoader 

93 else: 

94 self.refObjLoader = None 

95 

96 if self.config.sourceSelector.name == 'matcher': 

97 if self.config.sourceSelector['matcher'].sourceFluxType != self.config.sourceFluxType: 

98 raise RuntimeError("The sourceFluxType in the sourceSelector['matcher'] must match " 

99 "the configured sourceFluxType") 

100 

101 self.makeSubtask("matcher") 

102 self.makeSubtask("sourceSelector") 

103 self.makeSubtask("referenceSelector") 

104 

105 def setRefObjLoader(self, refObjLoader): 

106 """Sets the reference object loader for the task 

107 

108 Parameters 

109 ---------- 

110 refObjLoader 

111 An instance of a reference object loader task or class 

112 """ 

113 self.refObjLoader = refObjLoader 

114 

115 @pipeBase.timeMethod 

116 def loadAndMatch(self, exposure, sourceCat): 

117 """Load reference objects overlapping an exposure and match to sources 

118 detected on that exposure. 

119 

120 Parameters 

121 ---------- 

122 exposure : `lsst.afw.image.Exposure` 

123 exposure that the sources overlap 

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

125 catalog of sources detected on the exposure 

126 

127 Returns 

128 ------- 

129 result : `lsst.pipe.base.Struct` 

130 Result struct with Components: 

131 

132 - ``refCat`` : reference object catalog of objects that overlap the 

133 exposure (`lsst.afw.table.SimpleCatalog`) 

134 - ``matches`` : Matched sources and references 

135 (`list` of `lsst.afw.table.ReferenceMatch`) 

136 - ``matchMeta`` : metadata needed to unpersist matches 

137 (`lsst.daf.base.PropertyList`) 

138 

139 Notes 

140 ----- 

141 ignores config.matchDistanceSigma 

142 """ 

143 if self.refObjLoader is None: 

144 raise RuntimeError("Running matcher task with no refObjLoader set in __ini__ or setRefObjLoader") 

145 import lsstDebug 

146 debug = lsstDebug.Info(__name__) 

147 

148 expMd = self._getExposureMetadata(exposure) 

149 

150 sourceSelection = self.sourceSelector.run(sourceCat) 

151 

152 sourceFluxField = "slot_%sFlux_instFlux" % (self.config.sourceFluxType) 

153 

154 loadRes = self.refObjLoader.loadPixelBox( 

155 bbox=expMd.bbox, 

156 wcs=expMd.wcs, 

157 filterName=expMd.filterName, 

158 photoCalib=expMd.photoCalib, 

159 ) 

160 

161 refSelection = self.referenceSelector.run(loadRes.refCat) 

162 

163 matchMeta = self.refObjLoader.getMetadataBox( 

164 bbox=expMd.bbox, 

165 wcs=expMd.wcs, 

166 filterName=expMd.filterName, 

167 photoCalib=expMd.photoCalib, 

168 ) 

169 

170 matchRes = self.matcher.matchObjectsToSources( 

171 refCat=refSelection.sourceCat, 

172 sourceCat=sourceSelection.sourceCat, 

173 wcs=expMd.wcs, 

174 sourceFluxField=sourceFluxField, 

175 refFluxField=loadRes.fluxField, 

176 match_tolerance=None, 

177 ) 

178 

179 distStats = self._computeMatchStatsOnSky(matchRes.matches) 

180 self.log.info( 

181 "Found %d matches with scatter = %0.3f +- %0.3f arcsec; " % 

182 (len(matchRes.matches), distStats.distMean.asArcseconds(), distStats.distStdDev.asArcseconds()) 

183 ) 

184 

185 if debug.display: 

186 frame = int(debug.frame) 

187 displayAstrometry( 

188 refCat=refSelection.sourceCat, 

189 sourceCat=sourceSelection.sourceCat, 

190 matches=matchRes.matches, 

191 exposure=exposure, 

192 bbox=expMd.bbox, 

193 frame=frame, 

194 title="Matches", 

195 ) 

196 

197 return pipeBase.Struct( 

198 refCat=loadRes.refCat, 

199 refSelection=refSelection, 

200 sourceSelection=sourceSelection, 

201 matches=matchRes.matches, 

202 matchMeta=matchMeta, 

203 ) 

204 

205 def _computeMatchStatsOnSky(self, matchList): 

206 """Compute on-sky radial distance statistics for a match list 

207 

208 Parameters 

209 ---------- 

210 matchList : `list` of `lsst.afw.table.ReferenceMatch` 

211 list of matches between reference object and sources; 

212 the distance field is the only field read and it must be set to distance in radians 

213 

214 Returns 

215 ------- 

216 result : `lsst.pipe.base.Struct` 

217 Result struct with components: 

218 

219 - ``distMean`` : clipped mean of on-sky radial separation (`float`) 

220 - ``distStdDev`` : clipped standard deviation of on-sky radial 

221 separation (`float`) 

222 - ``maxMatchDist`` : distMean + self.config.matchDistanceSigma * 

223 distStdDev (`float`) 

224 """ 

225 distStatsInRadians = makeMatchStatistics(matchList, afwMath.MEANCLIP | afwMath.STDEVCLIP) 

226 distMean = distStatsInRadians.getValue(afwMath.MEANCLIP)*lsst.geom.radians 

227 distStdDev = distStatsInRadians.getValue(afwMath.STDEVCLIP)*lsst.geom.radians 

228 return pipeBase.Struct( 

229 distMean=distMean, 

230 distStdDev=distStdDev, 

231 maxMatchDist=distMean + self.config.matchDistanceSigma * distStdDev, 

232 ) 

233 

234 def _getExposureMetadata(self, exposure): 

235 """Extract metadata from an exposure. 

236 

237 Parameters 

238 ---------- 

239 exposure : `lsst.afw.image.Exposure` 

240 

241 Returns 

242 ------- 

243 result : `lsst.pipe.base.Struct` 

244 Result struct with components: 

245 

246 - ``bbox`` : parent bounding box (`lsst.geom.Box2I`) 

247 - ``wcs`` : exposure WCS (`lsst.afw.geom.SkyWcs`) 

248 - ``photoCalib`` : photometric calibration (`lsst.afw.image.PhotoCalib`) 

249 - ``filterName`` : name of filter (`str`) 

250 - ``epoch`` : date of exposure (`astropy.time.Time`) 

251 

252 """ 

253 exposureInfo = exposure.getInfo() 

254 filterName = exposureInfo.getFilter().getName() or None 

255 if filterName == "_unknown_": 

256 filterName = None 

257 epoch = None 

258 if exposure.getInfo().hasVisitInfo(): 

259 epochTaiMjd = exposure.getInfo().getVisitInfo().getDate().get(system=DateTime.MJD, 

260 scale=DateTime.TAI) 

261 epoch = astropy.time.Time(epochTaiMjd, scale="tai", format="mjd") 

262 

263 return pipeBase.Struct( 

264 bbox=exposure.getBBox(), 

265 wcs=exposureInfo.getWcs(), 

266 photoCalib=exposureInfo.getPhotoCalib(), 

267 filterName=filterName, 

268 epoch=epoch, 

269 )