Coverage for tests/test_tableUtils.py: 16%

119 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-19 04:04 -0700

1# LSST Data Management System 

2# Copyright 2016 LSST Corporation. 

3# 

4# This product includes software developed by the 

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

6# 

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

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

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

10# (at your option) any later version. 

11# 

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

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

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

15# GNU General Public License for more details. 

16# 

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

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

19# see <http://www.lsstcorp.org/LegalNotices/>. 

20# 

21# The classes in this test are a little non-standard to reduce code 

22# duplication and support automated unittest discovery. 

23# A base class includes all the code that implements the testing and 

24# itself inherits from unittest.TestCase. unittest automated discovery 

25# will scan all classes that inherit from unittest.TestCase and invoke 

26# any test methods found. To prevent this base class from being executed 

27# the test methods are placed in a different class that does not inherit 

28# from unittest.TestCase. The actual test classes then inherit from 

29# both the testing class and the implementation class allowing test 

30# discovery to only run tests found in the subclasses. 

31 

32import math 

33import unittest 

34 

35import numpy as np 

36 

37import lsst.utils.tests 

38import lsst.geom 

39import lsst.afw.geom as afwGeom 

40import lsst.afw.table as afwTable 

41 

42 

43class UpdateTestCase(lsst.utils.tests.TestCase): 

44 """A test case for the lsst.afw.table.updateRefCentroids and updateSourceCoords 

45 """ 

46 

47 def setUp(self): 

48 self.crval = lsst.geom.SpherePoint(44.0, 45.0, lsst.geom.degrees) 

49 self.crpix = lsst.geom.Point2D(15000, 4000) 

50 

51 arcsecPerPixel = 1/3600.0 

52 cdMatrix = afwGeom.makeCdMatrix(arcsecPerPixel * lsst.geom.arcseconds) 

53 self.wcs = afwGeom.makeSkyWcs(crval=self.crval, crpix=self.crpix, cdMatrix=cdMatrix) 

54 

55 refSchema = afwTable.SimpleTable.makeMinimalSchema() 

56 self.refCentroidKey = afwTable.Point2DKey.addFields( 

57 refSchema, "centroid", "centroid", "pixels") 

58 self.refCoordKey = afwTable.CoordKey(refSchema["coord"]) 

59 self.refHasCentroidKey = refSchema.addField("hasCentroid", type="Flag") 

60 self.refCat = afwTable.SimpleCatalog(refSchema) 

61 

62 # an alias is required to make src.getCentroid() work; 

63 # simply defining a field named "slot_Centroid" doesn't suffice 

64 srcSchema = afwTable.SourceTable.makeMinimalSchema() 

65 self.srcCentroidKey = afwTable.Point2DKey.addFields(srcSchema, "base_SdssCentroid", 

66 "centroid", "pixels") 

67 self.srcCentroidErrKey = afwTable.CovarianceMatrix2fKey.addFields(srcSchema, "base_SdssCentroid", 

68 ["x", "y"], "pixels") 

69 self.srcCoordErrKey = afwTable.CoordKey.addErrorFields(srcSchema) 

70 

71 srcAliases = srcSchema.getAliasMap() 

72 srcAliases.set("slot_Centroid", "base_SdssCentroid") 

73 self.srcCoordKey = afwTable.CoordKey(srcSchema["coord"]) 

74 self.sourceCat = afwTable.SourceCatalog(srcSchema) 

75 

76 def tearDown(self): 

77 del self.wcs 

78 del self.refCat 

79 del self.sourceCat 

80 

81 def testNull(self): 

82 """Check that an empty list causes no problems for either function""" 

83 afwTable.updateRefCentroids(self.wcs, []) 

84 afwTable.updateSourceCoords(self.wcs, []) 

85 

86 def testRefCenter(self): 

87 """Check that a ref obj at the center is handled as expected""" 

88 refObj = self.refCat.addNew() 

89 refObj.set(self.refCoordKey, self.crval) 

90 

91 # initial centroid should be nan and hasCentroid False 

92 nanRefCentroid = self.refCat[0].get(self.refCentroidKey) 

93 for val in nanRefCentroid: 

94 self.assertTrue(math.isnan(val)) 

95 self.assertFalse(self.refCat[0].get(self.refHasCentroidKey)) 

96 

97 # computed centroid should be crpix and hasCentroid True 

98 afwTable.updateRefCentroids(self.wcs, self.refCat) 

99 refCentroid = self.refCat[0].get(self.refCentroidKey) 

100 self.assertPairsAlmostEqual(refCentroid, self.crpix) 

101 self.assertTrue(self.refCat[0].get(self.refHasCentroidKey)) 

102 

103 # coord should not be changed 

104 self.assertEqual(self.refCat[0].get(self.refCoordKey), self.crval) 

105 

106 def testSourceCenter(self): 

107 """Check that a source at the center is handled as expected""" 

108 src = self.sourceCat.addNew() 

109 src.set(self.srcCentroidKey, self.crpix) 

110 

111 # initial coord should be nan; as a sanity-check 

112 nanSourceCoord = self.sourceCat[0].get(self.srcCoordKey) 

113 for val in nanSourceCoord: 

114 self.assertTrue(math.isnan(val)) 

115 

116 # compute coord should be crval 

117 afwTable.updateSourceCoords(self.wcs, self.sourceCat) 

