Skip to article frontmatterSkip to article content

Analyzing Ring Diffraction Pattern

Chapter 2: Diffraction


Analyzing Ring Diffraction Pattern

Download

OpenInColab

part of

MSE672: Introduction to Transmission Electron Microscopy

Spring 2025
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.2025.11.4':
    print('installing pyTEMlib')
    !{sys.executable} -m pip install git+https://github.com/pycroscopy/pyTEMlib.git@main -q --upgrade

print('done')
done

Load the plotting and figure packages

Import the python packages that we will use:

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

  • three dimensional plotting and some libraries from the book

  • kinematic scattering library.

%matplotlib  widget
import os
import sys

import matplotlib.pyplot as plt
import numpy as np

if 'google.colab' in sys.modules:
    from google.colab import output
    from google.colab import drive
    output.enable_custom_widget_manager()
    
# 3D plotting package 
from mpl_toolkits.mplot3d import Axes3D # 3D plotting

# additional package 
import itertools 
import skimage
import scipy

# Import microscopt library
sys.path.insert(0, os.path.abspath("../../pyTEMlib/"))  # point to the pyTEMlib location
import pyTEMlib
    
# it is a good idea to show the version numbers at this point for archiving reasons.
__notebook_version__ = '2025.11.23'
print('pyTEM version: ', pyTEMlib.__version__)
print('notebook version: ', __notebook_version__)
pyTEM version:  0.2025.12.0
notebook version:  2025.11.23

Load Ring-Diffraction Pattern

First we select the diffraction pattern

Load the GOLD-NP-DIFF.dm3 file as an example.

The dynamic range of diffraction patterns is too high for computer screens and so we take the logarithm of the intensity.

# ------Input -------------
load_your_own_data = False
# -------------------------
if 'google.colab' in sys.modules:
        drive.mount("/content/drive")

if load_your_own_data:
    fileWidget = ft.FileWidget()
if load_your_own_data:
    datasets = fileWidget.datasets
    main_dataset = fileWidget.selected_dataset
else:  # load example
    datasets = pyTEMlib.file_tools.open_file(os.path.join("../example_data", "GOLD-NP-DIFF.dm3"))
    main_dataset =  datasets['Channel_000']

view = main_dataset.plot(vmax=20000)
Loading...

Finding the center

First try with cross correlation of rotated images

Cross- and Auto- Correlation

Cross correlation and auto correlation are based on a multiplication in Fourier space. In the case of a an auto-correlation it is the same data while in the cross correlation it is another data (here the transposed (rotated) diffraction pattern)"

## Access the data of the loaded image
    
diff_pattern = np.array(main_dataset)
diff_pattern = diff_pattern-diff_pattern.min()
correlation = 'auto'
dif_ft = np.fft.fft2(diff_pattern)
if correlation == 'auto':
    auto_correlation  = np.fft.fftshift(np.fft.ifft2(dif_ft*dif_ft))
    center = np.unravel_index(np.argmax(auto_correlation.real, axis=None), auto_correlation.real.shape)
    plt.figure()
    plt.title('Auto-Correlation')
    plt.imshow(auto_correlation.real);
else:   
    dif_ft2 = np.fft.fft2(diff_pattern.T)
    cross_correlation  = np.fft.fftshift(np.fft.ifft2(dif_ft*dif_ft2))
    center = np.unravel_index(np.argmax(cross_correlation.real, axis=None), cross_correlation.real.shape)
    plt.figure()
    plt.title('Cross-Correlation')
    plt.imshow(auto_correlation.real);

shift = np.array(center - np.array(dif_ft.shape)/2)
print(f'center = {center} which is a shift of {shift[0]} px in x and {shift[1]} px in y direction')
plt.scatter([center[1]],[center[0]]);
Loading...

How well did we do?

Select the center yourself

The beam stop confuses the cross correlation sometimes and then we need to adjust the selection

from matplotlib.widgets import  EllipseSelector
    
print(np.array(center)-2048)
center = np.array(center)

plt.figure(figsize=(8, 6))
plt.imshow(np.log(3.+diff_pattern).T, origin = 'upper')
selector = EllipseSelector(plt.gca(), None,interactive=True )  # gca get current axis (plot)

# selector.to_draw.set_visible(True)
radius = 559 
center = np.array(center)

selector.extents = (center[0]-radius,center[0]+radius,center[1]-radius,center[1]+radius)
Loading...

Get center coordinates from selection

xmin, xmax, ymin, ymax = selector.extents
x_center, y_center = selector.center
x_shift = x_center - diff_pattern.shape[0]/2
y_shift = y_center - diff_pattern.shape[1]/2
print(f'radius = {(xmax-xmin)/2:.0f} pixels')

center = (x_center, y_center )
print(f'new center = {center} [pixels]')


out_tags ={}
out_tags['center'] = center
radius = 559 pixels
new center = (np.float64(1054.5367965367968), np.float64(1102.2077922077922)) [pixels]

Ploting Diffraction Pattern in Polar Coordinates",

The Transformation Routine

We use the polar transformation routine of scipy-image (skimage).

If the center is correct a ring in carthesian coordinates is a line in polar coordinates

center = (x_center, y_center)
polar_projection = skimage.transform.warp_polar(diff_pattern, center=center).T
polar_projection[polar_projection<0.] =0.
polar_projection += 1e-12
log_polar =np.log2(polar_projection)
log_polar -= log_polar.min()
plt.figure()
plt.imshow(log_polar, cmap='gray')
plt.gca().set_aspect("auto")
plt.colorbar()
Loading...

Diffraction Profile

A simple sum over all angles gives us then the diffraction profile (intensity profile of diffraction pattern)

center = np.array(center)
out_tags={'center': center}
#center[1] = 1057
#center[0]= 1103
#center[1]=1055

# polar_projection = warp(diff_pattern,center)
below_zero = polar_projection<0.
polar_projection[below_zero]=0.

out_tags['polar_projection'] = polar_projection

# Sum over all angles (axis 1)
profile = polar_projection.sum(axis=1)
profile_0 = polar_projection[:,0:20].sum(axis=1)
profile_360 = polar_projection[:,340:360].sum(axis=1)
profile_180 = polar_projection[:,190:210].sum(axis=1)

profile_90 = polar_projection[:,80:100].sum(axis=1)
profile_270 = polar_projection[:,260:280].sum(axis=1)


out_tags['radial_average'] = profile

scale = main_dataset.u.slope


plt.figure()
plt.imshow(np.log2(polar_projection)+32,extent=(0,360,polar_projection.shape[0]*scale,scale),cmap="gray")
ax = plt.gca()
ax.set_aspect("auto");
plt.xlabel('angle [degree]');
plt.ylabel('distance [1/nm]')

plt.plot(profile/profile.max()*200,np.linspace(1,len(profile),len(profile))*scale,c='r');
#plt.plot(profile_0/profile_0.max()*200,np.linspace(1,len(profile),len(profile))*scale,c='orange');
#plt.plot(profile_360/profile_360.max()*200,np.linspace(1,len(profile),len(profile))*scale,c='orange');
#plt.plot(profile_180/profile_180.max()*200,np.linspace(1,len(profile),len(profile))*scale,c='b');
plt.plot(profile_90/profile_90.max()*200,np.linspace(1,len(profile),len(profile))*scale,c='orange');
plt.plot(profile_270/profile_270.max()*200,np.linspace(1,len(profile),len(profile))*scale,c='b');


