ruststartracker 0.2.9__tar.gz → 0.2.10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ruststartracker-0.2.9 → ruststartracker-0.2.10}/PKG-INFO +1 -1
- {ruststartracker-0.2.9 → ruststartracker-0.2.10}/pyproject.toml +2 -2
- {ruststartracker-0.2.9 → ruststartracker-0.2.10}/ruststartracker/libruststartracker.pyi +3 -21
- ruststartracker-0.2.10/ruststartracker/test_backend.py +257 -0
- ruststartracker-0.2.9/ruststartracker/test_backend.py +0 -144
- {ruststartracker-0.2.9 → ruststartracker-0.2.10}/LICENSE +0 -0
- {ruststartracker-0.2.9 → ruststartracker-0.2.10}/README.md +0 -0
- {ruststartracker-0.2.9 → ruststartracker-0.2.10}/build_script.py +0 -0
- {ruststartracker-0.2.9 → ruststartracker-0.2.10}/ruststartracker/__init__.py +0 -0
- {ruststartracker-0.2.9 → ruststartracker-0.2.10}/ruststartracker/catalog.py +0 -0
- {ruststartracker-0.2.9 → ruststartracker-0.2.10}/ruststartracker/py.typed +0 -0
- {ruststartracker-0.2.9 → ruststartracker-0.2.10}/ruststartracker/star.py +0 -0
- {ruststartracker-0.2.9 → ruststartracker-0.2.10}/ruststartracker/test_catalog.py +0 -0
- {ruststartracker-0.2.9 → ruststartracker-0.2.10}/ruststartracker/test_integration.py +0 -0
- {ruststartracker-0.2.9 → ruststartracker-0.2.10}/ruststartracker/test_star.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "ruststartracker"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.10"
|
|
4
4
|
description = "Lightweight Python Star Tracker With Rust Backend"
|
|
5
5
|
authors = ["Nicolas Tobler <nitobler@gmail.com>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -44,7 +44,7 @@ requires = ["poetry-core", "astroquery>=0.4.10"]
|
|
|
44
44
|
build-backend = "poetry.core.masonry.api"
|
|
45
45
|
|
|
46
46
|
[tool.bumpversion]
|
|
47
|
-
current_version = "0.2.
|
|
47
|
+
current_version = "0.2.10"
|
|
48
48
|
commit = true
|
|
49
49
|
tag = true
|
|
50
50
|
tag_name = "v{new_version}"
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from collections.abc import Iterator
|
|
2
|
-
|
|
3
1
|
import numpy as np
|
|
4
2
|
import numpy.typing as npt
|
|
5
3
|
from typing_extensions import Self
|
|
@@ -26,24 +24,6 @@ class StarMatcher:
|
|
|
26
24
|
float,
|
|
27
25
|
]: ...
|
|
28
26
|
|
|
29
|
-
class TriangleFinder:
|
|
30
|
-
def __init__(
|
|
31
|
-
self,
|
|
32
|
-
ab: npt.NDArray[np.float32],
|
|
33
|
-
ac: npt.NDArray[np.float32],
|
|
34
|
-
bc: npt.NDArray[np.float32],
|
|
35
|
-
) -> None: ...
|
|
36
|
-
def get(self) -> list[int]: ...
|
|
37
|
-
|
|
38
|
-
class IterTriangleFinder:
|
|
39
|
-
def __init__(
|
|
40
|
-
self,
|
|
41
|
-
ab: npt.NDArray[np.float32],
|
|
42
|
-
ac: npt.NDArray[np.float32],
|
|
43
|
-
bc: npt.NDArray[np.float32],
|
|
44
|
-
) -> None: ...
|
|
45
|
-
def __iter__(self) -> Iterator[list[int]]: ...
|
|
46
|
-
|
|
47
27
|
class UnitVectorLookup:
|
|
48
28
|
def __init__(self, vec: npt.NDArray[np.float32]) -> None: ...
|
|
49
29
|
def lookup_nearest(self, key: npt.NDArray[np.float32]) -> int: ...
|
|
@@ -53,7 +33,9 @@ class UnitVectorLookup:
|
|
|
53
33
|
magnitudes: npt.NDArray[np.float32],
|
|
54
34
|
max_angle_rad: float,
|
|
55
35
|
max_magnitude: float,
|
|
56
|
-
|
|
36
|
+
inter_star_angle: float,
|
|
37
|
+
tolerance_angle: float,
|
|
38
|
+
) -> tuple[list[list[int]], list[float], list[float], list[list[int]]]: ...
|
|
57
39
|
def look_up_close_angles(
|
|
58
40
|
self,
|
|
59
41
|
vectors: npt.NDArray[np.float32],
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pytest
|
|
5
|
+
import scipy.spatial
|
|
6
|
+
|
|
7
|
+
from ruststartracker import libruststartracker
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_lookup_nearest():
|
|
11
|
+
rng = np.random.default_rng(42)
|
|
12
|
+
n_vecs = 2617
|
|
13
|
+
|
|
14
|
+
vec = rng.normal(size=[n_vecs, 3]).astype(np.float32)
|
|
15
|
+
vec /= np.linalg.norm(vec, axis=-1, keepdims=True)
|
|
16
|
+
|
|
17
|
+
uvl = libruststartracker.UnitVectorLookup(vec)
|
|
18
|
+
|
|
19
|
+
keys = rng.normal(size=[10, 3]).astype(np.float32)
|
|
20
|
+
keys /= np.linalg.norm(keys, axis=-1, keepdims=True)
|
|
21
|
+
|
|
22
|
+
for key in keys:
|
|
23
|
+
np.testing.assert_array_equal(
|
|
24
|
+
uvl.lookup_nearest(key),
|
|
25
|
+
np.linalg.norm(vec - key, axis=-1).argmin().item(),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_close_angle_lookup():
|
|
30
|
+
rng = np.random.default_rng(42)
|
|
31
|
+
n_vecs = 2617
|
|
32
|
+
|
|
33
|
+
vec = rng.normal(size=[n_vecs, 3]).astype(np.float32)
|
|
34
|
+
vec /= np.linalg.norm(vec, axis=-1, keepdims=True)
|
|
35
|
+
|
|
36
|
+
uvl = libruststartracker.UnitVectorLookup(vec)
|
|
37
|
+
|
|
38
|
+
angle_threshold = np.radians(15)
|
|
39
|
+
|
|
40
|
+
threshold = np.cos(angle_threshold).item()
|
|
41
|
+
pairs_gt = []
|
|
42
|
+
angles_gt = []
|
|
43
|
+
for a in range(len(vec)):
|
|
44
|
+
dotp = np.sum(vec[a] * vec[a + 1 :], axis=-1)
|
|
45
|
+
b = np.nonzero(dotp >= threshold)[0]
|
|
46
|
+
pairs_gt.append(np.array([np.full(len(b), a), (a + 1) + b]))
|
|
47
|
+
angles_gt.append(np.arccos(dotp[b]))
|
|
48
|
+
angles_gt = np.concatenate(angles_gt, axis=0)
|
|
49
|
+
pairs_gt = np.concatenate(pairs_gt, axis=-1).T
|
|
50
|
+
args = np.argsort(angles_gt)
|
|
51
|
+
pairs_gt = pairs_gt[args]
|
|
52
|
+
angles_gt = angles_gt[args]
|
|
53
|
+
|
|
54
|
+
res = uvl.look_up_close_angles(
|
|
55
|
+
np.array(vec, dtype=np.float32),
|
|
56
|
+
np.ones(len(vec), dtype=np.float32),
|
|
57
|
+
np.cos(angle_threshold).item(),
|
|
58
|
+
10,
|
|
59
|
+
)
|
|
60
|
+
pairs = np.array([r[0] for r in res])
|
|
61
|
+
angles_proxy = np.array([r[1] for r in res])
|
|
62
|
+
angles = np.arccos(angles_proxy)
|
|
63
|
+
args = np.argsort(angles)
|
|
64
|
+
pairs = pairs[args]
|
|
65
|
+
angles = angles[args]
|
|
66
|
+
|
|
67
|
+
# Check if the same angles are returned (order has already been normalized by the sorting)
|
|
68
|
+
np.testing.assert_allclose(angles, angles_gt)
|
|
69
|
+
|
|
70
|
+
# There are some cases where the float32 accuracy is insufficient to tell
|
|
71
|
+
# angles apart. Consequently the order may be slightly different. However,
|
|
72
|
+
# we're able to test if the not-matching indices align with items that have
|
|
73
|
+
# at minimum one other angle of the exact same value
|
|
74
|
+
i = (pairs != pairs_gt).any(axis=-1)
|
|
75
|
+
assert np.mean(i) < 0.1, "More than 10 percent of pairs do not match"
|
|
76
|
+
assert (np.unique(angles[i], return_counts=True)[-1] >= 2).all()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_get_inter_star_index():
|
|
80
|
+
os.environ["RUST_BACKTRACE"] = "1"
|
|
81
|
+
|
|
82
|
+
rng = np.random.default_rng(42)
|
|
83
|
+
n_vecs = 2617
|
|
84
|
+
|
|
85
|
+
vec = rng.normal(size=[n_vecs, 3]).astype(np.float32)
|
|
86
|
+
vec /= np.linalg.norm(vec, axis=-1, keepdims=True)
|
|
87
|
+
|
|
88
|
+
uvl = libruststartracker.UnitVectorLookup(vec)
|
|
89
|
+
|
|
90
|
+
angle_threshold = np.radians(15)
|
|
91
|
+
|
|
92
|
+
threshold = np.cos(angle_threshold).item()
|
|
93
|
+
pairs_gt = []
|
|
94
|
+
angles_gt = []
|
|
95
|
+
for a in range(len(vec)):
|
|
96
|
+
dotp = np.sum(vec[a] * vec[a + 1 :], axis=-1)
|
|
97
|
+
b = np.nonzero(dotp >= threshold)[0]
|
|
98
|
+
pairs_gt.append(np.array([np.full(len(b), a), (a + 1) + b]))
|
|
99
|
+
angles_gt.append(np.arccos(dotp[b]))
|
|
100
|
+
angles_gt = np.concatenate(angles_gt, axis=0)
|
|
101
|
+
pairs_gt = np.concatenate(pairs_gt, axis=-1).T
|
|
102
|
+
args = np.argsort(angles_gt)
|
|
103
|
+
pairs_gt = pairs_gt[args]
|
|
104
|
+
angles_gt = angles_gt[args]
|
|
105
|
+
|
|
106
|
+
angle_proxy_gt = np.cos(angles_gt)
|
|
107
|
+
|
|
108
|
+
lookup_center = np.radians(7).item()
|
|
109
|
+
lookup_tolerance = np.radians(0.1).item()
|
|
110
|
+
|
|
111
|
+
pairs, angles_proxy, poly, looked_up_pairs = uvl.get_inter_star_index(
|
|
112
|
+
np.array(vec, dtype=np.float32),
|
|
113
|
+
np.ones(len(vec), dtype=np.float32),
|
|
114
|
+
angle_threshold,
|
|
115
|
+
10,
|
|
116
|
+
lookup_center,
|
|
117
|
+
lookup_tolerance,
|
|
118
|
+
)
|
|
119
|
+
pairs = np.array(pairs)
|
|
120
|
+
angles_proxy = np.array(angles_proxy)
|
|
121
|
+
poly = np.array(poly)[::-1] # Reverse polynomial to match numpy's polyval order
|
|
122
|
+
looked_up_pairs = np.array(looked_up_pairs)
|
|
123
|
+
|
|
124
|
+
# angles are monotonically increasing, so the angle proxy should decreasing
|
|
125
|
+
assert np.diff(angles_proxy).max() <= 0, "Angle proxy is not sorted"
|
|
126
|
+
np.testing.assert_allclose(angles_proxy, angle_proxy_gt, rtol=1e-4, atol=1e-12)
|
|
127
|
+
|
|
128
|
+
# There are some cases where the float32 accuracy is insufficient to tell
|
|
129
|
+
# angles apart. Consequently the order may be slightly different. However,
|
|
130
|
+
# we're able to test if the not-matching indices align with items that have
|
|
131
|
+
# at minimum one other angle of the exact same value
|
|
132
|
+
i = (pairs != pairs_gt).any(axis=-1)
|
|
133
|
+
assert np.mean(i) < 0.1, "More than 10 percent of pairs do not match"
|
|
134
|
+
assert (np.unique(angles_proxy[i], return_counts=True)[-1] >= 2).all()
|
|
135
|
+
|
|
136
|
+
scale = len(pairs_gt) + 1
|
|
137
|
+
|
|
138
|
+
lookup_index = np.polyval(poly, (1.0 - angles_proxy) * 10.0) * scale
|
|
139
|
+
|
|
140
|
+
transformed_angle_proxy_gt = (1.0 - angle_proxy_gt) * 10.0
|
|
141
|
+
|
|
142
|
+
indices = np.arange(angle_proxy_gt.size)
|
|
143
|
+
scaled_indices = indices * (1.0 / scale)
|
|
144
|
+
poly_gt = np.polyfit(transformed_angle_proxy_gt, scaled_indices, 2)
|
|
145
|
+
lookup_index_gt = np.polyval(poly_gt, transformed_angle_proxy_gt) * scale
|
|
146
|
+
max_val = (lookup_index_gt - indices).max()
|
|
147
|
+
min_val = (lookup_index_gt - indices).min()
|
|
148
|
+
print(max_val, min_val)
|
|
149
|
+
poly_gt[-1] -= max_val / scale
|
|
150
|
+
lookup_index_gt = np.polyval(poly_gt, transformed_angle_proxy_gt) * scale
|
|
151
|
+
|
|
152
|
+
if False:
|
|
153
|
+
import matplotlib.pyplot as plt
|
|
154
|
+
|
|
155
|
+
fig, axs = plt.subplots()
|
|
156
|
+
axs.plot(angle_proxy_gt, label="angle proxy gt")
|
|
157
|
+
axs.plot(angles_proxy, label="angle proxy")
|
|
158
|
+
axs.legend()
|
|
159
|
+
|
|
160
|
+
fig, axs = plt.subplots(2, sharex=True)
|
|
161
|
+
axs[0].plot(angle_proxy_gt, lookup_index_gt, "--", label="lookup index gt")
|
|
162
|
+
axs[0].plot(angle_proxy_gt, indices, label="actual index gt")
|
|
163
|
+
axs[0].plot(angles_proxy, lookup_index, "--", label="lookup index")
|
|
164
|
+
axs[0].plot(angles_proxy, indices, label="actual index")
|
|
165
|
+
axs[0].legend()
|
|
166
|
+
|
|
167
|
+
axs[1].plot(angle_proxy_gt, lookup_index_gt - indices, label="lookup error gt")
|
|
168
|
+
axs[1].plot(angles_proxy, lookup_index - indices, label="lookup error")
|
|
169
|
+
axs[1].legend()
|
|
170
|
+
|
|
171
|
+
fig, axs = plt.subplots()
|
|
172
|
+
axs.plot(poly_gt, "x", label="polynomial fit gt")
|
|
173
|
+
axs.plot(poly, "x", label="polynomial fit")
|
|
174
|
+
axs.legend()
|
|
175
|
+
plt.show()
|
|
176
|
+
|
|
177
|
+
np.testing.assert_allclose(lookup_index_gt - indices, lookup_index - indices, atol=0.1)
|
|
178
|
+
|
|
179
|
+
# Fail if the polynomial fit is not accurate enough to represent the angles closely
|
|
180
|
+
assert (
|
|
181
|
+
max_val - min_val < 400
|
|
182
|
+
), "Polynomial fit is not accurate enough to preserve order of angles"
|
|
183
|
+
|
|
184
|
+
# Check if the polynomial fit is correct
|
|
185
|
+
np.testing.assert_allclose(poly, poly_gt, rtol=1e-3, atol=1e-5)
|
|
186
|
+
|
|
187
|
+
# Check if the looked up pairs are correct
|
|
188
|
+
|
|
189
|
+
mask = (angles_gt > lookup_center - lookup_tolerance) * (
|
|
190
|
+
angles_gt < lookup_center + lookup_tolerance
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
matching_pairs_gt = pairs_gt[mask]
|
|
194
|
+
|
|
195
|
+
# Due to f32 precision limitations and cosines close to 1.0,
|
|
196
|
+
# we may miss some pairs that are very close to the lookup center.
|
|
197
|
+
min_len = min(len(matching_pairs_gt), len(looked_up_pairs))
|
|
198
|
+
assert (
|
|
199
|
+
min_len / len(matching_pairs_gt) > 0.95
|
|
200
|
+
), "looked up less than 95 percent of the correct pairs"
|
|
201
|
+
|
|
202
|
+
# There are some cases where the float32 accuracy is insufficient to tell
|
|
203
|
+
# angles apart. Consequently the order may be slightly different. However,
|
|
204
|
+
# we're able to test if the not-matching indices align with items that have
|
|
205
|
+
# at minimum one other angle of the exact same value
|
|
206
|
+
i = (matching_pairs_gt[:min_len] != looked_up_pairs[:min_len]).any(axis=-1)
|
|
207
|
+
assert np.mean(i) < 0.1, "More than 10 percent of pairs do not match"
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def test_star_matcher():
|
|
211
|
+
rng = np.random.default_rng(42)
|
|
212
|
+
|
|
213
|
+
os.environ["RUST_BACKTRACE"] = "1"
|
|
214
|
+
|
|
215
|
+
n_cat_stars = 2617
|
|
216
|
+
|
|
217
|
+
vec = rng.normal(size=[n_cat_stars, 3]).astype(np.float32)
|
|
218
|
+
vec /= np.linalg.norm(vec, axis=-1, keepdims=True)
|
|
219
|
+
|
|
220
|
+
magnitudes = rng.uniform(0, 10, size=vec.shape[:1]).astype(np.float32)
|
|
221
|
+
|
|
222
|
+
key = rng.normal(size=[3]).astype(np.float32)
|
|
223
|
+
key /= np.linalg.norm(key, axis=-1, keepdims=True)
|
|
224
|
+
|
|
225
|
+
angle_threshold = np.radians(7)
|
|
226
|
+
dotp = np.sum(key * vec, axis=-1)
|
|
227
|
+
threshold = np.cos(angle_threshold).item()
|
|
228
|
+
b = np.nonzero(dotp >= threshold)[0]
|
|
229
|
+
obs_index = rng.permutation(b)
|
|
230
|
+
obs = vec[obs_index]
|
|
231
|
+
|
|
232
|
+
rot = scipy.spatial.transform.Rotation.from_rotvec([1, 1, 1])
|
|
233
|
+
|
|
234
|
+
obs_rotated = rot.apply(obs).astype(np.float32)
|
|
235
|
+
|
|
236
|
+
index = libruststartracker.StarMatcher(
|
|
237
|
+
vec,
|
|
238
|
+
magnitudes,
|
|
239
|
+
10,
|
|
240
|
+
np.radians(10).item(),
|
|
241
|
+
np.radians(0.1).item(),
|
|
242
|
+
4,
|
|
243
|
+
999.0,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
res = index.find(obs_rotated)
|
|
247
|
+
|
|
248
|
+
assert res is not None
|
|
249
|
+
|
|
250
|
+
quat, match_ids, obs_indices, n_matches, matched_obs, time_s = res
|
|
251
|
+
np.testing.assert_allclose(quat, rot.inv().as_quat(), rtol=1e-6)
|
|
252
|
+
assert n_matches >= 4
|
|
253
|
+
assert len(obs_index) == len(match_ids)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
if __name__ == "__main__":
|
|
257
|
+
pytest.main([__file__])
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
|
|
3
|
-
import numpy as np
|
|
4
|
-
import pytest
|
|
5
|
-
import scipy.spatial
|
|
6
|
-
|
|
7
|
-
from ruststartracker import libruststartracker
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def test_triangle_finder():
|
|
11
|
-
ab = np.array([[234, 5643], [1, 2], [2, 4], [3, 9], [2, 6]])
|
|
12
|
-
ac = np.array([[345, 2343], [8, 2], [3, 4], [1, 7], [0, 5], [3, 1]])
|
|
13
|
-
bc = np.array([[435, 4355], [1, 0], [4, 8], [8, 1], [1, 9]])
|
|
14
|
-
|
|
15
|
-
f = libruststartracker.TriangleFinder(ab, ac, bc)
|
|
16
|
-
assert f.get() == [1, 2, 8]
|
|
17
|
-
assert list(libruststartracker.IterTriangleFinder(ab, ac, bc)) == [
|
|
18
|
-
[1, 2, 8],
|
|
19
|
-
[2, 4, 8],
|
|
20
|
-
[3, 9, 1],
|
|
21
|
-
]
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def test_unit_vector_lookup():
|
|
25
|
-
rng = np.random.default_rng(42)
|
|
26
|
-
n_vecs = 2617
|
|
27
|
-
|
|
28
|
-
vec = rng.normal(size=[n_vecs, 3]).astype(np.float32)
|
|
29
|
-
vec /= np.linalg.norm(vec, axis=-1, keepdims=True)
|
|
30
|
-
|
|
31
|
-
uvl = libruststartracker.UnitVectorLookup(vec)
|
|
32
|
-
|
|
33
|
-
keys = rng.normal(size=[10, 3]).astype(np.float32)
|
|
34
|
-
keys /= np.linalg.norm(keys, axis=-1, keepdims=True)
|
|
35
|
-
|
|
36
|
-
for key in keys:
|
|
37
|
-
np.testing.assert_array_equal(
|
|
38
|
-
uvl.lookup_nearest(key),
|
|
39
|
-
np.linalg.norm(vec - key, axis=-1).argmin().item(),
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
angle_threshold = np.radians(15)
|
|
43
|
-
|
|
44
|
-
threshold = np.cos(angle_threshold).item()
|
|
45
|
-
results = []
|
|
46
|
-
angles = []
|
|
47
|
-
for a in range(len(vec)):
|
|
48
|
-
dotp = np.sum(vec[a] * vec[a + 1 :], axis=-1)
|
|
49
|
-
b = np.nonzero(dotp >= threshold)[0]
|
|
50
|
-
results.append(np.array([np.full(len(b), a), (a + 1) + b]))
|
|
51
|
-
angles.append(np.arccos(dotp[b]))
|
|
52
|
-
angles = np.concatenate(angles, axis=0)
|
|
53
|
-
args = np.argsort(angles)
|
|
54
|
-
close_indices_gt = np.concatenate(results, axis=-1).T[args]
|
|
55
|
-
angles_gt = angles[args]
|
|
56
|
-
|
|
57
|
-
close_indices, angles, poly = uvl.get_inter_star_index(
|
|
58
|
-
np.array(vec[:, :3], dtype=np.float32),
|
|
59
|
-
np.ones(len(vec), dtype=np.float32),
|
|
60
|
-
angle_threshold,
|
|
61
|
-
10,
|
|
62
|
-
)
|
|
63
|
-
close_indices = np.array(close_indices)
|
|
64
|
-
angles = np.array(angles)
|
|
65
|
-
poly = np.array(poly)
|
|
66
|
-
|
|
67
|
-
# There are some cases where the float32 accuracy is insufficient to tell
|
|
68
|
-
# angles apart. Consequently the order may be alightly different. However,
|
|
69
|
-
# we're able to test if the not-matching indices align with items that have
|
|
70
|
-
# at minimum one other angle of the exact same value
|
|
71
|
-
i = (close_indices != close_indices_gt).any(axis=-1)
|
|
72
|
-
assert (np.unique(angles[i], return_counts=True)[-1] >= 2).all()
|
|
73
|
-
|
|
74
|
-
np.testing.assert_allclose(angles, angles_gt)
|
|
75
|
-
|
|
76
|
-
actual_index = np.arange(angles_gt.size)
|
|
77
|
-
|
|
78
|
-
poly_gt = np.polyfit(angles_gt, actual_index, 2)
|
|
79
|
-
lookup_index = np.polyval(poly_gt, angles_gt)
|
|
80
|
-
|
|
81
|
-
poly_gt[-1] -= (lookup_index - actual_index).max()
|
|
82
|
-
lookup_index = np.polyval(poly_gt, angles_gt)
|
|
83
|
-
|
|
84
|
-
if False:
|
|
85
|
-
import matplotlib.pyplot as plt
|
|
86
|
-
|
|
87
|
-
fig, axs = plt.subplots(2)
|
|
88
|
-
axs[0].plot(angles, lookup_index, "--")
|
|
89
|
-
axs[0].plot(angles, actual_index)
|
|
90
|
-
axs[1].plot(lookup_index - actual_index)
|
|
91
|
-
fig.savefig("angles.png")
|
|
92
|
-
plt.close(fig)
|
|
93
|
-
|
|
94
|
-
np.testing.assert_allclose(poly, poly_gt[::-1], rtol=0.001)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def test_star_matcher():
|
|
98
|
-
rng = np.random.default_rng(42)
|
|
99
|
-
|
|
100
|
-
os.environ["RUST_BACKTRACE"] = "1"
|
|
101
|
-
|
|
102
|
-
n_cat_stars = 2617
|
|
103
|
-
|
|
104
|
-
vec = rng.normal(size=[n_cat_stars, 3]).astype(np.float32)
|
|
105
|
-
vec /= np.linalg.norm(vec, axis=-1, keepdims=True)
|
|
106
|
-
|
|
107
|
-
magnitudes = rng.uniform(0, 10, size=vec.shape[:1]).astype(np.float32)
|
|
108
|
-
|
|
109
|
-
key = rng.normal(size=[3]).astype(np.float32)
|
|
110
|
-
key /= np.linalg.norm(key, axis=-1, keepdims=True)
|
|
111
|
-
|
|
112
|
-
angle_threshold = np.radians(7)
|
|
113
|
-
dotp = np.sum(key * vec, axis=-1)
|
|
114
|
-
threshold = np.cos(angle_threshold).item()
|
|
115
|
-
b = np.nonzero(dotp >= threshold)[0]
|
|
116
|
-
obs_index = rng.permutation(b)
|
|
117
|
-
obs = vec[obs_index]
|
|
118
|
-
|
|
119
|
-
rot = scipy.spatial.transform.Rotation.from_rotvec([1, 1, 1])
|
|
120
|
-
|
|
121
|
-
obs_rotated = rot.apply(obs).astype(np.float32)
|
|
122
|
-
|
|
123
|
-
index = libruststartracker.StarMatcher(
|
|
124
|
-
vec,
|
|
125
|
-
magnitudes,
|
|
126
|
-
10,
|
|
127
|
-
np.radians(10).item(),
|
|
128
|
-
np.radians(0.1).item(),
|
|
129
|
-
4,
|
|
130
|
-
999.0,
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
res = index.find(obs_rotated)
|
|
134
|
-
|
|
135
|
-
assert res is not None
|
|
136
|
-
|
|
137
|
-
quat, match_ids, obs_indices, n_matches, matched_obs, time_s = res
|
|
138
|
-
np.testing.assert_allclose(quat, rot.inv().as_quat(), rtol=1e-6)
|
|
139
|
-
assert n_matches >= 4
|
|
140
|
-
assert len(obs_index) == len(match_ids)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if __name__ == "__main__":
|
|
144
|
-
pytest.main([__file__])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|