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-2017 LSST Corporation. 

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 <http://www.lsstcorp.org/LegalNotices/>. 

21# 

22 

23import os.path 

24import math 

25import unittest 

26 

27import numpy as np 

28 

29import lsst.utils.tests 

30import lsst.geom 

31import lsst.afw.geom as afwGeom 

32import lsst.afw.table as afwTable 

33import lsst.afw.image as afwImage 

34import lsst.meas.base as measBase 

35from lsst.daf.persistence import Butler 

36from lsst.meas.algorithms import LoadIndexedReferenceObjectsTask 

37from lsst.meas.astrom import AstrometryTask 

38 

39 

40class TestAstrometricSolver(lsst.utils.tests.TestCase): 

41 

42 def setUp(self): 

43 refCatDir = os.path.join(os.path.dirname(__file__), "data", "sdssrefcat") 

44 

45 self.bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(3001, 3001)) 

46 crpix = lsst.geom.Box2D(self.bbox).getCenter() 

47 self.tanWcs = afwGeom.makeSkyWcs(crpix=crpix, 

48 crval=lsst.geom.SpherePoint(215.5, 53.0, lsst.geom.degrees), 

49 cdMatrix=afwGeom.makeCdMatrix(scale=5.1e-5*lsst.geom.degrees)) 

50 self.exposure = afwImage.ExposureF(self.bbox) 

51 self.exposure.setWcs(self.tanWcs) 

52 self.exposure.setFilterLabel(afwImage.FilterLabel(band="r", physical="rTest")) 

53 butler = Butler(refCatDir) 

54 self.refObjLoader = LoadIndexedReferenceObjectsTask(butler=butler) 

55 

56 def tearDown(self): 

57 del self.tanWcs 

58 del self.exposure 

59 del self.refObjLoader 

60 

61 def testTrivial(self): 

62 """Test fit with no distortion 

63 """ 

64 self.doTest(afwGeom.makeIdentityTransform()) 

65 

66 def testRadial(self): 

67 """Test fit with radial distortion 

68 

69 The offset comes from the fact that the CCD is not centered 

70 """ 

71 self.doTest(afwGeom.makeRadialTransform([0, 1.01, 1e-7])) 

72 

73 def testUsedFlag(self): 

74 """Test that the solver will record number of sources used to table 

75 if it is passed a schema on initialization. 

76 """ 

77 self.exposure.setWcs(self.tanWcs) 

78 loadRes = self.refObjLoader.loadPixelBox(bbox=self.bbox, wcs=self.tanWcs, filterName="r") 

79 refCat = loadRes.refCat 

80 refCentroidKey = afwTable.Point2DKey(refCat.schema["centroid"]) 

81 refFluxRKey = refCat.schema["r_flux"].asKey() 

82 

83 sourceSchema = afwTable.SourceTable.makeMinimalSchema() 

84 measBase.SingleFrameMeasurementTask(schema=sourceSchema) # expand the schema 

85 config = AstrometryTask.ConfigClass() 

86 config.wcsFitter.order = 2 

87 config.wcsFitter.numRejIter = 0 

88 # schema must be passed to the solver task constructor 

89 solver = AstrometryTask(config=config, refObjLoader=self.refObjLoader, schema=sourceSchema) 

90 sourceCat = afwTable.SourceCatalog(sourceSchema) 

91 sourceCat.reserve(len(refCat)) 

92 sourceCentroidKey = afwTable.Point2DKey(sourceSchema["slot_Centroid"]) 

93 sourceInstFluxKey = sourceSchema["slot_ApFlux_instFlux"].asKey() 

94 sourceInstFluxErrKey = sourceSchema["slot_ApFlux_instFluxErr"].asKey() 

95 

96 for refObj in refCat: 

97 src = sourceCat.addNew() 

98 src.set(sourceCentroidKey, refObj.get(refCentroidKey)) 

99 src.set(sourceInstFluxKey, refObj.get(refFluxRKey)) 

100 src.set(sourceInstFluxErrKey, refObj.get(refFluxRKey)/100) 

101 

102 results = solver.run( 

103 sourceCat=sourceCat, 

104 exposure=self.exposure, 

105 ) 

106 # check that the used flag is set the right number of times 

107 count = 0 

108 for source in sourceCat: 

109 if source.get('calib_astrometry_used'): 

110 count += 1 

111 self.assertEqual(count, len(results.matches)) 

112 

113 def doTest(self, pixelsToTanPixels, order=3): 

114 """Test using pixelsToTanPixels to distort the source positions 

115 """ 

116 distortedWcs = afwGeom.makeModifiedWcs(pixelTransform=pixelsToTanPixels, wcs=self.tanWcs, 

117 modifyActualPixels=False) 

118 self.exposure.setWcs(distortedWcs) 

119 sourceCat = self.makeSourceCat(distortedWcs) 

120 config = AstrometryTask.ConfigClass() 

121 config.wcsFitter.order = order 

122 config.wcsFitter.numRejIter = 0 

123 solver = AstrometryTask(config=config, refObjLoader=self.refObjLoader) 

124 results = solver.run( 

125 sourceCat=sourceCat, 

126 exposure=self.exposure, 

127 ) 

128 fitWcs = self.exposure.getWcs() 

129 self.assertRaises(Exception, self.assertWcsAlmostEqualOverBBox, fitWcs, distortedWcs) 