118 srcCoord = self.sourceCat[0].get(self.srcCoordKey) 

119 self.assertPairsAlmostEqual(srcCoord, self.crval) 

120 

121 # centroid should not be changed; also make sure that getCentroid words 

122 self.assertEqual(self.sourceCat[0].getCentroid(), self.crpix) 

123 

124 def testLists(self): 

125 """Check updating lists of reference objects and sources""" 

126 # arbitrary but reasonable values that are intentionally different than 

127 # testCatalogs 

128 maxPix = 1000 

129 numPoints = 10 

130 self.setCatalogs(maxPix=maxPix, numPoints=numPoints) 

131 

132 # update the catalogs as lists 

133 afwTable.updateSourceCoords(self.wcs, [s for s in self.sourceCat]) 

134 afwTable.updateRefCentroids(self.wcs, [r for r in self.refCat]) 

135 

136 self.checkCatalogs() 

137 

138 def testCatalogs(self): 

139 """Check updating catalogs of reference objects and sources""" 

140 # arbitrary but reasonable values that are intentionally different than 

141 # testLists 

142 maxPix = 2000 

143 numPoints = 9 

144 self.setCatalogs(maxPix=maxPix, numPoints=numPoints) 

145 

146 # update the catalogs 

147 afwTable.updateSourceCoords(self.wcs, self.sourceCat) 

148 afwTable.updateRefCentroids(self.wcs, self.refCat) 

149 

150 # check that centroids and coords match 

151 self.checkCatalogs() 

152 

153 def testCoordErrors(self): 

154 """Check that updateSourceCoords has correctly propagated the centroid 

155 errors. 

156 """ 

157 maxPix = 2000 

158 numPoints = 10 

159 

160 self.setCatalogs(maxPix=maxPix, numPoints=numPoints) 

161 scale = (1.0 * lsst.geom.arcseconds).asDegrees() 

162 # update the catalogs 

163 afwTable.updateSourceCoords(self.wcs, self.sourceCat) 

164 for src in self.sourceCat: 

165 center = src.get(self.srcCentroidKey) 

166 skyCenter = self.wcs.pixelToSky(center) 

167 localGnomonicWcs = lsst.afw.geom.makeSkyWcs( 

168 center, skyCenter, np.diag((scale, scale))) 

169 measurementToLocalGnomonic = self.wcs.getTransform().then( 

170 localGnomonicWcs.getTransform().inverted() 

171 ) 

172 localMatrix = measurementToLocalGnomonic.getJacobian(center) 

173 radMatrix = np.radians(localMatrix / 3600) 

174 

175 centroidErr = src.get(self.srcCentroidErrKey) 

176 coordErr = radMatrix.dot(centroidErr.dot(radMatrix.T)) 

177 catCoordErr = src.get(self.srcCoordErrKey) 

178 np.testing.assert_almost_equal(coordErr, catCoordErr) 

179 

180 def checkCatalogs(self, maxPixDiff=1e-5, maxSkyDiff=0.001*lsst.geom.arcseconds): 

181 """Check that the source and reference object catalogs have equal centroids and coords""" 

182 self.assertEqual(len(self.sourceCat), len(self.refCat)) 

183 

184 for src, refObj in zip(self.sourceCat, self.refCat): 

185 self.assertTrue(refObj.get(self.refHasCentroidKey)) 

186 srcCentroid = src.get(self.srcCentroidKey) 

187 refCentroid = refObj.get(self.refCentroidKey) 

188 self.assertPairsAlmostEqual( 

189 srcCentroid, refCentroid, maxDiff=maxPixDiff) 

190 

191 srcCoord = src.get(self.srcCoordKey) 

192 refCoord = refObj.get(self.refCoordKey) 

193 self.assertSpherePointsAlmostEqual( 

194 srcCoord, refCoord, maxSep=maxSkyDiff) 

195 

196 def setCatalogs(self, maxPix, numPoints): 

197 """Set the source centroids and reference object coords 

198 

199 Set self.sourceCat centroids to a square grid of points 

200 and set self.refCat coords to the corresponding sky positions 

201 

202 The catalogs must be empty to start 

203 

204 @param[in] maxPix maximum pixel position; used for both x and y; 

205 the min is the negative of maxPix 

206 @param[in] numPoints number of points in x or y; total points = numPoints*numPoints 

207 """ 

208 if len(self.sourceCat) != 0: 

209 raise RuntimeError("self.sourceCat must be empty") 

210 if len(self.refCat) != 0: 

211 raise RuntimeError("self.refCat must be empty") 

212 

213 for i in np.linspace(-maxPix, maxPix, numPoints): 

214 for j in np.linspace(-maxPix, maxPix, numPoints): 

215 centroid = lsst.geom.Point2D(i, j) 

216 centroidErr = np.array([[0.05, 0], [0, 0.05]]) 

217 src = self.sourceCat.addNew() 

218 src.set(self.srcCentroidKey, centroid) 

219 src.set(self.srcCentroidErrKey, centroidErr) 

220 

221 refObj = self.refCat.addNew() 

222 coord = self.wcs.pixelToSky(centroid) 

223 refObj.set(self.refCoordKey, coord) 

224 

225 

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

227 pass 

228 

229 

230def setup_module(module): 

231 lsst.utils.tests.init() 

232 

233 

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

235 lsst.utils.tests.init() 

236 unittest.main()