Coverage for tests / test_transforms.py: 21%

103 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-17 08:50 +0000

1# 

2# Developed for the LSST Data Management System. 

3# This product includes software developed by the LSST Project 

4# (https://www.lsst.org). 

5# See the COPYRIGHT file at the top-level directory of this distribution 

6# for details of code ownership. 

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 GNU General Public License 

19# along with this program. If not, see <https://www.gnu.org/licenses/>. 

20# 

21 

22import unittest 

23 

24import numpy as np 

25 

26import lsst.utils.tests 

27from lsst.geom import ( 

28 LinearTransform, AffineTransform, Point2D, Extent2D, SpherePoint, SphereTransform, degrees 

29) 

30from lsst.sphgeom import UnitVector3d 

31 

32 

33class TransformTests: 

34 

35 def testVectorization(self): 

36 x1 = np.random.randn(4, 3) 

37 y1 = np.random.randn(4, 3) 

38 x2, y2 = self.transform(x1, y1) 

39 self.assertEqual(x1.shape, x2.shape) 

40 self.assertEqual(x2.shape, y2.shape) 

41 for i in range(4): 

42 for j in range(3): 

43 x3, y3 = self.transform(Point2D(x1[i, j], y1[i, j])) 

44 self.assertAlmostEqual(x3, x2[i, j], 15) 

45 self.assertAlmostEqual(y3, y2[i, j], 15) 

46 

47 

48class LinearTransformTestCase(lsst.utils.tests.TestCase, TransformTests): 

49 

50 def setUp(self): 

51 np.random.seed(5) 

52 matrix = np.random.randn(2, 2) 

53 self.transform = LinearTransform(matrix) 

54 

55 

56class AffineTransformTestCase(lsst.utils.tests.TestCase, TransformTests): 

57 

58 def setUp(self): 

59 np.random.seed(6) 

60 matrix = np.random.randn(2, 2) 

61 vector = np.random.randn(2) 

62 self.transform = AffineTransform(LinearTransform(matrix), Extent2D(vector)) 

63 

64 

65class SphereTransformTestCase(lsst.utils.tests.TestCase): 

66 

67 def setUp(self): 

68 self.x = UnitVector3d(1.0, 0.0, 0.0) 

69 self.y = UnitVector3d(0.0, 1.0, 0.0) 

70 self.z = UnitVector3d(0.0, 0.0, 1.0) 

71 self.rng = np.random.default_rng(500) 

72 

73 def make_rotation_in_plane(self, angle, i1, i2): 

74 """Make a spherical rotation transform in one plane. 

75 

76 Parameters 

77 ---------- 

78 angle : `lsst.geom.Angle` 

79 Angle to rotate by. 

80 i1 : `int` 

81 Index of the first dimension rotated (0, 1, or 2). 

82 i2 : `int` 

83 Index of the second dimension rotated (0, 1, or 2). 

84 

85 Returns 

86 ------- 

87 transform : `lsst.geom.SphereTransform` 

88 Rotation in the given plane. 

89 """ 

90 rot2d = LinearTransform.makeRotation(angle) 

91 matrix2d = rot2d.getMatrix() 

92 matrix3d = np.identity(3) 

93 matrix3d[i1, i1] = matrix2d[0, 0] 

94 matrix3d[i1, i2] = matrix2d[0, 1] 

95 matrix3d[i2, i1] = matrix2d[1, 0] 

96 matrix3d[i2, i2] = matrix2d[1, 1] 

97 return SphereTransform(matrix3d) 

98 

99 def make_random_rotation(self): 

100 """Make a random SphereTransform.""" 

101 t1 = self.make_rotation_in_plane(self.rng.uniform(0, 360)*degrees, 0, 1) 

102 t2 = self.make_rotation_in_plane(self.rng.uniform(0, 360)*degrees, 1, 2) 

103 t3 = self.make_rotation_in_plane(self.rng.uniform(0, 360)*degrees, 2, 0) 

104 return t3 * t2 * t1 

105 

106 def test_identity(self): 

107 """Test the identity transform.""" 

108 identity = SphereTransform() 

109 self.assertEqual(self.x, identity(self.x)) 

110 self.assertEqual(self.y, identity(self.y)) 

111 self.assertEqual(self.z, identity(self.z)) 

