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
Please restart the session (in Runtime menu) in Google Colab.
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.
# --------- 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 = 100.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 3.70 pm for acceleration voltage 100.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 100keV:
The classic velocity of the electron is 187553726.08 m/s or 62.56% of the speed of light
The relativistic velocity of the electron is 164352479.65 m/s or 54.82% 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= 30 * 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
FUNCTIONS
calculate_holz(dif)
Calculate HOLZ lines (of allowed reflections)
calculate_kikuchi(atoms, tags, verbose)
Calculate Kikuchi lines (of allowed reflections)
calculate_laue_zones(atoms, tags, verbose)
Calculate Laue Zones (of allowed reflections)
###########################
Below is the expression given in most books.
However, that would only work for orthogonal crystal systems
Laue_Zone = abs(np.dot(hkl_allowed,tags['zone_hkl']))
works only for orthogonal systems
Below expression works for all crystal systems
Remember we have already tilted, and so the dot product is trivial
and gives only the z-component.
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
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
magnitude of scattering vector in 1/Angstrom -- (=> exp(-i*g.r),
physics negative convention)
Returns
-------
fL+fG: float
atomic scattering vector
gaussian(xx, pp)
Gaussian function
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_dynamically_activated(out_tags, verbose=False)
Get dynamically activated forbidden reflections
get_incident_wave_vector(atoms, tags, verbose)
Incident wave vector K0 in vacuum and material
get_metric_tensor(matrix)
The metric tensor of the lattice.
get_reflection_families(hkl_sorted, g_sorted, f_sorted, verbose=False)
Determine reflection families and multiplicity
get_structure_factors(atoms, g_hkl)
Calculate structure factors for given reciprocal lattice points g_hkl
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
kinematic_scattering(atoms, verbose=False)
All kinematic scattering calculation
Calculates Bragg spots, Kikuchi lines, excess, and deficient HOLZ lines
Parameters
----------
atoms: ase.Atoms
object with crystal structure:
and with experimental parameters in info attribute:
'acceleration_voltage_V', 'zone_hkl', 'Sg_max', 'hkl_max'
Optional parameters are:
'mistilt', convergence_angle_mrad', and 'crystal_name'
verbose = True will give extended output of the calculation
verbose: boolean
default is False
Returns
-------
atoms:
There are three sub_dictionaries in info attribute:
['allowed'], ['forbidden'], and ['HOLZ']
['allowed'] and ['forbidden'] dictionaries contain:
['sg'], ['hkl'], ['g'], ['structure_factor'], ['intensities'],
['ZOLZ'], ['FOLZ'], ['SOLZ'], ['HOLZ'], ['HHOLZ'], ['label'], and ['Laue_zone']
the ['HOLZ'] dictionary contains:
['slope'], ['distance'], ['theta'], ['g_deficient'], ['g_excess'],
['hkl'], ['intensities'],
['ZOLZ'], ['FOLZ'], ['SOLZ'], ['HOLZ'], and ['HHOLZ']
Please note that the Kikuchi lines are the HOLZ lines of ZOLZ
There are also a few parameters stored in the main dictionary:
['wave_length_nm'], ['reciprocal_unit_cell'], ['inner_potential_V'],
['incident_wave_vector'],['volume'], ['theta'], ['phi'],
and ['incident_wave_vector_vacuum']
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
output_verbose(atoms, tags)
Verbose output of experimental parameters
plot_cbed_parameter()
Plot CBED pattern parameters
plot_diffraction_pattern(atoms, diffraction_pattern=None)
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
grey: bool
plotting in greyscale if True
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
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)
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