plt.plot([0,360],[3.8,3.8])
plt.plot([0,360],[6.3,6.3])
plt.colorbar()
Loading...

Determine Bragg Peaks

Peak finding is actually not as simple as it looks

import scipy as sp
import scipy.signal as signal

scale = main_dataset.u.slope*4.28/3.75901247*1.005
# find_Bragg peaks in profile
peaks, g= signal.find_peaks(profile,rel_height =0.7, width=7)  # np.std(second_deriv)*9)

print(peaks*scale)

out_tags['ring_radii_px'] = peaks


plt.figure()

plt.imshow(log_polar,extent=(0,360,polar_projection.shape[0]*scale,scale),cmap='gray')

ax = plt.gca()
ax.set_aspect("auto");
plt.xlabel('angle [degree]');
plt.ylabel('distance [1/nm]')

plt.plot(profile/profile.max()*200,np.linspace(1,len(profile),len(profile))*scale,c='r');

for i in peaks:
    if i*scale > 3.5:
        plt.plot((0,360),(i*scale,i*scale), linestyle='--', c = 'steelblue')
Loading...

Calculate Ring Pattern

see Structure Factors notebook for details.

# Initialize the dictionary with all the input
atoms = pyTEMlib.kinematic_scattering.structure_by_name('gold')

main_dataset.structures['Structure_000'] = atoms


#Reciprocal Lattice 
# We use the linear algebra package of numpy to invert the unit_cell \"matrix\"
reciprocal_unit_cell = atoms.cell.reciprocal() # transposed of inverted unit_cell

#INPUT
hkl_max = 7#  maximum allowed Miller index

acceleration_voltage = 200.0 *1000.0 #V
wave_length  = pyTEMlib.kinematic_scattering.get_wavelength(acceleration_voltage)


h  = np.linspace(-hkl_max,hkl_max,2*hkl_max+1)   # all to be evaluated single Miller Index
hkl  = np.array(list(itertools.product(h,h,h) )) # all to be evaluated Miller indices
g_hkl = np.dot(hkl,reciprocal_unit_cell)  

# Calculate Structure Factors

structure_factors = []

base = atoms.positions # in Carthesian coordinates
for j  in range(len(g_hkl)):
    F = 0
    for b in range(len(base)):
        f = pyTEMlib.kinematic_scattering.feq(atoms[b].symbol,np.linalg.norm(g_hkl[j])) # Atomic form factor for element and momentum change (g vector)
        F += f * np.exp(-2*np.pi*1j*(g_hkl[j]*base[b]).sum())        
    structure_factors.append(F)
F = structure_factors = np.array(structure_factors)

# Allowed reflections have a non zero structure factor F (with a  bit of numerical error)
allowed = np.absolute(structure_factors) > 0.001

distances = np.linalg.norm(g_hkl, axis = 1)

print(f' Of the evaluated {hkl.shape[0]} Miller indices {allowed.sum()} are allowed. ')
# We select now all the 
zero = distances == 0.
allowed = np.logical_and(allowed,np.logical_not(zero))

F = F[allowed]
g_hkl = g_hkl[allowed]
hkl = hkl[allowed]
distances = distances[allowed]

sorted_allowed = np.argsort(distances)

distances = distances[sorted_allowed]
hkl = hkl[sorted_allowed]
F = F[sorted_allowed]

# How many have unique distances and what is their muliplicity

unique, indices  = np.unique(distances, return_index=True)

print(f' Of the {allowed.sum()} allowed Bragg reflections there are {len(unique)} families of reflections.')

intensity = np.absolute(F[indices]**2*(np.roll(indices,-1)-indices))
print('\n index \t  hkl \t      1/d [1/Ang]       d [pm]     F      multip.  intensity' )
family = []

reflection = 0
for j in range(len(unique)-1):
    i = indices[j]    
    i2 = indices[j+1]   
    family.append(hkl[i+np.argmax(hkl[i:i2].sum(axis=1))])
    index = '{'+f'{family[j][0]:.0f} {family[j][1]:.0f} {family[j][2]:.0f}'+'}'
    print(f'{i:3g}\t {index} \t  {distances[i]:.2f}  \t {1/distances[i]*100:.0f} \t {np.absolute(F[i]):4.2f} \t  {indices[j+1]-indices[j]:3g} \t {intensity[j]:.2f}') 
    #out_tags['reflections'+str(reflection)]={}
    out_tags['reflections-'+str(reflection)+'-index'] = index
    out_tags['reflections-'+str(reflection)+'-recip_distances'] = distances[i]
    out_tags['reflections-'+str(reflection)+'-structure_factor'] = np.absolute(F[i])
    out_tags['reflections-'+str(reflection)+'-multiplicity'] = indices[j+1]-indices[j]
    out_tags['reflections-'+str(reflection)+'-intensity'] = intensity[j]
    reflection +=1
 Of the evaluated 3375 Miller indices 855 are allowed. 
 Of the 854 allowed Bragg reflections there are 39 families of reflections.

 index 	  hkl 	      1/d [1/Ang]       d [pm]     F      multip.  intensity
  0	 {1 1 1} 	  0.42  	 235 	 27.00 	    8 	 5832.86
  8	 {0 0 2} 	  0.49  	 204 	 24.48 	    6 	 3596.79
 14	 {2 0 2} 	  0.69  	 144 	 18.27 	   12 	 4004.58
 26	 {1 1 3} 	  0.81  	 123 	 15.54 	    8 	 1930.91
 34	 {1 3 1} 	  0.81  	 123 	 15.54 	   16 	 3861.82
 50	 {2 2 2} 	  0.85  	 118 	 14.82 	    8 	 1756.96
 58	 {0 0 4} 	  0.98  	 102 	 12.57 	    6 	 948.09
 64	 {3 3 1} 	  1.07  	 94 	 11.33 	    8 	 1026.40
 72	 {3 1 3} 	  1.07  	 94 	 11.33 	   16 	 2052.80
 88	 {0 4 2} 	  1.10  	 91 	 10.97 	   24 	 2889.10
