Coverage for tests/test_pessimisticPatternMatcherB3D.py: 16%

126 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-02-07 11:19 +0000

1# This file is part of meas_astrom. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

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

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

7# for details of code ownership. 

8# 

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

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

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

12# (at your option) any later version. 

13# 

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

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

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

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

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

21 

22from copy import copy 

23import unittest 

24import logging 

25 

26import numpy as np 

27 

28from lsst.meas.astrom.pessimistic_pattern_matcher_b_3D \ 

29 import PessimisticPatternMatcherB 

30 

31__deg_to_rad__ = np.pi/180 

32 

33 

34class TestPessimisticPatternMatcherB(unittest.TestCase): 

35 

36 """Unittest suite for the Pessimistic Pattern Matcher B. 

37 """ 

38 

39 def setUp(self): 

40 np.random.seed(12345) 

41 

42 n_points = 1000 

43 # reference_obj_array is a number array representing 

44 # 3D points randomly draw on a 1 sq deg patch. 

45 self.reference_obj_array = np.empty((n_points, 4)) 

46 cos_theta_array = np.random.uniform( 

47 np.cos(np.pi/2 + 0.5*__deg_to_rad__), 

48 np.cos(np.pi/2 - 0.5*__deg_to_rad__), size=n_points) 

49 sin_theta_array = np.sqrt(1 - cos_theta_array**2) 

50 phi_array = np.random.uniform(-0.5, 0.5, size=n_points)*__deg_to_rad__ 

51 self.reference_obj_array[:, 0] = sin_theta_array*np.cos(phi_array) 

52 self.reference_obj_array[:, 1] = sin_theta_array*np.sin(phi_array) 

53 self.reference_obj_array[:, 2] = cos_theta_array 

54 self.reference_obj_array[:, 3] = ( 

55 np.random.power(1.2, size=n_points)*4 + 20) 

56 

57 # Our initial source catalog is a straight copy of the reference 

58 # array at first. In some of the tests we will add rotations and 

59 # shifts to the data in order to test the input and outputs of our 

60 # matcher. 

61 self.source_obj_array = copy(self.reference_obj_array) 

62 self.log = logging.getLogger(__name__) 

63 

64 def testConstructPattern(self): 

65 """ Test that a specified pattern can be found in the reference 

66 data and that the explicit ids match. 

67 """ 

68 self.pyPPMb = PessimisticPatternMatcherB( 

69 reference_array=self.reference_obj_array[:, :3], 

70 log=self.log) 

71 

72 pattern_struct = self.pyPPMb._construct_pattern_and_shift_rot_matrix( 

73 self.source_obj_array[:6, :3], 6, np.cos(np.radians(60. / 3600.)), 

74 np.cos(np.radians(1.0)) ** 2, np.radians(5./3600.)) 

75 pattern_list = pattern_struct.ref_candidates 

76 self.assertGreater(len(pattern_list), 0) 

77 self.assertEqual(pattern_list[0], 0) 

78 self.assertEqual(pattern_list[1], 1) 

79 self.assertEqual(pattern_list[2], 2) 

80 self.assertEqual(pattern_list[3], 3) 

81 self.assertEqual(pattern_list[4], 4) 

82 self.assertEqual(pattern_list[5], 5) 

83 

84 pattern_struct = self.pyPPMb._construct_pattern_and_shift_rot_matrix( 

85 self.source_obj_array[:9, :3], 6, np.cos(np.radians(60. / 3600.)), 

86 np.cos(np.radians(1.0)) ** 2, np.radians(5./3600.)) 

87 pattern_list = pattern_struct.ref_candidates 

88 self.assertGreater(len(pattern_list), 0) 

89 self.assertEqual(pattern_list[0], 0) 

90 self.assertEqual(pattern_list[1], 1) 

91 self.assertEqual(pattern_list[2], 2) 

92 self.assertEqual(pattern_list[3], 3) 

