Skip to main content

fpm_rs/simulation/
presets.rs

1//! Small deterministic simulation presets for tests and benchmarks.
2//!
3//! These functions configure the existing [`super::Simulator`] and return its
4//! normal [`super::SimulationResult`]. They intentionally do not introduce a
5//! second synthetic-dataset abstraction.
6
7use crate::{
8    Result,
9    experiment::{ArrayPose, Illumination, Optics, PlanarLedArray, PupilAberration},
10    model::{ImagePlaneModel, ReconstructionShape},
11};
12
13use super::{CameraModel, SimulationResult, Simulator, SyntheticObject};
14
15/// Stable identifier for [`noiseless_mixed_fpm`].
16pub const NOISELESS_MIXED_PRESET: &str = "noiseless_mixed_v1";
17/// Stable identifier for [`aberrated_pupil_fpm`].
18pub const ABERRATED_PUPIL_PRESET: &str = "aberrated_pupil_v1";
19/// Stable identifier for [`poisson_gaussian_fpm`].
20pub const POISSON_GAUSSIAN_PRESET: &str = "poisson_gaussian_v1";
21
22/// A 3×3 LED, 32×32 measurement, 64×64 mixed-object ideal acquisition.
23pub fn noiseless_mixed_fpm(seed: u64) -> Result<SimulationResult> {
24    let model = base_model(base_optics())?;
25    Simulator::ideal(model)
26        .object(SyntheticObject::mixed_test_pattern((64, 64))?)
27        .seed(seed)
28        .simulate()
29}
30
31/// A known defocus/astigmatism mismatch suitable for EPRY pupil recovery.
32pub fn aberrated_pupil_fpm(seed: u64) -> Result<SimulationResult> {
33    let assumed_optics = base_optics();
34    let true_optics = Optics {
35        defocus_distance: Some(-18e-6),
36        pupil_aberration: Some(PupilAberration {
37            astigmatism: 0.25,
38            spherical: 0.12,
39            ..PupilAberration::default()
40        }),
41        ..assumed_optics.clone()
42    };
43    let true_model = base_model(true_optics)?;
44    let reconstruction_model = base_model(assumed_optics)?;
45    Simulator::new(true_model)
46        .object(SyntheticObject::mixed_test_pattern((64, 64))?)
47        .reconstruction_model(reconstruction_model)
48        .seed(seed)
49        .simulate()
50}
51
52/// A count-domain acquisition with shot noise, Gaussian read noise, offset,
53/// quantization, and a finite 16-bit detector range.
54pub fn poisson_gaussian_fpm(seed: u64) -> Result<SimulationResult> {
55    let model = base_model(base_optics())?;
56    let camera = CameraModel::new()
57        .photons_per_pixel(400.0)
58        .gain(1.5)
59        .offset_counts(100.0)
60        .read_noise_electrons(2.0)
61        .shot_noise(true)
62        .bit_depth(16)
63        .quantize(true);
64    Simulator::new(model)
65        .object(SyntheticObject::mixed_test_pattern((64, 64))?)
66        .camera(camera)
67        .seed(seed)
68        .simulate()
69}
70
71fn base_optics() -> Optics {
72    Optics {
73        wavelength_vacuum_m: 532e-9,
74        objective_na: 0.10,
75        magnification: 4.0,
76        camera_pixel_size: 6.5e-6,
77        illumination_refractive_index: 1.0,
78        objective_medium_refractive_index: 1.0,
79        defocus_distance: None,
80        pupil_aberration: None,
81    }
82}
83
84fn base_model(optics: Optics) -> Result<ImagePlaneModel> {
85    let illumination = Illumination::from_geometry(PlanarLedArray::new(
86        (3, 3),
87        (4e-3, 4e-3),
88        (1.0, 1.0),
89        ArrayPose::from_translation([0.0, 0.0, -90e-3]),
90    ))?;
91    ImagePlaneModel::from_experiment(
92        &optics,
93        &illumination,
94        (32, 32),
95        ReconstructionShape::Exact((64, 64)),
96    )
97}