112	 {2 2 4} 	  1.20  	 83 	 9.77 	   24 	 2291.50
136	 {1 5 1} 	  1.27  	 78 	 9.05 	   16 	 1310.07
152	 {3 3 3} 	  1.27  	 78 	 9.05 	   16 	 1310.07
168	 {4 0 4} 	  1.39  	 72 	 8.08 	   12 	 783.51
180	 {1 3 5} 	  1.45  	 69 	 7.60 	   48 	 2775.70
228	 {2 4 4} 	  1.47  	 68 	 7.46 	   30 	 1669.40
258	 {6 2 0} 	  1.55  	 64 	 6.94 	   24 	 1155.47
282	 {3 5 3} 	  1.61  	 62 	 6.60 	   24 	 1045.13
306	 {2 2 6} 	  1.63  	 61 	 6.49 	    8 	 337.38
314	 {2 6 2} 	  1.63  	 61 	 6.49 	   16 	 674.76
330	 {4 4 4} 	  1.70  	 59 	 6.11 	    8 	 298.61
338	 {1 5 5} 	  1.75  	 57 	 5.85 	   24 	 822.23
362	 {5 5 1} 	  1.75  	 57 	 5.85 	   24 	 822.23
386	 {6 0 4} 	  1.77  	 57 	 5.77 	   24 	 799.86
410	 {4 2 6} 	  1.83  	 54 	 5.48 	   48 	 1439.07
458	 {5 3 5} 	  1.88  	 53 	 5.27 	   72 	 2002.47
530	 {3 3 7} 	  2.01  	 50 	 4.81 	   24 	 554.73
554	 {6 4 4} 	  2.02  	 49 	 4.76 	   24 	 542.78
578	 {0 6 6} 	  2.08  	 48 	 4.56 	   12 	 249.42
590	 {5 5 5} 	  2.12  	 47 	 4.42 	   56 	 1095.37
646	 {6 6 2} 	  2.14  	 47 	 4.38 	    8 	 153.42
654	 {6 2 6} 	  2.14  	 47 	 4.38 	   16 	 306.83
670	 {3 5 7} 	  2.23  	 45 	 4.10 	   48 	 806.10
718	 {6 6 4} 	  2.30  	 43 	 3.92 	   24 	 368.66
742	 {5 7 5} 	  2.44  	 41 	 3.58 	   48 	 614.38
790	 {7 3 7} 	  2.54  	 39 	 3.37 	   24 	 271.77
814	 {6 6 6} 	  2.55  	 39 	 3.34 	    8 	 89.26
822	 {7 7 5} 	  2.72  	 37 	 3.01 	   24 	 217.16

We can have a look what we saved in the file