93 self.assertEqual(pattern_list[4], 4) 

94 self.assertEqual(pattern_list[5], 5) 

95 

96 pattern_struct = self.pyPPMb._construct_pattern_and_shift_rot_matrix( 

97 self.source_obj_array[[2, 4, 8, 16, 32, 64], :3], 6, 

98 np.cos(np.radians(60. / 3600.)), np.cos(np.radians(1.0)) ** 2, 

99 np.radians(5./3600.)) 

100 pattern_list = pattern_struct.ref_candidates 

101 self.assertEqual(pattern_list[0], 2) 

102 self.assertEqual(pattern_list[1], 4) 

103 self.assertEqual(pattern_list[2], 8) 

104 self.assertEqual(pattern_list[3], 16) 

105 self.assertEqual(pattern_list[4], 32) 

106 self.assertEqual(pattern_list[5], 64) 

107 

108 def testMatchPerfect(self): 

109 """ Input objects that have no shift or rotation to the matcher 

110 and test that we return a match. 

111 """ 

112 self.pyPPMb = PessimisticPatternMatcherB( 

113 reference_array=self.reference_obj_array[:, :3], 

114 log=self.log) 

115 

116 match_struct = self.pyPPMb.match( 

117 source_array=self.source_obj_array, n_check=9, n_match=6, 

118 n_agree=2, max_n_patterns=100, max_shift=60., max_rotation=5.0, 

119 max_dist=5., min_matches=30, pattern_skip_array=None) 

120 self.assertEqual(len(match_struct.match_ids), 

121 len(self.reference_obj_array)) 

122 self.assertTrue( 

123 np.all(match_struct.distances_rad < 0.01/3600.0 * __deg_to_rad__)) 

124 

125 def testOptimisticMatch(self): 

126 """ Test the optimistic mode of the pattern matcher. That is 

127 the algorithm with the early exit strategy as described in 

128 Tabur 2007. 

129 """ 

130 self.pyPPMb = PessimisticPatternMatcherB( 

131 reference_array=self.reference_obj_array[:, :3], 

132 log=self.log) 

133 

134 match_struct = self.pyPPMb.match( 

135 source_array=self.source_obj_array, n_check=9, n_match=6, 

136 n_agree=1, max_n_patterns=100, max_shift=60., max_rotation=6.0, 

137 max_dist=5., min_matches=30, pattern_skip_array=None) 

138 self.assertEqual(len(match_struct.match_ids), 

139 len(self.reference_obj_array)) 

140 self.assertTrue( 

141 np.all(match_struct.distances_rad < 0.01/3600.0 * __deg_to_rad__)) 

142 

143 def testMatchSkip(self): 

144 """ Test the ability to skip specified patterns in the matching 

145 process. 

146 """ 

147 self.pyPPMb = PessimisticPatternMatcherB( 

148 reference_array=self.reference_obj_array[:, :3], 

149 log=self.log) 

150 

151 match_struct = self.pyPPMb.match( 

152 source_array=self.source_obj_array, n_check=9, n_match=6, 

153 n_agree=2, max_n_patterns=100, max_shift=60., max_rotation=5.0, 

154 max_dist=5., min_matches=30, pattern_skip_array=np.array([0])) 

155 self.assertEqual(len(match_struct.match_ids), 

156 len(self.reference_obj_array)) 

157 self.assertTrue( 

158 np.all(match_struct.distances_rad < 0.01/3600.0 * __deg_to_rad__)) 

159 

160 def testMatchMoreSources(self): 

161 """ Test the case where we have more sources than references 

162 but no rotation or shift. 

163 """ 

164 self.pyPPMb = PessimisticPatternMatcherB( 

165 reference_array=self.reference_obj_array[:500, :3], 

166 log=self.log) 

167 

168 match_struct = self.pyPPMb.match( 

169 source_array=self.source_obj_array, n_check=9, n_match=6, 

170 n_agree=2, max_n_patterns=100, max_shift=60.0, max_rotation=5.0, 

171 max_dist=5., min_matches=30, pattern_skip_array=None) 

