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