1use rand::Rng;
2use rand_distr::{Distribution, Normal, Poisson};
3use serde::{Deserialize, Serialize};
4
5use crate::{Result, array_layout::checked_len_2d, error::Error, model::ImagePlaneModel};
6
7#[derive(Clone, Debug, Serialize, Deserialize)]
9pub struct CameraModel {
10 pub photons_per_pixel: f64,
12 pub gain_counts_per_electron: f64,
14 pub offset_counts: f64,
16 pub read_noise_electrons: f64,
18 pub dark_current_electrons: f64,
20 pub shot_noise: bool,
22 pub pixel_sensitivity: Option<Vec<f64>>,
24 pub bit_depth: Option<u8>,
26 pub saturation_counts: Option<f64>,
28 pub quantize: bool,
30 pub bad_pixels: Vec<usize>,
32 pub bad_pixel_value_counts: Option<f64>,
34}
35
36impl Default for CameraModel {
37 fn default() -> Self {
38 Self {
39 photons_per_pixel: 1_000.0,
40 gain_counts_per_electron: 1.0,
41 offset_counts: 0.0,
42 read_noise_electrons: 0.0,
43 dark_current_electrons: 0.0,
44 shot_noise: false,
45 pixel_sensitivity: None,
46 bit_depth: Some(16),
47 saturation_counts: None,
48 quantize: true,
49 bad_pixels: Vec::new(),
50 bad_pixel_value_counts: None,
51 }
52 }
53}
54
55impl CameraModel {
56 pub fn new() -> Self {
58 Self::default()
59 }
60
61 pub fn ideal() -> Self {
63 Self {
64 photons_per_pixel: 1.0,
65 gain_counts_per_electron: 1.0,
66 offset_counts: 0.0,
67 read_noise_electrons: 0.0,
68 dark_current_electrons: 0.0,
69 shot_noise: false,
70 pixel_sensitivity: None,
71 bit_depth: None,
72 saturation_counts: None,
73 quantize: false,
74 bad_pixels: Vec::new(),
75 bad_pixel_value_counts: None,
76 }
77 }
78
79 pub fn photons_per_pixel(mut self, value: f64) -> Self {
81 self.photons_per_pixel = value;
82 self
83 }
84
85 pub fn gain(mut self, counts_per_electron: f64) -> Self {
87 self.gain_counts_per_electron = counts_per_electron;
88 self
89 }
90
91 pub fn offset_counts(mut self, value: f64) -> Self {
93 self.offset_counts = value;
94 self
95 }
96
97 pub fn read_noise_electrons(mut self, value: f64) -> Self {
99 self.read_noise_electrons = value;
100 self
101 }
102
103 pub fn dark_current_electrons(mut self, value: f64) -> Self {
105 self.dark_current_electrons = value;
106 self
107 }
108
109 pub fn shot_noise(mut self, enabled: bool) -> Self {
111 self.shot_noise = enabled;
112 self
113 }
114
115 pub fn pixel_sensitivity(mut self, values: Vec<f64>) -> Self {
117 self.pixel_sensitivity = Some(values);
118 self
119 }
120
121 pub fn bit_depth(mut self, bits: u8) -> Self {
123 self.bit_depth = Some(bits);
124 self
125 }
126
127 pub fn saturation(mut self, counts: f64) -> Self {
129 self.saturation_counts = Some(counts);
130 self
131 }
132
133 pub fn quantize(mut self, enabled: bool) -> Self {
135 self.quantize = enabled;
136 self
137 }
138
139 pub fn bad_pixels(mut self, indices: Vec<usize>, value_counts: f64) -> Self {
141 self.bad_pixels = indices;
142 self.bad_pixel_value_counts = Some(value_counts);
143 self
144 }
145
146 pub fn validate(&self) -> Result<()> {
149 for (name, value) in [
150 ("photons_per_pixel", self.photons_per_pixel),
151 ("gain_counts_per_electron", self.gain_counts_per_electron),
152 ] {
153 if !value.is_finite() || value <= 0.0 {
154 return Err(Error::InvalidParameter {
155 name,
156 reason: "must be finite and positive".into(),
157 });
158 }
159 }
160 if !self.offset_counts.is_finite()
161 || !self.read_noise_electrons.is_finite()
162 || self.read_noise_electrons < 0.0
163 || !self.dark_current_electrons.is_finite()
164 || self.dark_current_electrons < 0.0
165 {
166 return Err(Error::InvalidParameter {
167 name: "camera noise/offset",
168 reason: "offset must be finite; read noise and dark current must be non-negative"
169 .into(),
170 });
171 }
172 if self.bit_depth.is_some_and(|bits| bits == 0 || bits > 32) {
173 return Err(Error::InvalidParameter {
174 name: "bit_depth",
175 reason: "must be between 1 and 32".into(),
176 });
177 }
178 if self
179 .saturation_counts
180 .is_some_and(|value| !value.is_finite() || value <= 0.0)
181 {
182 return Err(Error::InvalidParameter {
183 name: "saturation_counts",
184 reason: "must be finite and positive".into(),
185 });
186 }
187 if self
188 .bad_pixel_value_counts
189 .is_some_and(|value| !value.is_finite() || value < 0.0)
190 {
191 return Err(Error::InvalidParameter {
192 name: "bad_pixel_value_counts",
193 reason: "must be finite and non-negative".into(),
194 });
195 }
196 if !self.bad_pixels.is_empty()
197 && self.bad_pixel_value_counts.is_none()
198 && !self.maximum_count().is_finite()
199 {
200 return Err(Error::InvalidParameter {
201 name: "bad_pixels",
202 reason: "a finite bad-pixel value or camera maximum is required".into(),
203 });
204 }
205 if self.pixel_sensitivity.as_ref().is_some_and(|values| {
206 values
207 .iter()
208 .any(|value| !value.is_finite() || *value < 0.0)
209 }) {
210 return Err(Error::InvalidParameter {
211 name: "pixel_sensitivity",
212 reason: "values must be finite and non-negative".into(),
213 });
214 }
215 let mut bad_pixels = self.bad_pixels.clone();
216 bad_pixels.sort_unstable();
217 if bad_pixels.windows(2).any(|pair| pair[0] == pair[1]) {
218 return Err(Error::InvalidParameter {
219 name: "bad_pixels",
220 reason: "indices must be unique".into(),
221 });
222 }
223 Ok(())
224 }
225
226 pub fn validate_for_frame(&self, frame_len: usize) -> Result<()> {
228 self.validate()?;
229 if self
230 .pixel_sensitivity
231 .as_ref()
232 .is_some_and(|values| values.len() != frame_len)
233 {
234 return Err(Error::InvalidParameter {
235 name: "pixel_sensitivity",
236 reason: format!("must contain {frame_len} values"),
237 });
238 }
239 if self.bad_pixels.iter().any(|&pixel| pixel >= frame_len) {
240 return Err(Error::InvalidParameter {
241 name: "bad_pixels",
242 reason: format!("indices must be below the frame size {frame_len}"),
243 });
244 }
245 Ok(())
246 }
247
248 pub fn measure_frame<R: Rng + ?Sized>(&self, intensity: &mut [f64], rng: &mut R) -> Result<()> {
250 self.validate_for_frame(intensity.len())?;
251 let read_noise = (self.read_noise_electrons > 0.0)
252 .then(|| Normal::new(0.0, self.read_noise_electrons))
253 .transpose()
254 .map_err(|error| Error::Numerical(error.to_string()))?;
255 for (pixel, value) in intensity.iter_mut().enumerate() {
256 let sensitivity = self
257 .pixel_sensitivity
258 .as_ref()
259 .map_or(1.0, |values| values[pixel]);
260 let expected_electrons =
261 value.max(0.0) * sensitivity * self.photons_per_pixel + self.dark_current_electrons;
262 let mut electrons = if self.shot_noise && expected_electrons > 0.0 {
263 Poisson::new(expected_electrons)
264 .map_err(|error| Error::Numerical(error.to_string()))?
265 .sample(rng)
266 } else {
267 expected_electrons
268 };
269 if let Some(distribution) = &read_noise {
270 electrons += distribution.sample(rng);
271 }
272 let mut counts = electrons * self.gain_counts_per_electron + self.offset_counts;
273 counts = counts.clamp(0.0, self.maximum_count());
274 if self.quantize {
275 counts = counts.round();
276 }
277 *value = counts;
278 }
279 for &pixel in &self.bad_pixels {
280 let mut value = self
281 .bad_pixel_value_counts
282 .unwrap_or_else(|| self.maximum_count())
283 .clamp(0.0, self.maximum_count());
284 if self.quantize {
285 value = value.round();
286 }
287 intensity[pixel] = value;
288 }
289 Ok(())
290 }
291
292 pub fn compile_reconstruction_model(
297 &self,
298 mut model: ImagePlaneModel,
299 ) -> Result<ImagePlaneModel> {
300 let image_len = checked_len_2d(model.image_shape)?;
301 self.validate_for_frame(image_len)?;
302 let scale = self.photons_per_pixel * self.gain_counts_per_electron;
303 model.frame_gains = Some(match &model.frame_gains {
304 Some(gains) => gains.iter().map(|gain| gain * scale).collect(),
305 None => vec![scale; model.frame_count()],
306 });
307 let additive_counts =
308 self.dark_current_electrons * self.gain_counts_per_electron + self.offset_counts;
309 if let Some(background) = &mut model.background {
310 for value in background {
311 *value = *value * scale + additive_counts;
312 }
313 } else if additive_counts != 0.0 {
314 model.background = Some(vec![additive_counts; image_len]);
315 }
316 model.validate()?;
317 Ok(model)
318 }
319
320 fn maximum_count(&self) -> f64 {
321 let digital_maximum = self
322 .bit_depth
323 .map_or(f64::INFINITY, |bits| (2_f64).powi(bits as i32) - 1.0);
324 self.saturation_counts
325 .unwrap_or(f64::INFINITY)
326 .min(digital_maximum)
327 }
328}