Sampling and antialiasing

Hide code cell source
import abtem
import ase
import matplotlib.pyplot as plt

abtem.config.set({"local_diagnostics.progress_bar": False});

Sampling and antialiasing#

In abTEM, the wave function is represented on a rectangular grid of \(N_x \times N_y\) grid points (gpts) or pixels. Given an orthogonal cell with the sidelengths \(L_x\) and \(L_y\), in the \(x\) and \(y\)-direction, the real space sampling (or dimensions of the pixels) is \(\Delta x = L_x / N_x\) in \(x\) and \(\Delta y=L_y / N_y\). The real space coordinates take on only discrete values of:

\[\begin{split} x_i = i \Delta x \quad i = 0,1, \ldots , N_x - 1 \\ y_j = j \Delta y \quad j = 0,1, \ldots , N_y - 1 \\ \end{split}\]

The Fourier transform of this grid of values (or image) will also have \(N_x \times N_y\) grid points, however, in reciprocal space the sampling is determined by the supercell dimensions given by the inverse relations \(\Delta k_x = 1/L_x\) and \(\Delta k_y = 1/L_y\). The reciprocal space coordinates take on values:

\[\begin{split} k_{x,i} = - k_{x,max} + i \Delta k_x \\ k_{x,j} = - k_{y,max} + j \Delta k_y \end{split}\]

where the maximum spatial frequencies are imposed by the real-space sampling

\[ k_{x,max} = \frac{1}{2\Delta_x} \quad \mathrm{and} \quad k_{y,max} = \frac{1}{2\Delta_y} \quad . \]

We demonstrate the relationship between the real space sampling and the maximum simulated spatial frequencies (\(k_{x,max}\) and \(k_{y,max}\)) in abTEM below.

We simulate two otherwise identical exit wave functions at different (uniform) samplings of \(\Delta=\Delta_x=\Delta_y=0.08 \ \mathrm{Å}\) and \(\Delta= 0.04 \mathrm{Å}\).

atoms = ase.build.bulk("Au", cubic=True) * (5, 5, 40)

exit_wave_rough = abtem.Probe(
    energy=100e3, semiangle_cutoff=9.5, sampling=0.08
).multislice(atoms)

exit_wave_fine = abtem.Probe(
    energy=100e3, semiangle_cutoff=9.5, sampling=0.04
).multislice(atoms)

We plot the diffraction patterns on a power scale to relatively enhance the intensity at high scattering angle.

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4))

exit_wave_rough.diffraction_patterns(max_angle="full").show(
    ax=ax1, power=0.1, title="sampling = 0.08 Å"
)
exit_wave_fine.diffraction_patterns(max_angle="full").show(
    ax=ax2, power=0.1, title="sampling = 0.04 Å"
)
plt.tight_layout()
../../_images/fdfad7a7a59251e1946c37813e8721788198fe0ebe2c90c8e400fa751803470c.png

You should note that the maximum spatial frequency on the plot axes is doubled in the figure with the fine sampling.

You should also note that even though we are plotting the diffraction patterns on a power scale, the intensity goes to zero rather abruptly at around \(2/3\) of the maximum simulated frequency. This is necessary to counteract antialiasing artifacts arising due to the periodicity assumption of the Fourier transform, see chapter 6.8 in Kirklands textbook for a detailed explanation.

Hence, the maximum usable spatial frequency is

\[ k_{max,antialiasing} = \frac{2}{3} \frac{1}{2\Delta} = \frac{1}{3\Delta} \quad , \]

this frequency corresponds directly to a maximum scattering angle

\[ \alpha_{max,antialiasing} = \frac{\lambda}{3 \Delta} \quad . \]

where \(\lambda\) is the relativistic electron wavelength.

This means that for simulating large scattering angles you need to ensure that your sampling is fine enough. As an example, consider a case where we want to simulate \(100 \ \mathrm{keV}\) electron scattering up to angles of \(200 \ \mathrm{mrad}\). Plugging these values into the above equation and solving for the sampling gives \(\Delta \approx 0.062 \ \mathrm{Å}\), i.e. we require a sampling of at least \(\Delta = 0.062 \ \mathrm{Å}\) in order to simulate scattering up to \(200 \ \mathrm{mrad}\).

We can print the maximum scattering angle as below:

print(f"Maximum scattering angles = {exit_wave_rough.cutoff_angles} mrad")
Maximum scattering angles = (154.22652479693227, 154.22652479693227) mrad

When obtaining a diffraction pattern we typically wish to crop it to the maximum scattering. We do this through the max_angle keyword, the default value of max_angle is "cutoff", with this setting abTEM crops the diffraction pattern to \(\alpha_{max,antialiasing}\) or equivalently \(k_{max,antialiasing}\). You may also also set max_angle="valid", which will crop the diffraction pattern to the largest rectangle inside the antialias aperture.

fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(12, 4))

exit_wave_rough.diffraction_patterns(max_angle="full").show(
    ax=ax1, power=0.1, units="mrad", title="cropped to 'full'"
)

exit_wave_rough.diffraction_patterns(max_angle="cutoff").show(
    ax=ax2, power=0.1, units="mrad", title="cropped to 'cutoff'"
)

exit_wave_rough.diffraction_patterns(max_angle="valid").show(
    ax=ax3, power=0.1, units="mrad", title="cropped to 'valid'"
)

plt.tight_layout()
../../_images/d7e96c73e390588a97d7759fb79e0179fc6968e4c27047393bd409d5eafde24d.png

Finally we demonstrate the perhaps non-intuitive fact that the only way to improve the sampling in reciprocal space, .i.e decrease \(\Delta k\), is to increase the size of the supercell in \(x\) and \(y\). Below we double the supercell and see that reciprocal space sampling is doubled.

repeated_atoms = atoms.copy() * (2, 2, 1)

exit_wave_repeated = abtem.Probe(
    energy=100e3, semiangle_cutoff=9.5, sampling=0.08
).multislice(repeated_atoms)

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4))

exit_wave_rough.diffraction_patterns(max_angle=30).show(
    ax=ax1, power=0.2, units="mrad", title="small supercell"
)
exit_wave_repeated.diffraction_patterns(max_angle=30).show(
    ax=ax2, power=0.2, units="mrad", title="large supercell"
)

plt.tight_layout()
../../_images/1a2d56f52a4f17184211bfd772378f0cb7539a64f8eef69c6b9c21b5df5fdcab.png