1use serde::{Deserialize, Serialize};
2
3use crate::{Result, error::Error};
4
5use super::{LEDArray, LEDSphere, Optics, RotatingLEDArc, SphericalLEDArm};
6
7pub type SourceWeight = (usize, f64);
8pub type MultiplexingMatrix = Vec<Vec<SourceWeight>>;
9
10#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
12pub struct KVector {
13 pub kx: f64,
14 pub ky: f64,
15}
16
17impl KVector {
18 pub fn new(kx: f64, ky: f64) -> Self {
19 Self { kx, ky }
20 }
21}
22
23pub trait IlluminationSource {
24 fn k_vectors(&self, optics: &Optics) -> Result<Vec<KVector>>;
25
26 fn frame_gains(&self) -> Result<Option<Vec<f64>>> {
28 Ok(None)
29 }
30
31 fn multiplexing_matrix(&self) -> Result<Option<MultiplexingMatrix>> {
33 Ok(None)
34 }
35}
36
37#[derive(Clone, Debug, Serialize, Deserialize)]
38pub struct AngleList {
39 pub angles: Vec<(f64, f64)>,
41}
42
43impl AngleList {
44 pub fn new(angles: Vec<(f64, f64)>) -> Self {
45 Self { angles }
46 }
47}
48
49impl IlluminationSource for AngleList {
50 fn k_vectors(&self, optics: &Optics) -> Result<Vec<KVector>> {
51 optics.validate()?;
52 let k = optics.medium_wavenumber();
53 let vectors = self
54 .angles
55 .iter()
56 .map(|&(theta_x, theta_y)| {
57 if !theta_x.is_finite()
58 || !theta_y.is_finite()
59 || theta_x.abs() > std::f64::consts::FRAC_PI_2
60 || theta_y.abs() > std::f64::consts::FRAC_PI_2
61 {
62 return Err(Error::InvalidParameter {
63 name: "illumination angles",
64 reason: "angles must be finite and between -pi/2 and pi/2".into(),
65 });
66 }
67 Ok(KVector::new(k * theta_x.sin(), k * theta_y.sin()))
68 })
69 .collect::<Result<Vec<_>>>()?;
70 validate_k_vectors(&vectors, optics)?;
71 Ok(vectors)
72 }
73}
74
75#[derive(Clone, Debug, Default, Serialize, Deserialize)]
77pub struct CodedIllumination {
78 pub source_k_vectors: Vec<KVector>,
79 pub frame_weights: MultiplexingMatrix,
80}
81
82impl CodedIllumination {
83 pub fn validate(&self, optics: &Optics) -> Result<()> {
84 validate_k_vectors(&self.source_k_vectors, optics)?;
85 validate_multiplexing_matrix(&self.frame_weights, self.source_k_vectors.len())
86 }
87}
88
89#[derive(Clone, Debug, Serialize, Deserialize)]
90pub enum Illumination {
91 KVectors(Vec<KVector>),
92 Angles(AngleList),
93 LEDArray(LEDArray),
94 LEDSphere(LEDSphere),
95 SphericalLEDArm(SphericalLEDArm),
96 RotatingLEDArc(RotatingLEDArc),
97 Coded(CodedIllumination),
98 Calibrated {
101 k_vectors: Vec<KVector>,
102 frame_gains: Option<Vec<f64>>,
103 frame_weights: Option<MultiplexingMatrix>,
104 },
105}
106
107impl IlluminationSource for Illumination {
108 fn k_vectors(&self, optics: &Optics) -> Result<Vec<KVector>> {
109 match self {
110 Self::KVectors(vectors) => {
111 validate_k_vectors(vectors, optics)?;
112 Ok(vectors.clone())
113 }
114 Self::Angles(angles) => angles.k_vectors(optics),
115 Self::LEDArray(array) => array.k_vectors(optics),
116 Self::LEDSphere(sphere) => sphere.k_vectors(optics),
117 Self::SphericalLEDArm(arm) => arm.k_vectors(optics),
118 Self::RotatingLEDArc(arc) => arc.k_vectors(optics),
119 Self::Coded(coded) => coded.k_vectors(optics),
120 Self::Calibrated {
121 k_vectors,
122 frame_gains,
123 frame_weights,
124 } => {
125 validate_calibrated(
126 k_vectors,
127 frame_gains.as_deref(),
128 frame_weights.as_deref(),
129 optics,
130 )?;
131 Ok(k_vectors.clone())
132 }
133 }
134 }
135
136 fn frame_gains(&self) -> Result<Option<Vec<f64>>> {
137 match self {
138 Self::LEDArray(array) => array.frame_gains(),
139 Self::LEDSphere(sphere) => sphere.frame_gains(),
140 Self::SphericalLEDArm(arm) => arm.frame_gains(),
141 Self::RotatingLEDArc(arc) => arc.frame_gains(),
142 Self::Calibrated {
143 k_vectors,
144 frame_gains,
145 frame_weights,
146 } => {
147 let frame_count = frame_weights.as_ref().map_or(k_vectors.len(), Vec::len);
148 validate_frame_gains(frame_gains.as_deref(), frame_count)?;
149 Ok(frame_gains.clone())
150 }
151 Self::KVectors(_) | Self::Angles(_) | Self::Coded(_) => Ok(None),
152 }
153 }
154
155 fn multiplexing_matrix(&self) -> Result<Option<MultiplexingMatrix>> {
156 match self {
157 Self::Coded(coded) => coded.multiplexing_matrix(),
158 Self::Calibrated {
159 k_vectors,
160 frame_gains,
161 frame_weights,
162 } => {
163 let frame_count = frame_weights.as_ref().map_or(k_vectors.len(), Vec::len);
164 validate_frame_gains(frame_gains.as_deref(), frame_count)?;
165 if let Some(matrix) = frame_weights {
166 validate_multiplexing_matrix(matrix, k_vectors.len())?;
167 }
168 Ok(frame_weights.clone())
169 }
170 Self::KVectors(_)
171 | Self::Angles(_)
172 | Self::LEDArray(_)
173 | Self::LEDSphere(_)
174 | Self::SphericalLEDArm(_)
175 | Self::RotatingLEDArc(_) => Ok(None),
176 }
177 }
178}
179
180impl IlluminationSource for CodedIllumination {
181 fn k_vectors(&self, optics: &Optics) -> Result<Vec<KVector>> {
182 self.validate(optics)?;
183 Ok(self.source_k_vectors.clone())
184 }
185
186 fn multiplexing_matrix(&self) -> Result<Option<MultiplexingMatrix>> {
187 validate_multiplexing_matrix(&self.frame_weights, self.source_k_vectors.len())?;
188 Ok(Some(self.frame_weights.clone()))
189 }
190}
191
192impl IlluminationSource for Vec<KVector> {
193 fn k_vectors(&self, optics: &Optics) -> Result<Vec<KVector>> {
194 validate_k_vectors(self, optics)?;
195 Ok(self.clone())
196 }
197}
198
199fn validate_k_vectors(vectors: &[KVector], optics: &Optics) -> Result<()> {
200 optics.validate()?;
201 if vectors.is_empty() {
202 return Err(Error::InvalidParameter {
203 name: "k_vectors",
204 reason: "at least one illumination source is required".into(),
205 });
206 }
207 let maximum_transverse = optics.medium_wavenumber() * (1.0 + 128.0 * f64::EPSILON);
208 if vectors.iter().any(|vector| {
209 !vector.kx.is_finite()
210 || !vector.ky.is_finite()
211 || vector.kx.hypot(vector.ky) > maximum_transverse
212 }) {
213 return Err(Error::InvalidParameter {
214 name: "k_vectors",
215 reason: "vectors must be finite propagating transverse wave vectors with magnitude no greater than the medium wavenumber"
216 .into(),
217 });
218 }
219 Ok(())
220}
221
222fn validate_multiplexing_matrix(matrix: &[Vec<SourceWeight>], source_count: usize) -> Result<()> {
223 if matrix.is_empty() {
224 return Err(Error::InvalidParameter {
225 name: "frame_weights",
226 reason: "at least one measured frame is required".into(),
227 });
228 }
229 for (frame, row) in matrix.iter().enumerate() {
230 let mut seen = vec![false; source_count];
231 if row.is_empty()
232 || row.iter().any(|&(source, weight)| {
233 let duplicate = source < source_count && seen[source];
234 if source < source_count {
235 seen[source] = true;
236 }
237 source >= source_count || !weight.is_finite() || weight <= 0.0 || duplicate
238 })
239 {
240 return Err(Error::InvalidParameter {
241 name: "frame_weights",
242 reason: format!(
243 "frame {frame} must contain unique valid source indices with finite positive weights"
244 ),
245 });
246 }
247 }
248 Ok(())
249}
250
251fn validate_calibrated(
252 k_vectors: &[KVector],
253 frame_gains: Option<&[f64]>,
254 frame_weights: Option<&[Vec<SourceWeight>]>,
255 optics: &Optics,
256) -> Result<()> {
257 validate_k_vectors(k_vectors, optics)?;
258 if let Some(matrix) = frame_weights {
259 validate_multiplexing_matrix(matrix, k_vectors.len())?;
260 }
261 validate_frame_gains(
262 frame_gains,
263 frame_weights.map_or(k_vectors.len(), |matrix| matrix.len()),
264 )
265}
266
267fn validate_frame_gains(frame_gains: Option<&[f64]>, frame_count: usize) -> Result<()> {
268 if frame_gains.is_some_and(|gains| {
269 gains.len() != frame_count
270 || gains
271 .iter()
272 .any(|value| !value.is_finite() || *value <= 0.0)
273 }) {
274 return Err(Error::InvalidParameter {
275 name: "frame_gains",
276 reason: format!("must contain {frame_count} finite positive values"),
277 });
278 }
279 Ok(())
280}