Skip to main content

fpm_rs/algorithms/
fpie.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/// Regularized ptychographic iterative-engine reconstruction adapted to FPM.
16///
17/// # Method
18///
19/// `Fpie` performs the same detector-amplitude projection as
20/// [`super::AlternatingProjection`], but preconditions each object correction
21/// with the rPIE denominator
22/// `(1 - stability) * |pupil|^2 + stability * max(|pupil|^2)`. This blends
23/// local inverse-pupil weighting with a global power bound, reducing unstable
24/// updates where the pupil transfer is weak. The corrected low-resolution
25/// spectrum is inserted into the corresponding overlapping patch of the
26/// high-resolution object spectrum.
27///
28/// This crate adapts the rPIE update, originally formulated for scanned
29/// ptychography, to image-plane Fourier ptychography.
30///
31/// # Reference
32///
33/// A. Maiden, D. Johnson, and P. Li, “Further improvements to the
34/// ptychographical iterative engine,” *Optica* **4**(7), 736–745 (2017),
35/// [doi:10.1364/OPTICA.4.000736](https://doi.org/10.1364/OPTICA.4.000736).
36#[derive(Clone, Debug)]
37pub struct Fpie {
38    /// Number of complete passes through the acquisition schedule.
39    pub iterations: usize,
40    /// Relaxation factor applied to each object-spectrum correction.
41    pub object_step: f64,
42    /// Blend between local pupil power (`0`) and maximum pupil power (`1`) in
43    /// the rPIE denominator.
44    pub stability: f64,
45    /// Number of measured frames supplied to each reconstruction step.
46    pub batch_size: usize,
47    /// Positive numerical floor added to the rPIE denominator.
48    pub epsilon: f64,
49    /// Loss used for diagnostics; the projection itself always enforces the
50    /// measured amplitude.
51    pub loss_type: LossType,
52}
53
54impl Default for Fpie {
55    fn default() -> Self {
56        Self {
57            iterations: 50,
58            object_step: 0.8,
59            stability: 0.1,
60            batch_size: 1,
61            epsilon: 1e-10,
62            loss_type: LossType::AmplitudeMse,
63        }
64    }
65}
66
67impl Fpie {
68    pub fn iterations(mut self, iterations: usize) -> Self {
69        self.iterations = iterations;
70        self
71    }
72
73    pub fn object_step(mut self, step: f64) -> Self {
74        self.object_step = step;
75        self
76    }
77
78    pub fn stability(mut self, stability: f64) -> Self {
79        self.stability = stability.clamp(0.0, 1.0);
80        self
81    }
82}
83
84impl ReconstructionAlgorithm for Fpie {
85    fn validate(&self) -> Result<()> {
86        if !self.object_step.is_finite() || self.object_step <= 0.0 {
87            return Err(Error::InvalidParameter {
88                name: "object_step",
89                reason: "must be finite and positive".into(),
90            });
91        }
92        if !self.stability.is_finite() || !(0.0..=1.0).contains(&self.stability) {
93            return Err(Error::InvalidParameter {
94                name: "stability",
95                reason: "must be finite and between zero and one".into(),
96            });
97        }
98        if !self.epsilon.is_finite() || self.epsilon <= 0.0 || self.batch_size == 0 {
99            return Err(Error::InvalidParameter {
100                name: "epsilon/batch_size",
101                reason: "epsilon must be positive and batch size non-zero".into(),
102            });
103        }
104        Ok(())
105    }
106
107    fn step<M: MeasurementRead>(
108        &mut self,
109        problem: &ReconstructionProblem<M>,
110        state: &mut ReconstructionState,
111        batch: &Batch,
112        _iteration: usize,
113    ) -> Result<StepDiagnostics> {
114        projection_update(
115            problem,
116            state,
117            batch,
118            UpdateConfiguration {
119                object_step: self.object_step,
120                pupil_step: None,
121                epsilon: self.epsilon,
122                loss_type: self.loss_type,
123                object_denominator: ObjectDenominator::Rpie(self.stability),
124                constrain_pupil: true,
125                gain_update: None,
126                background_update: None,
127            },
128        )
129    }
130
131    fn iterations(&self) -> usize {
132        self.iterations
133    }
134
135    fn batch_size(&self) -> usize {
136        self.batch_size
137    }
138}