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 epoch=expMd.epoch, 

160 ) 

161 

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

163 

164 matchMeta = self.refObjLoader.getMetadataBox( 

165 bbox=expMd.bbox, 

166 wcs=expMd.wcs, 

167 filterName=expMd.filterName, 

168 photoCalib=expMd.photoCalib, 

169 epoch=expMd.epoch, 

170 ) 

171 

172 matchRes = self.matcher.matchObjectsToSources( 

173 refCat=refSelection.sourceCat, 

174 sourceCat=sourceSelection.sourceCat, 

175 wcs=expMd.wcs, 

176 sourceFluxField=sourceFluxField, 

177 refFluxField=loadRes.fluxField, 

178 match_tolerance=None, 

179 ) 

180 

181 distStats = self._computeMatchStatsOnSky(matchRes.matches) 

182 self.log.info( 

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

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

185 ) 

186 

187 if debug.display: 

188 frame = int(debug.frame) 

189 displayAstrometry( 

190 refCat=refSelection.sourceCat, 

191 sourceCat=sourceSelection.sourceCat, 

192 matches=matchRes.matches, 

193 exposure=exposure, 

194 bbox=expMd.bbox, 

195 frame=frame, 

196 title="Matches", 

197 ) 

198 

199 return pipeBase.Struct( 

200 refCat=loadRes.refCat, 

201 refSelection=refSelection, 

202 sourceSelection=sourceSelection, 

203 matches=matchRes.matches, 

204 matchMeta=matchMeta, 

205 ) 

206 

207 def _computeMatchStatsOnSky(self, matchList): 

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

209 

210 Parameters 

211 ---------- 

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

213 list of matches between reference object and sources; 

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

215 

216 Returns 

217 ------- 

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

219 Result struct with components: 

220 

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

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

223 separation (`float`) 

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

225 distStdDev (`float`) 

226 """ 

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

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

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

230 return pipeBase.Struct( 

231 distMean=distMean, 

232 distStdDev=distStdDev, 

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

234 ) 

235 

236 def _getExposureMetadata(self, exposure): 

237 """Extract metadata from an exposure. 

238 

239 Parameters 

240 ---------- 

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

242 

243 Returns 

244 ------- 

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

246 Result struct with components: 

247 

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

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

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

251 - ``filterName`` : name of filter band (`str`) 

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

253 

254 """ 

255 exposureInfo = exposure.getInfo() 

256 filterLabel = exposureInfo.getFilterLabel() 

257 filterName = filterLabel.bandLabel if filterLabel is not None else None 

258 epoch = None 

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

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

261 scale=DateTime.TAI) 

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

263 

264 return pipeBase.Struct( 

265 bbox=exposure.getBBox(), 

266 wcs=exposureInfo.getWcs(), 

267 photoCalib=exposureInfo.getPhotoCalib(), 

268 filterName=filterName, 

269 epoch=epoch, 

270 )