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.

Lattice Determination with HOLZ

Chapter 2: Diffraction


Lattice Determination with HOLZ

Download

Open In Colab

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

Load relevant python packages

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 pyTEMlib -q --upgrade
print('done')
done

Import numerical and plotting python packages

Import the python packages that we will use:

Beside the basic numerical (numpy) and plotting (pylab of matplotlib) libraries,

and some libraries from the book

  • kinematic scattering library.

  • file_tools library

%matplotlib  widget
import matplotlib.pyplot as plt
import numpy as np
import sys
import os
if 'google.colab' in sys.modules:
    from google.colab import output
    output.enable_custom_widget_manager()

# Import libraries from pyTEMlib
import pyTEMlib

__notebook_version__ = '2026.01.2'

print('pyTEM version: ', pyTEMlib.__version__)
print('notebook version: ', __notebook_version__)
pyTEM version:  0.2026.1.2
notebook version:  2026.01.2

Define crystal

### Please choose another crystal like: Silicon, Aluminium, GaAs , ZnO
atoms = pyTEMlib.crystal_tools.structure_by_name('silicon')

Plot the unit cell


## Just to be sure the crystal structure is right
from ase.visualize.plot import plot_atoms

plot_atoms(atoms, radii=0.3, rotation=('0x,4y,0z'))
<Axes: >

Load Diffraction Pattern

if'google.colab' in sys.modules:
    if not os.path.exists('.//Zuo-HOLZ-experiment.jpg'):
        !wget  https://github.com/gduscher/MSE672-Introduction-to-TEM/raw/main/Diffraction/images/Zuo-HOLZ-experiment.jpg
    image = plt.imread("./Zuo-HOLZ-experiment.jpg")
else:
    image = plt.imread("images/Zuo-HOLZ-experiment.jpg")
plt.figure()
plt.imshow(image);
Loading...

Parameters for Diffraction Calculation

Please note that we are using a rather small number of reflections: the maximum number of hkl is 1

tags = {'acceleration_voltage': 99.2 * 1000.0,  # V
      'convergence_angle': 9.15,  # mrad;
      'zone_hkl': np.array([1, 2, -2]),
      'Sg_max': .03,  # 1/A  maximum allowed excitation error
      'hkl_max': 12}  # Highest evaluated Miller indices

diff_dict = {}
diff_dict = pyTEMlib.diffraction_tools.get_bragg_reflections(atoms, tags, verbose=True) 

laue_zones = diff_dict['allowed']['Laue_Zone']
FOLZ = diff_dict['HOLZ']['FOLZ']
SOLZ = diff_dict['HOLZ']['SOLZ']
xy = pyTEMlib.diffraction_tools.plotting_coordinates(diff_dict['allowed']['g'], feature='spot')
kikuchi = pyTEMlib.diffraction_tools.plotting_coordinates(diff_dict['Kikuchi']['g'], rotation=np.pi/2, feature='line')

holz = pyTEMlib.diffraction_tools.plotting_coordinates(diff_dict['HOLZ']['g_deficient'], rotation=np.pi/2, feature='line')
plt.figure()
plt.scatter(xy[:, 0], xy[:,1], cmap='tab10', c=laue_zones)
line = kikuchi[0]
plt.axline( (line[0], line[1]), slope=line[2], linewidth=2, label='Kikuchi')
for line in kikuchi:
    plt.axline( (line[0], line[1]), slope=line[2], linewidth=2)
if FOLZ.sum()>0:
    line = (holz[FOLZ])[0]
    plt.axline( (line[0], line[1]), slope=line[2], color='b', alpha=0.2, label='FOLZ')
    for line in holz[FOLZ]:
        plt.axline( (line[0], line[1]), slope=line[2], color='r', alpha = 0.2)
if SOLZ.sum()>0:
    line = (holz[SOLZ])[0]
    plt.axline( (line[0], line[1]), slope=line[2], color='b', alpha=0.2, label='SOLZ')
    for line in holz[SOLZ]:
        plt.axline( (line[0], line[1]), slope=line[2], color='b', alpha = 0.2)
plt.axis('equal')
plt.scatter(0,0)
plt.legend();
Of the 275 possible reflection 45 are allowed.
Of those, there are 4 in ZOLZ  and 41 in HOLZ
Of the 46 forbidden reflection in ZOLZ  0 can be dynamically activated.
Loading...