main_dataset.metadata['SAED'] = out_tags
main_dataset.metadata
{'experiment': {'single_exposure_time': 0.0, 'exposure_time': 0.5, 'convergence_angle': 0.0, 'collection_angle': 0.0, 'number_of_frames': 1, 'microscope': 'Libra 200 MC', 'acceleration_voltage': 199990.28125}, 'filename': '../example_data\\GOLD-NP-DIFF.dm3', 'SAED': {'center': array([1054.53679654, 1102.20779221]), 'polar_projection': array([[6.14519799e-08, 6.14519799e-08, 6.14519799e-08, ..., 6.14519799e-08, 6.14519799e-08, 6.14519799e-08], [5.31134512e-08, 5.32014454e-08, 5.32888344e-08, ..., 5.28428452e-08, 5.29344500e-08, 5.30245525e-08], [5.47043797e-08, 5.43659155e-08, 5.40293630e-08, ..., 5.57346563e-08, 5.53884095e-08, 5.50450973e-08], ..., [1.00000000e-12, 1.00000000e-12, 1.00000000e-12, ..., 1.00000000e-12, 1.00000000e-12, 1.00000000e-12], [1.00000000e-12, 1.00000000e-12, 1.00000000e-12, ..., 1.00000000e-12, 1.00000000e-12, 1.00000000e-12], [1.00000000e-12, 1.00000000e-12, 1.00000000e-12, ..., 1.00000000e-12, 1.00000000e-12, 1.00000000e-12]], shape=(1449, 360)), 'radial_average': array([2.21227128e-05, 1.92457320e-05, 1.88279849e-05, ..., 2.06343925e-06, 2.04091447e-06, 1.98836273e-06], shape=(1449,)), 'ring_radii_px': array([ 28, 78, 122, 196, 352, 401, 574, 672, 900, 992, 1050, 1198]), 'reflections-0-index': '{1 1 1}', 'reflections-0-recip_distances': np.float64(0.42470962865207135), 'reflections-0-structure_factor': np.float64(27.001981488308132), 'reflections-0-multiplicity': np.int64(8), 'reflections-0-intensity': np.float64(5832.856034359481), 'reflections-1-index': '{0 0 2}', 'reflections-1-recip_distances': np.float64(0.4904124368593988), 'reflections-1-structure_factor': np.float64(24.483978581619574), 'reflections-1-multiplicity': np.int64(6), 'reflections-1-intensity': np.float64(3596.7912431112363), 'reflections-2-index': '{2 0 2}', 'reflections-2-recip_distances': np.float64(0.6935479193630009), 'reflections-2-structure_factor': np.float64(18.267861546028193), 'reflections-2-multiplicity': np.int64(12), 'reflections-2-intensity': np.float64(4004.577185578267), 'reflections-3-index': '{1 1 3}', 'reflections-3-recip_distances': np.float64(0.8132570227932421), 'reflections-3-structure_factor': np.float64(15.535885121433875), 'reflections-3-multiplicity': np.int64(8), 'reflections-3-intensity': np.float64(1930.9098120511235), 'reflections-4-index': '{1 3 1}', 'reflections-4-recip_distances': np.float64(0.8132570227932422), 'reflections-4-structure_factor': np.float64(15.535885121433877), 'reflections-4-multiplicity': np.int64(16), 'reflections-4-intensity': np.float64(3861.819624102248), 'reflections-5-index': '{2 2 2}', 'reflections-5-recip_distances': np.float64(0.8494192573041427), 'reflections-5-structure_factor': np.float64(14.819583263744343), 'reflections-5-multiplicity': np.int64(8), 'reflections-5-intensity': np.float64(1756.9603848884115), 'reflections-6-index': '{0 0 4}', 'reflections-6-recip_distances': np.float64(0.9808248737187976), 'reflections-6-structure_factor': np.float64(12.570414158304297), 'reflections-6-multiplicity': np.int64(6), 'reflections-6-intensity': np.float64(948.0918726677828), 'reflections-7-index': '{3 3 1}', 'reflections-7-recip_distances': np.float64(1.0688291264628202), 'reflections-7-structure_factor': np.float64(11.326958231196201), 'reflections-7-multiplicity': np.int64(8), 'reflections-7-intensity': np.float64(1026.399862170107), 'reflections-8-index': '{3 1 3}', 'reflections-8-recip_distances': np.float64(1.0688291264628205), 'reflections-8-structure_factor': np.float64(11.326958231196205), 'reflections-8-multiplicity': np.int64(16), 'reflections-8-intensity': np.float64(2052.799724340215), 'reflections-9-index': '{0 4 2}', 'reflections-9-recip_distances': np.float64(1.0965955458289391), 'reflections-9-structure_factor': np.float64(10.971746279580813), 'reflections-9-multiplicity': np.int64(24), 'reflections-9-intensity': np.float64(2889.10119416389), 'reflections-10-index': '{2 2 4}', 'reflections-10-recip_distances': np.float64(1.2012602338204004), 'reflections-10-structure_factor': np.float64(9.771334418145713), 'reflections-10-multiplicity': np.int64(24), 'reflections-10-intensity': np.float64(2291.4954314697366), 'reflections-11-index': '{1 5 1}', 'reflections-11-recip_distances': np.float64(1.2741288859562139), 'reflections-11-structure_factor': np.float64(9.048739478365496), 'reflections-11-multiplicity': np.int64(16), 'reflections-11-intensity': np.float64(1310.0749783572842), 'reflections-12-index': '{3 3 3}', 'reflections-12-recip_distances': np.float64(1.274128885956214), 'reflections-12-structure_factor': np.float64(9.048739478365498), 'reflections-12-multiplicity': np.int64(16), 'reflections-12-intensity': np.float64(1310.0749783572849), 'reflections-13-index': '{4 0 4}', 'reflections-13-recip_distances': np.float64(1.3870958387260017), 'reflections-13-structure_factor': np.float64(8.080352576248991), 'reflections-13-multiplicity': np.int64(12), 'reflections-13-intensity': np.float64(783.5051730779244), 'reflections-14-index': '{1 3 5}', 'reflections-14-recip_distances': np.float64(1.450659551542253), 'reflections-14-structure_factor': np.float64(7.604412728610221), 'reflections-14-multiplicity': np.int64(48), 'reflections-14-intensity': np.float64(2775.700461458359), 'reflections-15-index': '{2 4 4}', 'reflections-15-recip_distances': np.float64(1.4712373105781964), 'reflections-15-structure_factor': np.float64(7.4596757183737825), 'reflections-15-multiplicity': np.int64(30), 'reflections-15-intensity': np.float64(1669.4028546988623), 'reflections-16-index': '{6 2 0}', 'reflections-16-recip_distances': np.float64(1.5508202933492128), 'reflections-16-structure_factor': np.float64(6.938615373588864), 'reflections-16-multiplicity': np.int64(24), 'reflections-16-intensity': np.float64(1155.4651992624897), 'reflections-17-index': '{3 5 3}', 'reflections-17-recip_distances': np.float64(1.607924703129322), 'reflections-17-structure_factor': np.float64(6.599014341582308), 'reflections-17-multiplicity': np.int64(24), 'reflections-17-intensity': np.float64(1045.1277667298154), 'reflections-18-index': '{2 2 6}', 'reflections-18-recip_distances': np.float64(1.6265140455864842), 'reflections-18-structure_factor': np.float64(6.494057177048987), 'reflections-18-multiplicity': np.int64(8), 'reflections-18-intensity': np.float64(337.38222895025166), 'reflections-19-index': '{2 6 2}', 'reflections-19-recip_distances': np.float64(1.6265140455864844), 'reflections-19-structure_factor': np.float64(6.494057177048987), 'reflections-19-multiplicity': np.int64(16), 'reflections-19-intensity': np.float64(674.7644579005033), 'reflections-20-index': '{4 4 4}', 'reflections-20-recip_distances': np.float64(1.6988385146082854), 'reflections-20-structure_factor': np.float64(6.109532224675425), 'reflections-20-multiplicity': np.int64(8), 'reflections-20-intensity': np.float64(298.61107203477957), 'reflections-21-index': '{1 5 5}', 'reflections-21-recip_distances': np.float64(1.7511226591493427), 'reflections-21-structure_factor': np.float64(5.8531681438729315), 'reflections-21-multiplicity': np.int64(24), 'reflections-21-intensity': np.float64(822.2298556907735), 'reflections-22-index': '{5 5 1}', 'reflections-22-recip_distances': np.float64(1.751122659149343), 'reflections-22-structure_factor': np.float64(5.853168143872933), 'reflections-22-multiplicity': np.int64(24), 'reflections-22-intensity': np.float64(822.229855690774), 'reflections-23-index': '{6 0 4}', 'reflections-23-recip_distances': np.float64(1.7682071872218084), 'reflections-23-structure_factor': np.float64(5.7730001082976115), 'reflections-23-multiplicity': np.int64(24), 'reflections-23-intensity': np.float64(799.8607260097015), 'reflections-24-index': '{4 2 6}', 'reflections-24-recip_distances': np.float64(1.8349553169407786), 'reflections-24-structure_factor': np.float64(5.475463226438261), 'reflections-24-multiplicity': np.int64(48), 'reflections-24-intensity': np.float64(1439.0734821157293), 'reflections-25-index': '{5 3 5}', 'reflections-25-recip_distances': np.float64(1.8834647020422266), 'reflections-25-structure_factor': np.float64(5.2737163509881615), 'reflections-25-multiplicity': np.int64(72), 'reflections-25-intensity': np.float64(2002.470058848952), 'reflections-26-index': '{3 3 7}', 'reflections-26-recip_distances': np.float64(2.0070993997039013), 'reflections-26-structure_factor': np.float64(4.807655967320757), 'reflections-26-multiplicity': np.int64(24), 'reflections-26-intensity': np.float64(554.7253416027572), 'reflections-27-index': '{6 4 4}', 'reflections-27-recip_distances': np.float64(2.022022277287853), 'reflections-27-structure_factor': np.float64(4.755611995620097), 'reflections-27-multiplicity': np.int64(24), 'reflections-27-intensity': np.float64(542.7802908692581), 'reflections-28-index': '{0 6 6}', 'reflections-28-recip_distances': np.float64(2.080643758089003), 'reflections-28-structure_factor': np.float64(4.559050974930633), 'reflections-28-multiplicity': np.int64(12), 'reflections-28-intensity': np.float64(249.41934950419147), 'reflections-29-index': '{5 5 5}', 'reflections-29-recip_distances': np.float64(2.1235481432603565), 'reflections-29-structure_factor': np.float64(4.422685732877285), 'reflections-29-multiplicity': np.int64(56), 'reflections-29-intensity': np.float64(1095.3683491405918), 'reflections-30-index': '{6 6 2}', 'reflections-30-recip_distances': np.float64(2.1376582529256405), 'reflections-30-structure_factor': np.float64(4.37914286212942), 'reflections-30-multiplicity': np.int64(8), 'reflections-30-intensity': np.float64(153.4151376555124), 'reflections-31-index': '{6 2 6}', 'reflections-31-recip_distances': np.float64(2.137658252925641), 'reflections-31-structure_factor': np.float64(4.37914286212942), 'reflections-31-multiplicity': np.int64(16), 'reflections-31-intensity': np.float64(306.8302753110248), 'reflections-32-index': '{3 5 7}', 'reflections-32-recip_distances': np.float64(2.2339349661969248), 'reflections-32-structure_factor': np.float64(4.098006979092962), 'reflections-32-multiplicity': np.int64(48), 'reflections-32-intensity': np.float64(806.095737633342), 'reflections-33-index': '{6 6 4}', 'reflections-33-recip_distances': np.float64(2.3002382226587366), 'reflections-33-structure_factor': np.float64(3.9192757947321013), 'reflections-33-multiplicity': np.int64(24), 'reflections-33-intensity': np.float64(368.65734612415065), 'reflections-34-index': '{5 7 5}', 'reflections-34-recip_distances': np.float64(2.4397710683797262), 'reflections-34-structure_factor': np.float64(3.577655721287873), 'reflections-34-multiplicity': np.int64(48), 'reflections-34-intensity': np.float64(614.3817820830649), 'reflections-35-index': '{7 3 7}', 'reflections-35-recip_distances': np.float64(2.536432846056741), 'reflections-35-structure_factor': np.float64(3.3650622751716925), 'reflections-35-multiplicity': np.int64(24), 'reflections-35-intensity': np.float64(271.76745877880853), 'reflections-36-index': '{6 6 6}', 'reflections-36-recip_distances': np.float64(2.548257771912428), 'reflections-36-structure_factor': np.float64(3.340273246671722), 'reflections-36-multiplicity': np.int64(8), 'reflections-36-intensity': np.float64(89.25940289944677), 'reflections-37-index': '{7 7 5}', 'reflections-37-recip_distances': np.float64(2.7194685170931825), 'reflections-37-structure_factor': np.float64(3.008047711514111), 'reflections-37-multiplicity': np.int64(24), 'reflections-37-intensity': np.float64(217.16042483388674)}}