130 self.assertWcsAlmostEqualOverBBox(distortedWcs, fitWcs, self.bbox, 

131 maxDiffSky=0.01*lsst.geom.arcseconds, maxDiffPix=0.02) 

132 

133 srcCoordKey = afwTable.CoordKey(sourceCat.schema["coord"]) 

134 refCoordKey = afwTable.CoordKey(results.refCat.schema["coord"]) 

135 refCentroidKey = afwTable.Point2DKey(results.refCat.schema["centroid"]) 

136 maxAngSep = 0*lsst.geom.radians 

137 maxPixSep = 0 

138 for refObj, src, d in results.matches: 

139 refCoord = refObj.get(refCoordKey) 

140 refPixPos = refObj.get(refCentroidKey) 

141 srcCoord = src.get(srcCoordKey) 

142 srcPixPos = src.getCentroid() 

143 

144 angSep = refCoord.separation(srcCoord) 

145 maxAngSep = max(maxAngSep, angSep) 

146 

147 pixSep = math.hypot(*(srcPixPos-refPixPos)) 

148 maxPixSep = max(maxPixSep, pixSep) 

149 print("max angular separation = %0.4f arcsec" % (maxAngSep.asArcseconds(),)) 

150 print("max pixel separation = %0.3f" % (maxPixSep,)) 

151 self.assertLess(maxAngSep.asArcseconds(), 0.0038) 

152 self.assertLess(maxPixSep, 0.021) 

153 

154 # try again, invoking the reference selector 

155 config.referenceSelector.doUnresolved = True 

156 config.referenceSelector.unresolved.name = 'resolved' 

157 solverRefSelect = AstrometryTask(config=config, refObjLoader=self.refObjLoader) 

158 self.exposure.setWcs(distortedWcs) 

159 resultsRefSelect = solverRefSelect.run( 

160 sourceCat=sourceCat, 

161 exposure=self.exposure, 

162 ) 

163 self.assertLess(len(resultsRefSelect.matches), len(results.matches)) 

164 

165 # try again, but without fitting the WCS, no reference selector 

166 config.referenceSelector.doUnresolved = False 

167 config.forceKnownWcs = True 

168 solverNoFit = AstrometryTask(config=config, refObjLoader=self.refObjLoader) 

169 self.exposure.setWcs(distortedWcs) 

170 resultsNoFit = solverNoFit.run( 

171 sourceCat=sourceCat, 

172 exposure=self.exposure, 

173 ) 

174 self.assertIsNone(resultsNoFit.scatterOnSky) 

175 

176 # fitting should result in matches that are at least as good 

177 # (strictly speaking fitting might result in a larger match list with 

178 # some outliers, but in practice this test passes) 

179 meanFitDist = np.mean([match.distance for match in results.matches]) 

180 meanNoFitDist = np.mean([match.distance for match in resultsNoFit.matches]) 

181 self.assertLessEqual(meanFitDist, meanNoFitDist) 

182 

183 # try once again, without fitting the WCS, with the reference selector 

184 # (this goes through a different code path) 

185 config.referenceSelector.doUnresolved = True 

186 solverNoFitRefSelect = AstrometryTask(config=config, refObjLoader=self.refObjLoader) 

187 resultsNoFitRefSelect = solverNoFitRefSelect.run( 

188 sourceCat=sourceCat, 

189 exposure=self.exposure, 

190 ) 

191 self.assertLess(len(resultsNoFitRefSelect.matches), len(resultsNoFit.matches)) 

192 

193 def makeSourceCat(self, distortedWcs): 

194 """Make a source catalog by reading the position reference stars and distorting the positions 

195 """ 

196 loadRes = self.refObjLoader.loadPixelBox(bbox=self.bbox, wcs=distortedWcs, filterName="r") 

197 refCat = loadRes.refCat 

198 refCentroidKey = afwTable.Point2DKey(refCat.schema["centroid"]) 

199 refFluxRKey = refCat.schema["r_flux"].asKey() 

200 

201 sourceSchema = afwTable.SourceTable.makeMinimalSchema() 

202 measBase.SingleFrameMeasurementTask(schema=sourceSchema) # expand the schema 

203 sourceCat = afwTable.SourceCatalog(sourceSchema) 

204 sourceCentroidKey = afwTable.Point2DKey(sourceSchema["slot_Centroid"]) 

205 sourceInstFluxKey = sourceSchema["slot_ApFlux_instFlux"].asKey() 

206 sourceInstFluxErrKey = sourceSchema["slot_ApFlux_instFluxErr"].asKey() 

207 

208 sourceCat.reserve(len(refCat)) 

209 for refObj in refCat: 

210 src = sourceCat.addNew() 

211 src.set(sourceCentroidKey, refObj.get(refCentroidKey)) 

212 src.set(sourceInstFluxKey, refObj.get(refFluxRKey)) 

213 src.set(sourceInstFluxErrKey, refObj.get(refFluxRKey)/100) 

214 return sourceCat 

215 

216 

217class MemoryTester(lsst.utils.tests.MemoryTestCase): 

218 pass 

219 

220 

221def setup_module(module): 

222 lsst.utils.tests.init() 

223 

224 

225if __name__ == "__main__": 225 ↛ 226line 225 didn't jump to line 226, because the condition on line 225 was never true

226 lsst.utils.tests.init() 

227 unittest.main()