172 self.assertEqual(len(match_struct.match_ids), 

173 len(self.reference_obj_array[:500])) 

174 self.assertTrue( 

175 np.all(match_struct.distances_rad < 0.01/3600.0 * __deg_to_rad__)) 

176 

177 def testMatchMoreReferences(self): 

178 """ Test the case where we have more references than sources 

179 but no rotation or shift. 

180 """ 

181 self.pyPPMb = PessimisticPatternMatcherB( 

182 reference_array=self.reference_obj_array[:, :3], 

183 log=self.log) 

184 

185 match_struct = self.pyPPMb.match( 

186 source_array=self.source_obj_array[:500], n_check=9, n_match=6, 

187 n_agree=2, max_n_patterns=100, max_shift=60., max_rotation=1.0, 

188 max_dist=5., min_matches=30, pattern_skip_array=None) 

189 self.assertEqual(len(match_struct.match_ids), 

190 len(self.reference_obj_array[:500])) 

191 self.assertTrue( 

192 np.all(match_struct.distances_rad < 0.01/3600.0 * __deg_to_rad__)) 

193 

194 def testShift(self): 

195 """ Test the matcher when a shift is applied to the data. 

196 

197 We say shift here as while we are rotating the unit-sphere in 3D, on 

198 our 'focal plane' this will appear as a shift. 

199 """ 

200 self.pyPPMb = PessimisticPatternMatcherB( 

201 reference_array=self.reference_obj_array[:, :3], 

202 log=self.log) 

203 theta = np.radians(45.0 / 3600.) 

204 cos_theta = np.cos(theta) 

205 sin_theta = np.sin(theta) 

206 theta_rotation = self.pyPPMb._create_spherical_rotation_matrix( 

207 np.array([0, 0, 1]), cos_theta, sin_theta) 

208 

209 self.source_obj_array[:, :3] = np.dot( 

210 theta_rotation, 

211 self.source_obj_array[:, :3].transpose()).transpose() 

212 

213 match_struct = self.pyPPMb.match( 

214 source_array=self.source_obj_array, n_check=9, n_match=6, 

215 n_agree=2, max_n_patterns=100, max_shift=60, max_rotation=5.0, 

216 max_dist=5., min_matches=30, pattern_skip_array=None) 

217 

218 self.assertEqual(len(match_struct.match_ids), 

219 len(self.reference_obj_array)) 

220 self.assertTrue( 

221 np.all(match_struct.distances_rad < 0.01/3600.0 * __deg_to_rad__)) 

222 

223 def testRotation(self): 

224 """ Test the matcher for when a roation is applied to the data. 

225 """ 

226 self.pyPPMb = PessimisticPatternMatcherB( 

227 reference_array=self.reference_obj_array[:, :3], 

228 log=self.log) 

229 phi = 2.5*__deg_to_rad__ 

230 cos_phi = np.cos(phi) 

231 sin_phi = np.sin(phi) 

232 phi_rotation = self.pyPPMb._create_spherical_rotation_matrix( 

233 np.array([1, 0, 0]), cos_phi, sin_phi) 

234 

235 self.source_obj_array[:, :3] = np.dot( 

236 phi_rotation, self.source_obj_array[:, :3].transpose()).transpose() 

237 

238 match_struct = self.pyPPMb.match( 

239 source_array=self.source_obj_array, n_check=9, n_match=6, 

240 n_agree=2, max_n_patterns=100, max_shift=60, max_rotation=5.0, 

241 max_dist=5., min_matches=30, pattern_skip_array=None) 

242 

243 self.assertEqual(len(match_struct.match_ids), 

244 len(self.reference_obj_array)) 

245 self.assertTrue( 

246 np.all(match_struct.distances_rad < 0.01/3600.0 * __deg_to_rad__)) 

247 

