arcsec_core/lib.rs
1//! Core astrometry for the [arcsec] plate solver.
2//!
3//! Given the pixels of an astronomical image and an approximate pointing, this crate
4//! works out exactly where the image lies on the sky and returns a FITS-style WCS
5//! solution. It implements the star-pattern matching approach introduced by ASTAP and
6//! reads ASTAP's star databases (`.1476`, `.290` and `.001`), plus Astrometry.net
7//! index files for hint-free ("blind") position estimates.
8//!
9//! The pipeline, driven by [`pipeline::solve_image`]:
10//!
11//! 1. **Detection** ([`detection`]) — background and noise estimation, then a
12//! multi-pass star finder measuring centroid, HFD and SNR.
13//! 2. **Patterns** ([`quads`]) — 4-star quads described by five distance ratios.
14//! 3. **Search** ([`pipeline`]) — a square spiral around the hint; at each position
15//! catalogue stars ([`catalog`]) are projected onto the tangent plane
16//! ([`math::coords`]), turned into quads and matched against the image.
17//! 4. **Fit and verify** ([`math::lsq`], [`wcs`]) — a least-squares plate fit,
18//! checked star by star before it is accepted.
19//!
20//! Angles are radians throughout the API unless a name says otherwise.
21//!
22//! # Example
23//!
24//! ```no_run
25//! use std::path::PathBuf;
26//! use arcsec_core::ImageBuffer;
27//! use arcsec_core::pipeline::{SearchSpeed, SolveMethod, SolveParams, solve_image};
28//!
29//! # fn load_pixels() -> ImageBuffer { ImageBuffer::new(4096, 4096) }
30//! let img: ImageBuffer = load_pixels(); // row-major f32 pixels from your FITS reader
31//! let params = SolveParams {
32//! ra_hint: 83.82_f64.to_radians(),
33//! dec_hint: (-5.39_f64).to_radians(),
34//! fov: 1.2_f64.to_radians(),
35//! search_radius: 10.0_f64.to_radians(),
36//! quad_tolerance: 0.007,
37//! hfd_min: 1.5,
38//! max_stars: 500,
39//! db_path: PathBuf::from("/usr/share/astap/data"),
40//! db_name: "d50".into(),
41//! binning: 1,
42//! method: SolveMethod::Quads,
43//! threads: 0,
44//! speed: SearchSpeed::Auto,
45//! };
46//! let wcs = solve_image(&img, ¶ms)?;
47//! println!(
48//! "centre RA {:.4}°, Dec {:.4}°, scale {:.2}\"/px",
49//! wcs.ra0.to_degrees(),
50//! wcs.dec0.to_degrees(),
51//! wcs.cdelt2 * 3600.0
52//! );
53//! # Ok::<(), arcsec_core::ArcsecError>(())
54//! ```
55//!
56//! Progress is reported through the [`log`] crate at `info` level; install any
57//! logger to see it.
58//!
59//! [arcsec]: https://github.com/cruzzil/arcsec
60//! [`log`]: https://docs.rs/log
61
62#![warn(missing_docs)]
63// Library-only API hygiene, on top of the workspace lints (which the CLI shares).
64#![warn(
65 clippy::must_use_candidate,
66 clippy::return_self_not_must_use,
67 clippy::missing_errors_doc,
68 clippy::missing_panics_doc,
69 clippy::doc_markdown
70)]
71
72extern crate alloc;
73
74use core::sync::atomic::{AtomicUsize, Ordering};
75
76/// Process-wide worker-thread limit. 0 = one per available core.
77static MAX_THREADS: AtomicUsize = AtomicUsize::new(0);
78
79/// Set the maximum number of worker threads any stage may use.
80///
81/// Detection bands, the background histogram, the pixel-range scan and the spiral
82/// search each spawn their own workers, so a single knob has to reach all of them;
83/// threading a parameter through every signature would be worse. `1` makes the whole
84/// solve single-threaded, which is what you want when running many solves in
85/// parallel yourself, or when profiling.
86pub fn set_max_threads(n: usize) {
87 MAX_THREADS.store(n, Ordering::Relaxed);
88}
89
90/// Resolve the thread limit: the configured value, or one per core if unset.
91#[must_use]
92pub fn max_threads() -> usize {
93 match MAX_THREADS.load(Ordering::Relaxed) {
94 0 => std::thread::available_parallelism().map_or(1, core::num::NonZero::get),
95 n => n,
96 }
97}
98
99pub mod catalog;
100pub mod detection;
101pub mod error;
102pub mod math;
103pub mod pipeline;
104pub mod quads;
105pub mod types;
106pub mod wcs;
107
108#[cfg(test)]
109mod test_support;
110
111pub use catalog::{AnetIndex, AnetIndexEntry, AnetStar, load_anet_index, peek_anet_scale};
112pub use error::{ArcsecError, Result};
113pub use pipeline::{BlindSolveParams, blind_solve};
114pub use types::{
115 ImageBuffer, MatchedStar, PairedPositions, PlateConstants, Quad, QuadList, Star, StarList,
116 WcsSolution,
117};