cherry_rs/specs/
fields.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
use anyhow::Result;
use serde::{Deserialize, Serialize};

use crate::core::Float;

/// Specifies a pupil sampling method.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum PupilSampling {
    /// A square grid of rays in the the entrance pupil.
    ///
    /// Spacing is the spacing between rays in the grid in normalized pupil
    /// distances, i.e. [0, 1]. A spacing of 1.0 means that one ray will lie
    /// at the pupil center (the chief ray), and the others will lie at the
    /// pupil edge (marginal rays).
    SquareGrid { spacing: Float },

    /// The chief and marginal rays.
    ChiefAndMarginalRays,
}

/// Specifies an object field.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum FieldSpec {
    /// The angle the field makes with the optical axis, in degrees.
    Angle {
        angle: Float,
        pupil_sampling: PupilSampling,
    },

    /// The height of the field above the optical axis.
    ObjectHeight {
        height: Float,
        pupil_sampling: PupilSampling,
    },
}

impl PupilSampling {
    /// Validate the pupil sampling method.
    pub fn validate(&self) -> Result<()> {
        match self {
            PupilSampling::SquareGrid { spacing } => {
                if spacing.is_nan() {
                    anyhow::bail!("Pupil grid spacing must be a number");
                }
                if *spacing < 0.0 || *spacing > 1.0 {
                    anyhow::bail!("Pupil grid spacing must be in the range [0, 1]");
                }
            }
            PupilSampling::ChiefAndMarginalRays => {}
        }
        Ok(())
    }
}

impl Default for PupilSampling {
    fn default() -> Self {
        Self::SquareGrid { spacing: 0.1 }
    }
}

impl FieldSpec {
    /// Validate the field specification.
    pub fn validate(&self) -> Result<()> {
        match self {
            FieldSpec::Angle {
                angle,
                pupil_sampling,
            } => {
                if angle.is_nan() {
                    anyhow::bail!("Field angle must be a number");
                }
                if *angle < 0.0 || *angle > 90.0 {
                    anyhow::bail!("Field angle must be in the range [0, 90]");
                }
                pupil_sampling.validate()?;
            }
            FieldSpec::ObjectHeight {
                height,
                pupil_sampling,
            } => {
                if height.is_nan() {
                    anyhow::bail!("Field height must be a number");
                }
                pupil_sampling.validate()?;
            }
        }
        Ok(())
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_pupil_sampling_validate() {
        let square_grid = PupilSampling::SquareGrid { spacing: 0.1 };
        assert!(square_grid.validate().is_ok());

        let square_grid = PupilSampling::SquareGrid { spacing: 1.1 };
        assert!(square_grid.validate().is_err());

        let square_grid = PupilSampling::SquareGrid { spacing: -0.1 };
        assert!(square_grid.validate().is_err());

        let square_grid = PupilSampling::SquareGrid {
            spacing: Float::NAN,
        };
        assert!(square_grid.validate().is_err());
    }

    #[test]
    fn test_field_spec_validate() {
        let angle = FieldSpec::Angle {
            angle: 45.0,
            pupil_sampling: PupilSampling::SquareGrid { spacing: 0.1 },
        };
        assert!(angle.validate().is_ok());

        let angle = FieldSpec::Angle {
            angle: 95.0,
            pupil_sampling: PupilSampling::SquareGrid { spacing: 0.1 },
        };
        assert!(angle.validate().is_err());

        let angle = FieldSpec::Angle {
            angle: -5.0,
            pupil_sampling: PupilSampling::SquareGrid { spacing: 0.1 },
        };
        assert!(angle.validate().is_err());

        let angle = FieldSpec::Angle {
            angle: 45.0,
            pupil_sampling: PupilSampling::SquareGrid { spacing: 1.1 },
        };
        assert!(angle.validate().is_err());

        let angle = FieldSpec::Angle {
            angle: 45.0,
            pupil_sampling: PupilSampling::SquareGrid { spacing: -0.1 },
        };
        assert!(angle.validate().is_err());

        let angle = FieldSpec::Angle {
            angle: 45.0,
            pupil_sampling: PupilSampling::SquareGrid {
                spacing: Float::NAN,
            },
        };
        assert!(angle.validate().is_err());

        let height = FieldSpec::ObjectHeight {
            height: 0.1,
            pupil_sampling: PupilSampling::SquareGrid { spacing: 0.1 },
        };
        assert!(height.validate().is_ok());

        let height = FieldSpec::ObjectHeight {
            height: 0.1,
            pupil_sampling: PupilSampling::SquareGrid { spacing: 1.1 },
        };
        assert!(height.validate().is_err());
    }
}