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.

Analyzing Ring Diffraction Pattern

Chapter 2: Diffraction


Analyzing Ring Diffraction Pattern

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.

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 git+https://github.com/pycroscopy/pyTEMlib.git@main -q --upgrade

print('done')
installing pyTEMlib

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 pyTEMlib

  • diffraction_tools library.

%matplotlib  widget
import os
import sys

import matplotlib
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()

# additional package 
import itertools 
import skimage
import scipy

# Import microscopt library
import pyTEMlib
    
# it is a good idea to show the version numbers at this point for archiving reasons.
__notebook_version__ = '2026.01.08'
print('pyTEM version: ', pyTEMlib.__version__)
print('notebook version: ', __notebook_version__)
pyTEM version:  0.2025.12.1
notebook version:  2026.01.08

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]]);
center = (np.int64(1090), np.int64(1182)) which is a shift of 66.0 px in x and 158.0 px in y direction
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)
[-958 -866]
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.5367965367964), np.float64(1111.0735930735932)) [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')
[ 0.45793573  0.7250649   0.90315102  2.49320563  4.49031421  5.01185213
  7.3142512   8.56085401 11.39751143 12.5932325  13.35645871 15.20092206]
Loading...

Calculate Ring Pattern

see Structure Factors notebook for details.

# Initialize the dictionary with all the input
atoms = pyTEMlib.crystal_tools.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.utilities.get_wavelength(acceleration_voltage, unit='A')


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)):
        # Atomic form factor for element and momentum change (g vector)
        f = pyTEMlib.diffraction_tools.form_factor(atoms[b].symbol,np.linalg.norm(g_hkl[j]))  
        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
out_tags['reflections'] = {}
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)] = {'index': index,
                                                'recip_distances': distances[i],
                                                'structure_factor': np.absolute(F[i]),
                                                'multiplicity': indices[j+1]-indices[j],
                                                'intensity': intensity[j]}
    reflection +=1