Comparison

Comparison between experimental profile and kinematic theory

The grain size will have an influence on the width of the diffraction rings"

# -------Input of grain size ----
resolution  = 0 # 1/nm
thickness = 100 # Ang
# -------------------------------

width = (1/thickness + resolution) / scale
scale = main_dataset.u.slope  *1.085*1.0/10*1.01

intensity2 = intensity/intensity.max()*10

gauss = scipy.signal.windows.gaussian(len(profile), std=width)
simulated_profile = np.zeros(len(profile))
rec_dist = np.linspace(1,len(profile),len(profile))*scale

x  =[]
yAu = []
yC  = []
for i in rec_dist:
    yAu.append(pyTEMlib.kinematic_scattering.feq('Au', i))
    yC.append(pyTEMlib.kinematic_scattering.feq('C', i))
        
plt.figure()
plt.plot(rec_dist,profile/profile.max()*150, color='blue', label='experiment');
for j in range(len(unique)-1):
    if unique[j] < len(profile)*scale:
        # plot lines
        plt.plot([unique[j],unique[j]], [0, intensity2[j]],c='r')
        # plot indices
        index = '{'+f'{family[j][0]:.0f} {family[j][1]:.0f} {family[j][2]:.0f}'+'}' # pretty index string
        plt.text(unique[j],-3, index, horizontalalignment='center',
              verticalalignment='top', rotation = 'vertical', fontsize=8, color = 'red')
        
        # place Gaussian with appropriate width in profile
        g = np.roll(gauss,int(-len(profile)/2+unique[j]/scale))* intensity2[j]*np.array(yAu)*1.3#rec_dist**2*10
        simulated_profile = simulated_profile + g
plt.plot(np.linspace(1,len(profile),len(profile))*scale,simulated_profile, label='simulated');
plt.plot(rec_dist,np.array(yAu)**2, label='form_factor')
plt.xlabel(r'angle (1/$\AA$)')
plt.legend()
plt.ylim(-35,210);
Loading...

Publication Quality Output

Now we have all the ingredients to make a publication quality plot of the data.

from matplotlib import patches
fig = plt.figure(figsize=(9, 6)) 

extent= np.array([-center[0], diff_pattern.shape[0]-center[0],-diff_pattern.shape[1]+center[1], center[1]])*scale*1.005

plt.imshow(np.log2(1+diff_pattern.T),cmap='gray', extent=(extent*1.0), vmin=np.max(np.log2(1+diff_pattern))*0.5)
plt.xlabel(r'reciprocal distance [nm$^{-1}$]')
ax = fig.gca()
#ax.add_artist(circle1);
plt.plot(np.linspace(1,len(profile),len(profile))*scale,profile/profile.max(), color='y')
plt.plot((0,len(profile)*scale),(0,0),c='r')

for j in range(len(unique)-1):
    i = indices[j]   
    if distances[i] < len(profile)*scale:
        plt.plot([distances[i],distances[i]], [0, intensity2[j]/20],c='r')
        arc = patches.Arc((0,0), distances[i]*2, distances[i]*2, 
                          angle=90.0, 
                          theta1=0.0, theta2=270.0, 
                          color='r', fill= False, alpha = 0.5)
        ax.add_artist(arc);
plt.scatter(0,0)

for i in range(6):
    index = '{'+f'{family[i][0]:.0f} {family[i][1]:.0f} {family[i][2]:.0f}'+'}' # pretty index string
    plt.text(unique[i],-0.05, index, horizontalalignment='center',
             verticalalignment='top', rotation = 'vertical', fontsize=8, color = 'white')
Loading...

Quiz

What would the figure caption for above figure be?

What does the above figure convey?

  • center is determined accurately

  • relative distances are accurately described

  • scaling accurately for reference crystal - calibration?

What is the accuracy?

Change the scale by 1% and see what happens

So we can determine the lattce parameter better than 1% if we use high scattering angles!

Logging the results

