ruststartracker 0.2.3__tar.gz → 0.2.6__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.3
3
+ Version: 0.2.6
4
4
  Summary: Lightweight Python Star Tracker With Rust Backend
5
5
  License: MIT
6
6
  Author: Nicolas Tobler
@@ -113,3 +113,14 @@ Gaia DR3 data is © European Space Agency and is released under the [**Creative
113
113
  > Gaia Collaboration, Vallenari et al. (2022), *A\&A* **674**, A1.
114
114
  > [DOI: 10.1051/0004-6361/202243940](https://doi.org/10.1051/0004-6361/202243940)
115
115
 
116
+ ### Hipparcos and Tycho Data
117
+
118
+ This project includes data from the European Space Agency (ESA) mission **Hipparcos**.
119
+
120
+ The Hipparcos and Tycho Catalogues were processed by the Hipparcos and Tycho Data Analysis Consortium.
121
+
122
+ The Hipparcos and Tycho Catalogues are © European Space Agency and are released under the [**Creative Commons Attribution 3.0 IGO (CC BY 3.0 IGO)**](https://creativecommons.org/licenses/by/3.0/igo/) license.
123
+
124
+ > Perryman, M. A. C., et al. (1997), *Astronomy & Astrophysics* **323**, L49-L52.
125
+ 1997A&A...323L..49P
126
+
@@ -91,3 +91,14 @@ Gaia DR3 data is © European Space Agency and is released under the [**Creative
91
91
 
92
92
  > Gaia Collaboration, Vallenari et al. (2022), *A\&A* **674**, A1.
93
93
  > [DOI: 10.1051/0004-6361/202243940](https://doi.org/10.1051/0004-6361/202243940)
94
+
95
+ ### Hipparcos and Tycho Data
96
+
97
+ This project includes data from the European Space Agency (ESA) mission **Hipparcos**.
98
+
99
+ The Hipparcos and Tycho Catalogues were processed by the Hipparcos and Tycho Data Analysis Consortium.
100
+
101
+ The Hipparcos and Tycho Catalogues are © European Space Agency and are released under the [**Creative Commons Attribution 3.0 IGO (CC BY 3.0 IGO)**](https://creativecommons.org/licenses/by/3.0/igo/) license.
102
+
103
+ > Perryman, M. A. C., et al. (1997), *Astronomy & Astrophysics* **323**, L49-L52.
104
+ 1997A&A...323L..49P
@@ -0,0 +1,126 @@
1
+ """Build rust backend.
2
+
3
+ This script is automatically run when the pyproject is installed.
4
+ """
5
+
6
+ import csv
7
+ import io
8
+ import pathlib
9
+ import shutil
10
+ import subprocess
11
+
12
+
13
+ def download_gaia_data(output_file: pathlib.Path) -> None:
14
+ """Download Gaia data and save it to the specified output file.
15
+
16
+ Args:
17
+ output_file: Path to save the downloaded Gaia data (csv).
18
+ """
19
+ from astroquery.gaia import Gaia
20
+
21
+ Gaia.ROW_LIMIT = -1
22
+
23
+ query = """
24
+ SELECT source_id, ra, dec, parallax, pmra, pmdec, phot_g_mean_mag
25
+ FROM gaiadr3.gaia_source
26
+ WHERE phot_g_mean_mag < 7
27
+ AND parallax > 0
28
+ AND astrometric_params_solved = 31
29
+ AND visibility_periods_used >= 8
30
+ """
31
+
32
+ print("Executing query on Gaia database...")
33
+ job = Gaia.launch_job_async(
34
+ query,
35
+ dump_to_file=True,
36
+ output_format="csv",
37
+ output_file=str(output_file.absolute()),
38
+ verbose=True,
39
+ )
40
+ if not job.is_finished():
41
+ print("Job did not finish successfully.")
42
+ return
43
+
44
+ print(f"Saved to: {output_file}")
45
+
46
+
47
+ def download_hipparcos_data(output_file: pathlib.Path) -> None:
48
+ """Download Hipparcos data and save it to the specified output file.
49
+
50
+ Args:
51
+ output_file: Path to save the downloaded Hipparcos data (csv).
52
+ """
53
+ from astroquery.vizier import Vizier
54
+
55
+ Vizier.ROW_LIMIT = -1
56
+
57
+ print("Executing query on Hipparcos database...")
58
+ query = Vizier.query_constraints(
59
+ catalog=["I/311/hip2"],
60
+ Hpmag="<7",
61
+ )
62
+
63
+ if not query:
64
+ print("Job did not finish successfully.")
65
+ return
66
+
67
+ # Initialize a string buffer to write the CSV data to
68
+ csv_output = io.StringIO()
69
+ writer = csv.writer(csv_output)
70
+
71
+ # Write the header row
72
+ # Use column names that are common in Gaia and Hipparcos
73
+ header = ["source_id", "ra", "dec", "parallax", "pmra", "pmdec", "phot_g_mean_mag"]
74
+ writer.writerow(header)
75
+
76
+ # Iterate over the rows of the Astropy table
77
+ for row in query[0]:
78
+ # Extract the required data points
79
+ source_id = row["HIP"]
80
+ ra = row["RArad"]
81
+ dec = row["DErad"]
82
+ parallax = row["Plx"]
83
+ pmra = row["pmRA"]
84
+ pmdec = row["pmDE"]
85
+ # Use Vmag as the proxy for phot_g_mean_mag
86
+ phot_g_mean_mag = row["Hpmag"]
87
+
88
+ # Create a list for the new row and write it to the CSV writer
89
+ new_row = [source_id, ra, dec, parallax, pmra, pmdec, phot_g_mean_mag]
90
+ writer.writerow(new_row)
91
+
92
+ # Get the complete CSV string from the buffer
93
+ csv_string = csv_output.getvalue()
94
+
95
+ # You can save this string to a file
96
+ with output_file.open("w") as f:
97
+ f.write(csv_string)
98
+
99
+ print(f"Saved to: {output_file}")
100
+
101
+
102
+ def build_script() -> None:
103
+ """Build rust backend and move shared library to correct folder."""
104
+ cwd = pathlib.Path(__file__).parent.expanduser().absolute()
105
+
106
+ gaia_file = cwd / "ruststartracker/gaia_data_j2016.csv"
107
+ if not gaia_file.exists():
108
+ download_gaia_data(gaia_file)
109
+
110
+ hipparcos_file = cwd / "ruststartracker/hipparcos_data_j1991.25.csv"
111
+ if not hipparcos_file.exists():
112
+ download_hipparcos_data(hipparcos_file)
113
+
114
+ subprocess.check_call( # noqa: S603
115
+ ["cargo", "build", "--release", "--features", "improc,gaia,hipparcos"], # noqa: S607
116
+ cwd=cwd,
117
+ stdout=None,
118
+ stderr=None,
119
+ )
120
+ shutil.copy(
121
+ cwd / "target/release/libruststartracker.so", cwd / "ruststartracker/libruststartracker.so"
122
+ )
123
+
124
+
125
+ if __name__ == "__main__":
126
+ build_script()
@@ -1,12 +1,13 @@
1
1
  [tool.poetry]
2
2
  name = "ruststartracker"
3
- version = "0.2.3"
3
+ version = "0.2.6"
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
9
  { path = "ruststartracker/gaia_data_j2016.csv", format = ["sdist", "wheel"] },
10
+ { path = "ruststartracker/hipparcos_data_j1991.25.csv", format = ["sdist", "wheel"] },
10
11
  { path = "ruststartracker/libruststartracker.so", format = ["wheel"] },
11
12
  ]
12
13
 
@@ -33,6 +34,7 @@ scipy-stubs = "^1.14.1.5"
33
34
  ruff = "^0.9.3"
34
35
  bump-my-version = "^1.2.0"
35
36
  astroquery = "^0.4.10"
37
+ typing-extensions = "^4.12.2"
36
38
 
37
39
  [tool.poetry.build]
38
40
  script = "build_script.py"
@@ -42,7 +44,7 @@ requires = ["poetry-core", "astroquery>=0.4.10"]
42
44
  build-backend = "poetry.core.masonry.api"
43
45
 
44
46
  [tool.bumpversion]
45
- current_version = "0.2.3"
47
+ current_version = "0.2.6"
46
48
  commit = true
47
49
  tag = true
48
50
  tag_name = "v{new_version}"
@@ -7,14 +7,17 @@ import pathlib
7
7
 
8
8
  import numpy as np
9
9
  import numpy.typing as npt
10
+ from typing_extensions import Self
10
11
 
11
12
  AU: float = 149597870.693
12
13
  """Astronomical unit."""
13
14
 
14
- INTERNAL_CATALOG_FILE = (
15
- pathlib.Path(__file__).parent.expanduser().absolute() / "gaia_data_j2016.csv"
15
+ GAIA_CATALOG_FILE = pathlib.Path(__file__).parent.expanduser().absolute() / "gaia_data_j2016.csv"
16
+ """Location of the internal Gaia star catalog file."""
17
+ HIPPARCOS_CATALOG_FILE = (
18
+ pathlib.Path(__file__).parent.expanduser().absolute() / "hipparcos_data_j1991.25.csv"
16
19
  )
17
- """Location of the internal star catalog file."""
20
+ """Location of the internal Hipparcos star catalog file."""
18
21
 
19
22
 
20
23
  def time_to_epoch(t: datetime.datetime) -> float:
@@ -44,24 +47,41 @@ class StarCatalog:
44
47
  """Proper motion of the declination in mad."""
45
48
  magnitude: npt.NDArray[np.float32]
46
49
  """Magnitude values."""
47
- epoch: float = 2016.0
50
+ epoch: float
48
51
  """Epoch of the catalog in years."""
49
52
 
53
+ @classmethod
54
+ def from_gaia(cls, *, max_magnitude: float = 6.0) -> Self:
55
+ """Read internally provided Gaia catalog file.
56
+
57
+ Args:
58
+ max_magnitude: Maximum magnitude to include.
59
+ """
60
+ return cls(GAIA_CATALOG_FILE, epoch=2016.0, max_magnitude=max_magnitude)
61
+
62
+ @classmethod
63
+ def from_hipparcos(cls, *, max_magnitude: float = 6.0) -> Self:
64
+ """Read internally provided Hipparcos catalog file.
65
+
66
+ Args:
67
+ max_magnitude: Maximum magnitude to include.
68
+ """
69
+ return cls(HIPPARCOS_CATALOG_FILE, epoch=1991.25, max_magnitude=max_magnitude)
70
+
50
71
  def __init__(
51
- self, filename: pathlib.Path | str | None = None, max_magnitude: float = 6.0
72
+ self, filename: pathlib.Path | str, *, epoch: float, max_magnitude: float = 6.0
52
73
  ) -> None:
53
74
  """Read catalog from file.
54
75
 
55
76
  Args:
56
77
  filename: Star catalog filename.
78
+ epoch: Epoch of the catalog in years.
57
79
  max_magnitude: Maximum magnitude to include.
58
80
  """
59
- # Use locally included star catalog is no file is given
60
- filename = INTERNAL_CATALOG_FILE if filename is None else pathlib.Path(filename)
61
-
81
+ self.epoch = epoch
62
82
  keep_columns = ("ra", "dec", "parallax", "pmra", "pmdec", "phot_g_mean_mag")
63
83
 
64
- with filename.open("r") as f:
84
+ with pathlib.Path(filename).open("r") as f:
65
85
  it = csv.reader(f, delimiter=",", strict=True)
66
86
 
67
87
  # Get columns
@@ -84,6 +84,8 @@ def extract_observations(
84
84
  class StarCatalog:
85
85
  @classmethod
86
86
  def from_gaia(cls, *, max_magnitude: float | None) -> Self: ...
87
+ @classmethod
88
+ def from_hipparcos(cls, *, max_magnitude: float | None) -> Self: ...
87
89
  def normalized_positions(
88
90
  self, *, epoch: float | None, observer_position: np.ndarray | None
89
91
  ) -> npt.NDArray[np.float32]: ...
@@ -0,0 +1,101 @@
1
+ import datetime
2
+ import time
3
+
4
+ import astropy.time # type: ignore[import]
5
+ import numpy as np
6
+ import pytest
7
+
8
+ import ruststartracker.catalog
9
+ import ruststartracker.libruststartracker
10
+
11
+
12
+ def test_time_to_epoch():
13
+ np.testing.assert_allclose(
14
+ ruststartracker.catalog.time_to_epoch(
15
+ datetime.datetime.fromisoformat("2000-01-01T11:58:56")
16
+ ),
17
+ 2000.0,
18
+ rtol=1e-20,
19
+ atol=1 / (365 * 86400),
20
+ )
21
+
22
+
23
+ @pytest.mark.parametrize(
24
+ "iso_date",
25
+ [
26
+ "2000-01-01T11:58:56",
27
+ "2024-01-01T11:58:56",
28
+ ],
29
+ )
30
+ def test_time_to_epoch_astropy(iso_date: str):
31
+ ground_truth = float(astropy.time.Time(iso_date).jyear) # type: ignore
32
+ np.testing.assert_allclose(
33
+ ruststartracker.catalog.time_to_epoch(datetime.datetime.fromisoformat(iso_date)),
34
+ ground_truth,
35
+ rtol=1e-20,
36
+ atol=64 / (365 * 86400),
37
+ )
38
+
39
+
40
+ def test_extract_observations():
41
+ positions = ruststartracker.catalog.StarCatalog.from_gaia().normalized_positions()
42
+
43
+ assert positions.ndim == 2
44
+ assert positions.shape[1] == 3
45
+ np.testing.assert_allclose(np.linalg.norm(positions, axis=-1), 1.0, rtol=1e-5)
46
+
47
+
48
+ def test_gaia_python_rust():
49
+ t0 = time.monotonic()
50
+ positions = ruststartracker.catalog.StarCatalog.from_gaia().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
+
60
+ def test_hipparcos_python_rust():
61
+ t0 = time.monotonic()
62
+ positions = ruststartracker.catalog.StarCatalog.from_hipparcos().normalized_positions(
63
+ epoch=2025.0
64
+ )
65
+ print(f"Python catalog took {time.monotonic() - t0:.3f} seconds")
66
+ t0 = time.monotonic()
67
+ positions2 = ruststartracker.libruststartracker.StarCatalog.from_hipparcos(
68
+ max_magnitude=6.0
69
+ ).normalized_positions(epoch=2025.0, observer_position=None)
70
+ print(f"Rust catalog took {time.monotonic() - t0:.3f} seconds")
71
+ np.testing.assert_allclose(positions, positions2, rtol=1e-5, atol=1e-5)
72
+
73
+
74
+ def test_compare_hipparcos_gaia():
75
+ def filt(cat: ruststartracker.catalog.StarCatalog, m: float = 0):
76
+ positions = cat.normalized_positions(epoch=2025.0)
77
+ mags = cat.magnitude
78
+ mask = (positions[..., 2] > 0.9) * (mags > 5 - m) * (mags < 8 - m)
79
+ return positions[mask]
80
+
81
+ positions_hipparcos = filt(ruststartracker.catalog.StarCatalog.from_hipparcos(max_magnitude=8))
82
+ positions_gaia = filt(ruststartracker.catalog.StarCatalog.from_gaia(max_magnitude=8), m=0.9)
83
+
84
+ dists = np.linalg.norm(
85
+ positions_hipparcos[np.newaxis, :, :] - positions_gaia[:, np.newaxis, :], axis=-1
86
+ )
87
+ matches = np.min(dists, axis=0) < 0.00005
88
+
89
+ assert np.mean(matches > 0.9)
90
+
91
+ if False:
92
+ import matplotlib.pyplot as plt
93
+
94
+ plt.plot(positions_gaia[..., 0], positions_gaia[..., 1], "+", label="gaia")
95
+ plt.plot(positions_hipparcos[..., 0], positions_hipparcos[..., 1], "x", label="hipparcos")
96
+ plt.legend()
97
+ plt.show()
98
+
99
+
100
+ if __name__ == "__main__":
101
+ pytest.main([__file__])
@@ -24,7 +24,7 @@ def prepare() -> tuple[ruststartracker.StarTracker, np.ndarray]:
24
24
 
25
25
  dist_coefs = np.array([-0.44120807, -0.15954202, 0.00767012, -0.00213292, -1.64788247])
26
26
 
27
- catalog = ruststartracker.StarCatalog()
27
+ catalog = ruststartracker.StarCatalog.from_gaia()
28
28
  star_catalog_vecs = catalog.normalized_positions(epoch=2024)
29
29
  star_catalog_magnitudes = catalog.magnitude
30
30
 
@@ -20,12 +20,10 @@ def test_extract_observations(impl: str):
20
20
  t0 = time.monotonic()
21
21
  if impl == "python":
22
22
  centers, intensities = ruststartracker.star._extract_observations(img, threshold=30)
23
- elif impl == "rust":
23
+ else:
24
24
  centers, intensities = ruststartracker.libruststartracker.extract_observations(
25
25
  img, 30, 3, 300
26
26
  )
27
- else:
28
- raise AssertionError
29
27
  print(f"Extracting observations took {time.monotonic() - t0:.5f} seconds")
30
28
 
31
29
  assert isinstance(centers, np.ndarray)
@@ -1,65 +0,0 @@
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,61 +0,0 @@
1
- import datetime
2
- import time
3
-
4
- import astropy.time # type: ignore[import]
5
- import numpy as np
6
- import pytest
7
-
8
- import ruststartracker.catalog
9
- import ruststartracker.libruststartracker
10
-
11
-
12
- def test_time_to_epoch():
13
- np.testing.assert_allclose(
14
- ruststartracker.catalog.time_to_epoch(
15
- datetime.datetime.fromisoformat("2000-01-01T11:58:56")
16
- ),
17
- 2000.0,
18
- rtol=1e-20,
19
- atol=1 / (365 * 86400),
20
- )
21
-
22
-
23
- @pytest.mark.parametrize(
24
- "iso_date",
25
- [
26
- "2000-01-01T11:58:56",
27
- "2024-01-01T11:58:56",
28
- ],
29
- )
30
- def test_time_to_epoch_astropy(iso_date: str):
31
- ground_truth = float(astropy.time.Time(iso_date).jyear) # type: ignore
32
- np.testing.assert_allclose(
33
- ruststartracker.catalog.time_to_epoch(datetime.datetime.fromisoformat(iso_date)),
34
- ground_truth,
35
- rtol=1e-20,
36
- atol=64 / (365 * 86400),
37
- )
38
-
39
-
40
- def test_extract_observations():
41
- positions = ruststartracker.catalog.StarCatalog().normalized_positions()
42
-
43
- assert positions.ndim == 2
44
- assert positions.shape[1] == 3
45
- np.testing.assert_allclose(np.linalg.norm(positions, axis=-1), 1.0, rtol=1e-5)
46
-
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
-
60
- if __name__ == "__main__":
61
- pytest.main([__file__])
File without changes