tb_angular_spectrum_propagate — 2D typed op

Data kinds: cimagecimage

Call: fullseye.apply(img, "tb_angular_spectrum_propagate", a=0.5, b=0.5) (the 2-D model is one image plus two scalar knobs a,b∈[0,1])

tb_angular_spectrum_propagate: input → output

*The figure is the actual output on a synthetic 128×128 input. Left: input, right: output. Point clouds are drawn as a top-down scatter (brightness = z), 1-D series as a line plot, volumes as the maximum-intensity projection along z, videos as the middle frame, complex images as magnitude; return values that are not pictures are shown as the values themselves.*

Sweeping knob a (0.1 / 0.5 / 0.9, the other knob at its default):

tb_angular_spectrum_propagate: knob a sweep (docs site)

Sweeping knob b (0.1 / 0.5 / 0.9, the other knob at its default):

tb_angular_spectrum_propagate: knob b sweep (docs site)

Stages (the ops that come before → this op, left to right):

tb_angular_spectrum_propagate: stages (docs site)

On other images (synthetic scene / photo / coins. Top row: inputs, bottom row: their outputs. Knobs at default):

tb_angular_spectrum_propagate: other inputs (docs site)

Usage

Exact scalar free-space propagation of a complex field (angular spectrum).

`U(z) = IFFT{ FFT{U(0)} * exp(i*2*pi*z*sqrt(1/lambda^2 - fx^2 - fy^2)) }`

in the `exp(-i*omega*t)` convention, so a positive *distance_um*

propagates forward. Components beyond the propagating cone

(`fx^2 + fy^2 > 1/lambda^2`) are attenuated by

`exp(-2*pi*|z|*sqrt(fx^2 + fy^2 - 1/lambda^2))`, which is the physical

evanescent decay — not zeroed, so `distance_um = 0` is an *exact*

identity and the transfer function is continuous through it.

Unlike Fresnel propagation this makes no paraxial approximation: it is the

exact solution of the Helmholtz equation for a band-limited field, valid

from a fraction of a wavelength outward.

Returns a complex128 array with the same shape as *field*.

Ground truth it reproduces (measured): `distance_um = 0` returns the field

bit-identically (it short-circuits the transform pair); propagating `+z`

then `-z` returns the original to a relative L2 error of 4.3e-16 to

5.3e-16 for a band-limited field (measured on three: 64x64 random at

+/-50 um, a 64x64 Gaussian at +/-250 um, a 128x128 random at +/-500 um);

total power is conserved to between 0 and 3.5e-16 relative on the same

three. A field *with*

evanescent content does not round-trip — those components are gone by

construction, in both directions, because that is what physically happens.

*field* is a field in the space domain, not a spectrum: do not hand it

the fftshifted output of :func:complexops.cx_fft. Real input is promoted

to complex, which loses nothing.

Aliasing: the discrete transfer function is periodic, so a field that

diffracts past the array edge wraps around. The practical guard is the

usual one — pad the field so the propagated support stays inside, and keep

`pixel_pitch_um below lambda/(2*NA)`. No warning can detect this

reliably from the array alone, so none is invented.

Raises `ValueError`: *field* is not 2-D, smaller than 2x2, larger than

:data:MAX_FIELD_ELEMENTS, masked, or non-finite; non-positive or

non-finite *wavelength_um* / *pixel_pitch_um*; non-finite *distance_um*.

Typed bridge of the optics op `angular_spectrum_propagate into the 2-D evolution registry: the same implementation, called under the op(v, a, b) convention. a drives wavelength_um (default 0.55) and b drives distance_um` (default 100).

References (sample data, literature)

• Sample-data catalog (download URLs / licences) — 2-D uses skimage.data (BSD/public domain) plus synthetic images; 3-D lists download URLs for real data sources (Stanford, PDS, …).

• Operator provenance and references — the sources of the research/methods this op family came from.

Try it in Studio

The program below has been verified to run (same input as the figure). In Studio's help this block becomes buttons that load and run it on the spot.

img_to_cimage 0.50 0.50
tb_angular_spectrum_propagate 0.50 0.50

▸ Load this pipeline  ·  Load & run

Runnable examples (verified samples that actually call this op)

The examples below call the underlying ledger op angular_spectrum_propagate. This bridge op is the same implementation adapted to the fn(v, a, b) convention, so the behaviour carries over unchanged (only the call form differs).

optics_imagingpy -3.11 examples/optics_imaging.py

Ops the type connects to (they accept cimage as input)

identity · tb_cx_ifft · tb_cx_magnitude · tb_cx_phase · tb_cx_real · tb_cx_imag · tb_cx_log_magnitude · tb_cx_apply_transfer_function

Same category (typed)

tb_points_to_voxel · tb_estimate_point_normals · tb_iss_keypoints · tb_project_points · tb_render_point_depth · tb_statistical_outlier_removal · tb_radius_outlier_removal · tb_voxel_grid_downsample


*Provenance: ops.py — 2D operator registry. This per-op note is generated by tools/opdocs.py md (do not hand-edit).*

© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.