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
//! Core astrometry for the [arcsec] plate solver.
//!
//! Given the pixels of an astronomical image and an approximate pointing, this crate
//! works out exactly where the image lies on the sky and returns a FITS-style WCS
//! solution. It implements the star-pattern matching approach introduced by ASTAP and
//! reads ASTAP's star databases (`.1476`, `.290` and `.001`). For hint-free ("blind")
//! solving it builds its own pattern index from those databases ([`mod@index`],
//! [`pipeline::index_solve()`]), and also reads Astrometry.net index files.
//!
//! The pipeline, driven by [`pipeline::solve_image`]:
//!
//! 1. **Detection** ([`detection`]) — background and noise estimation, then a
//! multi-pass star finder measuring centroid, HFD and SNR.
//! 2. **Patterns** ([`quads`]) — 4-star quads described by five distance ratios.
//! 3. **Search** ([`pipeline`]) — a square spiral around the hint; at each position
//! catalogue stars ([`catalog`]) are projected onto the tangent plane
//! ([`math::coords`]), turned into quads and matched against the image.
//! 4. **Fit and verify** ([`math::lsq`], [`wcs`]) — a least-squares plate fit,
//! checked star by star before it is accepted.
//!
//! Angles are radians throughout the API unless a name says otherwise.
//!
//! # Example
//!
//! ```no_run
//! use std::path::PathBuf;
//! use arcsec_core::ImageBuffer;
//! use arcsec_core::pipeline::{SearchSpeed, SolveMethod, SolveParams, solve_image};
//!
//! # fn load_pixels() -> ImageBuffer { ImageBuffer::new(4096, 4096) }
//! let img: ImageBuffer = load_pixels(); // row-major f32 pixels from your FITS reader
//! let params = SolveParams {
//! ra_hint: 83.82_f64.to_radians(),
//! dec_hint: (-5.39_f64).to_radians(),
//! fov: 1.2_f64.to_radians(),
//! search_radius: 10.0_f64.to_radians(),
//! quad_tolerance: 0.007,
//! hfd_min: 1.5,
//! max_stars: 500,
//! db_path: PathBuf::from("/usr/share/astap/data"),
//! db_name: "d50".into(),
//! binning: 1,
//! method: SolveMethod::Quads,
//! threads: 0,
//! speed: SearchSpeed::Auto,
//! };
//! let wcs = solve_image(&img, ¶ms)?;
//! println!(
//! "centre RA {:.4}°, Dec {:.4}°, scale {:.2}\"/px",
//! wcs.ra0.to_degrees(),
//! wcs.dec0.to_degrees(),
//! wcs.cdelt2 * 3600.0
//! );
//! # Ok::<(), arcsec_core::ArcsecError>(())
//! ```
//!
//! Progress is reported through the [`log`] crate at `info` level; install any
//! logger to see it.
//!
//! [arcsec]: https://github.com/cruzzil/arcsec
//! [`log`]: https://docs.rs/log
// Library-only API hygiene, on top of the workspace lints (which the CLI shares).
extern crate alloc;
use ;
/// Process-wide worker-thread limit. 0 = one per available core.
static MAX_THREADS: AtomicUsize = new;
/// Set the maximum number of worker threads any stage may use.
///
/// Detection bands, the background histogram, the pixel-range scan and the spiral
/// search each spawn their own workers, so a single knob has to reach all of them;
/// threading a parameter through every signature would be worse. `1` makes the whole
/// solve single-threaded, which is what you want when running many solves in
/// parallel yourself, or when profiling.
thread_local!
/// Resolve the thread limit: this thread's override from [`with_max_threads`], else
/// the process-wide value from [`set_max_threads`], else one per core.
/// Run `f` with the thread limit set to `n` on this thread only (0 = no override).
///
/// [`set_max_threads`] is process-wide, which is right for a command-line tool and
/// wrong for a library host running several solves at once with different
/// budgets. Every stage reads the limit on the thread that called the solver, and
/// the solvers that hand work to their own threads pass the override on, so a
/// solve inside `f` keeps to `n` threads whatever the rest of the process does.
/// The previous value is restored when `f` returns or unwinds.
/// This thread's override from [`with_max_threads`], 0 if none: what a solver
/// passes on to a thread it spawns.
/// Synthetic skies, images and star databases for tests: compiled for this crate's
/// own tests, and with the `test-support` feature for the tests of the crates built
/// on it (the C library's end-to-end test). Not a stable API.
pub use ;
pub use ;
pub use ;
pub use ;