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__all__ = ["MatchOptimisticBTask", "MatchOptimisticBConfig", 

3 "MatchTolerance"] 

4 

5import math 

6 

7import lsst.pex.config as pexConfig 

8import lsst.pipe.base as pipeBase 

9 

10from .setMatchDistance import setMatchDistance 

11from .matchOptimisticB import matchOptimisticB, MatchOptimisticBControl 

12 

13 

14class MatchTolerance: 

15 """Stores match tolerances for use in `lsst.meas.astrom.AstrometryTask` and 

16 later iterations of the matcher. 

17 

18 MatchOptimsiticBTask relies on a maximum distance for matching 

19 set by either the default in MatchOptimisticBConfig or the 2 sigma 

20 scatter found after AstrometryTask has fit for a wcs. 

21 

22 Parameters 

23 ---------- 

24 maxMatchDist : `lsst.geom.Angle` 

25 Current maximum distance to consider a match. 

26 """ 

27 

28 def __init__(self, maxMatchDist=None): 

29 self.maxMatchDist = maxMatchDist 

30 

31 

32class MatchOptimisticBConfig(pexConfig.Config): 

33 """Configuration for MatchOptimisticBTask 

34 """ 

35 maxMatchDistArcSec = pexConfig.RangeField( 

36 doc="Maximum separation between reference objects and sources " 

37 "beyond which they will not be considered a match (arcsec)", 

38 dtype=float, 

39 default=3, 

40 min=0, 

41 ) 

42 numBrightStars = pexConfig.RangeField( 

43 doc="Number of bright stars to use", 

44 dtype=int, 

45 default=50, 

46 min=2, 

47 ) 

48 minMatchedPairs = pexConfig.RangeField( 

49 doc="Minimum number of matched pairs; see also minFracMatchedPairs", 

50 dtype=int, 

51 default=30, 

52 min=2, 

53 ) 

54 minFracMatchedPairs = pexConfig.RangeField( 

55 doc="Minimum number of matched pairs as a fraction of the smaller of " 

56 "the number of reference stars or the number of good sources; " 

57 "the actual minimum is the smaller of this value or minMatchedPairs", 

58 dtype=float, 

59 default=0.3, 

60 min=0, 

61 max=1, 

62 ) 

63 maxOffsetPix = pexConfig.RangeField( 

64 doc="Maximum allowed shift of WCS, due to matching (pixel). " 

65 "When changing this value, the LoadReferenceObjectsConfig.pixelMargin should also be updated.", 

66 dtype=int, 

67 default=300, 

68 max=4000, 

69 ) 

70 maxRotationDeg = pexConfig.RangeField( 

71 doc="Rotation angle allowed between sources and position reference objects (degrees)", 

72 dtype=float, 

73 default=1.0, 

74 max=6.0, 

75 ) 

76 allowedNonperpDeg = pexConfig.RangeField( 

77 doc="Allowed non-perpendicularity of x and y (degree)", 

78 dtype=float, 

79 default=3.0, 

80 max=45.0, 

81 ) 

82 numPointsForShape = pexConfig.Field( 

83 doc="number of points to define a shape for matching", 

84 dtype=int, 

85 default=6, 

86 ) 

87 maxDeterminant = pexConfig.Field( 

88 doc="maximum determinant of linear transformation matrix for a usable solution", 

89 dtype=float, 

90 default=0.02, 

91 ) 

92 

93 

94# The following block adds links to this task from the Task Documentation page. 

95# \addtogroup LSST_task_documentation 

96# \{ 

97# \page measAstrom_matchOptimisticBTask 

98# \ref MatchOptimisticBTask "MatchOptimisticBTask" 

99# Match sources to reference objects 

100# \} 

101 

102 

103class MatchOptimisticBTask(pipeBase.Task): 

104 """Match sources to reference objects using the Optimistic Pattern Matcher 

105 B algorithm of Tabur 2007. 

106 """ 

107 ConfigClass = MatchOptimisticBConfig 

108 _DefaultName = "matchObjectsToSources" 

109 

110 def __init__(self, **kwargs): 

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

112 

113 def filterStars(self, refCat): 

