ruststartracker 0.2.1__tar.gz → 0.2.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ruststartracker
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: Lightweight Python Star Tracker With Rust Backend
5
5
  License: MIT
6
6
  Author: Nicolas Tobler
@@ -14,6 +14,9 @@ Classifier: Programming Language :: Python :: 3.12
14
14
  Classifier: Programming Language :: Python :: 3.13
15
15
  Requires-Dist: numpy (>=1.26.0,<2.0.0)
16
16
  Requires-Dist: opencv-python-headless (>=4.9.0,<5.0.0)
17
+ Project-URL: GaiaAttribution, https://www.cosmos.esa.int/gaia
18
+ Project-URL: Homepage, https://github.com/ntobler/ruststartracker
19
+ Project-URL: License, https://opensource.org/licenses/MIT
17
20
  Description-Content-Type: text/markdown
18
21
 
19
22
  # Lightweight Python Star Tracker With Rust Backend
@@ -31,12 +34,41 @@ Features:
31
34
 
32
35
  ## Example
33
36
 
37
+ ### Rust
38
+
39
+ See [examples/basic.rs](examples/basic.rs)
40
+
41
+ ```rust
42
+ // Get catalog positions
43
+ let catalog: StarCatalog = StarCatalog::from_gaia(max_magnitude: ...).unwrap();
44
+ let stars_xyz: Vec<[f32; 3]> = catalog.normalized_positions(epoch: ..., observer_position: ...);
45
+ let stars_mag: Vec<f32> = catalog.magnitudes();
46
+
47
+ // Create StarTracker instance (reuse this)
48
+ let star_matcher = StarMatcher::new(
49
+ stars_xyz,
50
+ stars_mag,
51
+ max_lookup_magnitude: ...
52
+ max_inter_star_angle: ...,
53
+ inter_star_angle_tolerance: ...,
54
+ min_matches: ...,
55
+ timeout: ...
56
+ );
57
+
58
+ // Normalized observation in the camera frame
59
+ let obs_xyz_camera: Vec<[f32; 3]> = ...
60
+
61
+ let result = star_matcher.find(&obs_xyz_camera);
62
+ println!("Result: {:?}", result);
63
+ ```
64
+
65
+ ### Python
34
66
  ```python
35
67
  import ruststartracker
36
68
 
37
69
  # Get catalog positions
38
- catalog = ruststartracker.StarCatalog()
39
- star_catalog_vecs = catalog.normalized_positions()
70
+ catalog = ruststartracker.StarCatalog(max_magnitude=...)
71
+ star_catalog_vecs = catalog.normalized_positions(epoch=...)
40
72
 
41
73
  # Define opencv camera parameters, see https://docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html
42
74
  camera_params = ruststartracker.CameraParameters(
@@ -52,6 +84,7 @@ st = ruststartracker.StarTracker(
52
84
  max_inter_star_angle=...,
53
85
  inter_star_angle_tolerance=...,
54
86
  n_minimum_matches=...,
87
+ timeout_secs=...,
55
88
  )
56
89
 
57
90
  # Obtain numpy array image
@@ -66,11 +99,17 @@ print(result)
66
99
 
67
100
  ## Installation
68
101
 
69
- - Make sure rust tool chain (`cargo`) is installed and in the `PATH` environment variable.
70
- - Install with `pip install git+https://github.com/ntobler/ruststartracker.git`.
102
+ - Install with `pip install ruststartracker` (Currently only ARM/x86 Linux wheels available).
103
+
104
+ ## Attributions
105
+
106
+ ### Gaia Data
107
+
108
+ This project includes data from the European Space Agency (ESA) mission [**Gaia**](https://www.cosmos.esa.int/gaia), processed by the **Gaia Data Processing and Analysis Consortium (DPAC)**.
109
+ Funding for the DPAC has been provided by national institutions, in particular the institutions participating in the Gaia Multilateral Agreement.
71
110
 
72
- ## TODOs
111
+ Gaia DR3 data is © European Space Agency and is released under the [**Creative Commons Attribution 4.0 International License (CC BY 4.0)**](https://creativecommons.org/licenses/by/4.0/).
73
112
 
74
- - Improve error messages.
75
- - Return more diagnostic data.
113
+ > Gaia Collaboration, Vallenari et al. (2022), *A\&A* **674**, A1.
114
+ > [DOI: 10.1051/0004-6361/202243940](https://doi.org/10.1051/0004-6361/202243940)
76
115
 
@@ -0,0 +1,93 @@
1
+ # Lightweight Python Star Tracker With Rust Backend
2
+
3
+ Based on the methodology used in https://github.com/nasa/COTS-Star-Tracker, with following improvements:
4
+ - Reduced dependencies to opencv and numpy for lightweight usage in a Raspberry Pi.
5
+ - Reimplemented computationally expensive parts in rust. This includes most parts that are not image processing related.
6
+ - Added quadratic inter star angle index look up polynomial for faster triangle search.
7
+ - Added spatial index to look up neighboring stars.
8
+
9
+ Features:
10
+ - Attitude estimation from image and camera calibration parameters.
11
+ - Attitude estimation from list of star observation coordinates.
12
+ - Star catalog creation with temporal corrections.
13
+
14
+ ## Example
15
+
16
+ ### Rust
17
+
18
+ See [examples/basic.rs](examples/basic.rs)
19
+
20
+ ```rust
21
+ // Get catalog positions
22
+ let catalog: StarCatalog = StarCatalog::from_gaia(max_magnitude: ...).unwrap();
23
+ let stars_xyz: Vec<[f32; 3]> = catalog.normalized_positions(epoch: ..., observer_position: ...);
24
+ let stars_mag: Vec<f32> = catalog.magnitudes();
25
+
26
+ // Create StarTracker instance (reuse this)
27
+ let star_matcher = StarMatcher::new(
28
+ stars_xyz,
29
+ stars_mag,
30
+ max_lookup_magnitude: ...
31
+ max_inter_star_angle: ...,
32
+ inter_star_angle_tolerance: ...,
33
+ min_matches: ...,
34
+ timeout: ...
35
+ );
36
+
37
+ // Normalized observation in the camera frame
38
+ let obs_xyz_camera: Vec<[f32; 3]> = ...
39
+
40
+ let result = star_matcher.find(&obs_xyz_camera);
41
+ println!("Result: {:?}", result);
42
+ ```
43
+
44
+ ### Python
45
+ ```python
46
+ import ruststartracker
47
+
48
+ # Get catalog positions
49
+ catalog = ruststartracker.StarCatalog(max_magnitude=...)
50
+ star_catalog_vecs = catalog.normalized_positions(epoch=...)
51
+
52
+ # Define opencv camera parameters, see https://docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html
53
+ camera_params = ruststartracker.CameraParameters(
54
+ camera_matrix=...,
55
+ cam_resolution=...,
56
+ dist_coefs=...,
57
+ )
58
+
59
+ # Create StarTracker instance (reuse this)
60
+ st = ruststartracker.StarTracker(
61
+ star_catalog_vecs,
62
+ camera_params,
63
+ max_inter_star_angle=...,
64
+ inter_star_angle_tolerance=...,
65
+ n_minimum_matches=...,
66
+ timeout_secs=...,
67
+ )
68
+
69
+ # Obtain numpy array image
70
+ img = ...
71
+
72
+ # Find attitude from given image
73
+ result = st.process_image(img)
74
+
75
+ print(result)
76
+ # StarTrackerResult(quat=[-0.43977802991867065, -0.439766526222229, -0.4398997128009796, 0.6478340029716492], match_ids=[1435, 1272, 1140, 2035, 1070, 1438, 1338, 903, 260, 2141, 1771, 1727, 385, 1717, 2204, 2062, 1989, 1634, 708, 1357], n_matches=20, duration_s=0.0003700880042742938)
77
+ ```
78
+
79
+ ## Installation
80
+
81
+ - Install with `pip install ruststartracker` (Currently only ARM/x86 Linux wheels available).
82
+
83
+ ## Attributions
84
+
85
+ ### Gaia Data
86
+
87
+ This project includes data from the European Space Agency (ESA) mission [**Gaia**](https://www.cosmos.esa.int/gaia), processed by the **Gaia Data Processing and Analysis Consortium (DPAC)**.
88
+ Funding for the DPAC has been provided by national institutions, in particular the institutions participating in the Gaia Multilateral Agreement.
89
+
90
+ Gaia DR3 data is © European Space Agency and is released under the [**Creative Commons Attribution 4.0 International License (CC BY 4.0)**](https://creativecommons.org/licenses/by/4.0/).
91
+
92
+ > Gaia Collaboration, Vallenari et al. (2022), *A\&A* **674**, A1.
93
+ > [DOI: 10.1051/0004-6361/202243940](https://doi.org/10.1051/0004-6361/202243940)
@@ -0,0 +1,65 @@
1
+ """Build rust backend.
2
+
3
+ This script is automatically run when the pyproject is installed.
4
+ """
5
+
6
+ import pathlib
7
+ import shutil
8
+ import subprocess
9
+
10
+
11
+ def download_gaia_data(output_file: pathlib.Path) -> None:
12
+ """Download Gaia data and save it to the specified output file.
13
+
14
+ Args:
15
+ output_file: Path to save the downloaded Gaia data (csv).
16
+ """
17
+ from astroquery.gaia import Gaia
18
+
19
+ Gaia.ROW_LIMIT = 10000
20
+
21
+ query = """
22
+ SELECT source_id, ra, dec, parallax, pmra, pmdec, phot_g_mean_mag
23
+ FROM gaiadr3.gaia_source
24
+ WHERE phot_g_mean_mag < 7
25
+ AND parallax > 0
26
+ AND astrometric_params_solved = 31
27
+ AND visibility_periods_used >= 8
28
+ """
29
+
30
+ print("Executing query on Gaia database...")
31
+ job = Gaia.launch_job_async(
32
+ query,
33
+ dump_to_file=True,
34
+ output_format="csv",
35
+ output_file=str(output_file.absolute()),
36
+ verbose=True,
37
+ )
38
+ if not job.is_finished():
39
+ print("Job did not finish successfully.")
40
+ return
41
+
42
+ print(f"Saved to: {output_file}")
43
+
44
+
45
+ def build_script() -> None:
46
+ """Build rust backend and move shared library to correct folder."""
47
+ cwd = pathlib.Path(__file__).parent.expanduser().absolute()
48
+
49
+ gaia_file = cwd / "ruststartracker/gaia_data_j2016.csv"
50
+ if not gaia_file.exists():
51
+ download_gaia_data(gaia_file)
52
+
53
+ subprocess.check_call( # noqa: S603
54
+ ["cargo", "build", "--release", "--features", "improc,gaia"], # noqa: S607
55
+ cwd=cwd,
56
+ stdout=None,
57
+ stderr=None,
58
+ )
59
+ shutil.copy(
60
+ cwd / "target/release/libruststartracker.so", cwd / "ruststartracker/libruststartracker.so"
61
+ )
62
+
63
+
64
+ if __name__ == "__main__":
65
+ build_script()
@@ -1,15 +1,20 @@
1
1
  [tool.poetry]
2
2
  name = "ruststartracker"
3
- version = "0.2.1"
3
+ version = "0.2.3"
4
4
  description = "Lightweight Python Star Tracker With Rust Backend"
5
5
  authors = ["Nicolas Tobler <nitobler@gmail.com>"]
6
6
  readme = "README.md"
7
7
  license = "MIT"
8
8
  include = [
9
- { path = "ruststartracker/star_catalog.tsv", format = ["sdist", "wheel"] },
9
+ { path = "ruststartracker/gaia_data_j2016.csv", format = ["sdist", "wheel"] },
10
10
  { path = "ruststartracker/libruststartracker.so", format = ["wheel"] },
11
11
  ]
12
12
 
13
+ [tool.poetry.urls]
14
+ Homepage = "https://github.com/ntobler/ruststartracker"
15
+ License = "https://opensource.org/licenses/MIT"
16
+ GaiaAttribution = "https://www.cosmos.esa.int/gaia"
17
+
13
18
  [tool.poetry.dependencies]
14
19
  python = "^3.10"
15
20
  numpy = "^1.26.0"
@@ -27,16 +32,17 @@ mypy = "^1.13.0"
27
32
  scipy-stubs = "^1.14.1.5"
28
33
  ruff = "^0.9.3"
29
34
  bump-my-version = "^1.2.0"
35
+ astroquery = "^0.4.10"
30
36
 
31
37
  [tool.poetry.build]
32
- script = "rust_build.py"
38
+ script = "build_script.py"
33
39
 
34
40
  [build-system]
35
- requires = ["poetry-core"]
41
+ requires = ["poetry-core", "astroquery>=0.4.10"]
36
42
  build-backend = "poetry.core.masonry.api"
37
43
 
38
44
  [tool.bumpversion]
39
- current_version = "0.2.1"
45
+ current_version = "0.2.3"
40
46
  commit = true
41
47
  tag = true
42
48
  tag_name = "v{new_version}"
@@ -11,7 +11,9 @@ import numpy.typing as npt
11
11
  AU: float = 149597870.693
12
12
  """Astronomical unit."""
13
13
 
14
- INTERNAL_CATALOG_FILE = pathlib.Path(__file__).parent.expanduser().absolute() / "star_catalog.tsv"
14
+ INTERNAL_CATALOG_FILE = (
15
+ pathlib.Path(__file__).parent.expanduser().absolute() / "gaia_data_j2016.csv"
16
+ )
15
17
  """Location of the internal star catalog file."""
16
18
 
17
19
 
@@ -26,7 +28,7 @@ def time_to_epoch(t: datetime.datetime) -> float:
26
28
 
27
29
 
28
30
  class StarCatalog:
29
- """Star catalog from Hipparcos data."""
31
+ """Star catalog from Gaia data."""
30
32
 
31
33
  _data: npt.NDArray[np.float32]
32
34
  """Underlying data array."""
@@ -42,7 +44,7 @@ class StarCatalog:
42
44
  """Proper motion of the declination in mad."""
43
45
  magnitude: npt.NDArray[np.float32]
44
46
  """Magnitude values."""
45
- epoch: float = 1992.25
47
+ epoch: float = 2016.0
46
48
  """Epoch of the catalog in years."""
47
49
 
48
50
  def __init__(
@@ -57,13 +59,10 @@ class StarCatalog:
57
59
  # Use locally included star catalog is no file is given
58
60
  filename = INTERNAL_CATALOG_FILE if filename is None else pathlib.Path(filename)
59
61
 
60
- keep_columns = ("RArad", "DErad", "Plx", "pmRA", "pmDE", "Hpmag")
62
+ keep_columns = ("ra", "dec", "parallax", "pmra", "pmdec", "phot_g_mean_mag")
61
63
 
62
64
  with filename.open("r") as f:
63
- it = csv.reader(f, delimiter="\t", strict=True)
64
-
65
- # skip head
66
- [next(it) for _ in range(52)]
65
+ it = csv.reader(f, delimiter=",", strict=True)
67
66
 
68
67
  # Get columns
69
68
  columns = next(it)
@@ -71,10 +70,7 @@ class StarCatalog:
71
70
  keep_columns_indices = tuple(columns.index(x) for x in keep_columns)
72
71
  min_length = len(keep_columns_indices)
73
72
 
74
- mag_column_index = columns.index("Hpmag")
75
-
76
- # Skip unit and horizontal bar
77
- [next(it) for _ in range(2)]
73
+ mag_column_index = columns.index("phot_g_mean_mag")
78
74
 
79
75
  rows = [
80
76
  [float(line[j]) for j in keep_columns_indices]
@@ -83,12 +79,12 @@ class StarCatalog:
83
79
  ]
84
80
  self._data = np.array(rows, dtype=np.float32)
85
81
 
86
- self.ra = self._data[:, keep_columns.index("RArad")]
87
- self.de = self._data[:, keep_columns.index("DErad")]
88
- self.parallax = self._data[:, keep_columns.index("Plx")]
89
- self.proper_motion_ra = self._data[:, keep_columns.index("pmRA")]
90
- self.proper_motion_de = self._data[:, keep_columns.index("pmDE")]
91
- self.magnitude = self._data[:, keep_columns.index("Hpmag")]
82
+ self.ra = self._data[:, keep_columns.index("ra")]
83
+ self.de = self._data[:, keep_columns.index("dec")]
84
+ self.parallax = self._data[:, keep_columns.index("parallax")]
85
+ self.proper_motion_ra = self._data[:, keep_columns.index("pmra")]
86
+ self.proper_motion_de = self._data[:, keep_columns.index("pmdec")]
87
+ self.magnitude = self._data[:, keep_columns.index("phot_g_mean_mag")]
92
88
 
93
89
  deg2rad = math.pi / 180
94
90
  arcsec2deg = 1 / 3600
@@ -2,12 +2,15 @@ from collections.abc import Iterator
2
2
 
3
3
  import numpy as np
4
4
  import numpy.typing as npt
5
+ from typing_extensions import Self
5
6
 
6
7
  class StarMatcher:
7
8
  def __init__(
8
9
  self,
9
10
  stars_xyz: npt.NDArray[np.float32],
11
+ stars_mag: npt.NDArray[np.float32],
10
12
  max_inter_star_angle: float,
13
+ max_lookup_magnitude: float,
11
14
  inter_star_angle_tolerance: float,
12
15
  n_minimum_matches: int,
13
16
  timeout_secs: float,
@@ -19,7 +22,7 @@ class StarMatcher:
19
22
  npt.NDArray[np.uint32],
20
23
  npt.NDArray[np.uint32],
21
24
  int,
22
- list[list[float]],
25
+ npt.NDArray[np.float32],
23
26
  float,
24
27
  ]: ...
25
28
 
@@ -44,15 +47,43 @@ class IterTriangleFinder:
44
47
  class UnitVectorLookup:
45
48
  def __init__(self, vec: npt.NDArray[np.float32]) -> None: ...
46
49
  def lookup_nearest(self, key: npt.NDArray[np.float32]) -> int: ...
47
- def get_inter_star_index_numpy(
48
- self, vec: npt.NDArray[np.float32], angle_threshold: float
49
- ) -> tuple[list[list[int]], list[float], list[float]]: ...
50
50
  def get_inter_star_index(
51
- self, vec: npt.NDArray[np.float32], angle_threshold: float
51
+ self,
52
+ stars: npt.NDArray[np.float32],
53
+ magnitudes: npt.NDArray[np.float32],
54
+ max_angle_rad: float,
55
+ max_magnitude: float,
52
56
  ) -> tuple[list[list[int]], list[float], list[float]]: ...
53
57
  def look_up_close_angles(
54
- self, vectors: npt.NDArray[np.float32], max_angle_rad: float
58
+ self,
59
+ vectors: npt.NDArray[np.float32],
60
+ magnitudes: npt.NDArray[np.float32],
61
+ max_angle_rad: float,
62
+ max_magnitude: float,
55
63
  ) -> list[tuple[list[float], float]]: ...
56
64
  def look_up_close_angles_naive(
57
- self, vectors: npt.NDArray[np.float32], max_angle_rad: float
65
+ self,
66
+ vectors: npt.NDArray[np.float32],
67
+ magnitudes: npt.NDArray[np.float32],
68
+ max_angle_rad: float,
69
+ max_magnitude: float,
58
70
  ) -> list[tuple[list[float], float]]: ...
71
+
72
+ def get_threshold_from_histogram(
73
+ img: npt.NDArray[np.uint8],
74
+ *,
75
+ fraction: float,
76
+ ) -> int: ...
77
+ def extract_observations(
78
+ img: npt.NDArray[np.uint8],
79
+ threshold: int,
80
+ min_star_area: int,
81
+ max_star_area: int,
82
+ ) -> tuple[npt.NDArray[np.float32], npt.NDArray[np.float32]]: ...
83
+
84
+ class StarCatalog:
85
+ @classmethod
86
+ def from_gaia(cls, *, max_magnitude: float | None) -> Self: ...
87
+ def normalized_positions(
88
+ self, *, epoch: float | None, observer_position: np.ndarray | None
89
+ ) -> npt.NDArray[np.float32]: ...
@@ -87,8 +87,10 @@ class StarTracker:
87
87
  def __init__(
88
88
  self,
89
89
  stars_xyz: npt.NDArray[np.float32],
90
+ stars_mag: npt.NDArray[np.float32],
90
91
  camera_params: CameraParameters,
91
92
  *,
93
+ max_lookup_magnitude: float | None = None,
92
94
  max_inter_star_angle: float | None = None,
93
95
  inter_star_angle_tolerance: float = 0.0008,
94
96
  n_minimum_matches: int = 10,
@@ -98,11 +100,15 @@ class StarTracker:
98
100
 
99
101
  Args:
100
102
  stars_xyz: Positions of catalog stars
103
+ stars_mag: Magnitudes of catalog stars
101
104
  camera_params: Calibrated camera parameters
105
+ max_lookup_magnitude: Maximum magnitude of stars used in the triangulation. Reducing
106
+ this number means only bright stars are used for triangulation. This results in
107
+ faster lookup performance.
102
108
  max_inter_star_angle: Maximum angle between stars that should be indexed.
103
109
  Calculating large inter star angles is expensive. If None, the angle is
104
110
  calculated from the camera field of view.
105
- inter_star_angle_tolerance: Tolerance for inter star angle matching.
111
+ inter_star_angle_tolerance: Tolerance for inter star angle matching in rad.
106
112
  n_minimum_matches: Minimum amount of required matches for a successful
107
113
  attitude estimation
108
114
  timeout_secs: Maximum allowed search time in seconds. A StarTrackerError is raised
@@ -127,8 +133,13 @@ class StarTracker:
127
133
  dot_products = (corner_coords_xyz * np.array([0, 0, 1], dtype=np.float32)).sum(axis=-1)
128
134
  max_inter_star_angle = float(np.arccos(dot_products.max())) * 2
129
135
 
136
+ if max_lookup_magnitude is None:
137
+ max_lookup_magnitude = 100.0 # A very faint star. Almost infinity
138
+
130
139
  self._star_matcher = ruststartracker.libruststartracker.StarMatcher(
131
140
  np.ascontiguousarray(stars_xyz, dtype=np.float32),
141
+ np.ascontiguousarray(stars_mag, dtype=np.float32),
142
+ float(max_lookup_magnitude),
132
143
  float(max_inter_star_angle),
133
144
  float(inter_star_angle_tolerance),
134
145
  int(n_minimum_matches),
@@ -178,6 +189,20 @@ class StarTracker:
178
189
  max_star_area=max_star_area,
179
190
  )
180
191
 
192
+ if threshold is None:
193
+ threshold = ruststartracker.libruststartracker.get_threshold_from_histogram(
194
+ img, fraction=0.99
195
+ )
196
+
197
+ centroids, intensities = ruststartracker.libruststartracker.extract_observations(
198
+ img,
199
+ threshold,
200
+ min_star_area,
201
+ max_star_area,
202
+ )
203
+ centroids = np.array(centroids, dtype=np.float32)
204
+ intensities = np.array(intensities, dtype=np.float32)
205
+
181
206
  # At least 3 observations are required (one triangle)
182
207
  if len(centroids) < 3:
183
208
  raise StarTrackerError("Found too few star candidates (< 3) to continue.")
@@ -238,12 +263,12 @@ class StarTracker:
238
263
  quat, match_ids, obs_indices, n_matches, matched_obs, duration_s = result
239
264
 
240
265
  return StarTrackerResult(
241
- quat=np.asarray(quat, dtype=np.float32),
242
- match_ids=np.asarray(match_ids, dtype=np.uint32),
266
+ quat=quat,
267
+ match_ids=match_ids,
243
268
  n_matches=n_matches,
244
269
  duration_s=duration_s,
245
- mached_obs_x=np.asarray(matched_obs, dtype=np.float32),
246
- obs_indices=np.asarray(obs_indices, dtype=np.uint32),
270
+ mached_obs_x=matched_obs,
271
+ obs_indices=obs_indices,
247
272
  )
248
273
 
249
274
 
@@ -54,9 +54,12 @@ def test_unit_vector_lookup():
54
54
  close_indices_gt = np.concatenate(results, axis=-1).T[args]
55
55
  angles_gt = angles[args]
56
56
 
57
- close_indices, angles, poly = uvl.get_inter_star_index_numpy(vec, angle_threshold)
58
-
59
- close_indices, angles, poly = uvl.get_inter_star_index(vec[:, :3], angle_threshold)
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
+ )
60
63
  close_indices = np.array(close_indices)
61
64
  angles = np.array(angles)
62
65
  poly = np.array(poly)
@@ -101,6 +104,8 @@ def test_star_matcher():
101
104
  vec = rng.normal(size=[n_cat_stars, 3]).astype(np.float32)
102
105
  vec /= np.linalg.norm(vec, axis=-1, keepdims=True)
103
106
 
107
+ magnitudes = rng.uniform(0, 10, size=vec.shape[:1]).astype(np.float32)
108
+
104
109
  key = rng.normal(size=[3]).astype(np.float32)
105
110
  key /= np.linalg.norm(key, axis=-1, keepdims=True)
106
111
 
@@ -113,10 +118,16 @@ def test_star_matcher():
113
118
 
114
119
  rot = scipy.spatial.transform.Rotation.from_rotvec([1, 1, 1])
115
120
 
116
- obs_rotated = rot.apply(obs)
121
+ obs_rotated = rot.apply(obs).astype(np.float32)
117
122
 
118
123
  index = libruststartracker.StarMatcher(
119
- vec, np.radians(10).item(), np.radians(0.1).item(), 4, 999.0
124
+ vec,
125
+ magnitudes,
126
+ 10,
127
+ np.radians(10).item(),
128
+ np.radians(0.1).item(),
129
+ 4,
130
+ 999.0,
120
131
  )
121
132
 
122
133
  res = index.find(obs_rotated)
@@ -124,7 +135,7 @@ def test_star_matcher():
124
135
  assert res is not None
125
136
 
126
137
  quat, match_ids, obs_indices, n_matches, matched_obs, time_s = res
127
- np.testing.assert_allclose(quat, rot.inv().as_quat())
138
+ np.testing.assert_allclose(quat, rot.inv().as_quat(), rtol=1e-6)
128
139
  assert n_matches >= 4
129
140
  assert len(obs_index) == len(match_ids)
130
141
 
@@ -1,10 +1,12 @@
1
1
  import datetime
2
+ import time
2
3
 
3
4
  import astropy.time # type: ignore[import]
4
5
  import numpy as np
5
6
  import pytest
6
7
 
7
8
  import ruststartracker.catalog
9
+ import ruststartracker.libruststartracker
8
10
 
9
11
 
10
12
  def test_time_to_epoch():
@@ -43,5 +45,17 @@ def test_extract_observations():
43
45
  np.testing.assert_allclose(np.linalg.norm(positions, axis=-1), 1.0, rtol=1e-5)
44
46
 
45
47
 
48
+ def test_python_rust():
49
+ t0 = time.monotonic()
50
+ positions = ruststartracker.catalog.StarCatalog().normalized_positions(epoch=2025.0)
51
+ print(f"Python catalog took {time.monotonic() - t0:.3f} seconds")
52
+ t0 = time.monotonic()
53
+ positions2 = ruststartracker.libruststartracker.StarCatalog.from_gaia(
54
+ max_magnitude=6.0
55
+ ).normalized_positions(epoch=2025.0, observer_position=None)
56
+ print(f"Rust catalog took {time.monotonic() - t0:.3f} seconds")
57
+ np.testing.assert_allclose(positions, positions2, rtol=1e-5, atol=1e-5)
58
+
59
+
46
60
  if __name__ == "__main__":
47
61
  pytest.main([__file__])
@@ -25,7 +25,8 @@ def prepare() -> tuple[ruststartracker.StarTracker, np.ndarray]:
25
25
  dist_coefs = np.array([-0.44120807, -0.15954202, 0.00767012, -0.00213292, -1.64788247])
26
26
 
27
27
  catalog = ruststartracker.StarCatalog()
28
- star_catalog_vecs = catalog.normalized_positions()
28
+ star_catalog_vecs = catalog.normalized_positions(epoch=2024)
29
+ star_catalog_magnitudes = catalog.magnitude
29
30
 
30
31
  camera_params = ruststartracker.CameraParameters(
31
32
  camera_matrix=camera_matrix,
@@ -35,40 +36,15 @@ def prepare() -> tuple[ruststartracker.StarTracker, np.ndarray]:
35
36
 
36
37
  st = ruststartracker.StarTracker(
37
38
  star_catalog_vecs,
39
+ star_catalog_magnitudes,
38
40
  camera_params,
39
- inter_star_angle_tolerance=np.radians(0.1).item(),
41
+ inter_star_angle_tolerance=np.radians(0.05).item(),
40
42
  n_minimum_matches=5,
41
43
  )
42
44
 
43
45
  return st, star_catalog_vecs
44
46
 
45
47
 
46
- def test_example(prepare: tuple[ruststartracker.StarTracker, np.ndarray]):
47
- os.environ["RUST_BACKTRACE"] = "1"
48
-
49
- st, _ = prepare
50
-
51
- obs = np.array(
52
- [
53
- [0.11975033, -0.02227603, 0.9925541],
54
- [0.03917335, 0.04533212, 0.99820361],
55
- [0.05137746, -0.01717139, 0.99853167],
56
- [-0.14742009, 0.00734109, 0.98904673],
57
- [0.03396359, 0.05851033, 0.99770888],
58
- [-0.10286126, 0.04479652, 0.99368649],
59
- [-0.050927, -0.06002669, 0.9968968],
60
- [0.02815389, 0.02852981, 0.99919638],
61
- [-0.1390861, 0.05684676, 0.98864731],
62
- [-0.14276463, -0.13402131, 0.98064089],
63
- ]
64
- )
65
-
66
- result = st.process_observation_vectors(obs)
67
- np.testing.assert_allclose(
68
- result.quat, [0.1722, -0.4309, 0.8818, 0.08396], rtol=1e-3, atol=1e-3
69
- )
70
-
71
-
72
48
  def test_star_matcher(prepare: tuple[ruststartracker.StarTracker, np.ndarray]):
73
49
  os.environ["RUST_BACKTRACE"] = "1"
74
50