out_tags['analysis'] = 'Indexing_Diffraction_Rings'
out_tags['scale'] = scale
main_dataset.metadata
{'experiment': {'single_exposure_time': 0.0, 'exposure_time': 0.5, 'convergence_angle': 0.0, 'collection_angle': 0.0, 'number_of_frames': 1, 'microscope': 'Libra 200 MC', 'acceleration_voltage': 199990.28125}, 'filename': '../example_data\\GOLD-NP-DIFF.dm3', 'SAED': {'center': array([1054.53679654, 1102.20779221]), 'polar_projection': array([[6.14519799e-08, 6.14519799e-08, 6.14519799e-08, ..., 6.14519799e-08, 6.14519799e-08, 6.14519799e-08], [5.31134512e-08, 5.32014454e-08, 5.32888344e-08, ..., 5.28428452e-08, 5.29344500e-08, 5.30245525e-08], [5.47043797e-08, 5.43659155e-08, 5.40293630e-08, ..., 5.57346563e-08, 5.53884095e-08, 5.50450973e-08], ..., [1.00000000e-12, 1.00000000e-12, 1.00000000e-12, ..., 1.00000000e-12, 1.00000000e-12, 1.00000000e-12], [1.00000000e-12, 1.00000000e-12, 1.00000000e-12, ..., 1.00000000e-12, 1.00000000e-12, 1.00000000e-12], [1.00000000e-12, 1.00000000e-12, 1.00000000e-12, ..., 1.00000000e-12, 1.00000000e-12, 1.00000000e-12]], shape=(1449, 360)), 'radial_average': array([2.21227128e-05, 1.92457320e-05, 1.88279849e-05, ..., 2.06343925e-06, 2.04091447e-06, 1.98836273e-06], shape=(1449,)), 'ring_radii_px': array([ 28, 78, 122, 196, 352, 401, 574, 672, 900, 992, 1050, 1198]), 'reflections-0-index': '{1 1 1}', 'reflections-0-recip_distances': np.float64(0.42470962865207135), 'reflections-0-structure_factor': np.float64(27.001981488308132), 'reflections-0-multiplicity': np.int64(8), 'reflections-0-intensity': np.float64(5832.856034359481), 'reflections-1-index': '{0 0 2}', 'reflections-1-recip_distances': np.float64(0.4904124368593988), 'reflections-1-structure_factor': np.float64(24.483978581619574), 'reflections-1-multiplicity': np.int64(6), 'reflections-1-intensity': np.float64(3596.7912431112363), 'reflections-2-index': '{2 0 2}', 'reflections-2-recip_distances': np.float64(0.6935479193630009), 'reflections-2-structure_factor': np.float64(18.267861546028193), 'reflections-2-multiplicity': np.int64(12), 'reflections-2-intensity': np.float64(4004.577185578267), 'reflections-3-index': '{1 1 3}', 'reflections-3-recip_distances': np.float64(0.8132570227932421), 'reflections-3-structure_factor': np.float64(15.535885121433875), 'reflections-3-multiplicity': np.int64(8), 'reflections-3-intensity': np.float64(1930.9098120511235), 'reflections-4-index': '{1 3 1}', 'reflections-4-recip_distances': np.float64(0.8132570227932422), 'reflections-4-structure_factor': np.float64(15.535885121433877), 'reflections-4-multiplicity': np.int64(16), 'reflections-4-intensity': np.float64(3861.819624102248), 'reflections-5-index': '{2 2 2}', 'reflections-5-recip_distances': np.float64(0.8494192573041427), 'reflections-5-structure_factor': np.float64(14.819583263744343), 'reflections-5-multiplicity': np.int64(8), 'reflections-5-intensity': np.float64(1756.9603848884115), 'reflections-6-index': '{0 0 4}', 'reflections-6-recip_distances': np.float64(0.9808248737187976), 'reflections-6-structure_factor': np.float64(12.570414158304297), 'reflections-6-multiplicity': np.int64(6), 'reflections-6-intensity': np.float64(948.0918726677828), 'reflections-7-index': '{3 3 1}', 'reflections-7-recip_distances': np.float64(1.0688291264628202), 'reflections-7-structure_factor': np.float64(11.326958231196201), 'reflections-7-multiplicity': np.int64(8), 'reflections-7-intensity': np.float64(1026.399862170107), 'reflections-8-index': '{3 1 3}', 'reflections-8-recip_distances': np.float64(1.0688291264628205), 'reflections-8-structure_factor': np.float64(11.326958231196205), 'reflections-8-multiplicity': np.int64(16), 'reflections-8-intensity': np.float64(2052.799724340215), 'reflections-9-index': '{0 4 2}', 'reflections-9-recip_distances': np.float64(1.0965955458289391), 'reflections-9-structure_factor': np.float64(10.971746279580813), 'reflections-9-multiplicity': np.int64(24), 'reflections-9-intensity': np.float64(2889.10119416389), 'reflections-10-index': '{2 2 4}', 'reflections-10-recip_distances': np.float64(1.2012602338204004), 'reflections-10-structure_factor': np.float64(9.771334418145713), 'reflections-10-multiplicity': np.int64(24), 'reflections-10-intensity': np.float64(2291.4954314697366), 'reflections-11-index': '{1 5 1}', 'reflections-11-recip_distances': np.float64(1.2741288859562139), 'reflections-11-structure_factor': np.float64(9.048739478365496), 'reflections-11-multiplicity': np.int64(16), 'reflections-11-intensity': np.float64(1310.0749783572842), 'reflections-12-index': '{3 3 3}', 'reflections-12-recip_distances': np.float64(1.274128885956214), 'reflections-12-structure_factor': np.float64(9.048739478365498), 'reflections-12-multiplicity': np.int64(16), 'reflections-12-intensity': np.float64(1310.0749783572849), 'reflections-13-index': '{4 0 4}', 'reflections-13-recip_distances': np.float64(1.3870958387260017), 'reflections-13-structure_factor': np.float64(8.080352576248991), 'reflections-13-multiplicity': np.int64(12), 'reflections-13-intensity': np.float64(783.5051730779244), 'reflections-14-index': '{1 3 5}', 'reflections-14-recip_distances': np.float64(1.450659551542253), 'reflections-14-structure_factor': np.float64(7.604412728610221), 'reflections-14-multiplicity': np.int64(48), 'reflections-14-intensity': np.float64(2775.700461458359), 'reflections-15-index': '{2 4 4}', 'reflections-15-recip_distances': np.float64(1.4712373105781964), 'reflections-15-structure_factor': np.float64(7.4596757183737825), 'reflections-15-multiplicity': np.int64(30), 'reflections-15-intensity': np.float64(1669.4028546988623), 'reflections-16-index': '{6 2 0}', 'reflections-16-recip_distances': np.float64(1.5508202933492128), 'reflections-16-structure_factor': np.float64(6.938615373588864), 'reflections-16-multiplicity': np.int64(24), 'reflections-16-intensity': np.float64(1155.4651992624897), 'reflections-17-index': '{3 5 3}', 'reflections-17-recip_distances': np.float64(1.607924703129322), 'reflections-17-structure_factor': np.float64(6.599014341582308), 'reflections-17-multiplicity': np.int64(24), 'reflections-17-intensity': np.float64(1045.1277667298154), 'reflections-18-index': '{2 2 6}', 'reflections-18-recip_distances': np.float64(1.6265140455864842), 'reflections-18-structure_factor': np.float64(6.494057177048987), 'reflections-18-multiplicity': np.int64(8), 'reflections-18-intensity': np.float64(337.38222895025166), 'reflections-19-index': '{2 6 2}', 'reflections-19-recip_distances': np.float64(1.6265140455864844), 'reflections-19-structure_factor': np.float64(6.494057177048987), 'reflections-19-multiplicity': np.int64(16), 'reflections-19-intensity': np.float64(674.7644579005033), 'reflections-20-index': '{4 4 4}', 'reflections-20-recip_distances': np.float64(1.6988385146082854), 'reflections-20-structure_factor': np.float64(6.109532224675425), 'reflections-20-multiplicity': np.int64(8), 'reflections-20-intensity': np.float64(298.61107203477957), 'reflections-21-index': '{1 5 5}', 'reflections-21-recip_distances': np.float64(1.7511226591493427), 'reflections-21-structure_factor': np.float64(5.8531681438729315), 'reflections-21-multiplicity': np.int64(24), 'reflections-21-intensity': np.float64(822.2298556907735), 'reflections-22-index': '{5 5 1}', 'reflections-22-recip_distances': np.float64(1.751122659149343), 'reflections-22-structure_factor': np.float64(5.853168143872933), 'reflections-22-multiplicity': np.int64(24), 'reflections-22-intensity': np.float64(822.229855690774), 'reflections-23-index': '{6 0 4}', 'reflections-23-recip_distances': np.float64(1.7682071872218084), 'reflections-23-structure_factor': np.float64(5.7730001082976115), 'reflections-23-multiplicity': np.int64(24), 'reflections-23-intensity': np.float64(799.8607260097015), 'reflections-24-index': '{4 2 6}', 'reflections-24-recip_distances': np.float64(1.8349553169407786), 'reflections-24-structure_factor': np.float64(5.475463226438261), 'reflections-24-multiplicity': np.int64(48), 'reflections-24-intensity': np.float64(1439.0734821157293), 'reflections-25-index': '{5 3 5}', 'reflections-25-recip_distances': np.float64(1.8834647020422266), 'reflections-25-structure_factor': np.float64(5.2737163509881615), 'reflections-25-multiplicity': np.int64(72), 'reflections-25-intensity': np.float64(2002.470058848952), 'reflections-26-index': '{3 3 7}', 'reflections-26-recip_distances': np.float64(2.0070993997039013), 'reflections-26-structure_factor': np.float64(4.807655967320757), 'reflections-26-multiplicity': np.int64(24), 'reflections-26-intensity': np.float64(554.7253416027572), 'reflections-27-index': '{6 4 4}', 'reflections-27-recip_distances': np.float64(2.022022277287853), 'reflections-27-structure_factor': np.float64(4.755611995620097), 'reflections-27-multiplicity': np.int64(24), 'reflections-27-intensity': np.float64(542.7802908692581), 'reflections-28-index': '{0 6 6}', 'reflections-28-recip_distances': np.float64(2.080643758089003), 'reflections-28-structure_factor': np.float64(4.559050974930633), 'reflections-28-multiplicity': np.int64(12), 'reflections-28-intensity': np.float64(249.41934950419147), 'reflections-29-index': '{5 5 5}', 'reflections-29-recip_distances': np.float64(2.1235481432603565), 'reflections-29-structure_factor': np.float64(4.422685732877285), 'reflections-29-multiplicity': np.int64(56), 'reflections-29-intensity': np.float64(1095.3683491405918), 'reflections-30-index': '{6 6 2}', 'reflections-30-recip_distances': np.float64(2.1376582529256405), 'reflections-30-structure_factor': np.float64(4.37914286212942), 'reflections-30-multiplicity': np.int64(8), 'reflections-30-intensity': np.float64(153.4151376555124), 'reflections-31-index': '{6 2 6}', 'reflections-31-recip_distances': np.float64(2.137658252925641), 'reflections-31-structure_factor': np.float64(4.37914286212942), 'reflections-31-multiplicity': np.int64(16), 'reflections-31-intensity': np.float64(306.8302753110248), 'reflections-32-index': '{3 5 7}', 'reflections-32-recip_distances': np.float64(2.2339349661969248), 'reflections-32-structure_factor': np.float64(4.098006979092962), 'reflections-32-multiplicity': np.int64(48), 'reflections-32-intensity': np.float64(806.095737633342), 'reflections-33-index': '{6 6 4}', 'reflections-33-recip_distances': np.float64(2.3002382226587366), 'reflections-33-structure_factor': np.float64(3.9192757947321013), 'reflections-33-multiplicity': np.int64(24), 'reflections-33-intensity': np.float64(368.65734612415065), 'reflections-34-index': '{5 7 5}', 'reflections-34-recip_distances': np.float64(2.4397710683797262), 'reflections-34-structure_factor': np.float64(3.577655721287873), 'reflections-34-multiplicity': np.int64(48), 'reflections-34-intensity': np.float64(614.3817820830649), 'reflections-35-index': '{7 3 7}', 'reflections-35-recip_distances': np.float64(2.536432846056741), 'reflections-35-structure_factor': np.float64(3.3650622751716925), 'reflections-35-multiplicity': np.int64(24), 'reflections-35-intensity': np.float64(271.76745877880853), 'reflections-36-index': '{6 6 6}', 'reflections-36-recip_distances': np.float64(2.548257771912428), 'reflections-36-structure_factor': np.float64(3.340273246671722), 'reflections-36-multiplicity': np.int64(8), 'reflections-36-intensity': np.float64(89.25940289944677), 'reflections-37-index': '{7 7 5}', 'reflections-37-recip_distances': np.float64(2.7194685170931825), 'reflections-37-structure_factor': np.float64(3.008047711514111), 'reflections-37-multiplicity': np.int64(24), 'reflections-37-intensity': np.float64(217.16042483388674), 'analysis': 'Indexing_Diffraction_Rings', 'scale': np.float64(0.0012181957344571128)}}
h5_group = pyTEMlib.file_tools.save_dataset(datasets, filename='Gold_SAED.hf5')
h5_group.file.close()