114 """Extra filtering pass; subclass if desired. 

115 

116 Parameters 

117 ---------- 

118 refCat : `lsst.afw.table.SimpleCatalog` 

119 Catalog of reference objects. 

120 

121 Returns 

122 ------- 

123 trimmedRefCat : `lsst.afw.table.SimpleCatalog` 

124 Reference catalog with some filtering applied. Currently no 

125 filtering is applied. 

126 """ 

127 return refCat 

128 

129 @pipeBase.timeMethod 

130 def matchObjectsToSources(self, refCat, sourceCat, wcs, sourceFluxField, refFluxField, 

131 match_tolerance=None): 

132 """Match sources to position reference stars. 

133 

134 Parameters 

135 ---------- 

136 refCat : `lsst.afw.table.SimpleCatalog` 

137 Reference catalog to match. 

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

139 Catalog of sources found on an exposure. This should already be 

140 down-selected to "good"/"usable" sources in the calling Task. 

141 wcs : `lsst.afw.geom.SkyWcs` 

142 Current WCS of the exposure containing the sources. 

143 sourceFluxField : `str` 

144 Field of the sourceCat to use for flux 

145 refFluxField : `str` 

146 Field of the refCat to use for flux 

147 match_tolerance : `lsst.meas.astrom.MatchTolerance` 

148 Object containing information from previous 

149 `lsst.meas.astrom.AstrometryTask` match/fit cycles for use in 

150 matching. If `None` is config defaults. 

151 

152 Returns 

153 ------- 

154 matchResult : `lsst.pipe.base.Struct` 

155 Result struct with components 

156 

157 - ``matches`` : List of matches with distance below the maximum match 

158 distance (`list` of `lsst.afw.table.ReferenceMatch`). 

159 - ``useableSourceCat`` : Catalog of sources matched and suited for 

160 WCS fitting (`lsst.afw.table.SourceCatalog`). 

161 - ``match_tolerance`` : MatchTolerance object updated from this 

162 match iteration (`lsst.meas.astrom.MatchTolerance`). 

163 """ 

164 import lsstDebug 

165 debug = lsstDebug.Info(__name__) 

166 

167 preNumObj = len(refCat) 

168 refCat = self.filterStars(refCat) 

169 numRefObj = len(refCat) 

170 

171 if self.log: 

172 self.log.info("filterStars purged %d reference stars, leaving %d stars" % 

173 (preNumObj - numRefObj, numRefObj)) 

174 

175 if match_tolerance is None: 

176 match_tolerance = MatchTolerance() 

177 

178 # Make a name alias here for consistency with older code, and to make 

179 # it clear that this is a good/usable (cleaned) source catalog. 

180 usableSourceCat = sourceCat 

181 

182 numUsableSources = len(usableSourceCat) 

183 

184 if len(usableSourceCat) == 0: 

185 raise pipeBase.TaskError("No sources are usable") 

186 

187 minMatchedPairs = min(self.config.minMatchedPairs, 

188 int(self.config.minFracMatchedPairs * min([len(refCat), len(usableSourceCat)]))) 

189 

190 # match usable (possibly saturated) sources and then purge saturated sources from the match list 

191 usableMatches = self._doMatch( 

192 refCat=refCat, 

193 sourceCat=usableSourceCat, 

194 wcs=wcs, 

195 refFluxField=refFluxField, 

196 numUsableSources=numUsableSources, 

197 minMatchedPairs=minMatchedPairs, 

198 maxMatchDist=match_tolerance.maxMatchDist, 

199 sourceFluxField=sourceFluxField, 

200 verbose=debug.verbose, 

201 ) 

202 

203 # cull non-good sources 

204 matches = [] 

205 self._getIsGoodKeys(usableSourceCat.schema) 

206 for match in usableMatches: 

207 if self._isGoodTest(match.second): 

208 # Append the isGood match. 

209 matches.append(match) 

210 

211 self.log.debug("Found %d usable matches, of which %d had good sources", 

212 len(usableMatches), len(matches)) 

213 

214 if len(matches) == 0: 

215 raise RuntimeError("Unable to match sources") 

216 

217 self.log.info("Matched %d sources" % len(matches)) 

218 if len(matches) < minMatchedPairs: 

219 self.log.warn("Number of matches is smaller than request") 

220 

