Spherical illumination geometries¶
LEDSphere describes fixed LEDs on a spherical cap. SphericalLEDArm
describes one LED moved through a sequence of commanded polar/azimuth angles.
Both compile to the same transverse KVector representation used by the
reconstruction algorithms.
Angles are (theta, phi) in radians. theta is measured from the positive
optical (z) axis and must satisfy 0 <= theta < pi/2; phi is measured from
positive x toward positive y. Distances are metres. Rigid orientations are
stored as (rx, ry, rz) and applied in the order Rz * Ry * Rx.
Fixed LED sphere¶
For LED i, the model is
u_i = [sin(theta_i + dtheta_i) cos(phi_i + dphi_i),
sin(theta_i + dtheta_i) sin(phi_i + dphi_i),
cos(theta_i + dtheta_i)]
p_i = c + R r u_i
k_i = (2 pi n / lambda) [p_i.x, p_i.y] / |p_i|
where r is radius, c is center_offset, R is
orientation_radians, and (dtheta_i, dphi_i) comes from
angular_corrections.
The proposed geometrical error model separates:
- rigid assembly errors: sphere decentring and three-axis mounting rotation;
- construction errors: an independent angular placement correction for every LED;
- radius error: relevant only when the sphere is not centred on the sample.
That last qualification is important: in the plane-wave model, radius cancels
exactly for a perfectly centred sphere. Radius cannot be estimated from
illumination directions alone, and radial LED placement errors have no effect
unless combined with decentring or a finite-distance wavefront model. Likewise,
a global azimuth shift and a mount rotation about z are partly redundant; a
calibration should fix one of them.
use fpm_rs::experiment::LEDSphere;
let sphere = LEDSphere::new(
vec![(0.0, 0.0), (0.25, 0.0), (0.25, 1.57)],
90e-3,
)
.center_offset((0.4e-3, -0.2e-3, 0.0))
.orientation_deg((0.1, -0.2, 0.3))
.angular_corrections(vec![(0.0, 0.0), (0.001, -0.002), (-0.001, 0.001)]);
Moving spherical arm¶
The arm treats angle errors as shared kinematic errors rather than independent errors at every acquisition point:
theta_i' = theta_scale theta_i + theta_zero + direction_i B_theta / 2
phi_i' = phi_scale phi_i + phi_zero + direction_i B_phi / 2
p_i = c + R Rz(phi_i') Rot(a, theta_i') [0, 0, L]
a = [0, cos(epsilon), sin(epsilon)]
Here L is arm_length, c is pivot_offset, epsilon is
elevation_axis_tilt_radians, and B_theta/B_phi are total backlash dead-band
widths. A positive/negative move selects the positive/negative half of the dead
band. The first command has no inferred approach direction, and a repeated
command retains the previous branch. Azimuth commands should therefore be
unwrapped before crossing +-pi.
The proposed arm error model covers:
- pivot-to-sample displacement and rigid mount rotation;
- encoder zero and scale errors on both axes;
- non-orthogonality between the elevation and azimuth axes;
- deterministic direction-dependent backlash;
- arm-length error, which—like sphere radius—only affects directions when the pivot is displaced from the sample.
This is a compact systematic model suitable for calibration. It deliberately does not pretend that random repeatability, elastic sag, bearing runout, or thermal drift are independent acquisition noise. If measurements show those effects, use repeated-position residuals to choose an extension: a repeatability distribution, an angle-dependent sag term, or a periodic runout term. Adding all of them before inspecting residual structure would make the calibration poorly identifiable.
Rotating quarter-circle LED arc¶
RotatingLEDArc represents several LEDs fixed to a meridional quarter-circle
arm. The complete arm rotates around an axis nominally collinear with the
optical axis. Each LED is compiled at every commanded rotation; the source order
is rotation-major and then LED-position order.
For LED j at rotation m, the model is
theta_j' = theta_j + dtheta_j
r_j = radius + dr_j
phi_m' = rotation_scale phi_m + rotation_zero + direction_m B / 2
q_mj = Rz(phi_m') r_j u(theta_j', dphi_j)
p_mj = c + Ry(beta_y) Rx(beta_x) q_mj
k_mj = (2 pi n / lambda) [p_mj.x, p_mj.y] / |p_mj|
The proposed error model covers:
- rotation-axis decentring through
axis_origin_offset, including axial displacement of the arc centre from the sample; - loss of collinearity with the optical axis through the two-component
axis_tilt_radians; - rotation encoder zero, scale, and direction-dependent backlash;
- per-LED position along the arc (
delta_theta), out-of-plane mounting or arm twist (delta_phi), and radial/shape error (led_radial_offsets).
use fpm_rs::experiment::RotatingLEDArc;
let arc = RotatingLEDArc::new(
vec![0.0, 0.10, 0.20, 0.30],
vec![0.0, 0.5, 1.0, 0.5],
90e-3,
)
.axis_origin_offset((0.3e-3, -0.2e-3, 0.1e-3))
.axis_tilt_deg(0.1, -0.2)
.rotation_encoder_zero_deg(0.3)
.rotation_backlash_deg(0.05)
.led_angular_corrections(vec![(0.0, 0.0); 4])
.led_radial_offsets(vec![0.0; 4]);
There are several important identifiability limits. A uniform per-LED
delta_phi is indistinguishable from rotation encoder zero. Backlash cannot be
separated from zero offset during a purely monotonic sweep; at least one
direction reversal is needed. Radial errors do not affect the plane-wave
directions when the arc centre is exactly at the sample, and the azimuth of an
LED at theta = 0 is undefined. Axis offset and tilt also become strongly
correlated when only a narrow azimuth range is measured. Calibration should fix
one parameter in each degenerate pair or use independent mechanical metrology.
Simulating mismatch¶
Geometric errors are deterministic source geometry in this crate. Compile the
measured or perturbed parameters into the simulator's true model and compile the
nominal parameters into its reconstruction model. IlluminationAcquisitionErrors
remains reserved for non-geometric effects such as failed frames, gain variation,
and source-order mistakes.