Skip to main content

fpm_rs/reconstruction/
problem.rs

1use crate::{Result, error::Error, measurements::MeasurementRead, model::ImagePlaneModel};
2
3/// Validated pairing of measurements with a compiled image-plane model.
4///
5/// The owned measurement provider and model must agree on acquisition-frame count and
6/// low-resolution `(height, width)` shape. Algorithms borrow this value for a run.
7#[derive(Clone, Debug)]
8pub struct ReconstructionProblem<M> {
9    /// Resident or lazy measured-intensity provider.
10    pub measurements: M,
11    /// Compiled algorithm-facing model with matching frame count and image shape.
12    pub model: ImagePlaneModel,
13    /// Optional user-facing dataset or run label.
14    pub name: Option<String>,
15}
16
17impl<M: MeasurementRead> ReconstructionProblem<M> {
18    /// Owns and validates `measurements` and `model` as one reconstruction input.
19    pub fn new(measurements: M, model: ImagePlaneModel) -> Result<Self> {
20        let problem = Self {
21            measurements,
22            model,
23            name: None,
24        };
25        problem.validate()?;
26        Ok(problem)
27    }
28
29    /// Assigns a descriptive problem name without changing numerical inputs.
30    pub fn named(mut self, name: impl Into<String>) -> Self {
31        self.name = Some(name.into());
32        self
33    }
34
35    /// Validates the model and measurements, equal frame counts, and equal image shapes.
36    pub fn validate(&self) -> Result<()> {
37        self.model.validate()?;
38        self.measurements.validate()?;
39        if self.measurements.frame_count() != self.model.frame_count() {
40            return Err(Error::InvalidModel(format!(
41                "{} measurement frames but {} model frames",
42                self.measurements.frame_count(),
43                self.model.frame_count()
44            )));
45        }
46        if self.measurements.image_shape() != self.model.image_shape {
47            return Err(Error::InvalidShape(format!(
48                "measurement shape {:?} differs from model image shape {:?}",
49                self.measurements.image_shape(),
50                self.model.image_shape
51            )));
52        }
53        let mut positive_weight_frames = 0;
54        for frame in 0..self.measurements.frame_count() {
55            if self.measurements.frame_weight(frame)? > 0.0 {
56                positive_weight_frames += 1;
57            }
58            if self.measurements.frame_weight(frame)? > 0.0
59                && self
60                    .measurements
61                    .frame_mask(frame)?
62                    .is_some_and(|mask| mask.iter().all(|&value| value == 0))
63            {
64                return Err(Error::InvalidMeasurements(format!(
65                    "positive-weight frame {frame} has no unmasked pixels"
66                )));
67            }
68        }
69        if positive_weight_frames == 0 {
70            return Err(Error::InvalidMeasurements(
71                "at least one frame must have positive weight".into(),
72            ));
73        }
74        Ok(())
75    }
76}