1use std::path::Path;
2
3use ndarray::{Array2, Array3, ArrayView2, ArrayView3, ArrayViewMut2, Axis};
4use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _};
5
6use crate::{
7 Result,
8 array_layout::{StandardArray2, StandardArray3, checked_len_2d, checked_len_3d},
9 error::Error,
10 image_io::{GrayscaleScaling, load_grayscale, load_grayscale_tiff_pages},
11};
12
13use super::{FrameMetadata, ImageSet, MeasurementSpec, PreprocessingConfig};
14
15#[derive(Clone, Debug)]
16enum FrameArray<T> {
17 Shared(StandardArray2<T>),
18 PerFrame(StandardArray3<T>),
19}
20
21impl<T> FrameArray<T> {
22 fn as_slice(&self) -> &[T] {
23 match self {
24 Self::Shared(values) => values.as_slice(),
25 Self::PerFrame(values) => values.as_slice(),
26 }
27 }
28
29 fn frame(&self, index: usize, frame_len: usize) -> &[T] {
30 match self {
31 Self::Shared(values) => values.as_slice(),
32 Self::PerFrame(values) => {
33 let start = index * frame_len;
34 &values.as_slice()[start..start + frame_len]
35 }
36 }
37 }
38}
39
40#[derive(Clone, Debug)]
47pub struct MeasurementStack {
48 data: StandardArray3<f64>,
49 frame_metadata: Vec<FrameMetadata>,
50 dark_frame: Option<StandardArray2<f64>>,
51 flat_field: Option<StandardArray2<f64>>,
52 background: Option<FrameArray<f64>>,
53 masks: Option<FrameArray<u8>>,
54 preprocessing: PreprocessingConfig,
55}
56
57impl MeasurementStack {
58 pub fn from_vec(
61 data: Vec<f64>,
62 image_shape: (usize, usize),
63 frame_metadata: Vec<FrameMetadata>,
64 ) -> Result<Self> {
65 let frame_len = checked_len_2d(image_shape)?;
66 if frame_len == 0 {
67 return Err(Error::InvalidShape(
68 "measurement dimensions must be non-zero".into(),
69 ));
70 }
71 if data.is_empty() || !data.len().is_multiple_of(frame_len) {
72 return Err(Error::InvalidMeasurements(format!(
73 "data length {} is not a positive multiple of frame size {frame_len}",
74 data.len()
75 )));
76 }
77 if data.iter().any(|value| !value.is_finite()) {
78 return Err(Error::InvalidMeasurements(
79 "measurements contain non-finite values".into(),
80 ));
81 }
82 let frames = data.len() / frame_len;
83 let frame_metadata = if frame_metadata.is_empty() {
84 (0..frames).map(FrameMetadata::new).collect()
85 } else {
86 if frame_metadata.len() != frames {
87 return Err(Error::InvalidMeasurements(format!(
88 "{} metadata entries for {frames} frames",
89 frame_metadata.len()
90 )));
91 }
92 frame_metadata
93 };
94 let stack = Self {
95 data: StandardArray3::from_shape_vec((frames, image_shape.0, image_shape.1), data)?,
96 frame_metadata,
97 dark_frame: None,
98 flat_field: None,
99 background: None,
100 masks: None,
101 preprocessing: PreprocessingConfig::default(),
102 };
103 stack.validate()?;
104 Ok(stack)
105 }
106
107 pub fn new(data: Array3<f64>, frame_metadata: Vec<FrameMetadata>) -> Result<Self> {
109 let data = StandardArray3::try_from(data)?;
110 let shape = data.dim();
111 if shape.0 == 0 || shape.1 == 0 || shape.2 == 0 {
112 return Err(Error::InvalidShape(format!(
113 "measurement dimensions must be non-zero, got {shape:?}"
114 )));
115 }
116 if data.as_slice().iter().any(|value| !value.is_finite()) {
117 return Err(Error::InvalidMeasurements(
118 "measurements contain non-finite values".into(),
119 ));
120 }
121 let frame_metadata = if frame_metadata.is_empty() {
122 (0..shape.0).map(FrameMetadata::new).collect()
123 } else if frame_metadata.len() == shape.0 {
124 frame_metadata
125 } else {
126 return Err(Error::InvalidMeasurements(format!(
127 "{} metadata entries for {} frames",
128 frame_metadata.len(),
129 shape.0
130 )));
131 };
132 let stack = Self {
133 data,
134 frame_metadata,
135 dark_frame: None,
136 flat_field: None,
137 background: None,
138 masks: None,
139 preprocessing: PreprocessingConfig::default(),
140 };
141 stack.validate()?;
142 Ok(stack)
143 }
144
145 pub fn from_frames(frames: &[Vec<f64>], image_shape: (usize, usize)) -> Result<Self> {
147 let frame_len = checked_len_2d(image_shape)?;
148 if frames.iter().any(|frame| frame.len() != frame_len) {
149 return Err(Error::InvalidMeasurements(
150 "every frame must match the declared image shape".into(),
151 ));
152 }
153 let total = frame_len
154 .checked_mul(frames.len())
155 .ok_or_else(|| Error::ShapeOverflow {
156 shape: vec![frames.len(), image_shape.0, image_shape.1],
157 })?;
158 let mut data = Vec::with_capacity(total);
159 data.extend(frames.iter().flatten().copied());
160 Self::from_vec(data, image_shape, Vec::new())
161 }
162
163 pub fn from_image_files<P: AsRef<Path>>(
165 paths: &[P],
166 frame_metadata: Vec<FrameMetadata>,
167 ) -> Result<Self> {
168 if paths.is_empty() {
169 return Err(Error::InvalidMeasurements(
170 "at least one image path is required".into(),
171 ));
172 }
173 let mut shape = None;
174 let mut data = Vec::new();
175 for path in paths {
176 let frame = load_grayscale(path, GrayscaleScaling::NativeCounts)?;
177 if let Some(expected) = shape {
178 if frame.dim() != expected {
179 return Err(Error::InvalidMeasurements(format!(
180 "image {} has shape {:?}, expected {expected:?}",
181 path.as_ref().display(),
182 frame.dim()
183 )));
184 }
185 } else {
186 shape = Some(frame.dim());
187 }
188 data.extend(frame);
189 }
190 let metadata = if frame_metadata.is_empty() {
191 paths
192 .iter()
193 .enumerate()
194 .map(|(index, path)| {
195 let mut metadata = FrameMetadata::new(index);
196 metadata.label = Some(path.as_ref().display().to_string());
197 metadata
198 })
199 .collect()
200 } else {
201 frame_metadata
202 };
203 let shape = shape.ok_or_else(|| {
204 Error::InvalidMeasurements("image paths did not produce a frame".into())
205 })?;
206 Self::from_vec(data, shape, metadata)
207 }
208
209 pub fn from_tiff_stack(
211 path: impl AsRef<Path>,
212 frame_metadata: Vec<FrameMetadata>,
213 ) -> Result<Self> {
214 let path = path.as_ref();
215 let pages = load_grayscale_tiff_pages(path, GrayscaleScaling::NativeCounts)?;
216 let first = pages.first().ok_or_else(|| {
217 Error::InvalidMeasurements("TIFF stack does not contain an image".into())
218 })?;
219 let shape = first.dim();
220 if pages.iter().any(|page| page.dim() != shape) {
221 return Err(Error::InvalidMeasurements(
222 "all TIFF pages must have the same dimensions".into(),
223 ));
224 }
225 let metadata = if frame_metadata.is_empty() {
226 (0..pages.len())
227 .map(|index| {
228 let mut metadata = FrameMetadata::new(index);
229 metadata.label = Some(format!("{}#page={index}", path.display()));
230 metadata
231 })
232 .collect()
233 } else {
234 frame_metadata
235 };
236 Self::from_vec(pages.into_iter().flatten().collect(), shape, metadata)
237 }
238
239 pub fn from_manifest(path: impl AsRef<Path>) -> Result<Self> {
241 let path = path.as_ref();
242 let manifest = MeasurementSpec::load(path)?;
243 let base_directory = path.parent().unwrap_or_else(|| Path::new("."));
244 Self::from_manifest_definition(manifest, base_directory)
245 }
246
247 pub fn from_manifest_definition(
249 manifest: MeasurementSpec,
250 base_directory: impl AsRef<Path>,
251 ) -> Result<Self> {
252 let base_directory = base_directory.as_ref();
253 let paths: Vec<_> = manifest
254 .frames
255 .iter()
256 .map(|frame| resolve_path(base_directory, &frame.path))
257 .collect();
258 let metadata = manifest
259 .frames
260 .iter()
261 .enumerate()
262 .map(|(index, frame)| FrameMetadata {
263 frame_index: index,
264 illumination_index: frame.illumination_index,
265 original_frame_index: Some(index),
266 original_illumination_index: frame.illumination_index,
267 exposure_time: frame.exposure_time,
268 weight: frame.weight,
269 label: frame
270 .label
271 .clone()
272 .or_else(|| Some(frame.path.display().to_string())),
273 })
274 .collect();
275 let mut stack = Self::from_image_files(&paths, metadata)?;
276 if let Some(path) = &manifest.dark_frame {
277 stack.dark_frame = Some(StandardArray2::from_shape_vec(
278 stack.image_shape(),
279 load_manifest_image(base_directory, path, stack.image_shape())?,
280 )?);
281 }
282 if let Some(path) = &manifest.flat_field {
283 stack.flat_field = Some(StandardArray2::from_shape_vec(
284 stack.image_shape(),
285 load_manifest_image(base_directory, path, stack.image_shape())?,
286 )?);
287 }
288 if let Some(background) = &manifest.background {
289 let values = load_manifest_image_set(
290 base_directory,
291 background,
292 stack.image_shape(),
293 stack.frame_count(),
294 )?;
295 stack.background = Some(stack.frame_array_from_flat(values, "background")?);
296 }
297 if let Some(mask) = &manifest.mask {
298 let values = load_manifest_image_set(
299 base_directory,
300 mask,
301 stack.image_shape(),
302 stack.frame_count(),
303 )?
304 .into_iter()
305 .map(|value| u8::from(value != 0.0))
306 .collect();
307 stack.masks = Some(stack.frame_array_from_flat(values, "mask")?);
308 }
309 stack.preprocessing = manifest.preprocessing;
310 stack.validate()?;
311 Ok(stack)
312 }
313
314 pub fn data(&self) -> ArrayView3<'_, f64> {
316 self.data.ndarray_view()
317 }
318
319 pub fn frame_view(&self, index: usize) -> Result<ArrayView2<'_, f64>> {
321 self.check_frame(index)?;
322 Ok(self.data.ndarray_view().index_axis_move(Axis(0), index))
323 }
324
325 pub fn frame_view_mut(&mut self, index: usize) -> Result<ArrayViewMut2<'_, f64>> {
327 self.check_frame(index)?;
328 Ok(self.data.ndarray_view_mut().index_axis_move(Axis(0), index))
329 }
330
331 pub fn frame_count(&self) -> usize {
333 self.data.dim().0
334 }
335
336 pub fn image_shape(&self) -> (usize, usize) {
338 let shape = self.data.dim();
339 (shape.1, shape.2)
340 }
341
342 pub fn frame_len(&self) -> usize {
344 let shape = self.data.dim();
345 shape.1 * shape.2
346 }
347
348 pub fn as_slice(&self) -> &[f64] {
350 self.data.as_slice()
351 }
352
353 pub fn frame_metadata(&self) -> &[FrameMetadata] {
355 &self.frame_metadata
356 }
357
358 pub fn set_frame_weight(&mut self, index: usize, weight: f64) -> Result<()> {
360 self.check_frame(index)?;
361 if !weight.is_finite() || weight < 0.0 {
362 return Err(Error::InvalidMeasurements(format!(
363 "frame {index} has invalid weight {weight}"
364 )));
365 }
366 self.frame_metadata[index].weight = weight;
367 Ok(())
368 }
369
370 pub fn preprocessing(&self) -> &PreprocessingConfig {
372 &self.preprocessing
373 }
374
375 pub fn validate(&self) -> Result<()> {
378 let shape = self.data.dim();
379 let expected = checked_len_3d(shape)?;
380 if shape.0 == 0 || shape.1 == 0 || shape.2 == 0 || self.data.len() != expected {
381 return Err(Error::InvalidMeasurements(
382 "stored frame count, image shape, and data length are inconsistent".into(),
383 ));
384 }
385 if self.data.as_slice().iter().any(|value| !value.is_finite()) {
386 return Err(Error::InvalidMeasurements(
387 "measurements contain non-finite values".into(),
388 ));
389 }
390 if self.frame_metadata.len() != shape.0 {
391 return Err(Error::InvalidMeasurements(format!(
392 "{} metadata entries for {} frames",
393 self.frame_metadata.len(),
394 shape.0
395 )));
396 }
397 for (index, metadata) in self.frame_metadata.iter().enumerate() {
398 if metadata.frame_index != index {
399 return Err(Error::InvalidMeasurements(format!(
400 "metadata entry {index} identifies frame {}",
401 metadata.frame_index
402 )));
403 }
404 if !metadata.exposure_time.is_finite() || metadata.exposure_time <= 0.0 {
405 return Err(Error::InvalidMeasurements(format!(
406 "frame {index} has invalid exposure {}",
407 metadata.exposure_time
408 )));
409 }
410 if !metadata.weight.is_finite() || metadata.weight < 0.0 {
411 return Err(Error::InvalidMeasurements(format!(
412 "frame {index} has invalid weight {}",
413 metadata.weight
414 )));
415 }
416 }
417 let image_shape = self.image_shape();
418 if self
419 .dark_frame
420 .as_ref()
421 .is_some_and(|values| values.dim() != image_shape || !all_finite(values.as_slice()))
422 {
423 return Err(Error::InvalidMeasurements(
424 "dark frame must be finite and match the image shape".into(),
425 ));
426 }
427 if self.flat_field.as_ref().is_some_and(|values| {
428 values.dim() != image_shape
429 || values
430 .as_slice()
431 .iter()
432 .any(|value| !value.is_finite() || *value <= 0.0)
433 }) {
434 return Err(Error::InvalidMeasurements(
435 "flat field must be positive, finite, and match the image shape".into(),
436 ));
437 }
438 self.validate_frame_array(self.background.as_ref(), "background")?;
439 self.validate_frame_array(self.masks.as_ref(), "mask")?;
440 if self
441 .masks
442 .as_ref()
443 .is_some_and(|values| values.as_slice().iter().any(|&value| value > 1))
444 {
445 return Err(Error::InvalidMeasurements(
446 "mask values must be exactly zero or one".into(),
447 ));
448 }
449 if self.preprocessing.subtract_dark && self.dark_frame.is_none() {
450 return Err(Error::InvalidMeasurements(
451 "dark subtraction requested without a dark frame".into(),
452 ));
453 }
454 if self.preprocessing.divide_flat_field && self.flat_field.is_none() {
455 return Err(Error::InvalidMeasurements(
456 "flat-field correction requested without a flat field".into(),
457 ));
458 }
459 if self.preprocessing.subtract_background && self.background.is_none() {
460 return Err(Error::InvalidMeasurements(
461 "background subtraction requested without a background".into(),
462 ));
463 }
464 Ok(())
465 }
466
467 pub fn frame(&self, index: usize) -> Result<&[f64]> {
469 self.check_frame(index)?;
470 let frame_len = self.frame_len();
471 let start = index * frame_len;
472 Ok(&self.data.as_slice()[start..start + frame_len])
473 }
474
475 pub fn frame_mut(&mut self, index: usize) -> Result<&mut [f64]> {
477 self.check_frame(index)?;
478 let frame_len = self.frame_len();
479 let start = index * frame_len;
480 Ok(&mut self.data.as_slice_mut()[start..start + frame_len])
481 }
482
483 pub fn frame_weight(&self, index: usize) -> Result<f64> {
485 self.frame_metadata
486 .get(index)
487 .map(|metadata| metadata.weight)
488 .ok_or(Error::FrameOutOfRange {
489 index,
490 frames: self.frame_count(),
491 })
492 }
493
494 pub fn frame_mask(&self, index: usize) -> Result<Option<&[u8]>> {
496 self.check_frame(index)?;
497 Ok(self
498 .masks
499 .as_ref()
500 .map(|values| values.frame(index, self.frame_len())))
501 }
502
503 pub fn with_dark_frame(mut self, dark: Array2<f64>) -> Result<Self> {
505 let dark = StandardArray2::try_from(dark)?;
506 if dark.dim() != self.image_shape() || !all_finite(dark.as_slice()) {
507 return Err(Error::InvalidMeasurements(
508 "dark frame must be finite and match the image shape".into(),
509 ));
510 }
511 self.dark_frame = Some(dark);
512 self.preprocessing.subtract_dark = true;
513 Ok(self)
514 }
515
516 pub fn with_flat_field(mut self, flat: Array2<f64>) -> Result<Self> {
518 let flat = StandardArray2::try_from(flat)?;
519 if flat.dim() != self.image_shape()
520 || flat
521 .as_slice()
522 .iter()
523 .any(|value| !value.is_finite() || *value <= 0.0)
524 {
525 return Err(Error::InvalidMeasurements(
526 "flat field must be positive, finite, and match the image shape".into(),
527 ));
528 }
529 self.flat_field = Some(flat);
530 self.preprocessing.divide_flat_field = true;
531 Ok(self)
532 }
533
534 pub fn with_background(mut self, background: Array2<f64>) -> Result<Self> {
536 let values = StandardArray2::try_from(background)?;
537 if values.dim() != self.image_shape() || !all_finite(values.as_slice()) {
538 return Err(Error::InvalidMeasurements(
539 "background must be finite and match the image shape".into(),
540 ));
541 }
542 self.background = Some(FrameArray::Shared(values));
543 self.preprocessing.subtract_background = true;
544 Ok(self)
545 }
546
547 pub fn with_per_frame_background(mut self, background: Array3<f64>) -> Result<Self> {
549 let values = StandardArray3::try_from(background)?;
550 if values.dim() != self.data.dim() || !all_finite(values.as_slice()) {
551 return Err(Error::InvalidMeasurements(
552 "per-frame background must be finite and match the measurement stack".into(),
553 ));
554 }
555 self.background = Some(FrameArray::PerFrame(values));
556 self.preprocessing.subtract_background = true;
557 Ok(self)
558 }
559
560 pub fn with_masks(mut self, masks: Array2<u8>) -> Result<Self> {
562 let values = StandardArray2::try_from(masks)?;
563 if values.dim() != self.image_shape() || values.as_slice().iter().any(|&value| value > 1) {
564 return Err(Error::InvalidMeasurements(
565 "mask must be binary and match the image shape".into(),
566 ));
567 }
568 self.masks = Some(FrameArray::Shared(values));
569 Ok(self)
570 }
571
572 pub fn with_per_frame_masks(mut self, masks: Array3<u8>) -> Result<Self> {
574 let values = StandardArray3::try_from(masks)?;
575 if values.dim() != self.data.dim() || values.as_slice().iter().any(|&value| value > 1) {
576 return Err(Error::InvalidMeasurements(
577 "per-frame masks must be binary and match the measurement stack".into(),
578 ));
579 }
580 self.masks = Some(FrameArray::PerFrame(values));
581 Ok(self)
582 }
583
584 pub fn normalize_exposure(mut self) -> Self {
586 self.preprocessing.normalize_exposure = true;
587 self
588 }
589
590 pub fn clamp_negative(mut self) -> Self {
592 self.preprocessing.clamp_negative = true;
593 self
594 }
595
596 pub fn with_preprocessing(mut self, preprocessing: PreprocessingConfig) -> Result<Self> {
598 self.preprocessing = preprocessing;
599 self.validate()?;
600 Ok(self)
601 }
602
603 pub fn apply_preprocessing(mut self) -> Result<Self> {
608 self.validate()?;
609 let frame_len = self.frame_len();
610 let dark = self.dark_frame.as_ref().map(StandardArray2::as_slice);
611 let flat = self.flat_field.as_ref().map(StandardArray2::as_slice);
612 let preprocessing = self.preprocessing.clone();
613 let metadata = &self.frame_metadata;
614 let background = self.background.as_ref();
615 let data = self.data.as_slice_mut();
616 for (frame_index, frame) in data.chunks_exact_mut(frame_len).enumerate() {
617 let exposure = metadata[frame_index].exposure_time;
618 let frame_background = background.map(|values| values.frame(frame_index, frame_len));
619 for (pixel, value) in frame.iter_mut().enumerate() {
620 if preprocessing.subtract_dark
621 && let Some(dark) = dark
622 {
623 *value -= dark[pixel];
624 }
625 if preprocessing.subtract_background
626 && let Some(background) = frame_background
627 {
628 *value -= background[pixel];
629 }
630 if preprocessing.divide_flat_field
631 && let Some(flat) = flat
632 {
633 *value /= flat[pixel];
634 }
635 if preprocessing.normalize_exposure {
636 *value /= exposure;
637 }
638 if preprocessing.clamp_negative {
639 *value = value.max(0.0);
640 }
641 }
642 }
643 Ok(self)
644 }
645
646 pub(crate) fn dark_frame_slice(&self) -> Option<&[f64]> {
647 self.dark_frame.as_ref().map(StandardArray2::as_slice)
648 }
649
650 pub(crate) fn flat_field_slice(&self) -> Option<&[f64]> {
651 self.flat_field.as_ref().map(StandardArray2::as_slice)
652 }
653
654 pub(crate) fn background_slice(&self) -> Option<&[f64]> {
655 self.background.as_ref().map(FrameArray::as_slice)
656 }
657
658 pub(crate) fn masks_slice(&self) -> Option<&[u8]> {
659 self.masks.as_ref().map(FrameArray::as_slice)
660 }
661
662 fn check_frame(&self, index: usize) -> Result<()> {
663 if index >= self.frame_count() {
664 Err(Error::FrameOutOfRange {
665 index,
666 frames: self.frame_count(),
667 })
668 } else {
669 Ok(())
670 }
671 }
672
673 fn validate_frame_array<T>(&self, values: Option<&FrameArray<T>>, name: &str) -> Result<()> {
674 let Some(values) = values else {
675 return Ok(());
676 };
677 let valid = match values {
678 FrameArray::Shared(values) => values.dim() == self.image_shape(),
679 FrameArray::PerFrame(values) => values.dim() == self.data.dim(),
680 };
681 if valid {
682 Ok(())
683 } else {
684 Err(Error::InvalidMeasurements(format!(
685 "{name} dimensions do not match the measurement stack"
686 )))
687 }
688 }
689
690 fn frame_array_from_flat<T>(&self, values: Vec<T>, name: &str) -> Result<FrameArray<T>> {
691 if values.len() == self.frame_len() {
692 Ok(FrameArray::Shared(StandardArray2::from_shape_vec(
693 self.image_shape(),
694 values,
695 )?))
696 } else if values.len() == self.data.len() {
697 Ok(FrameArray::PerFrame(StandardArray3::from_shape_vec(
698 self.data.dim(),
699 values,
700 )?))
701 } else {
702 Err(Error::InvalidMeasurements(format!(
703 "{name} length must match one image or the complete frame stack"
704 )))
705 }
706 }
707}
708
709impl Serialize for MeasurementStack {
710 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
711 where
712 S: Serializer,
713 {
714 #[derive(Serialize)]
715 struct Representation<'a> {
716 data: &'a [f64],
717 image_shape: (usize, usize),
718 frames: usize,
719 frame_metadata: &'a [FrameMetadata],
720 dark_frame: Option<&'a [f64]>,
721 flat_field: Option<&'a [f64]>,
722 background: Option<&'a [f64]>,
723 masks: Option<&'a [u8]>,
724 preprocessing: &'a PreprocessingConfig,
725 }
726
727 Representation {
728 data: self.data.as_slice(),
729 image_shape: self.image_shape(),
730 frames: self.frame_count(),
731 frame_metadata: &self.frame_metadata,
732 dark_frame: self.dark_frame_slice(),
733 flat_field: self.flat_field_slice(),
734 background: self.background_slice(),
735 masks: self.masks_slice(),
736 preprocessing: &self.preprocessing,
737 }
738 .serialize(serializer)
739 }
740}
741
742impl<'de> Deserialize<'de> for MeasurementStack {
743 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
744 where
745 D: Deserializer<'de>,
746 {
747 #[derive(Deserialize)]
748 #[serde(deny_unknown_fields)]
749 struct Representation {
750 data: Vec<f64>,
751 image_shape: (usize, usize),
752 frames: usize,
753 frame_metadata: Vec<FrameMetadata>,
754 dark_frame: Option<Vec<f64>>,
755 flat_field: Option<Vec<f64>>,
756 background: Option<Vec<f64>>,
757 masks: Option<Vec<u8>>,
758 preprocessing: PreprocessingConfig,
759 }
760
761 let representation = Representation::deserialize(deserializer)?;
762 let expected = checked_len_3d((
763 representation.frames,
764 representation.image_shape.0,
765 representation.image_shape.1,
766 ))
767 .map_err(D::Error::custom)?;
768 if representation.data.len() != expected {
769 return Err(D::Error::custom(
770 "measurement data length does not match shape",
771 ));
772 }
773 let mut stack = Self::from_vec(
774 representation.data,
775 representation.image_shape,
776 representation.frame_metadata,
777 )
778 .map_err(D::Error::custom)?;
779 if stack.frame_count() != representation.frames {
780 return Err(D::Error::custom("measurement frame count is inconsistent"));
781 }
782 stack.dark_frame = representation
783 .dark_frame
784 .map(|values| StandardArray2::from_shape_vec(stack.image_shape(), values))
785 .transpose()
786 .map_err(D::Error::custom)?;
787 stack.flat_field = representation
788 .flat_field
789 .map(|values| StandardArray2::from_shape_vec(stack.image_shape(), values))
790 .transpose()
791 .map_err(D::Error::custom)?;
792 stack.background = representation
793 .background
794 .map(|values| stack.frame_array_from_flat(values, "background"))
795 .transpose()
796 .map_err(D::Error::custom)?;
797 stack.masks = representation
798 .masks
799 .map(|values| stack.frame_array_from_flat(values, "mask"))
800 .transpose()
801 .map_err(D::Error::custom)?;
802 stack.preprocessing = representation.preprocessing;
803 stack.validate().map_err(D::Error::custom)?;
804 Ok(stack)
805 }
806}
807
808fn all_finite(values: &[f64]) -> bool {
809 values.iter().all(|value| value.is_finite())
810}
811
812pub(super) fn resolve_path(base_directory: &Path, path: &Path) -> std::path::PathBuf {
813 if path.is_absolute() {
814 path.to_owned()
815 } else {
816 base_directory.join(path)
817 }
818}
819
820pub(super) fn load_manifest_image(
821 base_directory: &Path,
822 path: &Path,
823 expected_shape: (usize, usize),
824) -> Result<Vec<f64>> {
825 let resolved = resolve_path(base_directory, path);
826 let image = load_grayscale(&resolved, GrayscaleScaling::NativeCounts)?;
827 if image.dim() != expected_shape {
828 return Err(Error::InvalidMeasurements(format!(
829 "image {} has shape {:?}, expected {expected_shape:?}",
830 resolved.display(),
831 image.dim()
832 )));
833 }
834 Ok(image.into_iter().collect())
835}
836
837pub(super) fn load_manifest_image_set(
838 base_directory: &Path,
839 images: &ImageSet,
840 expected_shape: (usize, usize),
841 frame_count: usize,
842) -> Result<Vec<f64>> {
843 match images {
844 ImageSet::Single(path) => load_manifest_image(base_directory, path, expected_shape),
845 ImageSet::PerFrame(paths) => {
846 if paths.len() != frame_count {
847 return Err(Error::InvalidMeasurements(format!(
848 "manifest image set has {} entries for {frame_count} frames",
849 paths.len()
850 )));
851 }
852 let frame_len = checked_len_2d(expected_shape)?;
853 let total = frame_len
854 .checked_mul(frame_count)
855 .ok_or_else(|| Error::ShapeOverflow {
856 shape: vec![frame_count, expected_shape.0, expected_shape.1],
857 })?;
858 let mut values = Vec::with_capacity(total);
859 for path in paths {
860 values.extend(load_manifest_image(base_directory, path, expected_shape)?);
861 }
862 Ok(values)
863 }
864 }
865}