ruststartracker 0.2.1__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.1/LICENSE +21 -0
- ruststartracker-0.2.1/PKG-INFO +76 -0
- ruststartracker-0.2.1/README.md +57 -0
- ruststartracker-0.2.1/pyproject.toml +138 -0
- ruststartracker-0.2.1/rust_build.py +21 -0
- ruststartracker-0.2.1/ruststartracker/__init__.py +12 -0
- ruststartracker-0.2.1/ruststartracker/catalog.py +151 -0
- ruststartracker-0.2.1/ruststartracker/libruststartracker.pyi +58 -0
- ruststartracker-0.2.1/ruststartracker/py.typed +0 -0
- ruststartracker-0.2.1/ruststartracker/star.py +322 -0
- ruststartracker-0.2.1/ruststartracker/star_catalog.tsv +118010 -0
- ruststartracker-0.2.1/ruststartracker/test_backend.py +133 -0
- ruststartracker-0.2.1/ruststartracker/test_catalog.py +47 -0
- ruststartracker-0.2.1/ruststartracker/test_integration.py +93 -0
- ruststartracker-0.2.1/ruststartracker/test_star.py +111 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Nicolas Tobler
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: ruststartracker
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Lightweight Python Star Tracker With Rust Backend
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Nicolas Tobler
|
|
7
|
+
Author-email: nitobler@gmail.com
|
|
8
|
+
Requires-Python: >=3.10,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Requires-Dist: numpy (>=1.26.0,<2.0.0)
|
|
16
|
+
Requires-Dist: opencv-python-headless (>=4.9.0,<5.0.0)
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# Lightweight Python Star Tracker With Rust Backend
|
|
20
|
+
|
|
21
|
+
Based on the methodology used in https://github.com/nasa/COTS-Star-Tracker, with following improvements:
|
|
22
|
+
- Reduced dependencies to opencv and numpy for lightweight usage in a Raspberry Pi.
|
|
23
|
+
- Reimplemented computationally expensive parts in rust. This includes most parts that are not image processing related.
|
|
24
|
+
- Added quadratic inter star angle index look up polynomial for faster triangle search.
|
|
25
|
+
- Added spatial index to look up neighboring stars.
|
|
26
|
+
|
|
27
|
+
Features:
|
|
28
|
+
- Attitude estimation from image and camera calibration parameters.
|
|
29
|
+
- Attitude estimation from list of star observation coordinates.
|
|
30
|
+
- Star catalog creation with temporal corrections.
|
|
31
|
+
|
|
32
|
+
## Example
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import ruststartracker
|
|
36
|
+
|
|
37
|
+
# Get catalog positions
|
|
38
|
+
catalog = ruststartracker.StarCatalog()
|
|
39
|
+
star_catalog_vecs = catalog.normalized_positions()
|
|
40
|
+
|
|
41
|
+
# Define opencv camera parameters, see https://docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html
|
|
42
|
+
camera_params = ruststartracker.CameraParameters(
|
|
43
|
+
camera_matrix=...,
|
|
44
|
+
cam_resolution=...,
|
|
45
|
+
dist_coefs=...,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Create StarTracker instance (reuse this)
|
|
49
|
+
st = ruststartracker.StarTracker(
|
|
50
|
+
star_catalog_vecs,
|
|
51
|
+
camera_params,
|
|
52
|
+
max_inter_star_angle=...,
|
|
53
|
+
inter_star_angle_tolerance=...,
|
|
54
|
+
n_minimum_matches=...,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Obtain numpy array image
|
|
58
|
+
img = ...
|
|
59
|
+
|
|
60
|
+
# Find attitude from given image
|
|
61
|
+
result = st.process_image(img)
|
|
62
|
+
|
|
63
|
+
print(result)
|
|
64
|
+
# 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)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
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`.
|
|
71
|
+
|
|
72
|
+
## TODOs
|
|
73
|
+
|
|
74
|
+
- Improve error messages.
|
|
75
|
+
- Return more diagnostic data.
|
|
76
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
```python
|
|
17
|
+
import ruststartracker
|
|
18
|
+
|
|
19
|
+
# Get catalog positions
|
|
20
|
+
catalog = ruststartracker.StarCatalog()
|
|
21
|
+
star_catalog_vecs = catalog.normalized_positions()
|
|
22
|
+
|
|
23
|
+
# Define opencv camera parameters, see https://docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html
|
|
24
|
+
camera_params = ruststartracker.CameraParameters(
|
|
25
|
+
camera_matrix=...,
|
|
26
|
+
cam_resolution=...,
|
|
27
|
+
dist_coefs=...,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Create StarTracker instance (reuse this)
|
|
31
|
+
st = ruststartracker.StarTracker(
|
|
32
|
+
star_catalog_vecs,
|
|
33
|
+
camera_params,
|
|
34
|
+
max_inter_star_angle=...,
|
|
35
|
+
inter_star_angle_tolerance=...,
|
|
36
|
+
n_minimum_matches=...,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Obtain numpy array image
|
|
40
|
+
img = ...
|
|
41
|
+
|
|
42
|
+
# Find attitude from given image
|
|
43
|
+
result = st.process_image(img)
|
|
44
|
+
|
|
45
|
+
print(result)
|
|
46
|
+
# 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)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
- Make sure rust tool chain (`cargo`) is installed and in the `PATH` environment variable.
|
|
52
|
+
- Install with `pip install git+https://github.com/ntobler/ruststartracker.git`.
|
|
53
|
+
|
|
54
|
+
## TODOs
|
|
55
|
+
|
|
56
|
+
- Improve error messages.
|
|
57
|
+
- Return more diagnostic data.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "ruststartracker"
|
|
3
|
+
version = "0.2.1"
|
|
4
|
+
description = "Lightweight Python Star Tracker With Rust Backend"
|
|
5
|
+
authors = ["Nicolas Tobler <nitobler@gmail.com>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
include = [
|
|
9
|
+
{ path = "ruststartracker/star_catalog.tsv", format = ["sdist", "wheel"] },
|
|
10
|
+
{ path = "ruststartracker/libruststartracker.so", format = ["wheel"] },
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[tool.poetry.dependencies]
|
|
14
|
+
python = "^3.10"
|
|
15
|
+
numpy = "^1.26.0"
|
|
16
|
+
opencv-python-headless = "^4.9.0"
|
|
17
|
+
|
|
18
|
+
[tool.poetry.group.dev.dependencies]
|
|
19
|
+
matplotlib = "^3.9.2"
|
|
20
|
+
scipy = "^1.14.1"
|
|
21
|
+
viztracer = "^0.16.3"
|
|
22
|
+
pytest = "^8.3.3"
|
|
23
|
+
pytest-cov = "^6.0.0"
|
|
24
|
+
pre-commit = "^3.8.0"
|
|
25
|
+
astropy = "^6.1.4"
|
|
26
|
+
mypy = "^1.13.0"
|
|
27
|
+
scipy-stubs = "^1.14.1.5"
|
|
28
|
+
ruff = "^0.9.3"
|
|
29
|
+
bump-my-version = "^1.2.0"
|
|
30
|
+
|
|
31
|
+
[tool.poetry.build]
|
|
32
|
+
script = "rust_build.py"
|
|
33
|
+
|
|
34
|
+
[build-system]
|
|
35
|
+
requires = ["poetry-core"]
|
|
36
|
+
build-backend = "poetry.core.masonry.api"
|
|
37
|
+
|
|
38
|
+
[tool.bumpversion]
|
|
39
|
+
current_version = "0.2.1"
|
|
40
|
+
commit = true
|
|
41
|
+
tag = true
|
|
42
|
+
tag_name = "v{new_version}"
|
|
43
|
+
commit_message = "ci: bump version to {new_version}"
|
|
44
|
+
|
|
45
|
+
[[tool.bumpversion.files]]
|
|
46
|
+
filename = "pyproject.toml"
|
|
47
|
+
search = 'version = "{current_version}"'
|
|
48
|
+
replace = 'version = "{new_version}"'
|
|
49
|
+
|
|
50
|
+
[[tool.bumpversion.files]]
|
|
51
|
+
filename = "Cargo.toml"
|
|
52
|
+
search = 'version = "{current_version}"'
|
|
53
|
+
replace = 'version = "{new_version}"'
|
|
54
|
+
|
|
55
|
+
[tool.ruff]
|
|
56
|
+
line-length = 100
|
|
57
|
+
|
|
58
|
+
[tool.ruff.lint]
|
|
59
|
+
select = [
|
|
60
|
+
# Pyflakes
|
|
61
|
+
"F",
|
|
62
|
+
# pycodestyle error
|
|
63
|
+
"E",
|
|
64
|
+
# pycodestyle warning
|
|
65
|
+
"W",
|
|
66
|
+
# pydocstring
|
|
67
|
+
"D",
|
|
68
|
+
# mccabe
|
|
69
|
+
"C90",
|
|
70
|
+
# isort
|
|
71
|
+
"I",
|
|
72
|
+
# Pep8 naming
|
|
73
|
+
"N",
|
|
74
|
+
# pyupgrade
|
|
75
|
+
"UP",
|
|
76
|
+
# flake8-2020
|
|
77
|
+
"YTT",
|
|
78
|
+
# flake8-annotations
|
|
79
|
+
"ANN",
|
|
80
|
+
# flake8-bandit
|
|
81
|
+
"S",
|
|
82
|
+
# flake8-bugbear
|
|
83
|
+
"B",
|
|
84
|
+
# flake8-simplify
|
|
85
|
+
"SIM",
|
|
86
|
+
# boolean trap
|
|
87
|
+
"FBT001",
|
|
88
|
+
# Built in shadowing
|
|
89
|
+
"A",
|
|
90
|
+
# flake8-comprehensions
|
|
91
|
+
"C4",
|
|
92
|
+
"NPY",
|
|
93
|
+
"DTZ",
|
|
94
|
+
"T10",
|
|
95
|
+
"EXE",
|
|
96
|
+
"ISC",
|
|
97
|
+
"ICN",
|
|
98
|
+
"LOG",
|
|
99
|
+
"PIE",
|
|
100
|
+
"Q",
|
|
101
|
+
"RET",
|
|
102
|
+
"SLF",
|
|
103
|
+
"TID",
|
|
104
|
+
"TCH",
|
|
105
|
+
"ARG",
|
|
106
|
+
"PTH",
|
|
107
|
+
# Perflint
|
|
108
|
+
"PERF",
|
|
109
|
+
# Ruff-specific rules
|
|
110
|
+
"RUF"
|
|
111
|
+
]
|
|
112
|
+
ignore = [
|
|
113
|
+
"ISC001" # conflict with formatter
|
|
114
|
+
]
|
|
115
|
+
pydocstyle.convention = "google"
|
|
116
|
+
|
|
117
|
+
[tool.ruff.lint.per-file-ignores]
|
|
118
|
+
"test*" = ["D100", "D103", "SLF001", "ANN", "S101"]
|
|
119
|
+
|
|
120
|
+
[tool.mypy]
|
|
121
|
+
packages = ["ruststartracker"]
|
|
122
|
+
python_version = "3.10"
|
|
123
|
+
warn_return_any = true
|
|
124
|
+
warn_unused_configs = true
|
|
125
|
+
|
|
126
|
+
[tool.pytest.ini_options]
|
|
127
|
+
addopts = "--cov=ruststartracker --cov-report xml --cov-report term --cov-fail-under=95"
|
|
128
|
+
|
|
129
|
+
[tool.codespell]
|
|
130
|
+
ignore-words-list = "crate"
|
|
131
|
+
|
|
132
|
+
[tool.coverage.report]
|
|
133
|
+
exclude_lines = [
|
|
134
|
+
'if __name__ == "__main__":',
|
|
135
|
+
'if plot:',
|
|
136
|
+
'if debug:',
|
|
137
|
+
'if verbose:',
|
|
138
|
+
]
|
|
@@ -0,0 +1,21 @@
|
|
|
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 rust_build() -> None:
|
|
12
|
+
"""Build rust backend and move shared library to correct folder."""
|
|
13
|
+
cwd = pathlib.Path(__file__).parent.expanduser().absolute()
|
|
14
|
+
subprocess.check_call(["cargo", "build", "--release"], cwd=cwd) # noqa: S603, S607
|
|
15
|
+
shutil.copy(
|
|
16
|
+
cwd / "target/release/libruststartracker.so", cwd / "ruststartracker/libruststartracker.so"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
rust_build()
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Star tracker implementation with backend in rust."""
|
|
2
|
+
|
|
3
|
+
from ruststartracker.catalog import StarCatalog
|
|
4
|
+
from ruststartracker.star import CameraParameters, StarTracker, StarTrackerError, StarTrackerResult
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"CameraParameters",
|
|
8
|
+
"StarCatalog",
|
|
9
|
+
"StarTracker",
|
|
10
|
+
"StarTrackerError",
|
|
11
|
+
"StarTrackerResult",
|
|
12
|
+
]
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Create star catalog."""
|
|
2
|
+
|
|
3
|
+
import csv
|
|
4
|
+
import datetime
|
|
5
|
+
import math
|
|
6
|
+
import pathlib
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import numpy.typing as npt
|
|
10
|
+
|
|
11
|
+
AU: float = 149597870.693
|
|
12
|
+
"""Astronomical unit."""
|
|
13
|
+
|
|
14
|
+
INTERNAL_CATALOG_FILE = pathlib.Path(__file__).parent.expanduser().absolute() / "star_catalog.tsv"
|
|
15
|
+
"""Location of the internal star catalog file."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def time_to_epoch(t: datetime.datetime) -> float:
|
|
19
|
+
"""Convert a date time object to the Epoch in Julian years."""
|
|
20
|
+
# Get time stamp at the Julian year J2000
|
|
21
|
+
delta_t_j2000 = 64 # Delta T at J2000 https://en.wikipedia.org/wiki/%CE%94T_(timekeeping)
|
|
22
|
+
j2000 = datetime.datetime.fromisoformat("2000-01-01T12:00:00").timestamp() - delta_t_j2000
|
|
23
|
+
# Calculate difference from given datetime to j2000 and convert to Julian years
|
|
24
|
+
seconds_in_j_year = 365.25 * 86400
|
|
25
|
+
return (t.timestamp() - j2000) / seconds_in_j_year + 2000.0
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class StarCatalog:
|
|
29
|
+
"""Star catalog from Hipparcos data."""
|
|
30
|
+
|
|
31
|
+
_data: npt.NDArray[np.float32]
|
|
32
|
+
"""Underlying data array."""
|
|
33
|
+
ra: npt.NDArray[np.float32]
|
|
34
|
+
"""Right ascension at epoch in rads."""
|
|
35
|
+
de: npt.NDArray[np.float32]
|
|
36
|
+
"""Declination at epoch in rads."""
|
|
37
|
+
parallax: npt.NDArray[np.float32]
|
|
38
|
+
"""Parallax in rads."""
|
|
39
|
+
proper_motion_ra: npt.NDArray[np.float32]
|
|
40
|
+
"""Proper motion of the right ascension in mad."""
|
|
41
|
+
proper_motion_de: npt.NDArray[np.float32]
|
|
42
|
+
"""Proper motion of the declination in mad."""
|
|
43
|
+
magnitude: npt.NDArray[np.float32]
|
|
44
|
+
"""Magnitude values."""
|
|
45
|
+
epoch: float = 1992.25
|
|
46
|
+
"""Epoch of the catalog in years."""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self, filename: pathlib.Path | str | None = None, max_magnitude: float = 6.0
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Read catalog from file.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
filename: Star catalog filename.
|
|
55
|
+
max_magnitude: Maximum magnitude to include.
|
|
56
|
+
"""
|
|
57
|
+
# Use locally included star catalog is no file is given
|
|
58
|
+
filename = INTERNAL_CATALOG_FILE if filename is None else pathlib.Path(filename)
|
|
59
|
+
|
|
60
|
+
keep_columns = ("RArad", "DErad", "Plx", "pmRA", "pmDE", "Hpmag")
|
|
61
|
+
|
|
62
|
+
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)]
|
|
67
|
+
|
|
68
|
+
# Get columns
|
|
69
|
+
columns = next(it)
|
|
70
|
+
|
|
71
|
+
keep_columns_indices = tuple(columns.index(x) for x in keep_columns)
|
|
72
|
+
min_length = len(keep_columns_indices)
|
|
73
|
+
|
|
74
|
+
mag_column_index = columns.index("Hpmag")
|
|
75
|
+
|
|
76
|
+
# Skip unit and horizontal bar
|
|
77
|
+
[next(it) for _ in range(2)]
|
|
78
|
+
|
|
79
|
+
rows = [
|
|
80
|
+
[float(line[j]) for j in keep_columns_indices]
|
|
81
|
+
for line in it
|
|
82
|
+
if len(line) >= min_length and float(line[mag_column_index]) <= max_magnitude
|
|
83
|
+
]
|
|
84
|
+
self._data = np.array(rows, dtype=np.float32)
|
|
85
|
+
|
|
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")]
|
|
92
|
+
|
|
93
|
+
deg2rad = math.pi / 180
|
|
94
|
+
arcsec2deg = 1 / 3600
|
|
95
|
+
mas2arcsec = 1 / 1000
|
|
96
|
+
mas2rad = mas2arcsec * arcsec2deg * deg2rad
|
|
97
|
+
|
|
98
|
+
# Convert data to rad
|
|
99
|
+
self.ra *= deg2rad
|
|
100
|
+
self.de *= deg2rad
|
|
101
|
+
self.proper_motion_ra *= mas2rad
|
|
102
|
+
self.proper_motion_de *= mas2rad
|
|
103
|
+
self.parallax *= mas2rad
|
|
104
|
+
|
|
105
|
+
def normalized_positions(
|
|
106
|
+
self, *, epoch: float | None = None, observer_position: np.ndarray | None = None
|
|
107
|
+
) -> npt.NDArray[np.float32]:
|
|
108
|
+
"""Get star positions as normalized (x, y, z) vector.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
epoch: The Julian epoch at which the positions should be calculated in Julian
|
|
112
|
+
years. E.g. 2024.3. If None, the current local time us used.
|
|
113
|
+
observer_position: The position of the observer in the Equatorial coordinate
|
|
114
|
+
system relative to the sun. If None, position is not corrected.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Normalized (x, y, z) vector of star positions, shape=[n, 3]
|
|
118
|
+
"""
|
|
119
|
+
if epoch is None:
|
|
120
|
+
epoch = time_to_epoch(datetime.datetime.now(tz=datetime.timezone.utc))
|
|
121
|
+
|
|
122
|
+
delta_epoch = epoch - self.epoch
|
|
123
|
+
|
|
124
|
+
# Precalculate sin and cos
|
|
125
|
+
cos_ra: npt.NDArray[np.float32] = np.cos(self.ra)
|
|
126
|
+
sin_ra: npt.NDArray[np.float32] = np.sin(self.ra)
|
|
127
|
+
sin_de: npt.NDArray[np.float32] = np.sin(self.de)
|
|
128
|
+
cos_de: npt.NDArray[np.float32] = np.cos(self.de)
|
|
129
|
+
zeros = np.zeros_like(self.de)
|
|
130
|
+
|
|
131
|
+
# Get star positions as normalized vector (x, y, z)
|
|
132
|
+
vectors: npt.NDArray[np.float32] = np.stack(
|
|
133
|
+
[cos_de * cos_ra, cos_de * sin_ra, sin_de], axis=-1
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Correct proper motion
|
|
137
|
+
p_hat = np.stack([-sin_ra, cos_ra, zeros], axis=-1)
|
|
138
|
+
q_hat = np.stack([-sin_de * cos_ra, -sin_de * sin_ra, cos_de], axis=-1)
|
|
139
|
+
pm = delta_epoch * (
|
|
140
|
+
self.proper_motion_ra[..., np.newaxis] * p_hat
|
|
141
|
+
+ self.proper_motion_de[..., np.newaxis] * q_hat
|
|
142
|
+
)
|
|
143
|
+
vectors += pm
|
|
144
|
+
|
|
145
|
+
if observer_position is not None:
|
|
146
|
+
plx = self.parallax[:, np.newaxis] * (observer_position / AU)[np.newaxis, :]
|
|
147
|
+
vectors -= plx
|
|
148
|
+
|
|
149
|
+
# normalize star positions
|
|
150
|
+
vectors /= np.linalg.norm(vectors, axis=-1, keepdims=True)
|
|
151
|
+
return vectors
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from collections.abc import Iterator
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import numpy.typing as npt
|
|
5
|
+
|
|
6
|
+
class StarMatcher:
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
stars_xyz: npt.NDArray[np.float32],
|
|
10
|
+
max_inter_star_angle: float,
|
|
11
|
+
inter_star_angle_tolerance: float,
|
|
12
|
+
n_minimum_matches: int,
|
|
13
|
+
timeout_secs: float,
|
|
14
|
+
) -> None: ...
|
|
15
|
+
def find(
|
|
16
|
+
self, obs_xyz: npt.NDArray[np.float32]
|
|
17
|
+
) -> tuple[
|
|
18
|
+
npt.NDArray[np.float32],
|
|
19
|
+
npt.NDArray[np.uint32],
|
|
20
|
+
npt.NDArray[np.uint32],
|
|
21
|
+
int,
|
|
22
|
+
list[list[float]],
|
|
23
|
+
float,
|
|
24
|
+
]: ...
|
|
25
|
+
|
|
26
|
+
class TriangleFinder:
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
ab: npt.NDArray[np.float32],
|
|
30
|
+
ac: npt.NDArray[np.float32],
|
|
31
|
+
bc: npt.NDArray[np.float32],
|
|
32
|
+
) -> None: ...
|
|
33
|
+
def get(self) -> list[int]: ...
|
|
34
|
+
|
|
35
|
+
class IterTriangleFinder:
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
ab: npt.NDArray[np.float32],
|
|
39
|
+
ac: npt.NDArray[np.float32],
|
|
40
|
+
bc: npt.NDArray[np.float32],
|
|
41
|
+
) -> None: ...
|
|
42
|
+
def __iter__(self) -> Iterator[list[int]]: ...
|
|
43
|
+
|
|
44
|
+
class UnitVectorLookup:
|
|
45
|
+
def __init__(self, vec: npt.NDArray[np.float32]) -> None: ...
|
|
46
|
+
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
|
+
def get_inter_star_index(
|
|
51
|
+
self, vec: npt.NDArray[np.float32], angle_threshold: float
|
|
52
|
+
) -> tuple[list[list[int]], list[float], list[float]]: ...
|
|
53
|
+
def look_up_close_angles(
|
|
54
|
+
self, vectors: npt.NDArray[np.float32], max_angle_rad: float
|
|
55
|
+
) -> list[tuple[list[float], float]]: ...
|
|
56
|
+
def look_up_close_angles_naive(
|
|
57
|
+
self, vectors: npt.NDArray[np.float32], max_angle_rad: float
|
|
58
|
+
) -> list[tuple[list[float], float]]: ...
|
|
File without changes
|