221 return pipeBase.Struct( 

222 matches=matches, 

223 usableSourceCat=usableSourceCat, 

224 match_tolerance=match_tolerance, 

225 ) 

226 

227 def _getIsGoodKeys(self, schema): 

228 """Retrieve the keys needed for the isGoodTest from the source catalog 

229 schema. 

230 

231 Parameters 

232 ---------- 

233 schema : `lsst.afw.table.Schema` 

234 Source schema to retrieve `lsst.afw.table.Key` s from. 

235 """ 

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

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

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

239 

240 def _isGoodTest(self, source): 

241 """Test that an object is good for use in the WCS fitter. 

242 

243 This is a hard coded version of the isGood flag from the old SourceInfo 

244 class that used to be part of this class. 

245 

246 Parameters 

247 ---------- 

248 source : `lsst.afw.table.SourceRecord` 

249 Source to test. 

250 

251 Returns 

252 ------- 

253 isGood : `bool` 

254 Source passes CCD edge and saturated tests. 

255 """ 

256 return (not source.get(self.edgeKey) 

257 and not source.get(self.interpolatedCenterKey) 

258 and not source.get(self.saturatedKey)) 

259 

260 @pipeBase.timeMethod 

261 def _doMatch(self, refCat, sourceCat, wcs, refFluxField, numUsableSources, minMatchedPairs, 

262 maxMatchDist, sourceFluxField, verbose): 

263 """Implementation of matching sources to position reference stars. 

264 

265 Unlike matchObjectsToSources, this method does not check if the sources 

266 are suitable. 

267 

268 Parameters 

269 ---------- 

270 refCat : `lsst.afw.table.SimpleCatalog` 

271 Catalog of reference objects. 

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

273 Catalog of detected sources. 

274 wcs : `lsst.afw.geom.SkyWcs` 

275 Current best WCS of the image. 

276 refFluxFioeld : `str` 

277 Name of flux field in refCat to use. 

278 numUsableSources : `int` 

279 Total number of source usable for matching. 

280 mintMatchPairs : `int` 

281 Minimum number of objects to match between the refCat and sourceCat 

282 to consider a valid match. 

283 maxMatchDist : `lsst.geom.Angle` 

284 Maximum separation to considering a reference and a source a match. 

285 sourceFluxField : `str` 

286 Name of source catalog flux field. 

287 verbose : `bool` 

288 Print diagnostic information std::cout 

289 

290 Returns 

291 ------- 

292 matches : `list` of `lsst.afw.table.ReferenceMatch` 

293 """ 

294 numSources = len(sourceCat) 

295 posRefBegInd = numUsableSources - numSources 

296 if maxMatchDist is None: 

297 maxMatchDistArcSec = self.config.maxMatchDistArcSec 

298 else: 

299 maxMatchDistArcSec = min(maxMatchDist.asArcseconds(), self.config.maxMatchDistArcSec) 

300 configMatchDistPix = maxMatchDistArcSec/wcs.getPixelScale().asArcseconds() 

301 

302 matchControl = MatchOptimisticBControl() 

303 matchControl.refFluxField = refFluxField 

304 matchControl.sourceFluxField = sourceFluxField 

305 matchControl.numBrightStars = self.config.numBrightStars 

306 matchControl.minMatchedPairs = self.config.minMatchedPairs 

307 matchControl.maxOffsetPix = self.config.maxOffsetPix 

308 matchControl.numPointsForShape = self.config.numPointsForShape 

309 matchControl.maxDeterminant = self.config.maxDeterminant 

310 

311 for maxRotInd in range(4): 

312 matchControl.maxRotationDeg = self.config.maxRotationDeg * math.pow(2.0, 0.5*maxRotInd) 

313 for matchRadInd in range(3): 

314 matchControl.matchingAllowancePix = configMatchDistPix * math.pow(1.25, matchRadInd) 

315 

316 for angleDiffInd in range(3): 

317 matchControl.allowedNonperpDeg = self.config.allowedNonperpDeg*(angleDiffInd+1) 

318 matches = matchOptimisticB( 

319 refCat, 

320 sourceCat, 

321 matchControl, 

322 wcs, 

323 posRefBegInd, 

324 verbose, 

325 ) 

326 if matches is not None and len(matches) > 0: 

327 setMatchDistance(matches) 

328 return matches 

329 return matches