Skip to main content

fpm_rs/model/
sampling.rs

1use serde::{Deserialize, Serialize};
2
3use crate::error::{Error, Result};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
6pub enum CoordinateConvention {
7    /// Spectra are stored with zero frequency at the array centre. Positive
8    /// illumination k shifts the crop centre toward increasing array indices.
9    CenteredPositiveK,
10}
11
12#[derive(Clone, Debug, Serialize, Deserialize)]
13pub struct Sampling {
14    pub low_res_pixel_size: f64,
15    pub high_res_pixel_size: f64,
16    pub dkx: f64,
17    pub dky: f64,
18    pub wavelength: Option<f64>,
19    pub synthetic_na: Option<f64>,
20    pub coordinate_convention: CoordinateConvention,
21}
22
23impl Sampling {
24    pub fn new(
25        low_res_pixel_size: f64,
26        high_res_pixel_size: f64,
27        dkx: f64,
28        dky: f64,
29    ) -> Result<Self> {
30        let sampling = Self {
31            low_res_pixel_size,
32            high_res_pixel_size,
33            dkx,
34            dky,
35            wavelength: None,
36            synthetic_na: None,
37            coordinate_convention: CoordinateConvention::CenteredPositiveK,
38        };
39        sampling.validate()?;
40        Ok(sampling)
41    }
42
43    pub fn validate(&self) -> Result<()> {
44        for (name, value) in [
45            ("low_res_pixel_size", self.low_res_pixel_size),
46            ("high_res_pixel_size", self.high_res_pixel_size),
47            ("dkx", self.dkx),
48            ("dky", self.dky),
49        ] {
50            if !value.is_finite() || value <= 0.0 {
51                return Err(Error::InvalidParameter {
52                    name,
53                    reason: "must be finite and positive".into(),
54                });
55            }
56        }
57        if self.high_res_pixel_size > self.low_res_pixel_size {
58            return Err(Error::InvalidParameter {
59                name: "high_res_pixel_size",
60                reason: "must not exceed low-resolution pixel size".into(),
61            });
62        }
63        if self
64            .wavelength
65            .is_some_and(|value| !value.is_finite() || value <= 0.0)
66        {
67            return Err(Error::InvalidParameter {
68                name: "wavelength",
69                reason: "must be finite and positive when present".into(),
70            });
71        }
72        if self
73            .synthetic_na
74            .is_some_and(|value| !value.is_finite() || value <= 0.0)
75        {
76            return Err(Error::InvalidParameter {
77                name: "synthetic_na",
78                reason: "must be finite and positive when present".into(),
79            });
80        }
81        Ok(())
82    }
83}