Chapter 2: Diffraction
Analyzing Ring Diffraction Pattern¶
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 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)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
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]
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'] = centerradius = 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()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()
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]
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);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')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.
Navigation¶
Back: Structure Factors
Next: Scttering Geometry
Chapter 2: Diffraction
List of Content: Front
current_filename = 'Gold_SAED.hf5'
new_datasets = pyTEMlib.file_tools.open_file(current_filename)
chooser = pyTEMlib.file_tools.ChooseDataset(new_datasets)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')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.
Navigation¶
Back: Basic Crystallography
Chapter 2: Diffraction
List of Content: Front