112 

113 def test_matrix(self): 

114 """Test constructing from a matrix, accessing the matrix, computing 

115 the inverse transform, and composing transforms. 

116 """ 

117 transform = self.make_rotation_in_plane(90*degrees, 0, 1) 

118 self.assertFloatsAlmostEqual( 

119 transform.getMatrix(), 

120 np.array([[0.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]), 

121 ) 

122 self.assertFloatsAlmostEqual(np.array(transform(self.x)), np.array(self.y)) 

123 self.assertFloatsAlmostEqual(np.array(transform(self.z)), np.array(self.z)) 

124 # Test inversion and composition on the simple transform and a random 

125 # one. 

126 inv = transform.inverted() 

127 self.assertFloatsAlmostEqual(np.array(inv(self.y)), np.array(self.x)) 

128 self.assertFloatsAlmostEqual(np.array(inv(self.z)), np.array(self.z)) 

129 identity = transform.inverted() * transform 

130 self.assertFloatsAlmostEqual(identity.getMatrix(), SphereTransform().getMatrix()) 

131 random_transform = self.make_random_rotation() 

132 inv_random_transform = random_transform.inverted() 

133 self.assertFloatsAlmostEqual( 

134 (random_transform * inv_random_transform).getMatrix(), SphereTransform().getMatrix() 

135 ) 

136 self.assertFloatsAlmostEqual( 

137 (inv_random_transform * random_transform).getMatrix(), SphereTransform().getMatrix()) 

138 # Test transforming SpherePoints via the random one; they should remain 

139 # 90 degrees away from each other. 

140 spx = SpherePoint(self.x) 

141 spy = SpherePoint(self.y) 

142 spz = SpherePoint(self.z) 

143 rspx = random_transform(spx) 

144 rspy = random_transform(spy) 

145 rspz = random_transform(spz) 

146 self.assertFloatsAlmostEqual(rspx.separation(rspy).asDegrees(), 90.0, rtol=1E-15) 

147 self.assertFloatsAlmostEqual(rspy.separation(rspz).asDegrees(), 90.0, rtol=1E-15) 

148 self.assertFloatsAlmostEqual(rspz.separation(rspx).asDegrees(), 90.0, rtol=1E-15) 

149 

150 def test_fit_unit_vectors(self): 

151 """Test SphereTransform.fit_unit_vectors.""" 

152 z = np.linspace(0.0, 1.0, 50) 

153 self.rng.shuffle(z) 

154 from_array = np.zeros((50, 3), dtype=float) 

155 from_array[:, 0] = (1.0 - z**2)**0.5 

156 from_array[:, 2] = z 

157 to_array = np.zeros((50, 3), dtype=float) 

158 to_array[:, 1] = (1.0 - z**2)**0.5 

159 to_array[:, 2] = z 

160 weights = np.ones(50, dtype=float) 

161 transform = SphereTransform.fit_unit_vectors(from_array, to_array, weights) 

162 self.assertFloatsAlmostEqual(np.array(transform(self.x)), np.array(self.y)) 

163 self.assertFloatsAlmostEqual(np.array(transform(self.z)), np.array(self.z)) 

164 # Rotate all of the to_ points by an arbitrary transform and fit again. 

165 random_transform = self.make_random_rotation() 

166 to_array_2 = np.zeros((50, 3), dtype=float) 

167 to_array_2[:, 0] = random_transform.applyX(to_array[:, 0], to_array[:, 1], to_array[:, 2]) 

168 to_array_2[:, 1] = random_transform.applyY(to_array[:, 0], to_array[:, 1], to_array[:, 2]) 

169 to_array_2[:, 2] = random_transform.applyZ(to_array[:, 0], to_array[:, 1], to_array[:, 2]) 

170 transform_2 = SphereTransform.fit_unit_vectors(from_array, to_array_2, weights) 

171 # The fitted rotation should be the composition: 

172 self.assertFloatsAlmostEqual( 

173 transform_2.getMatrix(), (random_transform * transform).getMatrix(), rtol=1E-15, atol=1E-15 

174 ) 

175 

176 

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

178 pass 

179 

180 

181def setup_module(module): 

182 lsst.utils.tests.init() 

183 

184 

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

186 lsst.utils.tests.init() 

187 unittest.main()