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