1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
//! # tetra3
//!
//! A fast, robust **lost-in-space star plate solver** written in Rust.
//!
//! > **Status: Alpha** — The core solver is based on well-vetted algorithms but has
//! > only been tested against a limited set of images. The API is not yet stable and
//! > may change between releases. Having said that, it has been made to work on both
//! > low-SNR images taken with a backyard camera and high-star-density images from
//! > more complex telescopes.
//!
//! Given a set of star centroids extracted from a camera image, `tetra3` identifies
//! the stars against a catalog and returns the camera's pointing direction as a
//! quaternion — no prior attitude estimate required.
//!
//! **Documentation:** For tutorials, concept guides, and Python API reference, see the
//! [tetra3rs documentation](https://tetra3rs.dev/).
//!
//! ## Features
//!
//! - **Lost-in-space solving** — determines attitude from star patterns with no initial guess
//! - **Tracking mode** — when an attitude hint is available (e.g. the previous frame's
//! solution), skip the 4-star pattern-hash phase and match centroids directly against
//! catalog stars near the hinted boresight. Set [`SolveConfig::attitude_hint`] /
//! [`SolveConfig::hint_uncertainty_rad`]. Succeeds with as few as 3 stars, robust to
//! sparse / low-SNR fields, with automatic fallback to lost-in-space unless
//! [`SolveConfig::strict_hint`] is set.
//! - **Fast** — geometric hashing of 4-star patterns with breadth-first (brightest-first) search
//! - **Robust** — statistical verification via binomial false-positive probability
//! - **Multiscale** — supports a range of field-of-view scales in a single database
//! - **Proper motion** — propagates Gaia DR3 / Hipparcos catalog positions to any observation epoch
//! - **Zero-copy deserialization** — databases serialize with [rkyv](https://docs.rs/rkyv)
//! for instant loading. The pattern catalog is stored as a sharded
//! [`solver::PatternCatalog`] so databases of any size — including wide-FOV-range
//! multiscale databases that exceed 2 GB — can be saved and loaded safely.
//! - **Centroid extraction** — detect stars from images with local background subtraction,
//! connected-component labeling, and quadratic sub-pixel peak refinement (`image` feature)
//! - **Camera model** — unified [`CameraModel`] struct (focal length, optical center, parity,
//! distortion) used throughout the solve and calibration pipeline
//! - **Distortion calibration** — fit SIP polynomial or radial distortion models from one or
//! more solved images via [`calibrate_camera`]
//! - **WCS output** — solve results include FITS-standard WCS fields (CD matrix, CRVAL) and
//! [`SolveResult::pixel_to_world`] / [`SolveResult::world_to_pixel`] methods
//! - **Stellar aberration** — optional correction for the ~20″ apparent shift in star
//! positions caused by the observer's barycentric velocity; set
//! [`SolveConfig::observer_velocity_km_s`] (use [`earth_barycentric_velocity`] for
//! ground-based / Earth-orbiting observers)
//! - **Tested on real spacecraft imagery** — successfully solves NASA TESS Full Frame
//! Images (~12° FOV, significant optical distortion). Multi-image calibration across
//! 10 TESS sectors achieves sub-arcsec agreement with FITS WCS solutions
//!
//! ## Example
//!
//! ```no_run
//! use tetra3::{GenerateDatabaseConfig, SolverDatabase, SolveConfig, Centroid, SolveStatus};
//!
//! // Generate a database from the Gaia catalog
//! let config = GenerateDatabaseConfig {
//! max_fov_deg: 20.0,
//! epoch_proper_motion_year: Some(2025.0),
//! ..Default::default()
//! };
//! let db = SolverDatabase::generate_from_gaia("data/gaia_merged.bin", &config).unwrap();
//!
//! // Save for fast loading later, or load a previously saved database
//! db.save_to_file("data/my_database.rkyv").unwrap();
//! let db = SolverDatabase::load_from_file("data/my_database.rkyv").unwrap();
//!
//! // Solve from image centroids (pixel coordinates, origin at image center)
//! let centroids = vec![
//! Centroid { x: 100.0, y: 200.0, mass: Some(50.0), cov: None },
//! Centroid { x: -50.0, y: -10.0, mass: Some(45.0), cov: None },
//! // ... more centroids ...
//! ];
//!
//! let solve_config = SolveConfig {
//! fov_estimate_rad: (15.0_f32).to_radians(), // horizontal FOV
//! image_width: 1024,
//! image_height: 1024,
//! fov_max_error_rad: Some((2.0_f32).to_radians()),
//! ..Default::default()
//! };
//!
//! let result = db.solve_from_centroids(¢roids, &solve_config);
//! if result.status == SolveStatus::MatchFound {
//! let q = result.qicrs2cam.unwrap();
//! println!("Attitude: {q}");
//! println!("Matched {} stars in {:.1} ms",
//! result.num_matches.unwrap(), result.solve_time_ms);
//! }
//! ```
//!
//! ## Tracking mode
//!
//! For frame-to-frame solving where each solve seeds the next, pass the prior
//! attitude as a hint to skip the 4-star pattern-hash phase:
//!
//! ```no_run
//! # use tetra3::{SolveConfig, SolverDatabase, Centroid};
//! # fn dummy(prev: tetra3::solver::SolveResult, db: SolverDatabase, centroids: Vec<Centroid>) {
//! let config = SolveConfig {
//! attitude_hint: prev.qicrs2cam,
//! hint_uncertainty_rad: 1.0_f32.to_radians(),
//! camera_model: prev.camera_model.clone().unwrap(),
//! ..SolveConfig::new((15.0_f32).to_radians(), 1024, 1024)
//! };
//! let result = db.solve_from_centroids(¢roids, &config);
//! # }
//! ```
//!
//! The solver projects catalog stars near the hinted boresight, nearest-neighbor
//! matches them to centroids, and runs the same Wahba SVD + verification + WCS
//! refine path as lost-in-space. Tracking succeeds with as few as 3 matched stars
//! (LIS needs 4) and is robust to pattern-hash failures from sparse / low-SNR
//! fields. On failure it falls back to lost-in-space automatically unless
//! [`SolveConfig::strict_hint`] is `true`.
//!
//! ## Stellar aberration
//!
//! Stellar aberration shifts apparent star positions by up to ~20″ due to the
//! observer's barycentric velocity (~30 km/s for Earth). The pattern-matching step
//! is unaffected (inter-star angular separations are invariant to first order in
//! v/c), but the final attitude quaternion is biased by ~20″ unless corrected.
//!
//! Pass the observer's barycentric velocity (ICRS, km/s) via
//! [`SolveConfig::observer_velocity_km_s`]. The solver applies a first-order
//! correction to all catalog vectors before matching and refinement.
//!
//! For Earth-based or Earth-orbiting observers, [`earth_barycentric_velocity`]
//! provides an approximate velocity from a circular-orbit model:
//!
//! ```no_run
//! use tetra3::{earth_barycentric_velocity, SolveConfig};
//!
//! let v = earth_barycentric_velocity(9321.0); // days since J2000.0
//! let config = SolveConfig {
//! observer_velocity_km_s: Some(v),
//! ..SolveConfig::new((10.0_f32).to_radians(), 1024, 1024)
//! };
//! ```
//!
//! ## Algorithm overview
//!
//! 1. **Pattern generation** — select combinations of 4 bright centroids; compute 6 pairwise
//! angular separations and normalize into 5 edge ratios (a geometric invariant)
//! 2. **Hash lookup** — quantize the edge ratios into a key and probe a precomputed hash
//! table for matching catalog patterns
//! 3. **Attitude estimation** — solve Wahba's problem via SVD to find the rotation from
//! catalog (ICRS) to camera frame
//! 4. **Verification** — project nearby catalog stars into the camera frame, count matches,
//! and accept only if the false-positive probability (binomial CDF) is below threshold
//! 5. **Refinement** — re-estimate the rotation using all matched star pairs via iterative
//! SVD passes
//! 6. **WCS fit** — constrained 3-DOF tangent-plane refinement (rotation angle θ + CRVAL
//! offset) with sigma-clipping, producing FITS-standard WCS output
//!
//! ## Credits
//!
//! This crate is a Rust implementation of the **tetra3** / **cedar-solve** algorithm:
//!
//! - [**tetra3**](https://github.com/esa/tetra3) — the original Python implementation by
//! Gustav Pettersson at ESA
//! - [**cedar-solve**](https://github.com/smroid/cedar-solve) — Steven Rosenthal's C++/Rust
//! star plate solver, which this implementation closely follows
//! - **Paper**: G. Pettersson, "Tetra3: a fast and robust star identification algorithm,"
//! ESA GNC Conference, 2023
//!
//! This Rust implementation was developed by Steven Michael with assistance from
//! [Claude Code](https://claude.ai/claude-code) (Anthropic).
//!
/// Raw star catalogs (Gaia DR3, optionally Hipparcos)
pub
pub use CameraModel;
pub use *;
pub use ;
pub use ;
pub use ;
pub use earth_barycentric_velocity;
pub use *;
pub use *;
// Commonly used types
// Note: 32-bit floats are sufficient for most of the math
// We switch to 64-bit for the SVD used in the final solver step,
// as 32-bit floats have shown to be insufficiently accurate for that step.
pub type Quaternion = Quaternion;
pub type Vector3 = Vector3;
pub type Matrix2 = Matrix2;