haadf_vib reads itindex.html in Chrome, Edge or Firefox (a file viewer or preview pane may show only a
static snapshot), or serve the folder and open http://localhost:8124/:
python -m http.server 8124 --directory .
A square lattice of constant a, rotated by θ relative to the scan, one atom per cell, imaged incoherently with a Gaussian probe of half width at half maximum r. Pixel (i, j) of frame n is recorded at t = n·Tframe + i·tline + (j + ½)·τ, with tline = M·τ + flyback, and it records the lattice at its nominal position minus the specimen displacement at that instant, s(t) = drift + vibration. Nothing else is imposed: the satellites, the loss inside a row, aliasing and the drift shear all come from that timing.
Each vibration mode is generated as ξ(t) = Re[z(t)·e2πift], with z a complex Ornstein–Uhlenbeck envelope of rate πw. Its one-sided power spectrum is exactly a²·[L(f′−f) + L(f′+f)], a Lorentzian of full width w and total rms a. Isotropic modes split a between two independent x and y processes. Runs are seeded, so changing one knob does not re-roll the others.
Counts are Poisson on the column signal plus a uniform background. The defaults, 75 counts on a background 9× that, reproduce a raw single frame of a real crystal scanned at these settings: valley/peak 0.91, mean/σ ≈ 26, and Bragg/background ≈ 100–117 on the strongest reflections read.
A line-for-line port of vibration.py / haadf_vib.py: Hann-windowed |FFT|;
fit_reflections (the strongest Bragg reflections that each have their FFT column to themselves);
_sideband_power walks each column, averages the ±k satellites and removes a background taken
from an unrelated column; the satellite/Bragg ratio is Bessel-inverted to the displacement along g;
reflections in ≥ 3 directions are fitted for x and y together and reported as √(x²+y²);
the correction for each row’s Hann-weighted time average; power averaged over frames; then
reduce_spectrum for the floor, bands and peaks.
set, per bin is the Lorentzian spectrum you dialled in, integrated over each FFT bin and spread by the Hann window’s [¼, 1, ¼] power kernel — what an ideal satellite reading would show, drawn past Nyquist too. expected reading is what the scan can actually report: that spectrum after each row’s Hann-weighted time average (response ≈ sinc(x)/(1−x²), x = f·tactive), folded at the line rate, then divided by the method’s own correction at the frequency it appears at — below Nyquist that undoes the row average and leaves the set spectrum. extracted is the port of the Python method run on the simulated frames; it should scatter about expected reading.
Append ?selftest to the address to run the built-in checks.