Initial Overlay

image = plt.imread('images/Zuo-HOLZ-experiment.jpg')
atoms = pyTEMlib.crystal_tools.structure_by_name('Silicon')
tags = {'acceleration_voltage': 99.2 * 1000.0,  # V
      'convergence_angle': 7.15,  # mrad;
      'zone_hkl': np.array([1, 2, -2]),
      'Sg_max': .03,  # 1/A  maximum allowed excitation error
      'hkl_max': 10}  # Highest evaluated Miller indices

diff_dict = {}
diff_dict = pyTEMlib.diffraction_tools.get_bragg_reflections(atoms, tags, verbose=True) 

extent = np.array([-10.524,   9.945,  -7.932,  11.105])


spots = pyTEMlib.diffraction_tools.plotting_coordinates(diff_dict['allowed']['g'], rotation=-np.pi/2)
kikuchi = pyTEMlib.diffraction_tools.plotting_coordinates(diff_dict['Kikuchi']['g'], feature='line', rotation=-np.pi/2)
holz = pyTEMlib.diffraction_tools.plotting_coordinates(diff_dict['HOLZ']['g_deficient'], feature='line', rotation=-np.pi/2)

plt.figure()
plt.imshow(image, extent=extent)


plt.scatter(spots[:,0], spots[:, 1], cmap='tab10', c=diff_dict['allowed']['Laue_Zone'])
alpha =diff_dict['Kikuchi']['intensities']/ diff_dict['Kikuchi']['intensities'].max()*.5
for i, line in enumerate(kikuchi):
    plt.axline( (line[0], line[1]), slope=line[2], color='red',
                       alpha=1, linewidth=2)
alpha =diff_dict['HOLZ']['intensities']/ diff_dict['HOLZ']['intensities'].max()*.7
for i, line in enumerate(holz):
    plt.axline( (line[0], line[1]), slope=line[2], color='orange',
                       alpha=alpha[i], linewidth=2)
plt.gca().set_aspect('equal')
plt.xlabel('angle (mrad)');
plt.gca().set_xlim(-10, 10)
plt.gca().set_ylim(-7, 11)
#v = pyTEMlib.diffraction_tools.plot_diffraction_pattern(diff_dict, unit='1/nm')
c = plt.Circle((-0.5,1.1),7.2, fill=False, edgecolor='red')
plt.gca().add_patch(c);
extent
Of the 192 possible reflection 36 are allowed.
Of those, there are 4 in ZOLZ  and 32 in HOLZ
Of the 46 forbidden reflection in ZOLZ  0 can be dynamically activated.
array([-10.524, 9.945, -7.932, 11.105])
Loading...

Plotting with mistilt included and objective stigmation compensated

# -------Input ------------
mistilt_alpha =  -.7 # in mrad
mistilt_beta = 1  # in mrad
# -------------------------

image = plt.imread('images/Zuo-HOLZ-experiment.jpg')
atoms = pyTEMlib.crystal_tools.structure_by_name('Silicon')
tags = {'acceleration_voltage': 98.5 * 1000.0,  # V
      'convergence_angle': 7.15,  # mrad;
      'mistilt_alpha': mistilt_alpha/1000,  # in rad
      'mistilt_beta': mistilt_beta/1000,  # in rad
      'zone_hkl': np.array([1, 2, -2]),
      'Sg_max': .03,  # 1/A  maximum allowed excitation error
      'hkl_max': 10}  # Highest evaluated Miller indices


diff_dict = {}
diff_dict = pyTEMlib.diffraction_tools.get_bragg_reflections(atoms, tags, verbose=True) 

extent = np.array([-10.524,   9.945,  -7.932,  11.105]) - np.array([mistilt_alpha, mistilt_alpha, mistilt_beta, mistilt_beta])

extent[:2] += 0.4
extent[2:] +=0.2
spots = pyTEMlib.diffraction_tools.plotting_coordinates(diff_dict['allowed']['g'], rotation=-np.pi/2)
kikuchi = pyTEMlib.diffraction_tools.plotting_coordinates(diff_dict['Kikuchi']['g'], feature='line', rotation=-np.pi/2)
holz = pyTEMlib.diffraction_tools.plotting_coordinates(diff_dict['HOLZ']['g_deficient'], feature='line', rotation=-np.pi/2)

