Skip to main content

fpm_rs/algorithms/
alternating_projection.rs

1use crate::{
2    Result,
3    algorithms::objective::LossType,
4    diagnostics::StepDiagnostics,
5    error::Error,
6    measurements::MeasurementRead,
7    reconstruction::{Batch, ReconstructionProblem, ReconstructionState},
8};
9
10use super::{
11    ReconstructionAlgorithm,
12    common::{ObjectDenominator, UpdateConfiguration, projection_update},
13};
14
15/// Alternating-projection reconstruction for Fourier ptychographic microscopy.
16///
17/// # Method
18///
19/// For each measured frame, the algorithm extracts the corresponding patch of
20/// the current high-resolution object spectrum, multiplies it by the pupil, and
21/// propagates the resulting field to the detector plane. It replaces the
22/// predicted detector amplitude with the measured amplitude while retaining
23/// the predicted phase, propagates the corrected field back to Fourier space,
24/// and inserts the resulting correction into the object spectrum. Repeating
25/// this operation over overlapping Fourier patches makes them converge toward
26/// a mutually consistent complex object.
27///
28/// For incoherently multiplexed frames, one measured-to-predicted amplitude
29/// ratio is applied jointly to every source mode before their corrections are
30/// back-projected.
31///
32/// # Reference
33///
34/// G. Zheng, R. Horstmeyer, and C. Yang, “Wide-field, high-resolution Fourier
35/// ptychographic microscopy,” *Nature Photonics* **7**, 739–745 (2013),
36/// [doi:10.1038/nphoton.2013.187](https://doi.org/10.1038/nphoton.2013.187).
37#[derive(Clone, Debug)]
38pub struct AlternatingProjection {
39    /// Number of complete passes through the acquisition schedule.
40    pub iterations: usize,
41    /// Relaxation factor applied to each object-spectrum correction.
42    pub object_step: f64,
43    /// Number of measured frames supplied to each reconstruction step.
44    pub batch_size: usize,
45    /// Positive numerical floor used in divisions and dark-field handling.
46    pub epsilon: f64,
47    /// Loss used for diagnostics; the projection itself always enforces the
48    /// measured amplitude.
49    pub loss_type: LossType,
50}
51
52impl Default for AlternatingProjection {
53    fn default() -> Self {
54        Self {
55            iterations: 50,
56            object_step: 1.0,
57            batch_size: 1,
58            epsilon: 1e-10,
59            loss_type: LossType::AmplitudeMse,
60        }
61    }
62}
63
64impl AlternatingProjection {
65    pub fn iterations(mut self, iterations: usize) -> Self {
66        self.iterations = iterations;
67        self
68    }
69
70    pub fn object_step(mut self, object_step: f64) -> Self {
71        self.object_step = object_step;
72        self
73    }
74
75    pub fn batch_size(mut self, batch_size: usize) -> Self {
76        self.batch_size = batch_size;
77        self
78    }
79}
80
81impl ReconstructionAlgorithm for AlternatingProjection {
82    fn validate(&self) -> Result<()> {
83        if !self.object_step.is_finite() || self.object_step <= 0.0 {
84            return Err(Error::InvalidParameter {
85                name: "object_step",
86                reason: "must be finite and positive".into(),
87            });
88        }
89        if !self.epsilon.is_finite() || self.epsilon <= 0.0 {
90            return Err(Error::InvalidParameter {
91                name: "epsilon",
92                reason: "must be finite and positive".into(),
93            });
94        }
95        if self.batch_size == 0 {
96            return Err(Error::InvalidParameter {
97                name: "batch_size",
98                reason: "must be greater than zero".into(),
99            });
100        }
101        Ok(())
102    }
103
104    fn step<M: MeasurementRead>(
105        &mut self,
106        problem: &ReconstructionProblem<M>,
107        state: &mut ReconstructionState,
108        batch: &Batch,
109        _iteration: usize,
110    ) -> Result<StepDiagnostics> {
111        projection_update(
112            problem,
113            state,
114            batch,
115            UpdateConfiguration {
116                object_step: self.object_step,
117                pupil_step: None,
118                epsilon: self.epsilon,
119                loss_type: self.loss_type,
120                object_denominator: ObjectDenominator::Local,
121                constrain_pupil: true,
122                gain_update: None,
123                background_update: None,
124            },
125        )
126    }
127
128    fn iterations(&self) -> usize {
129        self.iterations
130    }
131
132    fn batch_size(&self) -> usize {
133        self.batch_size
134    }
135}