248 def testShiftRotation(self): 

249 """ Test both a shift and rotation being applied to the data. 

250 """ 

251 self.pyPPMb = PessimisticPatternMatcherB( 

252 reference_array=self.reference_obj_array[:, :3], 

253 log=self.log) 

254 theta = np.radians(45.0 / 3600.) 

255 cos_theta = np.cos(theta) 

256 sin_theta = np.sin(theta) 

257 theta_rotation = self.pyPPMb._create_spherical_rotation_matrix( 

258 np.array([0, 0, 1]), cos_theta, sin_theta) 

259 

260 phi = 2.5 * __deg_to_rad__ 

261 cos_phi = np.cos(phi) 

262 sin_phi = np.sin(phi) 

263 phi_rotation = self.pyPPMb._create_spherical_rotation_matrix( 

264 np.array([1, 0, 0]), cos_phi, sin_phi) 

265 

266 shift_rot_matrix = np.dot(theta_rotation, phi_rotation) 

267 

268 self.source_obj_array[:, :3] = np.dot( 

269 shift_rot_matrix, 

270 self.source_obj_array[:, :3].transpose()).transpose() 

271 

272 match_struct = self.pyPPMb.match( 

273 source_array=self.source_obj_array, n_check=9, n_match=6, 

274 n_agree=2, max_n_patterns=100, max_shift=60., max_rotation=5.0, 

275 max_dist=5., min_matches=30, pattern_skip_array=None) 

276 

277 self.assertEqual(len(match_struct.match_ids), 

278 len(self.reference_obj_array)) 

279 self.assertTrue( 

280 np.all(match_struct.distances_rad < 0.01/3600.0 * __deg_to_rad__)) 

281 

282 def testLinearDistortion(self): 

283 """ Create a simple linear distortion and test that the correct 

284 references are still matched. 

285 """ 

286 

287 self.pyPPMb = PessimisticPatternMatcherB( 

288 reference_array=self.reference_obj_array[:, :3], 

289 log=self.log) 

290 

291 max_z = np.cos(np.pi/2 + 0.5 * __deg_to_rad__) 

292 min_z = np.cos(np.pi/2 - 0.5 * __deg_to_rad__) 

293 # The max shift in position we add to the position will be 25 

294 # arcseconds. 

295 max_distort = 25.0 / 3600. * __deg_to_rad__ 

296 self.source_obj_array[:, 2] = ( 

297 self.source_obj_array[:, 2] 

298 - max_distort * (self.source_obj_array[:, 2] - min_z) 

299 / (max_z - min_z)) 

300 # Renomalize the 3 vectors to be unit length. 

301 distorted_dists = np.sqrt(self.source_obj_array[:, 0] ** 2 

302 + self.source_obj_array[:, 1] ** 2 

303 + self.source_obj_array[:, 2] ** 2) 

304 self.source_obj_array[:, 0] /= distorted_dists 

305 self.source_obj_array[:, 1] /= distorted_dists 

306 self.source_obj_array[:, 2] /= distorted_dists 

307 

308 match_struct = self.pyPPMb.match( 

309 source_array=self.source_obj_array, n_check=9, n_match=6, 

310 n_agree=2, max_n_patterns=100, max_shift=60., max_rotation=5.0, 

311 max_dist=5., min_matches=30, pattern_skip_array=None) 

312 

313 self.assertEqual(len(match_struct.match_ids), 

314 len(self.reference_obj_array)) 

315 self.assertTrue( 

316 np.all(match_struct.distances_rad < 10 / 3600.0 * __deg_to_rad__)) 

317 

318 def testNoReferenceSources(self): 

319 """Check that we get a helpful error when no reference objects are 

320 supplied. 

321 """ 

322 with self.assertRaisesRegex(ValueError, "No reference objects supplied"): 

323 PessimisticPatternMatcherB(np.ndarray((0, 3)), self.log) 

324 

325 

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

327 unittest.main()