Conclusion

We only need the scatterng factors to calculate the ring pattern.

A comparison between simulation and experiment can be very precise.

Normally one would do a fit of the most prominent peaks to establish the scale.

Appendix

Opening the hdf5 file and plot the data again

Open hf5 file

new_dataset = ft.open_file(current_filename)

And Plot

Because we saved all the results in all steps, it is straight forward to retrieve the publication quality plot again.

## Access the data of the loaded image


diff_pattern = np.array(new_dataset)
diff_pattern = diff_pattern-diff_pattern.min()
current_channel = new_dataset.h5_dataset.parent.parent
result_group = current_channel['Log_000']

center = result_group['center'][()]
scale = result_group['scale'][()]

from matplotlib import patches
fig = plt.figure(figsize=(9, 6)) 
ax = plt.gca()
extent= np.array([-center[0], diff_pattern.shape[0]-center[0],-diff_pattern.shape[1]+center[1], center[1]])*scale

plt.imshow(np.log2(1+diff_pattern).T, cmap="gray", extent=(extent), vmin=np.max(np.log2(1+diff_pattern))*0.5)
plt.xlabel(r'reciprocal distance [nm$^{-1}$]')

profile = result_group['radial_average'][()]
plt.plot(np.linspace(1,len(profile),len(profile))*scale,profile/profile.max()*10, color='y');#
plt.plot((0,len(profile)*scale),(0,0),c='r')
reflections = {}
for key in result_group:
    if 'reflection' in key:
        keys = key.split('-')
        
        if keys[1] not in reflections:
            reflections[keys[1]]={}
        reflections[keys[1]][keys[2]] =  result_group[key][()]
        #print( reflections[keys[1]][keys[2]] )
for key in reflections:
    distance = reflections[key]['recip_distances']
    if distance < len(profile)*scale:
        
        plt.plot([distance,distance], [0, reflections[key]['intensity']/1000],c='r')
        arc = patches.Arc((0,0), distance*2, distance*2, angle=90.0, theta1=0.0, theta2=270.0, color='r', fill= False, alpha = 0.5)#, **kwargs)
        ax.add_artist(arc);
plt.scatter(0,0);
for i in range(7):
    index = reflections[str(i)]['index'] # pretty index string
    plt.text(unique[i],-0.5, index, horizontalalignment='center',
          verticalalignment='top', rotation = 'vertical', fontsize=8, color = 'white')

3D Plot of Miller Indices

hkl_max = 4
h  = np.linspace(-hkl_max,hkl_max,2*hkl_max+1)  # all evaluated single Miller Indices
hkl  = np.array(list(itertools.product(h,h,h) )) # all evaluated Miller indices

# Plot 2D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(hkl[:,0], hkl[:,2], hkl[:,1], c='red', s=10)
plt.xlabel('h')
plt.ylabel('l')
fig.gca().set_zlabel('k')
#ax.set_aspect('equal')

Reciprocal Space and Miller Indices

For a reciprocal cubic unit cell with lattice parameter b=1ab = \frac{1}{a}:

ghkl=(hkl)(b000b000b)\vec{g}_{hkl} = \begin{pmatrix}h\\k\\l\end{pmatrix} \cdot \begin{pmatrix}b&0&0\\0&b&0\\0&0&b\end{pmatrix}

Or more general

ghkl=(hkl)(b1,1b1,2b1,3b2,1b2,2b2,3b3,1b3,2b3,3)\vec{g}_{hkl} = \begin{pmatrix}h\\k\\l\end{pmatrix} \cdot \begin{pmatrix}b_{1,1}&b_{1,2}&b_{1,3}\\b_{2,1}&b_{2,2}&b_{2,3}\\b_{3,1}&b_{3,2}&b_{3,3}\end{pmatrix}

The matrix is of course the reciprocal unit cell or the inverse of the structure matrix.

Therefore, we get any reciprocal lattice vector with the dot product of its Miller indices and the reciprocal lattice matrix.

Spacing of planes with Miller Indices hklhkl

