Coverage for tests/test_pessimisticPatternMatcherB3D.py: 18%
Shortcuts 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
Shortcuts 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, 2009, 2010 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#
23from copy import copy
24import unittest
26import numpy as np
28from lsst.meas.astrom.pessimistic_pattern_matcher_b_3D \
29 import PessimisticPatternMatcherB
30from lsst.log import Log
32__deg_to_rad__ = np.pi/180
35class TestPessimisticPatternMatcherB(unittest.TestCase):
37 """Unittest suite for the Pessimistic Pattern Matcher B.
38 """
40 def setUp(self):
41 np.random.seed(12345)
43 n_points = 1000
44 # reference_obj_array is a number array representing
45 # 3D points randomly draw on a 1 sq deg patch.
46 self.reference_obj_array = np.empty((n_points, 4))
47 cos_theta_array = np.random.uniform(
48 np.cos(np.pi/2 + 0.5*__deg_to_rad__),
49 np.cos(np.pi/2 - 0.5*__deg_to_rad__), size=n_points)
50 sin_theta_array = np.sqrt(1 - cos_theta_array**2)
51 phi_array = np.random.uniform(-0.5, 0.5, size=n_points)*__deg_to_rad__
52 self.reference_obj_array[:, 0] = sin_theta_array*np.cos(phi_array)
53 self.reference_obj_array[:, 1] = sin_theta_array*np.sin(phi_array)
54 self.reference_obj_array[:, 2] = cos_theta_array
55 self.reference_obj_array[:, 3] = (
56 np.random.power(1.2, size=n_points)*4 + 20)
58 # Our initial source catalog is a straight copy of the reference
59 # array at first. In some of the tests we will add rotations and
60 # shifts to the data in order to test the input and outputs of our
61 # matcher.
62 self.source_obj_array = copy(self.reference_obj_array)
63 self.log = Log()
65 def testConstructPattern(self):
66 """ Test that a specified pattern can be found in the reference
67 data and that the explicit ids match.
68 """
69 self.pyPPMb = PessimisticPatternMatcherB(
70 reference_array=self.reference_obj_array[:, :3],
71 log=self.log)
73 pattern_struct = self.pyPPMb._construct_pattern_and_shift_rot_matrix(
74 self.source_obj_array[:6, :3], 6, np.cos(np.radians(60. / 3600.)),
75 np.cos(np.radians(1.0)) ** 2, np.radians(5./3600.))
76 pattern_list = pattern_struct.ref_candidates
77 self.assertGreater(len(pattern_list), 0)
78 self.assertEqual(pattern_list[0], 0)
79 self.assertEqual(pattern_list[1], 1)
80 self.assertEqual(pattern_list[2], 2)
81 self.assertEqual(pattern_list[3], 3)
82 self.assertEqual(pattern_list[4], 4)
83 self.assertEqual(pattern_list[5], 5)
85 pattern_struct = self.pyPPMb._construct_pattern_and_shift_rot_matrix(
86 self.source_obj_array[:9, :3], 6, np.cos(np.radians(60. / 3600.)),
87 np.cos(np.radians(1.0)) ** 2, np.radians(5./3600.))
88 pattern_list = pattern_struct.ref_candidates
89 self.assertGreater(len(pattern_list), 0)
90 self.assertEqual(pattern_list[0], 0)
91 self.assertEqual(pattern_list[1], 1)
92 self.assertEqual(pattern_list[2], 2)
93 self.assertEqual(pattern_list[3], 3)
94 self.assertEqual(pattern_list[4], 4)
95 self.assertEqual(pattern_list[5], 5)
97 pattern_struct = self.pyPPMb._construct_pattern_and_shift_rot_matrix(
98 self.source_obj_array[[2, 4, 8, 16, 32, 64], :3], 6,
99 np.cos(np.radians(60. / 3600.)), np.cos(np.radians(1.0)) ** 2,
100 np.radians(5./3600.))
101 pattern_list = pattern_struct.ref_candidates
102 self.assertEqual(pattern_list[0], 2)
103 self.assertEqual(pattern_list[1], 4)
104 self.assertEqual(pattern_list[2], 8)
105 self.assertEqual(pattern_list[3], 16)
106 self.assertEqual(pattern_list[4], 32)
107 self.assertEqual(pattern_list[5], 64)
109 def testMatchPerfect(self):
110 """ Input objects that have no shift or rotation to the matcher
111 and test that we return a match.
112 """
113 self.pyPPMb = PessimisticPatternMatcherB(
114 reference_array=self.reference_obj_array[:, :3],
115 log=self.log)
117 match_struct = self.pyPPMb.match(
118 source_array=self.source_obj_array, n_check=9, n_match=6,
119 n_agree=2, max_n_patterns=100, max_shift=60., max_rotation=5.0,
120 max_dist=5., min_matches=30, pattern_skip_array=None)
121 self.assertEqual(len(match_struct.match_ids),
122 len(self.reference_obj_array))
123 self.assertTrue(
124 np.all(match_struct.distances_rad < 0.01/3600.0 * __deg_to_rad__))
126 def testOptimisticMatch(self):
127 """ Test the optimistic mode of the pattern matcher. That is
128 the algorithm with the early exit strategy as described in
129 Tabur 2007.
130 """
131 self.pyPPMb = PessimisticPatternMatcherB(
132 reference_array=self.reference_obj_array[:, :3],
133 log=self.log)
135 match_struct = self.pyPPMb.match(
136 source_array=self.source_obj_array, n_check=9, n_match=6,
137 n_agree=1, max_n_patterns=100, max_shift=60., max_rotation=6.0,
138 max_dist=5., min_matches=30, pattern_skip_array=None)
139 self.assertEqual(len(match_struct.match_ids),
140 len(self.reference_obj_array))
141 self.assertTrue(
142 np.all(match_struct.distances_rad < 0.01/3600.0 * __deg_to_rad__))
144 def testMatchSkip(self):
145 """ Test the ability to skip specified patterns in the matching
146 process.
147 """
148 self.pyPPMb = PessimisticPatternMatcherB(
149 reference_array=self.reference_obj_array[:, :3],
150 log=self.log)
152 match_struct = self.pyPPMb.match(
153 source_array=self.source_obj_array, n_check=9, n_match=6,
154 n_agree=2, max_n_patterns=100, max_shift=60., max_rotation=5.0,
155 max_dist=5., min_matches=30, pattern_skip_array=np.array([0]))
156 self.assertEqual(len(match_struct.match_ids),
157 len(self.reference_obj_array))
158 self.assertTrue(
159 np.all(match_struct.distances_rad < 0.01/3600.0 * __deg_to_rad__))
161 def testMatchMoreSources(self):
162 """ Test the case where we have more sources than references
163 but no rotation or shift.
164 """
165 self.pyPPMb = PessimisticPatternMatcherB(
166 reference_array=self.reference_obj_array[:500, :3],
167 log=self.log)
169 match_struct = self.pyPPMb.match(
170 source_array=self.source_obj_array, n_check=9, n_match=6,
171 n_agree=2, max_n_patterns=100, max_shift=60.0, max_rotation=5.0,
172 max_dist=5., min_matches=30, pattern_skip_array=None)
173 self.assertEqual(len(match_struct.match_ids),
174 len(self.reference_obj_array[:500]))
175 self.assertTrue(
176 np.all(match_struct.distances_rad < 0.01/3600.0 * __deg_to_rad__))
178 def testMatchMoreReferences(self):
179 """ Test the case where we have more references than sources
180 but no rotation or shift.
181 """
182 self.pyPPMb = PessimisticPatternMatcherB(
183 reference_array=self.reference_obj_array[:, :3],
184 log=self.log)
186 match_struct = self.pyPPMb.match(
187 source_array=self.source_obj_array[:500], n_check=9, n_match=6,
188 n_agree=2, max_n_patterns=100, max_shift=60., max_rotation=1.0,
189 max_dist=5., min_matches=30, pattern_skip_array=None)
190 self.assertEqual(len(match_struct.match_ids),
191 len(self.reference_obj_array[:500]))
192 self.assertTrue(
193 np.all(match_struct.distances_rad < 0.01/3600.0 * __deg_to_rad__))
195 def testShift(self):
196 """ Test the matcher when a shift is applied to the data.
198 We say shift here as while we are rotating the unit-sphere in 3D, on
199 our 'focal plane' this will appear as a shift.
200 """
201 self.pyPPMb = PessimisticPatternMatcherB(
202 reference_array=self.reference_obj_array[:, :3],
203 log=self.log)
204 theta = np.radians(45.0 / 3600.)
205 cos_theta = np.cos(theta)
206 sin_theta = np.sin(theta)
207 theta_rotation = self.pyPPMb._create_spherical_rotation_matrix(
208 np.array([0, 0, 1]), cos_theta, sin_theta)
210 self.source_obj_array[:, :3] = np.dot(
211 theta_rotation,
212 self.source_obj_array[:, :3].transpose()).transpose()
214 match_struct = self.pyPPMb.match(
215 source_array=self.source_obj_array, n_check=9, n_match=6,
216 n_agree=2, max_n_patterns=100, max_shift=60, max_rotation=5.0,
217 max_dist=5., min_matches=30, pattern_skip_array=None)
219 self.assertEqual(len(match_struct.match_ids),
220 len(self.reference_obj_array))
221 self.assertTrue(
222 np.all(match_struct.distances_rad < 0.01/3600.0 * __deg_to_rad__))
224 def testRotation(self):
225 """ Test the matcher for when a roation is applied to the data.
226 """
227 self.pyPPMb = PessimisticPatternMatcherB(
228 reference_array=self.reference_obj_array[:, :3],
229 log=self.log)
230 phi = 2.5*__deg_to_rad__
231 cos_phi = np.cos(phi)
232 sin_phi = np.sin(phi)
233 phi_rotation = self.pyPPMb._create_spherical_rotation_matrix(
234 np.array([1, 0, 0]), cos_phi, sin_phi)
236 self.source_obj_array[:, :3] = np.dot(
237 phi_rotation, self.source_obj_array[:, :3].transpose()).transpose()
239 match_struct = self.pyPPMb.match(
240 source_array=self.source_obj_array, n_check=9, n_match=6,
241 n_agree=2, max_n_patterns=100, max_shift=60, max_rotation=5.0,
242 max_dist=5., min_matches=30, pattern_skip_array=None)
244 self.assertEqual(len(match_struct.match_ids),
245 len(self.reference_obj_array))
246 self.assertTrue(
247 np.all(match_struct.distances_rad < 0.01/3600.0 * __deg_to_rad__))
249 def testShiftRotation(self):
250 """ Test both a shift and rotation being applied to the data.
251 """
252 self.pyPPMb = PessimisticPatternMatcherB(
253 reference_array=self.reference_obj_array[:, :3],
254 log=self.log)
255 theta = np.radians(45.0 / 3600.)
256 cos_theta = np.cos(theta)
257 sin_theta = np.sin(theta)
258 theta_rotation = self.pyPPMb._create_spherical_rotation_matrix(
259 np.array([0, 0, 1]), cos_theta, sin_theta)
261 phi = 2.5 * __deg_to_rad__
262 cos_phi = np.cos(phi)
263 sin_phi = np.sin(phi)
264 phi_rotation = self.pyPPMb._create_spherical_rotation_matrix(
265 np.array([1, 0, 0]), cos_phi, sin_phi)
267 shift_rot_matrix = np.dot(theta_rotation, phi_rotation)
269 self.source_obj_array[:, :3] = np.dot(
270 shift_rot_matrix,
271 self.source_obj_array[:, :3].transpose()).transpose()
273 match_struct = self.pyPPMb.match(
274 source_array=self.source_obj_array, n_check=9, n_match=6,
275 n_agree=2, max_n_patterns=100, max_shift=60., max_rotation=5.0,
276 max_dist=5., min_matches=30, pattern_skip_array=None)
278 self.assertEqual(len(match_struct.match_ids),
279 len(self.reference_obj_array))
280 self.assertTrue(
281 np.all(match_struct.distances_rad < 0.01/3600.0 * __deg_to_rad__))
283 def testLinearDistortion(self):
284 """ Create a simple linear distortion and test that the correct
285 references are still matched.
286 """
288 self.pyPPMb = PessimisticPatternMatcherB(
289 reference_array=self.reference_obj_array[:, :3],
290 log=self.log)
292 max_z = np.cos(np.pi/2 + 0.5 * __deg_to_rad__)
293 min_z = np.cos(np.pi/2 - 0.5 * __deg_to_rad__)
294 # The max shift in position we add to the position will be 25
295 # arcseconds.
296 max_distort = 25.0 / 3600. * __deg_to_rad__
297 self.source_obj_array[:, 2] = (
298 self.source_obj_array[:, 2]
299 - max_distort * (self.source_obj_array[:, 2] - min_z)
300 / (max_z - min_z))
301 # Renomalize the 3 vectors to be unit length.
302 distorted_dists = np.sqrt(self.source_obj_array[:, 0] ** 2
303 + self.source_obj_array[:, 1] ** 2
304 + self.source_obj_array[:, 2] ** 2)
305 self.source_obj_array[:, 0] /= distorted_dists
306 self.source_obj_array[:, 1] /= distorted_dists
307 self.source_obj_array[:, 2] /= distorted_dists
309 match_struct = self.pyPPMb.match(
310 source_array=self.source_obj_array, n_check=9, n_match=6,
311 n_agree=2, max_n_patterns=100, max_shift=60., max_rotation=5.0,
312 max_dist=5., min_matches=30, pattern_skip_array=None)
314 self.assertEqual(len(match_struct.match_ids),
315 len(self.reference_obj_array))
316 self.assertTrue(
317 np.all(match_struct.distances_rad < 10 / 3600.0 * __deg_to_rad__))
319 def testNoReferenceSources(self):
320 """Check that we get a helpful error when no reference objects are
321 supplied.
322 """
323 with self.assertRaisesRegex(ValueError, "No reference objects supplied"):
324 PessimisticPatternMatcherB(np.ndarray((0, 3)), self.log)
327if __name__ == '__main__': 327 ↛ 328line 327 didn't jump to line 328, because the condition on line 327 was never true
328 unittest.main()