HAADF Vibration Simulator

A perfect lattice, a Gaussian probe and real scan timing · the vibration spectrum read back from the FFT satellites the way haadf_vib reads it
The simulator is not running. Everything on this page is computed by JavaScript in your browser, and it has not started. Open index.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 .

Simulated HAADF

FFT, |F|

Circled: the reflections the satellites are read from. Dashed: the columns they are read along.

Vibration

Readout

Scenarios

Lattice & probe

Vibration modes

Each mode is a Lorentzian: centre f, full width w, rms amplitude a. A width of 0 is a pure tone of peak a·√2.

Drift

Scan

pixels (square)

Actions

What is simulated

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.

How the spectrum is read back

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.

The curves

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.