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.

Probe

Image Tools


OpenInColab

Probe

part of

pyTEMlib

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 χ\chi

Please see this notebook for a more detailed discussion of the Aberration Function

With the main aberration coefficients Cn,mC_{n,m}:

Coefficient NionCEOSName
C10C_{10}C1C_1defocus
C12aC_{12a}, C12bC_{12b}A1A_1astigmatism
C21aC_{21a}, C21bC_{21b}B2B_2coma
C23aC_{23a}, C23bC_{23b}A2A_2three-fold astigmatism
C30C_{30}C3C_3spherical aberration

As before the aberration function χ\chi in polar coordinates (of angles) θ\theta and ϕ\phi is defined according to Krivanek et al.:

χ(θ,ϕ)=nθn+11n+1nCn,m,acos(mϕ)+Cn,m,bsin(mϕ)\chi(\theta, \phi) = \sum_n \theta^{n+1} *\frac{1}{n+1} * \sum_{n} C_{n,m,a} \cos(m*\phi) + C_{n,m,b} \sin(m*\phi)

with:

  • nn: order (n=0,1,2,3,...n=0,1,2,3,...)

  • mm: symmetry m=...,n+1m = ..., n+1;

    • mm is all odd for n = even

    • mm 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 ϕ\phi and θ\theta 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 C10C_{10} and astigmatism C12aC_{12a} and C12bC_{12b} on the ronchigram can be explored below. Also change coma C21aC_{21a} and C21bC_{21b} and spherical aberration C30C_{30} 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}****
Loading...

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
Loading...
Loading...

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
Loading...
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)
Loading...
Loading...

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
Loading...

References

References
  1. 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