ghkl=1dd=1ghkl\begin{align*} |\vec{g}_{hkl}|& = \frac{1}{d}\\ d &= \frac{1}{|\vec{g}_{hkl}|} \end{align*}

The length of a vector is called its norm.

Be careful there are two different notations for the reciprocal lattice vectors:

  • materials science

  • physics

The notations are different in a factor 2π2\pi. The introduction of 2π2\pi in physics allows to take care of the nn more naturally.

In the materials science notation the reciprocal lattice points are directly associated with the Bragg reflections in your diffraction pattern.
(OK,s we are too lacy to keep track of 2π2\pi)

All Possible Reflections

Are then given by the all permutations of the Miller indices and the reiprocal unit cell matrix.

All considered Miller indices are then produced with the itertool package of python.


hkl_max = 9#  maximum allowed Miller index

h  = np.linspace(-hkl_max,hkl_max,2*hkl_max+1)   # all evaluated single Miller Indices
hkl  = np.array(list(itertools.product(h,h,h) )) # all evaluated Miller indices
g_hkl = np.dot(hkl,reciprocal_unit_cell)         # all evaluated reciprocal lattice points

print(f'Evaluation of {g_hkl.shape} reflections of {hkl.shape} Miller indices')

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(g_hkl[:,0], g_hkl[:,2], g_hkl[:,1], c='red', s=10)
plt.xlabel('u')
plt.ylabel('v')
fig.gca().set_zlabel('w')

Atomic form factor

If we look at the scattering power of a single atom that deflects an electron:

Single atom scattering

See Atomic Form Factor for details

Calculate Structure Factors

To extend the single atom view of the atomic form factor f(θf(\theta) to a crystal, we change to the structure factor F(θ)F(\theta). The structure factor F(θ)F(\theta) is a measure of the amplitude scattered by a unit cell of a crystal structure.

Because F(θ)F(\theta) is an amplitude like f(θ)f(\theta), it also has dimensions of length.We can define F(θ)F(\theta) as:

Fhkl(θ)=j=1inffi(θ)e[2πi(hxj+kyj+lzj)]F_{hkl}(\theta) = \sum_{j=1}^{\inf} f_i(\theta) \mathrm{e}^{[-2 \pi i (h x_j+k y_j + l z_j)]}

The sum is over all the ii atoms in the unit cell (with atomic coordinates xi,yi,zix_i, y_i, z_i

The structure factors fi(θ)f_i(\theta) are multiplied by a phase factor (the exponential function). The phase factor takes account of the difference in phase between waves scattered from atoms on different but parallel atomic planes with the same Miller indices(hkl) (hkl).

The scattering angle θ\theta is the angle between the angle between the incident and scattered electron beams.

Please identify all the variables in line 10 below. Please note that we only do a finite number of hkl

# Calculate Structure Factors

structure_factors = []

base = np.dot(tags['base'],tags['unit_cell'])  # transformation from relative to Carthesian coordinates
for j  in range(len(g_hkl)):
    F = 0
    for b in range(len(base)):
        f = ks.feq(tags['elements'][b],np.linalg.norm(np.dot(g_hkl[j], reciprocal_lattice))) # Atomic form factor for element and momentum change (g vector)
        F += f * np.exp(-2*np.pi*1j*(g_hkl[j]*base[b]).sum())        
    structure_factors.append(F)
F = structure_factors = np.array(structure_factors)

All Allowed Reflections

The structure factor determines whether a reflection is allowed or not.

If the structure factor is zero, the reflection is called forbidden.

# Allowed reflections have a non zero structure factor F (with a  bit of numerical error)
allowed = np.absolute(structure_factors) > 0.001

print(f' Of the evaluated {hkl.shape[0]} Miller indices {allowed.sum()} are allowed. ')

distances = np.linalg.norm(g_hkl, axis = 1)
# We select now all the 
zero = distances == 0.
allowed = np.logical_and(allowed,np.logical_not(zero))

F = F[allowed]
g_hkl = g_hkl[allowed]
hkl = hkl[allowed]
distances = distances[allowed]

Families of reflections

reflections with the same length of reciprocal lattice vector are called families

sorted_allowed = np.argsort(distances)

distances = distances[sorted_allowed]
hkl = hkl[sorted_allowed]
F = F[sorted_allowed]

# How many have unique distances and what is their muliplicity

unique, indices  = np.unique(distances, return_index=True)

print(f' Of the {allowed.sum()} allowed Bragg reflections there are {len(unique)} families of reflections.')

Intensities and Multiplicities

multiplicitity = np.roll(indices,-1)-indices
intensity = np.absolute(F[indices]**2*multiplicitity)
print('\n index \t     hkl \t 1/d [1/nm]     d [pm] \t  F \t multip. intensity' )
family = []
for j in range(len(unique)-1):
    i = indices[j]    
    i2 = indices[j+1]   
    family.append(hkl[i+np.argmax(hkl[i:i2].sum(axis=1))])
    print(f'{i:3g}\t {family[j]} \t  {distances[i]:.2f}  \t {1/distances[i]*1000:.0f} \t {np.absolute(F[i]):.2f}, \t  {indices[j+1]-indices[j]:3g} \t {intensity[j]:.2f}') 
    

Allowed reflections for Silicon:

  Fhkl2={(h,k,l   all odd(h,k,l  all evenand  h+k+l=4n\ \ |F_{hkl}|^2 = \begin{cases} ( h , k , l \ \ \mbox{ all odd} &\\ ( h ,| k , l \ \ \mbox{all even}& \mbox{and}\ \ h+k+l = 4n\end{cases}

Check above allowed reflections whether this condition is met for the zero order Laue zone.

Please note that the forbidden and alowed reflections are directly a property of the structure factor.

Diffraction with Parallel Illumination

Polycrystalline SampleSingle Crystalline Sample
ring patternspot pattern
depends on F(θ)F(\theta)depends on F(θ)F(\theta)
	| depends on excitation error $s$

Ring Pattern

Bragg's Law Bragg's Law

Ring Pattern:

  • The profile of a ring diffraction pattern (of a polycrystalline sample) is very close to what a you are used from X-ray diffraction.

  • The x-axis is directly the magnitude of the g=1/d|\vec{g}| = 1/d of a hkl plane set.

  • The intensity of a Bragg reflection is directly related to the square of the structure factor I=F2(θ)I = F^2(\theta)

  • The intensity of a ring is directly related to the multiplicity of the family of planes.

Ring Pattern Problem:

  • Where is the center of the ring pattern

  • Integration over all angles (spherical coordinates)

  • Indexing of pattern is analog to x-ray diffraction.

The Ring Diffraction Pattern are completely defined by the Structure Factor

from matplotlib import patches
fig, ax = plt.subplots()
plt.scatter(0,0);
img = np.zeros((1024,1024))
extent = np.array([-1,1,-1,1])*np.max(unique)
plt.imshow(img, extent = extent)

for radius in unique:   
    circle = patches.Circle((0,0), radius*2, color='r', fill= False, alpha = 0.3)#, **kwargs)
    ax.add_artist(circle);

Conclusion

The scattering geometry provides all the tools to determine which reciprocal lattice points are possible and which of them are allowed.

Next we need to transfer out knowledge into a diffraction pattern.