Chapter 2: Diffraction
The Electron¶
part of
MSE672: Introduction to Transmission Electron Microscopy
Spring 2026
by Gerd Duscher
Microscopy Facilities
Institute of Advanced Materials & Manufacturing
Materials Science & Engineering
The University of Tennessee, Knoxville
Background and methods to analysis and quantification of data acquired with transmission electron microscopes.
First we load the code to make figures from pyTEMlib
Import packages for figures and¶
Check Installed Packages¶
import sys
import importlib.metadata
def test_package(package_name):
"""Test if package exists and returns version or -1"""
try:
version = importlib.metadata.version(package_name)
except importlib.metadata.PackageNotFoundError:
version = '-1'
return version
if test_package('pyTEMlib') < '0.2026.1.0':
print('installing pyTEMlib')
!{sys.executable} -m pip install --upgrade pyTEMlib -q
print('done')done
Note for Google Colab
Restart Session in the Runtime Menu
Load the plotting and figure packages¶
%matplotlib widget
import matplotlib.pylab as plt
import numpy as np
import sys
if 'google.colab' in sys.modules:
from google.colab import output
output.enable_custom_widget_manager()
import scipy # we will use the constants part only
import pyTEMlibInteraction of Common Particles with Matter¶
We generally use electron, photons, and neutrons for diffraction/scattering experiments.
These particles interact with differently with matter:
| X-rays | $\leftrightarrow$ | electron density |
| neutrons | $\leftrightarrow$ | mass of nucleus |
| neutrons | $\leftrightarrow$ | magnetic moment |
| electrons | $\leftrightarrow$ | screened charge of nucleus |
We will deal with the nature of electrons more closely in the following
Non-relativistic de Broglie Wavelength¶
The electron is a elementary particle with spin (lepton).
Non--relativistic De Broglie wavelength of electron:
E is the kinetic energy of the electron: [eV].
The wave length in a TEM is usually a couple of picometers . This is a factor of 100 smaller than your XRD-source.
Obvioulsy, we are in the wave picture right now.
Change to 300 keV acceleration voltage and look at the relativ espeed to the speed of light .
# --------- input ---------------------------
acceleration_voltage_V = U = 100.0 * 1000.0 #V
# --------------------------------------------
## energy
E_kin = acceleration_voltage_V * scipy.constants.elementary_charge
h = scipy.constants.Planck
m0 = scipy.constants.electron_mass
c = scipy.constants.speed_of_light
wave_length_m = h/np.sqrt(2*m0*E_kin) # non-relativistic wavelength in m
# # please note that we will keep all length units in Angstrom if possible.
# # otherwise we use only SI units!!!
wave_length_A = wave_length_m *1e10 # now in Angstrom
print(f'Classic wave length is {wave_length_A*100.:.2f} pm for acceleration voltage {acceleration_voltage_V/1000.:.1f} kV')
# Notice that we change units in the output to make them most readable.
print(f' which is a velocity of {np.sqrt(2/m0*E_kin):.2f} m/s or {np.sqrt(2/m0*E_kin)/c*100:.2f}% of the speed of light')Classic wave length is 3.88 pm for acceleration voltage 100.0 kV
which is a velocity of 187553726.08 m/s or 62.56% of the speed of light
Relativistic Correction¶
In the table below we see that the speeds of the electron is rather close to the speed of light
The formula for relativistic corrected wavelength is:
Please note: All units are internally in SI units: kg, s, V, J, except the length wihich is in nm!
We multiply with the appropriate factors for the output
# ---------Input: Acceleration Voltage --
E0 = acceleration_voltage_V = 300.0 *1000.0 # V
# ---------------------------------------
E_kin = acceleration_voltage_V * scipy.constants.elementary_charge
h = scipy.constants.Planck
m0 = scipy.constants.electron_mass
c = scipy.constants.speed_of_light
#relativisitic wavelength
wave_length = h/np.sqrt(2* m0 * E_kin * (1 + E_kin / (2 * m0 * c**2))) #in m
print(f'The relativistically corrected wave length is {wave_length*1e12:.2f} pm for acceleration voltage {acceleration_voltage_V/1000:.1f} kV')The relativistically corrected wave length is 1.97 pm for acceleration voltage 300.0 kV
100kV : = 4 pm than diameter of an atom
The relativistic parameters are:
| E (keV) | (pm) | M/m | v/c |
|---|---|---|---|
| 10 | 12.2 | 1.0796 | 0.1950 |
| 30 | 6.98 | 1.129 | 0.3284 |
| 100 | 3.70 | 1.1957 | 0.5482 |
| 200 | 2.51 | 1.3914 | 0.6953 |
| 400 | 1.64 | 1.7828 | 0.8275 |
| 1000 | 0.87 | 2.9569 | 0.9411 |
The same functionality (and code) is used in the kinematic_scattering library and we can test the values of above table.
Please change the acceleration voltage (acceleration_voltage_V) above.
v = np.sqrt(E_kin * (E_kin + 2 * m0 * c**2)/(E_kin + m0 * c**2) **2)*c
print(f'For an acceleration voltage of {acceleration_voltage_V/1000:.0f}keV: ')
print(f'The classic velocity of the electron is {np.sqrt(2/m0 * E_kin):.2f} m/s or {np.sqrt(2 / m0 * E_kin)/c * 100:.2f}% of the speed of light')
print(f'The relativistic velocity of the electron is {v:.2f} m/s or {v/c*100:.2f}% of the speed of light')For an acceleration voltage of 300keV:
The classic velocity of the electron is 324852582.72 m/s or 108.36% of the speed of light
The relativistic velocity of the electron is 232796486.20 m/s or 77.65% of the speed of light
That means that the resolution is not limited by the wavelength!¶
The formula for relativistic corrected wavelength is:
# ----- Input -----
acceleration_voltage= 300 * 1000.0
# -------------------
wave_length = pyTEMlib.diffraction_tools.get_wavelength(acceleration_voltage, unit='A')
print(f'The relativistically corrected wave length is {wave_length*1e2:.2f} pm for acceleration voltage {acceleration_voltage/1000:.1f} kV')
# Wavelength in Angstrom
def get_wavelength(acceleration_voltage):
"""
Calculates the relativistic corrected de Broglie wave length of an electron
Input:
------
acceleration voltage in volt
Output:
-------
wave length in Angstrom
"""
E = acceleration_voltage * scipy.constants.elementary_charge
h = scipy.constants.Planck
m0 = scipy.constants.electron_mass
c = scipy.constants.speed_of_light
wavelength = h / np.sqrt(2 * m0 * E * (1 + (E / (2 * m0 * c ** 2))))
return wavelength * 10**10
The relativistically corrected wave length is 1.97 pm for acceleration voltage 300.0 kV
help(pyTEMlib.diffraction_tools.get_wavelength)Help on function get_wavelength in module pyTEMlib.utilities:
get_wavelength(acceleration_voltage: float, unit: str = 'm') -> float
Calculates the relativistic corrected de Broglie wavelength of an electron in meter
and converts it to the desired unit (m, mm, μm, nm, A, Å, pm)
Parameter:
---------
acceleration_voltage: float
acceleration voltage in volt
unit: str
unit of the wavelength, default is meter ('m')
Returns:
-------
wavelength: float
wave length in units given, default meter
help(pyTEMlib.diffraction_tools)Help on package pyTEMlib.diffraction_tools in pyTEMlib:
NAME
pyTEMlib.diffraction_tools
DESCRIPTION
diffraction tools subpackage
A collection of tools to analyze diffraction data
part of pyTEMlib
author: Gerd Duscher, UTK
PACKAGE CONTENTS
basic
diffraction_plot
dynamic
kinematic
kinematic copy
FUNCTIONS
calculate_holz(dif)
Calculate HOLZ lines (of allowed reflections)
center_of_laue_circle(atoms, tags)
Center of Laue circle in microscope coordinates
check_sanity(atoms, verbose_level=0)
Check sanity of input parameters
example(verbose=True)
same as zuo_fig_3_18
feq(element, q)
Atomic form factor parametrized in 1/Angstrom
find_angles(zone)
Microscope stage coordinates of zone
find_nearest_zone_axis(tags)
Test all zone axis up to a maximum of hkl_max
gaussian(xx, pp)
Gaussian function
get_all_g_vectors(hkl_max, atoms)
Get all reflections up to a maximum Miller index
get_all_miller_indices(hkl_max)
Get all Miller indices up to hkl_max
get_allowed_reflections(structure, verbose=False)
Calculate allowed reflections of a crystal structure
get_bragg_reflections(atoms, in_tags, verbose=False)
sort reflection in allowed and forbidden
get_cylinder_coordinates(zone_hkl, g, k0_magnitude)
Get cylindrical coordinates of g vectors around zone axis
get_dynamically_activated(out_tags, verbose=False)
Get dynamically activated forbidden reflections
get_form_factor(element, q)
Atomic form factor parametrized in 1/Angstrom but converted to 1/Angstrom
The atomic form factor is from reKirkland: Advanced Computing in
Electron Microscopy 2nd edition, Appendix C.
From Appendix C of Kirkland, "Advanced Computing in Electron Microscopy", 3rd ed.
Calculation of electron form factor for specific q:
Using equation Kirkland C.15
Parameters
----------
element: string
element name
q: float or numpy.ndarray
magnitude(s) of scattering vector(s) in 1/Angstrom -- (=> exp(-i*g.r),
physics negative convention)
Returns
-------
fL+fG: float
atomic scattering vector
get_incident_wave_vector(atoms, acceleration_voltage, verbose=False)
Incident wave vector K0 in vacuum and material
get_metric_tensor(matrix)
The metric tensor of the lattice.
get_propagator(
size_in_pixel,
delta_z,
number_layers,
wavelength,
field_of_view,
bandwidth_factor,
verbose=True
)
Get propagator function
has to be convoluted with wave function after transmission
Parameter
---------
size_in_pixel: int
number of pixels of one axis in square image
delta_z: float
distance between layers
number_layers: int
number of layers to make a propagator
wavelength: float
wavelength of incident electrons
field_of_view: float
field of view of image
bandwidth_factor: float
relative bandwidth to avoid anti-aliasing
Returns
-------
propagator: complex numpy array (layers x size_in_pixel x size_in_pixel)
get_reflection_families(hkl_sorted, g_sorted, f_sorted, verbose=False)
Determine reflection families and multiplicity
get_structure_factors(atoms, g_hkl)
Get structure factors for given reciprocal lattice points g_hkl
get_transmission(potential, acceleration_voltage)
Get transmission function
has to be multiplied in real space with wave function
Parameter
---------
potential: numpy array (nxn)
potential of a layer
acceleration_voltage: float
acceleration voltage in V
Returns
-------
complex numpy array (nxn)
get_unit_cell(atoms, tags)
Unit cell and reciprocal unit cell
get_wavelength(acceleration_voltage: float, unit: str = 'm') -> float
Calculates the relativistic corrected de Broglie wavelength of an electron in meter
and converts it to the desired unit (m, mm, μm, nm, A, Å, pm)
Parameter:
---------
acceleration_voltage: float
acceleration voltage in volt
unit: str
unit of the wavelength, default is meter ('m')
Returns:
-------
wavelength: float
wave length in units given, default meter
get_zone_rotation(tags)
zone axis in global coordinate system
interaction_parameter(acceleration_voltage)
Calculates interaction parameter sigma
Parameter
---------
acceleration_voltage: float
acceleration voltage in volt
Returns
-------
interaction parameter: float
interaction parameter (dimensionless)
make_chi(theta, phi, aberrations)
###
# Aberration function chi
###
phi and theta are meshgrids of the angles in polar coordinates.
aberrations is a dictionary with the aberrations coefficients
Note: an empty aberration dictionary will give you a perfect aberration
make_pretty_labels(hkls, hex_label=False)
Make pretty labels
Parameters
----------
hkls: np.ndarray
a numpy array with all the Miller indices to be labeled
hex_label: boolean - optional
if True this will make for Miller indices.
Returns
-------
hkl_label: list
list of labels in Latex format
multi_slice(
wave,
number_of_unit_cell_z,
number_layers,
transmission,
propagator
)
Multi-Slice Calculation
The wave function will be changed iteratively
Parameters
----------
wave: complex numpy array (nxn)
starting wave function
number_of_unit_cell_z: int
this gives the thickness in multiples of c lattice parameter
number_layers: int
number of layers per unit cell
transmission: complex numpy array
transmission function
propagator: complex numpy array
propagator function
Returns
-------
complex numpy array
objective_lens_function(ab, nx, ny, field_of_view, aperture_size=10)
Objective len function to be convoluted with exit wave to derive image function
Parameter:
----------
ab: dict
aberrations in nm should at least contain defocus (C10), and spherical aberration (C30)
nx: int
number of pixel in x direction
ny: int
number of pixel in y direction
field_of_view: float
field of view of potential
wavelength: float
wavelength in nm
aperture_size: float
aperture size in 1/nm
Returns:
--------
object function: numpy array (nx x ny)
extent: list
output_verbose(atoms, tags)
Verbose output of experimental parameters
plot_cbed_parameter()
Plot CBED pattern parameters
plot_diffraction_pattern(
atoms,
diffraction_pattern=None,
unit='mrad',
verbose=False
)
Plot of spot diffraction pattern with matplotlib
Parameters
----------
atoms: dictionary or ase.Atoms object
information stored as dictionary either directly or in info attribute of ase.Atoms object
diffraction_pattern: None or sidpy.Dataset
diffraction pattern in background
unit: str default: 'mrad'
unit for plotting, either 'mrad' or '1/nm'
Returns
-------
fig: matplotlib figure
reference to matplotlib figure
plot_holz_parameter()
Plot HOLZ pattern parameters
plot_kikuchi(grey=False)
Plot Kikuchi pattern parameters
plot_reciprocal_unit_cell_2d(atoms)
Plot # unit cell in reciprocal space in 2D
plot_ring_pattern(atoms, diffraction_pattern=None)
Plot of ring diffraction pattern with matplotlib
Parameters
----------
atoms: dictionary or sidpy.Dataset
information stored as dictionary either directly or in metadata attribute of sidpy.Dataset
grey: bool
plotting in greyscale if True
Returns
-------
fig: matplotlib figure
reference to matplotlib figure
plot_saed_parameter(gray=False)
Plot SAED pattern parameters
plotting_coordinates(
g,
rotation=0.0,
laue_circle=[0, 0],
feature='spot',
k0=None
)
Calculate plotting coordinates for spots and lines
Parameters
----------
g : numpy array
array of g-vectors in (cylinderical coordinates in rad)
rotation : float
rotation angle in radians
laue_circle : list
position of laue circle in (x,y)
feature : str
'spot' or 'line' for HOLZ/Kikuchi lines
k0 : float or None
wave vector in 1/nm, if None use g in mrad
Returns
-------
numpy array
array of plotting coordinates in (x,y) for spots and (x,y,slope) for lines
potential_1dim(element, r)
Calculates the projected potential of an atom of element
The projected potential will be in units of V nm^2,
however, internally we will use Angstrom instead of nm!
The basis for these calculations are the atomic form factors of Kirkland 2𝑛𝑑 edition
following the equation in Appendix C page 252.
Parameter
---------
element: str
name of 'element
r: numpy array [nxn]
impact parameters (distances from atom position) in nm
Returns
-------
numpy array (nxn)
projected potential in units of V nm^2
potential_2dim(element, nx, ny, n_cell_x, n_cell_y, lattice_parameter, base)
Make a super-cell with potentials
Limitation is that we only place atom potential with single pixel resolution
read_poscar(filename)
Deprecated - use pyTEMlib.file_tools.read_poscar
ring_pattern_calculation(structure, verbose=False)
Calculate the ring diffraction pattern of a crystal structure
Parameters
----------
structure: ase.Atoms or sidpy.Dataset
crystal structure
verbose: verbose print-outs
set to False
Returns
-------
tags: dict
dictionary with diffraction information added
scattering_matrix(tags, verbose_level=1)
Scattering matrix
scattering_profiles(diff_pattern, center)
Determine scattering profiles from diffraction pattern
Parameters
----------
diff_pattern : Dataset
2D diffraction pattern
center : tuple
center of the diffraction pattern (x,y) in pixels
Returns
-------
out_tags : dict
dictionary with the following entries:
'center' : center of the diffraction pattern (x,y) in pixels
'polar_projection' : 2D array with the polar projection (r, theta)
'radial_average' : 1D array with the radial average
set_center(main_dataset, center, scale=None)
Set the u and v axes of a diffraction pattern dataset to center on origin
stage_rotation_matrix(alpha, beta, gamma=0.0)
Microscope stage coordinate system
warp(diff, center)
Define original polar grid
Parameter:
----------
diff: sidpy object or numpy ndarray of
diffraction pattern
center: list or numpy array of length 2
coordinates of center in pixel
Return:
------
numpy array of diffraction pattern in polar coordinates
zone_mistilt(zone, angles)
Rotation of zone axis by mistilt
Parameters
----------
zone: list or numpy array of int
zone axis in Miller indices
angles: ist or numpy array of float
list of mistilt angles in degree
Returns
-------
new_zone_axis: np.ndarray (3)
new tilted zone axis
DATA
__all__ = ['read_poscar', 'example', 'zone_mistilt', 'check_sanity', '...
FILE
c:\users\gduscher\appdata\local\anaconda3\lib\site-packages\pytemlib\diffraction_tools\__init__.py
Particle Flux and Current¶
It is important todetermine the order of magitude of how many electrons are hitting the sample.
The electron sources deliver in the order of A current, but most of these electrons are not used.
In a modern electron microscope, we talk about a range of 1pA to 1nA in the electron beam.
We start with the defition of an Ampere:
That definition is enough to calculate the number ofelectron per time unit (flux).
print(f" elementary charge: {scipy.constants.physical_constants['elementary charge'][0]:.5g} {scipy.constants.physical_constants['elementary charge'][1]}")
print(f'\n 1pA is {1e-12/scipy.constants.elementary_charge:.3} electrons/s or {1e-12/scipy.constants.elementary_charge/1000:.0f} electrons/ms')
print(f' 10pA is {10e-12/scipy.constants.elementary_charge:.3} electrons/s')
print(f'100pA is {100e-12/scipy.constants.elementary_charge*1 :.3} electrons/s')
print(f'\n at 10pA an electron will hit the sample every {scipy.constants.elementary_charge/10e-12 * 1e9:.2f} ns ') elementary charge: 1.6022e-19 C
1pA is 6.24e+06 electrons/s or 6242 electrons/ms
10pA is 6.24e+07 electrons/s
100pA is 6.24e+08 electrons/s
at 10pA an electron will hit the sample every 16.02 ns
We see that we have much lower fluence in the TEM than in a laser (how could they do femtosecond pulses otherwise).
Question¶
How long does one have to integrate a detector to register 1 electron with an electron beam of 1.6pA?
Navigation¶
Back Chapter 1: Introduction
Next: Atomic Form Factor
Chapter 2: Diffraction
List of Content: Front