1use num_complex::Complex64;
2use serde::{Deserialize, Serialize};
3use std::sync::Arc;
4
5use crate::{
6 Array2, Result,
7 backend::{Backend, CpuBackend, FftDirection},
8 error::Error,
9 measurements::MeasurementRead,
10 model::{FourierOffset, ImagePlaneModel, Pupil, fftshift_copy},
11};
12
13use super::{ReconstructionCheckpoint, ReconstructionProblem};
14
15#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
16pub struct AdmmAuxiliaryState {
17 pub auxiliary_fields: Vec<Complex64>,
18 pub dual_fields: Vec<Complex64>,
19}
20
21#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
22pub enum AlgorithmAuxiliaryState {
23 Admm(AdmmAuxiliaryState),
24}
25
26#[derive(Clone, Debug)]
27pub struct ReconstructionScratch {
28 pub patch: Vec<Complex64>,
29 pub exit_spectrum: Vec<Complex64>,
30 pub field: Vec<Complex64>,
31 pub projected_field: Vec<Complex64>,
32 pub projected_spectrum: Vec<Complex64>,
33 pub difference: Vec<Complex64>,
34 pub object_gradient: Vec<Complex64>,
36 pub regularization_field: Vec<Complex64>,
37 pub pupil_gradient: Vec<Complex64>,
38 pub calibration_reference: Vec<f64>,
39 pub illumination_gradient: Vec<(f64, f64)>,
40 pub illumination_curvature: Vec<(f64, f64)>,
41 pub illumination_weight: Vec<f64>,
42 pub multiplex_fields: Vec<Complex64>,
44 pub multiplex_patches: Vec<Complex64>,
46 pub multiplex_offsets: Vec<FourierOffset>,
47 pub column: Vec<Complex64>,
48}
49
50impl ReconstructionScratch {
51 fn new(low_shape: (usize, usize), high_shape: (usize, usize)) -> Self {
52 let low_len = low_shape.0 * low_shape.1;
53 Self {
54 patch: vec![Complex64::default(); low_len],
55 exit_spectrum: vec![Complex64::default(); low_len],
56 field: vec![Complex64::default(); low_len],
57 projected_field: vec![Complex64::default(); low_len],
58 projected_spectrum: vec![Complex64::default(); low_len],
59 difference: vec![Complex64::default(); low_len],
60 object_gradient: Vec::new(),
61 regularization_field: Vec::new(),
62 pupil_gradient: vec![Complex64::default(); low_len],
63 calibration_reference: vec![0.0; low_len],
64 illumination_gradient: Vec::new(),
65 illumination_curvature: Vec::new(),
66 illumination_weight: Vec::new(),
67 multiplex_fields: Vec::new(),
68 multiplex_patches: Vec::new(),
69 multiplex_offsets: Vec::new(),
70 column: vec![Complex64::default(); low_shape.0.max(high_shape.0)],
71 }
72 }
73}
74
75#[derive(Clone)]
76pub struct ReconstructionState {
77 pub object_spectrum: Array2<Complex64>,
78 pub object_real_space_cache: Option<Array2<Complex64>>,
79 pub pupil: Pupil,
80 pub illumination_corrections: Option<Vec<(f64, f64)>>,
82 pub frame_gains: Option<Vec<f64>>,
83 pub background: Option<Vec<f64>>,
84 pub algorithm_auxiliary: Option<AlgorithmAuxiliaryState>,
85 pub scratch: ReconstructionScratch,
86 pub(crate) backend: Arc<dyn Backend>,
87}
88
89impl std::fmt::Debug for ReconstructionState {
90 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 formatter
92 .debug_struct("ReconstructionState")
93 .field("object_spectrum", &self.object_spectrum)
94 .field("pupil", &self.pupil)
95 .field("illumination_corrections", &self.illumination_corrections)
96 .field("frame_gains", &self.frame_gains)
97 .field("background", &self.background)
98 .field("algorithm_auxiliary", &self.algorithm_auxiliary)
99 .finish_non_exhaustive()
100 }
101}
102
103impl ReconstructionState {
104 pub fn effective_source_offset(
105 &self,
106 model: &ImagePlaneModel,
107 source: usize,
108 ) -> Result<FourierOffset> {
109 let base = model.source_offset(source)?;
110 let correction = match &self.illumination_corrections {
111 None => (0.0, 0.0),
112 Some(values) => *values.get(source).ok_or_else(|| {
113 Error::InvalidModel(
114 "illumination correction count does not match source count".into(),
115 )
116 })?,
117 };
118 if !correction.0.is_finite() || !correction.1.is_finite() {
119 return Err(Error::InvalidModel(
120 "illumination corrections must be finite".into(),
121 ));
122 }
123 let effective = FourierOffset::new(base.row + correction.0, base.column + correction.1);
124 model.validate_source_offset(source, effective)?;
125 Ok(effective)
126 }
127
128 pub fn initialize<M: MeasurementRead>(problem: &ReconstructionProblem<M>) -> Result<Self> {
129 let backend: Arc<dyn Backend> = Arc::new(CpuBackend::new(
130 problem.model.image_shape,
131 problem.model.reconstruction_shape,
132 )?);
133 Self::initialize_with_backend(problem, backend)
134 }
135
136 pub fn initialize_with_backend<M: MeasurementRead>(
137 problem: &ReconstructionProblem<M>,
138 backend: Arc<dyn Backend>,
139 ) -> Result<Self> {
140 problem.validate()?;
141 let low_shape = problem.model.image_shape;
142 let high_shape = problem.model.reconstruction_shape;
143 let low_len = low_shape.0 * low_shape.1;
144 let mut average_amplitude = vec![0.0; low_len];
145 let mut amplitude_weight = vec![0.0; low_len];
146 let mut total_amplitude = 0.0;
147 let mut total_weight = 0.0;
148 for frame_index in 0..problem.measurements.frame_count() {
149 let frame = problem.measurements.frame(frame_index)?;
150 let frame_weight = problem.measurements.frame_weight(frame_index)?;
151 if frame_weight == 0.0 {
152 continue;
153 }
154 let mask = problem.measurements.frame_mask(frame_index)?;
155 let gain = problem.model.frame_gain(frame_index)?;
156 for (pixel, (average, &intensity)) in
157 average_amplitude.iter_mut().zip(frame.iter()).enumerate()
158 {
159 if mask.is_some_and(|values| values[pixel] == 0) {
160 continue;
161 }
162 let background = problem.model.background_value(frame_index, pixel)?;
163 let amplitude = ((intensity - background) / gain).max(0.0).sqrt();
164 *average += frame_weight * amplitude;
165 amplitude_weight[pixel] += frame_weight;
166 total_amplitude += frame_weight * amplitude;
167 total_weight += frame_weight;
168 }
169 }
170 if total_weight == 0.0 {
171 return Err(Error::InvalidMeasurements(
172 "no positive-weight, unmasked measurements are available".into(),
173 ));
174 }
175 let fallback_amplitude = total_amplitude / total_weight;
176 for (value, &weight) in average_amplitude.iter_mut().zip(&litude_weight) {
177 *value = if weight > 0.0 {
178 *value / weight
179 } else {
180 fallback_amplitude
181 };
182 }
183 let mut object = vec![Complex64::default(); high_shape.0 * high_shape.1];
184 for row in 0..high_shape.0 {
185 let low_row = row * low_shape.0 / high_shape.0;
186 for column in 0..high_shape.1 {
187 let low_column = column * low_shape.1 / high_shape.1;
188 object[row * high_shape.1 + column] =
189 Complex64::new(average_amplitude[low_row * low_shape.1 + low_column], 0.0);
190 }
191 }
192 let mut column = vec![Complex64::default(); low_shape.0.max(high_shape.0)];
193 backend.fft2(&mut object, high_shape, FftDirection::Forward, &mut column)?;
194 let mut centered = vec![Complex64::default(); object.len()];
195 fftshift_copy(&object, &mut centered, high_shape);
196 let object_spectrum = Array2::from_vec(high_shape, centered)?;
197 Ok(Self {
198 object_spectrum,
199 object_real_space_cache: None,
200 pupil: problem.model.pupil.clone(),
201 illumination_corrections: None,
202 frame_gains: problem.model.frame_gains.clone(),
203 background: problem.model.background.clone(),
204 algorithm_auxiliary: None,
205 scratch: ReconstructionScratch::new(low_shape, high_shape),
206 backend,
207 })
208 }
209
210 pub fn from_object<M: MeasurementRead>(
211 problem: &ReconstructionProblem<M>,
212 object: Array2<Complex64>,
213 ) -> Result<Self> {
214 if object.shape() != problem.model.reconstruction_shape {
215 return Err(Error::InvalidShape(format!(
216 "initial object shape {:?} differs from reconstruction shape {:?}",
217 object.shape(),
218 problem.model.reconstruction_shape
219 )));
220 }
221 let mut state = Self::initialize(problem)?;
222 let mut raw_spectrum = object.into_vec();
223 state.backend.fft2(
224 &mut raw_spectrum,
225 problem.model.reconstruction_shape,
226 FftDirection::Forward,
227 &mut state.scratch.column,
228 )?;
229 fftshift_copy(
230 &raw_spectrum,
231 state.object_spectrum.as_mut_slice(),
232 problem.model.reconstruction_shape,
233 );
234 Ok(state)
235 }
236
237 pub fn from_checkpoint<M: MeasurementRead>(
238 problem: &ReconstructionProblem<M>,
239 checkpoint: &ReconstructionCheckpoint,
240 ) -> Result<Self> {
241 let backend: Arc<dyn Backend> = Arc::new(CpuBackend::new(
242 problem.model.image_shape,
243 problem.model.reconstruction_shape,
244 )?);
245 Self::from_checkpoint_with_backend(problem, checkpoint, backend)
246 }
247
248 pub fn from_checkpoint_with_backend<M: MeasurementRead>(
249 problem: &ReconstructionProblem<M>,
250 checkpoint: &ReconstructionCheckpoint,
251 backend: Arc<dyn Backend>,
252 ) -> Result<Self> {
253 checkpoint.validate_for_problem(problem)?;
254 let low_shape = problem.model.image_shape;
255 let high_shape = problem.model.reconstruction_shape;
256 Ok(Self {
257 object_spectrum: checkpoint.object_spectrum.clone(),
258 object_real_space_cache: None,
259 pupil: checkpoint.pupil.clone(),
260 illumination_corrections: checkpoint.illumination_corrections.clone(),
261 frame_gains: checkpoint.frame_gains.clone(),
262 background: checkpoint.background.clone(),
263 algorithm_auxiliary: checkpoint.algorithm_auxiliary.clone(),
264 scratch: ReconstructionScratch::new(low_shape, high_shape),
265 backend,
266 })
267 }
268}