Skip to main content

citra_solve/solver/
solution.rs

1//! Solution representation and output.
2
3use crate::core::types::{CatalogStar, RaDec};
4use crate::wcs::Wcs;
5
6/// A successful plate solve solution.
7#[derive(Debug, Clone)]
8pub struct Solution {
9    /// The WCS transformation.
10    pub wcs: Wcs,
11    /// Center of the image in sky coordinates.
12    pub center: RaDec,
13    /// Field of view width in degrees.
14    pub fov_width_deg: f64,
15    /// Field of view height in degrees.
16    pub fov_height_deg: f64,
17    /// Rotation angle (position angle of +Y axis) in degrees.
18    pub rotation_deg: f64,
19    /// Pixel scale in arcseconds per pixel.
20    pub pixel_scale_arcsec: f64,
21    /// RMS residual of the fit in arcseconds.
22    pub rms_arcsec: f64,
23    /// Number of stars matched.
24    pub num_matched_stars: usize,
25    /// Log-odds confidence measure.
26    pub log_odds: f64,
27    /// Matched stars (detected index, catalog star).
28    pub matched_stars: Vec<(usize, CatalogStar)>,
29}
30
31impl Solution {
32    /// Create a new solution from WCS and metadata.
33    pub fn new(
34        wcs: Wcs,
35        image_width: u32,
36        image_height: u32,
37        rms_arcsec: f64,
38        log_odds: f64,
39        matched_stars: Vec<(usize, CatalogStar)>,
40    ) -> Self {
41        let center = wcs.pixel_to_sky(image_width as f64 / 2.0, image_height as f64 / 2.0);
42        let pixel_scale = wcs.pixel_scale_arcsec();
43        let fov_width_deg = image_width as f64 * pixel_scale / 3600.0;
44        let fov_height_deg = image_height as f64 * pixel_scale / 3600.0;
45        let rotation_deg = wcs.rotation_deg();
46
47        Self {
48            wcs,
49            center,
50            fov_width_deg,
51            fov_height_deg,
52            rotation_deg,
53            pixel_scale_arcsec: pixel_scale,
54            rms_arcsec,
55            num_matched_stars: matched_stars.len(),
56            log_odds,
57            matched_stars,
58        }
59    }
60
61    /// Get the sky position of a pixel.
62    pub fn pixel_to_sky(&self, x: f64, y: f64) -> RaDec {
63        self.wcs.pixel_to_sky(x, y)
64    }
65
66    /// Get the pixel position of a sky coordinate.
67    pub fn sky_to_pixel(&self, radec: &RaDec) -> (f64, f64) {
68        self.wcs.sky_to_pixel(radec)
69    }
70
71    /// Generate a FITS WCS header.
72    pub fn to_fits_header(&self) -> String {
73        self.wcs.to_fits_header()
74    }
75
76    /// Get the corner coordinates of the image.
77    pub fn corners(&self, image_width: u32, image_height: u32) -> [RaDec; 4] {
78        [
79            self.wcs.pixel_to_sky(0.0, 0.0),
80            self.wcs.pixel_to_sky(image_width as f64, 0.0),
81            self.wcs
82                .pixel_to_sky(image_width as f64, image_height as f64),
83            self.wcs.pixel_to_sky(0.0, image_height as f64),
84        ]
85    }
86}
87
88impl std::fmt::Display for Solution {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        writeln!(f, "Plate Solve Solution:")?;
91        writeln!(
92            f,
93            "  Center: RA={:.6}° Dec={:.6}°",
94            self.center.ra_deg(),
95            self.center.dec_deg()
96        )?;
97        writeln!(
98            f,
99            "  FOV: {:.3}° x {:.3}°",
100            self.fov_width_deg, self.fov_height_deg
101        )?;
102        writeln!(f, "  Rotation: {:.2}°", self.rotation_deg)?;
103        writeln!(f, "  Scale: {:.4}\"/pixel", self.pixel_scale_arcsec)?;
104        writeln!(f, "  RMS: {:.2}\"", self.rms_arcsec)?;
105        writeln!(f, "  Matched stars: {}", self.num_matched_stars)?;
106        writeln!(f, "  Log-odds: {:.1}", self.log_odds)?;
107        Ok(())
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn test_solution_display() {
117        let wcs = Wcs::new(
118            (512.0, 384.0),
119            RaDec::from_degrees(180.0, 45.0),
120            [[-0.001, 0.0], [0.0, 0.001]],
121        );
122        let solution = Solution::new(wcs, 1024, 768, 1.5, 25.0, vec![]);
123        let display = format!("{}", solution);
124        assert!(display.contains("Plate Solve Solution"));
125        assert!(display.contains("Center"));
126    }
127}