plt.figure()
plt.imshow(image, extent=extent)


plt.scatter(spots[:,0], spots[:, 1], cmap='tab10', c=diff_dict['allowed']['Laue_Zone'])
alpha =diff_dict['Kikuchi']['intensities']/ diff_dict['Kikuchi']['intensities'].max()*.5
for i, line in enumerate(kikuchi):
    plt.axline( (line[0], line[1]), slope=line[2], color='red',
                       alpha=1, linewidth=2)
alpha =diff_dict['HOLZ']['intensities']/ diff_dict['HOLZ']['intensities'].max()
for i, line in enumerate(holz):
    plt.axline( (line[0], line[1]), slope=line[2], color='orange',
                       alpha=alpha[i], linewidth=2)
plt.gca().set_aspect('equal')
plt.xlabel('angle (mrad)');
plt.gca().set_xlim(-10, 10)
plt.gca().set_ylim(-7, 11)
#v = pyTEMlib.diffraction_tools.plot_diffraction_pattern(diff_dict, unit='1/nm')
c = plt.Circle((-0, 0),7.2, fill=False, edgecolor='red')
plt.gca().add_patch(c);
extent
mistilt
Of the 191 possible reflection 35 are allowed.
Of those, there are 4 in ZOLZ  and 31 in HOLZ
Of the 46 forbidden reflection in ZOLZ  0 can be dynamically activated.
array([-9.424, 11.045, -8.732, 10.305])
Loading...
# -----Input----
Sg_max = .02 # 1/nm  maximum allowed excitation error ; This parameter is related to the thickness
maximum_hkl = 10   # Highest evaluated Miller indices
# --------------
mistilt_alpha =  -.7 # in mrad
mistilt_beta = 1  # in mrad
image = plt.imread('images/Zuo-HOLZ-experiment.jpg')
atoms = pyTEMlib.crystal_tools.structure_by_name('Silicon')
tags = {'acceleration_voltage': 98.5 * 1000.0,  # V
      'convergence_angle': 7.15,  # mrad;
      'mistilt_alpha': mistilt_alpha/1000,  # in rad
      'mistilt_beta': mistilt_beta/1000,  # in rad
      'zone_hkl': np.array([1, 2, -2]),
      'Sg_max': Sg_max,  # 1/A  maximum allowed excitation error
      'hkl_max': maximum_hkl}  # Highest evaluated Miller indices



diff_dict = {}
diff_dict = pyTEMlib.diffraction_tools.get_bragg_reflections(atoms, tags, verbose=True) 

extent = np.array([-10.524,   9.945,  -7.932,  11.105]) - np.array([mistilt_alpha, mistilt_alpha, mistilt_beta, mistilt_beta])

extent[:2] += 0.4
extent[2:] +=0.2
spots = pyTEMlib.diffraction_tools.plotting_coordinates(diff_dict['allowed']['g'], rotation=-np.pi/2)
kikuchi = pyTEMlib.diffraction_tools.plotting_coordinates(diff_dict['Kikuchi']['g'], feature='line', rotation=-np.pi/2)
holz = pyTEMlib.diffraction_tools.plotting_coordinates(diff_dict['HOLZ']['g_deficient'], feature='line', rotation=-np.pi/2)

plt.figure()
plt.imshow(image, extent=extent)


plt.scatter(spots[:,0], spots[:, 1], cmap='tab10', c=diff_dict['allowed']['Laue_Zone'])
alpha =diff_dict['Kikuchi']['intensities']/ diff_dict['Kikuchi']['intensities'].max()*.5
for i, line in enumerate(kikuchi):
    plt.axline( (line[0], line[1]), slope=line[2], color='red',
                       alpha=1, linewidth=2)
alpha =diff_dict['HOLZ']['intensities']/ diff_dict['HOLZ']['intensities'].max()*.5
for i, line in enumerate(holz):
    plt.axline( (line[0], line[1]), slope=line[2], color='orange',
                       alpha=alpha[i], linewidth=2)
plt.gca().set_aspect('equal')
plt.xlabel('angle (mrad)');
plt.gca().set_xlim(-10, 10)
plt.gca().set_ylim(-7, 11)
#v = pyTEMlib.diffraction_tools.plot_diffraction_pattern(diff_dict, unit='1/nm')
c = plt.Circle((0.5, 0.5),7.2, fill=False, edgecolor='red')
plt.gca().add_patch(c);
extent
mistilt
Of the 130 possible reflection 25 are allowed.
Of those, there are 3 in ZOLZ  and 22 in HOLZ
Of the 30 forbidden reflection in ZOLZ  0 can be dynamically activated.
array([-9.424, 11.045, -8.732, 10.305])
Loading...

