Chapter 2: Diffraction
Lattice Determination with HOLZ¶
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
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);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.
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);
extentOf 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])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);
extentmistilt
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])# -----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);
extentmistilt
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])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.
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.
Navigation¶
Back: HOLZ Lines
Chapter 2: Diffraction
List of Content: Front