1use serde::{Deserialize, Serialize};
2
3use crate::error::{Error, Result};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
7pub enum CoordinateConvention {
8 CenteredPositiveK,
11}
12
13#[derive(Clone, Debug, Serialize, Deserialize)]
15pub struct Sampling {
16 pub low_res_pixel_size: f64,
18 pub high_res_pixel_size: f64,
20 pub dkx: f64,
22 pub dky: f64,
24 pub wavelength: Option<f64>,
26 pub synthetic_na: Option<f64>,
28 pub coordinate_convention: CoordinateConvention,
30}
31
32impl Sampling {
33 pub fn new(
35 low_res_pixel_size: f64,
36 high_res_pixel_size: f64,
37 dkx: f64,
38 dky: f64,
39 ) -> Result<Self> {
40 let sampling = Self {
41 low_res_pixel_size,
42 high_res_pixel_size,
43 dkx,
44 dky,
45 wavelength: None,
46 synthetic_na: None,
47 coordinate_convention: CoordinateConvention::CenteredPositiveK,
48 };
49 sampling.validate()?;
50 Ok(sampling)
51 }
52
53 pub fn validate(&self) -> Result<()> {
56 for (name, value) in [
57 ("low_res_pixel_size", self.low_res_pixel_size),
58 ("high_res_pixel_size", self.high_res_pixel_size),
59 ("dkx", self.dkx),
60 ("dky", self.dky),
61 ] {
62 if !value.is_finite() || value <= 0.0 {
63 return Err(Error::InvalidParameter {
64 name,
65 reason: "must be finite and positive".into(),
66 });
67 }
68 }
69 if self.high_res_pixel_size > self.low_res_pixel_size {
70 return Err(Error::InvalidParameter {
71 name: "high_res_pixel_size",
72 reason: "must not exceed low-resolution pixel size".into(),
73 });
74 }
75 if self
76 .wavelength
77 .is_some_and(|value| !value.is_finite() || value <= 0.0)
78 {
79 return Err(Error::InvalidParameter {
80 name: "wavelength",
81 reason: "must be finite and positive when present".into(),
82 });
83 }
84 if self
85 .synthetic_na
86 .is_some_and(|value| !value.is_finite() || value <= 0.0)
87 {
88 return Err(Error::InvalidParameter {
89 name: "synthetic_na",
90 reason: "must be finite and positive when present".into(),
91 });
92 }
93 Ok(())
94 }
95}