Probe¶
part of
a pycroscopy ecosystem package
Notebook by Gerd Duscher, 2023
Microscopy Facilities
Institute of Advanced Materials & Manufacturing
The University of Tennessee, Knoxville
Model based analysis and quantification of data acquired with transmission electron microscopes
December 2025 efocused
Content¶
Calculation of electron probe shape from aberrations. This requires first to calculate the phase plate (Ronchigram without sample) and then an inverse Fourier transform. The probe can then be used to simulate an atomic resolution image or one can derive that shape from a defocused image.
Load packages¶
Check for Newest Versions¶
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.2024.2.3':
print('installing pyTEMlib')
!{sys.executable} -m pip install --upgrade pyTEMlib -q
print('done')Load Necessary Packages¶
%matplotlib widget
import matplotlib.pyplot as plt
import numpy as np
import sys
import os
%load_ext autoreload
%autoreload 2
sys.path.insert(0,'..//..//pyTEMlib//')
if 'google.colab' in sys.modules:
from google.colab import output
from google.colab import drive
output.enable_custom_widget_manager()
from matplotlib.patches import Circle
import pyTEMlib
import pyTEMlib.probe_tools
print('pyTEM version: ', pyTEMlib.__version__)pyTEM version: 0.2026.1.0
Aberration Function ¶
Please see this notebook for a more detailed discussion of the Aberration Function
With the main aberration coefficients :
| Coefficient Nion | CEOS | Name |
|---|---|---|
| defocus | ||
| , | astigmatism | |
| , | coma | |
| , | three-fold astigmatism | |
| spherical aberration |
As before the aberration function in polar coordinates (of angles) and is defined according to Krivanek et al.:
with:
: order ()
: symmetry ;
is all odd for n = even
is all even for n = odd
In the following we program the equation above just as seen. The terms are divided into the theta (line 22) and the sum part (line 33). The product of these two terms is summed in line 39.
We assume that the aberrations are given up to fifth order.
We see that increasing the size of an coherently illuminated aperture is reducing the probe diameter and therefore improving spatial resolution. The goal is to obtain an as large radius as possible of quasi-coherent area (in reciprocal space) match it with an aperture and use that as a probforming configuration.
The Ronchigram will get us that coherent illumination and we will discuss this Ronchigram in detail in this notebook.
Calculate Probe from Ronchigram¶
Setting up the meshes of angles and in polar coordinates for which the aberrations will be calculated. This is analog to what we did in the Aberration Function notebook.
The effect of defocus and astigmatism and on the ronchigram can be explored below. Also change coma and and spherical aberration or anyother aberration you might want to test.
Load Aberrations¶
The aberrations are expected to be in a python dictionary.
Besides the aberration coefficients, for the calculation of the aberrations the wavelength and therefore the acceleration voltage is needed.
Here we load the aberration for a microscope: the following microscpes are available:
ZeissMC200
NionUS200
NionUS100
Spectra300
ab = pyTEMlib.probe_tools.get_target_aberrations("ZeissMC200",200000)
ab = pyTEMlib.probe_tools.get_target_aberrations("NionUS200",200000)
#ab = pyTEMlib.probe_tools.get_target_aberrations("NionUS100",60000)
ab = pyTEMlib.probe_tools.get_target_aberrations("Spectra300", 200000)
ab['C10'] = -5
reciprocal_FOV = ab['reciprocal_FOV'] = 150*1e-3
ab['FOV'] = 1/reciprocal_FOV
ab['extent'] = [-reciprocal_FOV*1000,reciprocal_FOV*1000,-reciprocal_FOV*1000,reciprocal_FOV*1000]
ab['size'] = 512
ab['wavelength'] = pyTEMlib.utilities.get_wavelength(ab['acceleration_voltage'], unit='A')
pyTEMlib.probe_tools.print_aberrations(ab) **** Using Target Values at 200.0kV f for Aberrations of {tem_name}****
Probe Shape Calculation¶
The probe shape is defined as the absolute of the inverse of the aberration function which is limited by an aperture.
ab.update({'A1': [-1.7256467648864592e-09, -4.33652950047942e-09],
'A2': [1.1832002758281756e-07, -9.356132757317088e-08],
'C3': [3.9123259711154475e-07, 0.0],
'C1': [-7.160069847952156e-09, 0.0],
'A4': [-1.2421582380458277e-06, -6.555555994007509e-07],
'A3': [9.14858860850468e-08, 8.24581795110536e-08],
'A5': [2.4305034548402935e-05, -4.3665588715379156e-05],
'B2': [-3.583665699539742e-08, -7.368501963663006e-08],
'B4': [2.7117076219729385e-07, 3.745259044319655e-06],
'S3': [1.892912397258682e-07, -5.2074838081861786e-08],
'C5': [-1.4203666265095235e-05, 0.0],
'D4': [-1.0812219869916382e-07, 4.316301635786145e-06],
'WD': [0.000120721584514962, 2.8815317878038834e-05]},)
sizeX = 512*2
probe_FOV = 10
ab['Cc'] = 1
ab['C10'] = 0
ronchi_FOV = 350 #mrad
condensor_aperture_radius = 30 # mrad
ronchi_condensor_aperture_radius = 30 # mrad
ab['FOV'] = probe_FOV
ab['convergence_angle'] = condensor_aperture_radius
probe, A_k, chi = pyTEMlib.probe_tools.get_probe( ab, sizeX, sizeX, verbose= True)
ab['FOV'] = 4/ronchi_FOV*sizeX/2 * ab['wavelength'] *1000
ab['convergence_angle'] = ronchi_condensor_aperture_radius ## let have a little bit of a view
ronchigram = pyTEMlib.probe_tools.get_ronchigram(1024, ab, scale = 'mrad' )
ab['FOV'] = probe_FOV
plt.figure()
fig, ax = plt.subplots(1, 2, figsize=(8, 4))
ax[0].imshow(probe, extent = [-int(ab['FOV']/2),int(ab['FOV']/2),-int(ab['FOV']/2),int(ab['FOV']/2)])
ax[0].set_ylabel('distance [nm]')
profile = probe[:, 256]
ax[0].plot(np.linspace(-ab['FOV']/2,ab['FOV']/2,profile.shape[0]), probe[int(probe.shape[1]/2),:]/probe[int(probe.shape[1]/2),:].max()*probe_FOV/2.1)
ax[0].set_title('Probe')
ax[1].imshow(ronchigram, extent = ab['ronchi_extent'])
ax[1].set_ylabel(ab['ronchi_label'])
ax[1].set_title('Ronchigram')
pyTEMlib.probe_tools.print_aberrations_polar(ab)
plt.show()
#condensor_aperture = Circle((0, 0), radius = condensor_aperture_radius)#, fill = False, color = 'red')
#plt.gca().add_patch(condensor_aperture);Acceleration voltage 200.0kV => wavelength 2.51pm
0.03
0.03
Probe Shape from Cross-Correlation¶
import skimage
import scipy
def get_probe_large(ab):
ab['FOV'] = 20
sizeX = 512*2
probe, A_k, chi = pyTEMlib.probe_tools.get_probe( ab, sizeX, sizeX, verbose= True)
res = np.zeros((512, 512))
res[256-32:256+32, 256-32:256+32 ] = skimage.transform.resize(probe, (64, 64))
return res
from skimage.draw import random_shapes
def get_large_image(number_of_electrons=1000):
image, _ = random_shapes((512, 512), min_shapes=15, max_shapes=26, shape='circle',
min_size=40, max_size = 60, allow_overlap=False, num_channels=1)
image = 1-np.squeeze(image)/image.max()
image[image<.1] = 0
image[image>0] = number_of_electrons
noise = np.random.poisson(image)
image = image+noise+np.random.random(image.shape)*noise.max()
return image
def large_image (ab, image=None, FOV=15*4) :
out = np.zeros([3, image.shape[0], image.shape[1]])
C10 = ab['C10']
ab['C10'] = C10-180
res = get_probe_large(ab)
out[1] = scipy.signal.fftconvolve(image, res, mode='same')
ab['C10'] = C10+180
res = get_probe_large(ab)
out[2] = scipy.signal.fftconvolve(image, res, mode='same')
ab['C10'] = C10
res = get_probe_large(ab)
out[0] = scipy.signal.fftconvolve(image, res, mode='same')
return out, res
ab['C10'] = 30
ab['C23b'] = 400
ab['C23a'] = -200
ab['C21a'] = -400
ab['C21b'] = -00
image = get_large_image(number_of_electrons=10)
imaged, res = large_image(ab, image, 90)
image *= 1000/image.max()
fig, axs = plt.subplots(1, 4, layout='constrained', figsize=(10, 4))
axs[0].imshow(imaged[0], cmap='gray')
axs[1].imshow(imaged[1], cmap='gray')
axs[2].imshow(imaged[2], cmap='gray')
axs[3].imshow(res)
axs[3].set_ylim(256-30,256+30)
axs[3].set_xlim(256-30,256+30)
plt.show()Acceleration voltage 200.0kV => wavelength 2.51pm
0.03
Acceleration voltage 200.0kV => wavelength 2.51pm
0.03
Acceleration voltage 200.0kV => wavelength 2.51pm
0.03
def get_probe_shape_from_images(images):
im1 = np.fft.fft2(np.array(images[0]))
im2 = np.fft.fft2(np.array(images[1]))
im3 = np.fft.fft2(np.array(images[2]))
g1 = np.fft.fft2(pyTEMlib.probe_tools.make_gauss(512,512,3))
dec = np.zeros([2, im1.shape[0], im1.shape[1]])
dec[0] = np.fft.ifft2(g1*(im2/im1).real.T)
dec[1] = np.fft.ifft2(g1*(im3/im1).real.T)
return dec
dec = get_probe_shape_from_images(imaged)
plt.figure()
plt.subplot(211)
plt.imshow(dec[0].real.T, vmin = dec.real.max()/8)
plt.ylim(256-30,256+30)
plt.xlim(256-30,256+30)
plt.subplot(221)
plt.imshow(dec[1].real, vmin = 0)# dec.real.max()/8)
plt.ylim(256-30,256+30)
plt.xlim(256-30,256+30)
plt.show()
pyTEMlib.probe_tools.print_aberrations(ab)C:\Users\gduscher\AppData\Local\Temp\ipykernel_84424\714317887.py:8: ComplexWarning: Casting complex values to real discards the imaginary part
dec[0] = np.fft.ifft2(g1*(im2/im1).real.T)
C:\Users\gduscher\AppData\Local\Temp\ipykernel_84424\714317887.py:9: ComplexWarning: Casting complex values to real discards the imaginary part
dec[1] = np.fft.ifft2(g1*(im3/im1).real.T)
Atomic Resolution Images¶
from ase import build
sizeX = 512
slab = build.fcc100('Al', size=(20, 20, 1), orthogonal=True)
ab['FOV'] = slab.cell[0,0]
print(ab['FOV'] len(slab.positions))
scale = sizeX/ab['FOV']
FOV = ab['FOV']
img = np.zeros([sizeX,sizeX])
for pos in slab.positions:
img += pyTEMlib.probe_tools.make_gauss(sizeX, sizeX, width=0.05*scale, x0=pos[0]*scale-sizeX/2, y0=pos[1]*scale-sizeX/2, intensity=13**2)
import scipy
img2 = scipy.signal.fftconvolve(img, probe, mode='same')
img2.shape
plt.figure()
plt.imshow(img2)
plt.show()57.27564927611035
References¶
- Krivanek, O. L., Dellby, N., & Lupini, A. R. (1999). Towards sub-Å electron beams. Ultramicroscopy, 78(1–4), 1–11. 10.1016/s0304-3991(99)00013-3