main_dataset.metadata['SAED'] = out_tags
 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	 {0 2 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	 {4 0 2} 	  1.10  	 91 	 10.97 	   24 	 2889.10
112	 {2 2 4} 	  1.20  	 83 	 9.77 	   24 	 2291.50
136	 {5 1 1} 	  1.27  	 78 	 9.05 	   16 	 1310.07
152	 {3 3 3} 	  1.27  	 78 	 9.05 	   16 	 1310.07
168	 {0 4 4} 	  1.39  	 72 	 8.08 	   12 	 783.51
180	 {1 5 3} 	  1.45  	 69 	 7.60 	   48 	 2775.70
228	 {4 4 2} 	  1.47  	 68 	 7.46 	   30 	 1669.40
258	 {6 2 0} 	  1.55  	 64 	 6.94 	   24 	 1155.47
282	 {3 3 5} 	  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	 {5 1 5} 	  1.75  	 57 	 5.85 	   24 	 822.23
362	 {5 5 1} 	  1.75  	 57 	 5.85 	   24 	 822.23
386	 {0 4 6} 	  1.77  	 57 	 5.77 	   24 	 799.86
410	 {2 4 6} 	  1.83  	 54 	 5.48 	   48 	 1439.07
458	 {3 5 5} 	  1.88  	 53 	 5.27 	   72 	 2002.47
530	 {3 7 3} 	  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	 {5 7 3} 	  2.23  	 45 	 4.10 	   48 	 806.10
718	 {4 6 6} 	  2.30  	 43 	 3.92 	   24 	 368.66
742	 {7 5 5} 	  2.44  	 41 	 3.58 	   48 	 614.38
790	 {7 7 3} 	  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
out_tags['reflections'][str(1)]
{'index': '{0 0 2}', 'recip_distances': np.float64(0.4904124368593988), 'structure_factor': np.float64(24.483978581619574), 'multiplicity': np.int64(6), 'intensity': np.float64(3596.7912431112363)}

We can have a look what we saved in the file

main_dataset.metadata['SAED']
{'center': array([1054.53679654, 1111.07359307]), 'polar_projection': array([[4.88957668e-08, 4.88957668e-08, 4.88957668e-08, ..., 4.88957668e-08, 4.88957668e-08, 4.88957668e-08], [6.46374743e-08, 6.50070318e-08, 6.53784993e-08, ..., 6.35336741e-08, 6.39014542e-08, 6.42691681e-08], [6.10132545e-08, 6.02004134e-08, 5.93858410e-08, ..., 6.34430682e-08, 6.26344137e-08, 6.18245319e-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([1.76024760e-05, 1.99754889e-05, 1.91173205e-05, ..., 2.41023998e-06, 2.35795202e-06, 2.32891835e-06], shape=(1449,)), 'ring_radii_px': array([ 36, 57, 71, 196, 353, 394, 575, 673, 896, 990, 1050, 1195]), 'reflections': {'0': {'index': '{1 1 1}', 'recip_distances': np.float64(0.42470962865207135), 'structure_factor': np.float64(27.001981488308132), 'multiplicity': np.int64(8), 'intensity': np.float64(5832.856034359481)}, '1': {'index': '{0 0 2}', 'recip_distances': np.float64(0.4904124368593988), 'structure_factor': np.float64(24.483978581619574), 'multiplicity': np.int64(6), 'intensity': np.float64(3596.7912431112363)}, '2': {'index': '{0 2 2}', 'recip_distances': np.float64(0.6935479193630009), 'structure_factor': np.float64(18.267861546028193), 'multiplicity': np.int64(12), 'intensity': np.float64(4004.577185578267)}, '3': {'index': '{1 1 3}', 'recip_distances': np.float64(0.8132570227932421), 'structure_factor': np.float64(15.535885121433875), 'multiplicity': np.int64(8), 'intensity': np.float64(1930.9098120511235)}, '4': {'index': '{1 3 1}', 'recip_distances': np.float64(0.8132570227932422), 'structure_factor': np.float64(15.535885121433877), 'multiplicity': np.int64(16), 'intensity': np.float64(3861.819624102248)}, '5': {'index': '{2 2 2}', 'recip_distances': np.float64(0.8494192573041427), 'structure_factor': np.float64(14.819583263744343), 'multiplicity': np.int64(8), 'intensity': np.float64(1756.9603848884115)}, '6': {'index': '{0 0 4}', 'recip_distances': np.float64(0.9808248737187976), 'structure_factor': np.float64(12.570414158304297), 'multiplicity': np.int64(6), 'intensity': np.float64(948.0918726677828)}, '7': {'index': '{3 3 1}', 'recip_distances': np.float64(1.0688291264628202), 'structure_factor': np.float64(11.326958231196201), 'multiplicity': np.int64(8), 'intensity': np.float64(1026.399862170107)}, '8': {'index': '{3 1 3}', 'recip_distances': np.float64(1.0688291264628205), 'structure_factor': np.float64(11.326958231196201), 'multiplicity': np.int64(16), 'intensity': np.float64(2052.799724340214)}, '9': {'index': '{4 0 2}', 'recip_distances': np.float64(1.0965955458289391), 'structure_factor': np.float64(10.971746279580813), 'multiplicity': np.int64(24), 'intensity': np.float64(2889.10119416389)}, '10': {'index': '{2 2 4}', 'recip_distances': np.float64(1.2012602338204004), 'structure_factor': np.float64(9.771334418145713), 'multiplicity': np.int64(24), 'intensity': np.float64(2291.4954314697366)}, '11': {'index': '{5 1 1}', 'recip_distances': np.float64(1.2741288859562139), 'structure_factor': np.float64(9.048739478365498), 'multiplicity': np.int64(16), 'intensity': np.float64(1310.0749783572849)}, '12': {'index': '{3 3 3}', 'recip_distances': np.float64(1.274128885956214), 'structure_factor': np.float64(9.048739478365498), 'multiplicity': np.int64(16), 'intensity': np.float64(1310.0749783572849)}, '13': {'index': '{0 4 4}', 'recip_distances': np.float64(1.3870958387260017), 'structure_factor': np.float64(8.080352576248991), 'multiplicity': np.int64(12), 'intensity': np.float64(783.5051730779244)}, '14': {'index': '{1 5 3}', 'recip_distances': np.float64(1.450659551542253), 'structure_factor': np.float64(7.604412728610221), 'multiplicity': np.int64(48), 'intensity': np.float64(2775.700461458359)}, '15': {'index': '{4 4 2}', 'recip_distances': np.float64(1.4712373105781964), 'structure_factor': np.float64(7.4596757183737825), 'multiplicity': np.int64(30), 'intensity': np.float64(1669.4028546988623)}, '16': {'index': '{6 2 0}', 'recip_distances': np.float64(1.5508202933492128), 'structure_factor': np.float64(6.938615373588864), 'multiplicity': np.int64(24), 'intensity': np.float64(1155.4651992624897)}, '17': {'index': '{3 3 5}', 'recip_distances': np.float64(1.607924703129322), 'structure_factor': np.float64(6.599014341582308), 'multiplicity': np.int64(24), 'intensity': np.float64(1045.1277667298154)}, '18': {'index': '{2 2 6}', 'recip_distances': np.float64(1.6265140455864842), 'structure_factor': np.float64(6.494057177048987), 'multiplicity': np.int64(8), 'intensity': np.float64(337.38222895025166)}, '19': {'index': '{2 6 2}', 'recip_distances': np.float64(1.6265140455864844), 'structure_factor': np.float64(6.494057177048987), 'multiplicity': np.int64(16), 'intensity': np.float64(674.7644579005033)}, '20': {'index': '{4 4 4}', 'recip_distances': np.float64(1.6988385146082854), 'structure_factor': np.float64(6.109532224675425), 'multiplicity': np.int64(8), 'intensity': np.float64(298.61107203477957)}, '21': {'index': '{5 1 5}', 'recip_distances': np.float64(1.7511226591493427), 'structure_factor': np.float64(5.8531681438729315), 'multiplicity': np.int64(24), 'intensity': np.float64(822.2298556907735)}, '22': {'index': '{5 5 1}', 'recip_distances': np.float64(1.751122659149343), 'structure_factor': np.float64(5.8531681438729315), 'multiplicity': np.int64(24), 'intensity': np.float64(822.2298556907735)}, '23': {'index': '{0 4 6}', 'recip_distances': np.float64(1.7682071872218084), 'structure_factor': np.float64(5.7730001082976115), 'multiplicity': np.int64(24), 'intensity': np.float64(799.8607260097015)}, '24': {'index': '{2 4 6}', 'recip_distances': np.float64(1.8349553169407786), 'structure_factor': np.float64(5.475463226438261), 'multiplicity': np.int64(48), 'intensity': np.float64(1439.0734821157293)}, '25': {'index': '{3 5 5}', 'recip_distances': np.float64(1.8834647020422266), 'structure_factor': np.float64(5.2737163509881615), 'multiplicity': np.int64(72), 'intensity': np.float64(2002.470058848952)}, '26': {'index': '{3 7 3}', 'recip_distances': np.float64(2.0070993997039013), 'structure_factor': np.float64(4.807655967320757), 'multiplicity': np.int64(24), 'intensity': np.float64(554.7253416027572)}, '27': {'index': '{6 4 4}', 'recip_distances': np.float64(2.022022277287853), 'structure_factor': np.float64(4.755611995620097), 'multiplicity': np.int64(24), 'intensity': np.float64(542.7802908692581)}, '28': {'index': '{0 6 6}', 'recip_distances': np.float64(2.080643758089003), 'structure_factor': np.float64(4.559050974930633), 'multiplicity': np.int64(12), 'intensity': np.float64(249.41934950419147)}, '29': {'index': '{5 5 5}', 'recip_distances': np.float64(2.1235481432603565), 'structure_factor': np.float64(4.422685732877285), 'multiplicity': np.int64(56), 'intensity': np.float64(1095.3683491405918)}, '30': {'index': '{6 6 2}', 'recip_distances': np.float64(2.1376582529256405), 'structure_factor': np.float64(4.37914286212942), 'multiplicity': np.int64(8), 'intensity': np.float64(153.4151376555124)}, '31': {'index': '{6 2 6}', 'recip_distances': np.float64(2.137658252925641), 'structure_factor': np.float64(4.379142862129421), 'multiplicity': np.int64(16), 'intensity': np.float64(306.83027531102493)}, '32': {'index': '{5 7 3}', 'recip_distances': np.float64(2.2339349661969248), 'structure_factor': np.float64(4.098006979092962), 'multiplicity': np.int64(48), 'intensity': np.float64(806.095737633342)}, '33': {'index': '{4 6 6}', 'recip_distances': np.float64(2.3002382226587366), 'structure_factor': np.float64(3.9192757947321013), 'multiplicity': np.int64(24), 'intensity': np.float64(368.65734612415065)}, '34': {'index': '{7 5 5}', 'recip_distances': np.float64(2.4397710683797262), 'structure_factor': np.float64(3.577655721287873), 'multiplicity': np.int64(48), 'intensity': np.float64(614.3817820830649)}, '35': {'index': '{7 7 3}', 'recip_distances': np.float64(2.536432846056741), 'structure_factor': np.float64(3.3650622751716925), 'multiplicity': np.int64(24), 'intensity': np.float64(271.76745877880853)}, '36': {'index': '{6 6 6}', 'recip_distances': np.float64(2.548257771912428), 'structure_factor': np.float64(3.340273246671722), 'multiplicity': np.int64(8), 'intensity': np.float64(89.25940289944677)}, '37': {'index': '{7 7 5}', 'recip_distances': np.float64(2.7194685170931825), 'structure_factor': np.float64(3.008047711514111), 'multiplicity': np.int64(24), '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.diffraction_tools.form_factor('Au', i))
    yC.append(pyTEMlib.diffraction_tools.form_factor('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

main_dataset.metadata['analysis'] = {'name': 'Indexing_Diffraction_Rings'}
main_dataset.metadata['SAED']['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, 1111.07359307]), 'polar_projection': array([[4.88957668e-08, 4.88957668e-08, 4.88957668e-08, ..., 4.88957668e-08, 4.88957668e-08, 4.88957668e-08], [6.46374743e-08, 6.50070318e-08, 6.53784993e-08, ..., 6.35336741e-08, 6.39014542e-08, 6.42691681e-08], [6.10132545e-08, 6.02004134e-08, 5.93858410e-08, ..., 6.34430682e-08, 6.26344137e-08, 6.18245319e-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([1.76024760e-05, 1.99754889e-05, 1.91173205e-05, ..., 2.41023998e-06, 2.35795202e-06, 2.32891835e-06], shape=(1449,)), 'ring_radii_px': array([ 36, 57, 71, 196, 353, 394, 575, 673, 896, 990, 1050, 1195]), 'reflections': {'0': {'index': '{1 1 1}', 'recip_distances': np.float64(0.42470962865207135), 'structure_factor': np.float64(27.001981488308132), 'multiplicity': np.int64(8), 'intensity': np.float64(5832.856034359481)}, '1': {'index': '{0 0 2}', 'recip_distances': np.float64(0.4904124368593988), 'structure_factor': np.float64(24.483978581619574), 'multiplicity': np.int64(6), 'intensity': np.float64(3596.7912431112363)}, '2': {'index': '{0 2 2}', 'recip_distances': np.float64(0.6935479193630009), 'structure_factor': np.float64(18.267861546028193), 'multiplicity': np.int64(12), 'intensity': np.float64(4004.577185578267)}, '3': {'index': '{1 1 3}', 'recip_distances': np.float64(0.8132570227932421), 'structure_factor': np.float64(15.535885121433875), 'multiplicity': np.int64(8), 'intensity': np.float64(1930.9098120511235)}, '4': {'index': '{1 3 1}', 'recip_distances': np.float64(0.8132570227932422), 'structure_factor': np.float64(15.535885121433877), 'multiplicity': np.int64(16), 'intensity': np.float64(3861.819624102248)}, '5': {'index': '{2 2 2}', 'recip_distances': np.float64(0.8494192573041427), 'structure_factor': np.float64(14.819583263744343), 'multiplicity': np.int64(8), 'intensity': np.float64(1756.9603848884115)}, '6': {'index': '{0 0 4}', 'recip_distances': np.float64(0.9808248737187976), 'structure_factor': np.float64(12.570414158304297), 'multiplicity': np.int64(6), 'intensity': np.float64(948.0918726677828)}, '7': {'index': '{3 3 1}', 'recip_distances': np.float64(1.0688291264628202), 'structure_factor': np.float64(11.326958231196201), 'multiplicity': np.int64(8), 'intensity': np.float64(1026.399862170107)}, '8': {'index': '{3 1 3}', 'recip_distances': np.float64(1.0688291264628205), 'structure_factor': np.float64(11.326958231196201), 'multiplicity': np.int64(16), 'intensity': np.float64(2052.799724340214)}, '9': {'index': '{4 0 2}', 'recip_distances': np.float64(1.0965955458289391), 'structure_factor': np.float64(10.971746279580813), 'multiplicity': np.int64(24), 'intensity': np.float64(2889.10119416389)}, '10': {'index': '{2 2 4}', 'recip_distances': np.float64(1.2012602338204004), 'structure_factor': np.float64(9.771334418145713), 'multiplicity': np.int64(24), 'intensity': np.float64(2291.4954314697366)}, '11': {'index': '{5 1 1}', 'recip_distances': np.float64(1.2741288859562139), 'structure_factor': np.float64(9.048739478365498), 'multiplicity': np.int64(16), 'intensity': np.float64(1310.0749783572849)}, '12': {'index': '{3 3 3}', 'recip_distances': np.float64(1.274128885956214), 'structure_factor': np.float64(9.048739478365498), 'multiplicity': np.int64(16), 'intensity': np.float64(1310.0749783572849)}, '13': {'index': '{0 4 4}', 'recip_distances': np.float64(1.3870958387260017), 'structure_factor': np.float64(8.080352576248991), 'multiplicity': np.int64(12), 'intensity': np.float64(783.5051730779244)}, '14': {'index': '{1 5 3}', 'recip_distances': np.float64(1.450659551542253), 'structure_factor': np.float64(7.604412728610221), 'multiplicity': np.int64(48), 'intensity': np.float64(2775.700461458359)}, '15': {'index': '{4 4 2}', 'recip_distances': np.float64(1.4712373105781964), 'structure_factor': np.float64(7.4596757183737825), 'multiplicity': np.int64(30), 'intensity': np.float64(1669.4028546988623)}, '16': {'index': '{6 2 0}', 'recip_distances': np.float64(1.5508202933492128), 'structure_factor': np.float64(6.938615373588864), 'multiplicity': np.int64(24), 'intensity': np.float64(1155.4651992624897)}, '17': {'index': '{3 3 5}', 'recip_distances': np.float64(1.607924703129322), 'structure_factor': np.float64(6.599014341582308), 'multiplicity': np.int64(24), 'intensity': np.float64(1045.1277667298154)}, '18': {'index': '{2 2 6}', 'recip_distances': np.float64(1.6265140455864842), 'structure_factor': np.float64(6.494057177048987), 'multiplicity': np.int64(8), 'intensity': np.float64(337.38222895025166)}, '19': {'index': '{2 6 2}', 'recip_distances': np.float64(1.6265140455864844), 'structure_factor': np.float64(6.494057177048987), 'multiplicity': np.int64(16), 'intensity': np.float64(674.7644579005033)}, '20': {'index': '{4 4 4}', 'recip_distances': np.float64(1.6988385146082854), 'structure_factor': np.float64(6.109532224675425), 'multiplicity': np.int64(8), 'intensity': np.float64(298.61107203477957)}, '21': {'index': '{5 1 5}', 'recip_distances': np.float64(1.7511226591493427), 'structure_factor': np.float64(5.8531681438729315), 'multiplicity': np.int64(24), 'intensity': np.float64(822.2298556907735)}, '22': {'index': '{5 5 1}', 'recip_distances': np.float64(1.751122659149343), 'structure_factor': np.float64(5.8531681438729315), 'multiplicity': np.int64(24), 'intensity': np.float64(822.2298556907735)}, '23': {'index': '{0 4 6}', 'recip_distances': np.float64(1.7682071872218084), 'structure_factor': np.float64(5.7730001082976115), 'multiplicity': np.int64(24), 'intensity': np.float64(799.8607260097015)}, '24': {'index': '{2 4 6}', 'recip_distances': np.float64(1.8349553169407786), 'structure_factor': np.float64(5.475463226438261), 'multiplicity': np.int64(48), 'intensity': np.float64(1439.0734821157293)}, '25': {'index': '{3 5 5}', 'recip_distances': np.float64(1.8834647020422266), 'structure_factor': np.float64(5.2737163509881615), 'multiplicity': np.int64(72), 'intensity': np.float64(2002.470058848952)}, '26': {'index': '{3 7 3}', 'recip_distances': np.float64(2.0070993997039013), 'structure_factor': np.float64(4.807655967320757), 'multiplicity': np.int64(24), 'intensity': np.float64(554.7253416027572)}, '27': {'index': '{6 4 4}', 'recip_distances': np.float64(2.022022277287853), 'structure_factor': np.float64(4.755611995620097), 'multiplicity': np.int64(24), 'intensity': np.float64(542.7802908692581)}, '28': {'index': '{0 6 6}', 'recip_distances': np.float64(2.080643758089003), 'structure_factor': np.float64(4.559050974930633), 'multiplicity': np.int64(12), 'intensity': np.float64(249.41934950419147)}, '29': {'index': '{5 5 5}', 'recip_distances': np.float64(2.1235481432603565), 'structure_factor': np.float64(4.422685732877285), 'multiplicity': np.int64(56), 'intensity': np.float64(1095.3683491405918)}, '30': {'index': '{6 6 2}', 'recip_distances': np.float64(2.1376582529256405), 'structure_factor': np.float64(4.37914286212942), 'multiplicity': np.int64(8), 'intensity': np.float64(153.4151376555124)}, '31': {'index': '{6 2 6}', 'recip_distances': np.float64(2.137658252925641), 'structure_factor': np.float64(4.379142862129421), 'multiplicity': np.int64(16), 'intensity': np.float64(306.83027531102493)}, '32': {'index': '{5 7 3}', 'recip_distances': np.float64(2.2339349661969248), 'structure_factor': np.float64(4.098006979092962), 'multiplicity': np.int64(48), 'intensity': np.float64(806.095737633342)}, '33': {'index': '{4 6 6}', 'recip_distances': np.float64(2.3002382226587366), 'structure_factor': np.float64(3.9192757947321013), 'multiplicity': np.int64(24), 'intensity': np.float64(368.65734612415065)}, '34': {'index': '{7 5 5}', 'recip_distances': np.float64(2.4397710683797262), 'structure_factor': np.float64(3.577655721287873), 'multiplicity': np.int64(48), 'intensity': np.float64(614.3817820830649)}, '35': {'index': '{7 7 3}', 'recip_distances': np.float64(2.536432846056741), 'structure_factor': np.float64(3.3650622751716925), 'multiplicity': np.int64(24), 'intensity': np.float64(271.76745877880853)}, '36': {'index': '{6 6 6}', 'recip_distances': np.float64(2.548257771912428), 'structure_factor': np.float64(3.340273246671722), 'multiplicity': np.int64(8), 'intensity': np.float64(89.25940289944677)}, '37': {'index': '{7 7 5}', 'recip_distances': np.float64(2.7194685170931825), 'structure_factor': np.float64(3.008047711514111), 'multiplicity': np.int64(24), 'intensity': np.float64(217.16042483388674)}}, 'scale': np.float64(0.0012181957344571128)}, 'analysis': {'name': 'Indexing_Diffraction_Rings'}}
h5_group = pyTEMlib.file_tools.save_dataset(datasets, filename='Gold_SAED.hf5')
h5_group.file.close()
C:\Users\gduscher\AppData\Local\anaconda3\Lib\site-packages\pyNSID\io\hdf_io.py:111: UserWarning: main_data_name should not contain the "-" character. Reformatted name from:GOLD-NP-DIFF to GOLD_NP_DIFF
  warn('main_data_name should not contain the "-" character. Reformatted'
C:\Users\gduscher\AppData\Local\anaconda3\Lib\site-packages\pyNSID\io\hdf_utils.py:381: FutureWarning: validate_h5_dimension may be removed in a future version
  warn('validate_h5_dimension may be removed in a future version',

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

current_filename = 'Gold_SAED.hf5'
new_datasets = pyTEMlib.file_tools.open_file(current_filename)
chooser = pyTEMlib.file_tools.ChooseDataset(new_datasets)
Loading...
new_dataset = chooser.dataset
new_dataset.metadata['SAED'].keys()
dict_keys(['center', 'radial_average', 'ring_radii_px', 'scale', 'reflections'])

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

new_dataset = chooser.dataset
diff_pattern = np.array(new_dataset)
diff_pattern = diff_pattern-diff_pattern.min()

center = new_dataset.metadata['SAED']['center']
scale = new_dataset.metadata['SAED']['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 = new_dataset.metadata['SAED']['radial_average']
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 key, reflection in new_dataset.metadata['SAED']['reflections'].items():
    distance = reflection['recip_distances']
    if distance < len(profile)*scale:
        plt.plot([distance,distance], [0, reflection['intensity']/10000],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 = new_dataset.metadata['SAED']['reflections'][str(i)]['index'] # pretty index string
    plt.text(unique[i],-0.05, index, horizontalalignment='center',
          verticalalignment='top', rotation = 'vertical', fontsize=8, color = 'white')
Loading...

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.