Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

The Electron

Chapter 2: Diffraction


The Electron

Download

OpenInColab

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 pyTEMlib

Interaction 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 12\frac{1}{2} (lepton).

Non--relativistic De Broglie wavelength of electron:

λ=hp=h2m0Ekin1.22Ekin\lambda = \frac{h}{p} = \frac{h}{\sqrt{2m_0E_{kin}}} \approx \frac{1.22}{\sqrt{E_{kin}}}

E is the kinetic energy of the electron: Ekin=eUE_{kin} = eU [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 cc

The formula for relativistic corrected wavelength is: λ=h2meEkin(1+Ekin2mec2)\lambda = \frac{h}{\sqrt{2m_e E_{kin} *(1+\frac{E_{kin}}{2 m_e c^2})}}

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 : λ\lambda = 4 pm << than diameter of an atom

The relativistic parameters are:

E (keV)λ\lambda (pm)M/m0_0v/c
1012.21.07960.1950
306.981.1290.3284
1003.701.19570.5482
2002.511.39140.6953
4001.641.78280.8275
10000.872.95690.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.

Relativistic velocity

v2c2=Ekin(Ekin+2mec2)(Ekin+mec2)2\frac{v^2}{c^2} = \frac{E_{kin}(E_{kin}+2m_e c^2)}{(E_{kin}+m_e c^2)^2}
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:

λ=h2meEkin(1+Ekin2mec2)\lambda = \frac{h}{\sqrt{2m_e E_{kin} *(1+\frac{E_{kin}}{2 m_e c^2})}}

# ----- 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 μ\muA 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:

A=CsA = \frac{C}{s}

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?