Skip to main content

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, &params)?;
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};