Plotting more HOLZ lines with intensity

In the image above, all major lines are reproduced.

On the bottom of the HOLZ plot, however, the faint lines are not there.

Increase the maximum hkl in the simulation to see how well we did

Influence of unit cell deformation

Introduce a bit of distortion in the unit cell and see what happens

# -----Input----
atoms = pyTEMlib.crystal_tools.structure_by_name('silicon')

atoms.cell[0,0] += 0.004  # in A
atoms.cell[1,1] += 0.00
atoms.cell[2,1] += 0.00
# --------------
print(atoms)

Sg_max = .02 # 1/nm  maximum allowed excitation error ; This parameter is related to the thickness
maximum_hkl = 10   # Highest evaluated Miller indices
# --------------
mistilt_alpha =  -.7 # in mrad
mistilt_beta = 1  # in mrad
image = plt.imread('images/Zuo-HOLZ-experiment.jpg')

tags = {'acceleration_voltage': 98.5 * 1000.0,  # V
      'convergence_angle': 7.15,  # mrad;
      'mistilt_alpha': mistilt_alpha/1000,  # in rad
      'mistilt_beta': mistilt_beta/1000,  # in rad
      'zone_hkl': np.array([1, 2, -2]),
      'Sg_max': Sg_max,  # 1/A  maximum allowed excitation error
      'hkl_max': maximum_hkl}  # Highest evaluated Miller indices


print(atoms)
diff_dict = {}
diff_dict = pyTEMlib.diffraction_tools.get_bragg_reflections(atoms, tags, verbose=True) 

extent = np.array([-10.524,   9.945,  -7.932,  11.105]) - np.array([mistilt_alpha, mistilt_alpha, mistilt_beta, mistilt_beta])

extent[:2] += 0.4
extent[2:] +=0.2
spots = pyTEMlib.diffraction_tools.plotting_coordinates(diff_dict['allowed']['g'], rotation=-np.pi/2)
kikuchi = pyTEMlib.diffraction_tools.plotting_coordinates(diff_dict['Kikuchi']['g'], feature='line', rotation=-np.pi/2)
holz = pyTEMlib.diffraction_tools.plotting_coordinates(diff_dict['HOLZ']['g_deficient'], feature='line', rotation=-np.pi/2)

plt.figure()
plt.imshow(image, extent=extent)


plt.scatter(spots[:,0], spots[:, 1], cmap='tab10', c=diff_dict['allowed']['Laue_Zone'])
alpha =diff_dict['Kikuchi']['intensities']/ diff_dict['Kikuchi']['intensities'].max()*.5
for i, line in enumerate(kikuchi):
    plt.axline( (line[0], line[1]), slope=line[2], color='red',
                       alpha=1, linewidth=2)
alpha =diff_dict['HOLZ']['intensities']/ diff_dict['HOLZ']['intensities'].max()*.5
for i, line in enumerate(holz):
    plt.axline( (line[0], line[1]), slope=line[2], color='orange',
                       alpha=alpha[i], linewidth=2)
plt.gca().set_aspect('equal')
plt.xlabel('angle (mrad)');
plt.gca().set_xlim(-10, 10)
plt.gca().set_ylim(-7, 11)
#v = pyTEMlib.diffraction_tools.plot_diffraction_pattern(diff_dict, unit='1/nm')
c = plt.Circle((0.5, 0.5),7.2, fill=False, edgecolor='red')
plt.gca().add_patch(c);
Lattice(symbols='Si8', pbc=True, cell=[5.43488, 5.43088, 5.43088])
Lattice(symbols='Si8', pbc=True, cell=[5.43488, 5.43088, 5.43088])
mistilt
Of the 129 possible reflection 62 are allowed.
Of those, there are 13 in ZOLZ  and 49 in HOLZ
Of the 20 forbidden reflection in ZOLZ  4 can be dynamically activated.
Loading...

Conclusion

Due to the high angles involved in the scattering but measured at low angles, HOLZ lines provide a very sensitive measurement of experimental and materials parameter.