Chapter 3: Imaging
3.4. Linear Image Approximation: Weak Phase Object#
part of
MSE672: Introduction to Transmission Electron Microscopy
Spring 2024
Gerd Duscher | Khalid Hattar |
Microscopy Facilities | Tennessee Ion Beam Materials Laboratory |
Materials Science & Engineering | Nuclear Engineering |
Institute of Advanced Materials & Manufacturing | |
Background and methods to analysis and quantification of data acquired with transmission electron microscopes.
3.4.1. Load important packages#
3.4.1.1. 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.2024.2.3':
print('installing pyTEMlib')
!{sys.executable} -m pip install --upgrade pyTEMlib -q
print('done')
done
3.4.1.2. Load Packages#
We will use
numpy and matplotlib (installed with magic comand %pylab.)
physical constants from scipy
The pyTEMlib kinematic scattering librarty is only used to determine the wavelength.
# import matplotlib and numpy
# use "inline" instead of "notebook" for non-interactive plots
%matplotlib widget
import matplotlib.pyplot as plt
import numpy as np
import sys
if 'google.colab' in sys.modules:
from google.colab import output
output.enable_custom_widget_manager()
import scipy.constants
# Import libraries from pyTEMlib
import pyTEMlib
import pyTEMlib.kinematic_scattering as ks
import pyTEMlib.dynamic_scattering as ds
# For archiving reasons it is a good idea to print the version numbers out at this point
print('pyTEM version: ',pyTEMlib.__version__)
__notebook__ = 'CH3-04-Linear_Image_Approximation'
__notebook_version__ = '2024_03_01'
You don't have igor2 installed. If you wish to open igor files, you will need to install it (pip install igor2) before attempting.
You don't have gwyfile installed. If you wish to open .gwy files, you will need to install it (pip install gwyfile) before attempting.
Symmetry functions of spglib enabled
Using kinematic_scattering library version {_version_ } by G.Duscher
pyTEM version: 0.2024.02.2
3.4.2. Weak Phase Object#
the simplest approximation is for the sample to be very thin and then then the incident electrons pass through the specimen with only small deviations in their paths, which can be approximated by a phase change only. These influeces of a specimen can be modeled as a simple transmission function \(t (x)\). The electron wave function after passing through the specimen is: $\( \Psi_t(x) = t(x) \Psi_{inc}(x) \)$
with \(\Psi_{inc}\):incident wave function
This transmisison function is the same that we used for a thin slice in the multislice algorithm. So we approximate here a sample by a thin slice.
The exit wave is then distorted by the aberration function \(\chi(\vec{k})\) of the objective lens. Because this distortion function is defined in reciprocal space it is convenient to address the image distortion as a convolution. We descibe this effect of the objective lens as shifts to the phase of each frequency component by a different amount according to the aberration function \(\chi(\vec{k})\). this convolution is a multiplicaiton in Fourier Space.
3.4.3. Projected Potential of Supercell#
As in the Dynamic Diffraction part in the Multislice notebook, we first define a potential.
I put the code from that notebook into a library dynamic_scattering
which we will use here.
Here we make a bcc iron crystal, which is unrealistically thin (1 unit cell).
nx = ny = 512
n_cell_x = 16
a = 2.866 # lattice parameter in Angstrom
potential = ds.potential_2dim('Fe', nx, nx, n_cell_x, n_cell_x, a, [[0,0], [0.5,0.5]])
pixel_size = a/(nx/n_cell_x)
image_extent=[0,nx*pixel_size, ny*pixel_size, 0 ]
plt.figure()
plt.imshow((potential), extent=image_extent)
plt.xlabel('distance ($\AA$)')
Text(0.5, 0, 'distance ($\\AA$)')
3.4.4. Transmission Function for Very Thin Specimen#
For a very thin specimen the weak phase approximation
is the simples way to calculate a high resolution (phase contrast) image.
In that approximation, the sample causes only a phase change to the incident plane wave.
To retrieve the exit we just multiply the transmission function \(t(\vec{x})\) with the plane wave \(\exp (2\pi i k_z z)\)
The specimen transmission function depends on the projected potential \(v_z(\vec{x})\) and the interaction parameter \(\sigma\): $\(t(\vec{x}) = \exp \left( i \sigma v_z(\vec{x})\right)\)$
with the interaction parameter \(\sigma\): $\( \sigma = \frac{2 \pi}{\lambda V} \left( \frac{m_0 c^2 + eV}{2m_0c^2+eV} \right) = \frac{2 \pi m e_0 \lambda}{h^2} \)\( with \) m = \gamma m_0\( and \)eV$ the incident electron energy.
Again we did this already in the Multislice notebook, and I put the code from that notebook into the dynamic_scattering
library.
acceleration_voltage = 200000
transmission = ds.get_transmission(potential, acceleration_voltage)
plt.figure()
plt.imshow(transmission.imag, extent = image_extent)
plt.xlabel('distance ($\AA$)')
Text(0.5, 0, 'distance ($\\AA$)')
Please note that there is no effect by multiplying with a plane wave
plane_wave = np.ones([nx,ny], dtype=complex)
exit_wave = plane_wave * transmission
plt.figure()
plt.imshow(np.abs(exit_wave.real), extent=image_extent)
plt.xlabel('distance ($\AA$)')
Text(0.5, 0, 'distance ($\\AA$)')
3.4.5. Aberration Function#
We make the aberration function like in the Contrast Transfer notebook
def make_chi(theta, phi, wavelength, ab):
"""Calculate aberration function chi
Input:
------
theta, phi: numpay array (n x m)
angle meshes of the reciprocal space
wavelength: float
wavelength in nm
ab: dict
aberrations in nm should at least contain defocus (C10), and spherical abeeration (C30)
Returns:
--------
chi: numpy array (nxm)
aberration function
"""
if 'C10' not in ab:
ab['C10'] = 0.
if 'C12a' not in ab:
ab['C12a'] = 0.
if 'C12b' not in ab:
ab['C12b'] = 0.
# defocus and astigmatism
t1 = np.power(theta, 2)/2 * (ab['C10'] + ab['C12a'] * np.cos(2 * phi) + ab['C12b'] * np.sin(2 * phi))
# coma and three fold astigmatism
if 'C21a' in ab and 'C21b' in ab and 'C23a' in ab and 'C23b' in ab:
t2 = np.power(theta, 3)/3 * (ab['C21a'] * np.cos(1*phi) + ab['C21b'] * np.sin(1*phi))
else:
t2 = theta*0.
# spherical aberration
if 'C30' not in ab:
ab['C30'] = 0.
t3 = np.power(theta, 4)/4 * ab['C30']
chi = t1 + t2+ t3
return chi * 2 * np.pi / wavelength # np.power(theta,6)/6*( ab['C50'] )
def objective_lens_function(ab, nx, ny, field_of_view, wavelength, aperture_size=10):
"""Objective len function to be convoluted with exit wave to derive image function
Input:
ab: dict
aberrations in nm should at least contain defocus (C10), and spherical abeeration (C30)
nx: int
number of pixel in x direction
ny: int
number of pixel in y direction
field_of_view: float
field of fiew of potential
wavelength: float
wavelength in nm
aperture_size: float
aperture size in 1/nm
Returns:
--------
object function: numpy array (nx x ny)
extent: list
"""
# Reciprocal plane in 1/nm
dk = 1 / field_of_view
t_xv, t_yv = np.mgrid[int(-nx/2):int(nx/2),int(-ny/2):int(ny/2)] *dk
# define reciprocal plane in angles
phi = np.arctan2(t_yv, t_xv)
theta = np.arctan2(np.sqrt(t_xv**2 + t_yv**2), 1/wavelength)
mask = theta < aperture_size * wavelength
# calculate chi
chi = make_chi(theta, phi, wavelength, ab)
extent = [-nx/2*dk, nx/2*dk, -nx/2*dk,nx/2*dk]
return np.exp(-1j*chi)*mask, extent
acceleration_voltage = 200000
ab={'C10':-910.0, 'C12a':0.0, 'C12b':0.0, 'C30': 2.2*1e7} # aberrations in Angstrom
wavelength = ks.get_wavelength(acceleration_voltage)
objective_lens, extent = objective_lens_function(ab, nx, nx, nx*pixel_size, wavelength, 1/1.8)
plt.figure()
plt.imshow(objective_lens.real, extent=extent)
plt.xlabel('reciprocal distance (1/nm)');
3.4.6. Image Simulation in Weak Phase Approximation#
In the weak phase approximation the image is just the convoltuion of the transmission function and the objective lens funtion.
If an aperture selects only the inner smooth part of the objetive function in Scherzer defocus, the image is naively to interpret as the dark parts as the atoms (remember the CTF is negative in that case)
Q_k = np.fft.fftshift(np.fft.fft2(transmission))
psi_r = np.fft.fftshift(np.fft.ifft2((Q_k*objective_lens)))
image = np.real(psi_r*np.conjugate(psi_r))
plt.figure()
plt.imshow(image.real);
3.4.7. Influence of Aberrations on Image#
Within this weak phase object aberration, we can already investigate the influence of lens aberrations on the image.
We do now all steps together and check the effect of the aberration, acceleration voltage, aperture, and element onto the final image (in weak phase approximation).
Try an astigmatism (\(C_{12}\)) value of 30 nm to see an effect. How small of a value is invisible? How does astigmatism (\(C_{12}\)) depend on defocus (\(C_{10}\))?
# -----Input -----
ab={'C10':-110.0, 'C12a': 30.0, 'C12b': 40.0, 'C30': 0.5} # aberrations in nm
# -------------
nx = ny = 1024
n_cell_x = 16
a = 2.4
acceleration_voltage = 200000
resolution = 1.
potential = ds.potential_2dim('Au', nx, nx, n_cell_x, n_cell_x, a, [[0,0], [0.5,0.5]])
pixel_size = a/(nx/n_cell_x)
transmission = ds.get_transmission(potential, acceleration_voltage)
wavelength = ks.get_wavelength(acceleration_voltage)
objective_lens, extent_r = objective_lens_function(ab, nx, nx, nx*pixel_size, wavelength, 1/resolution)
Q_k = np.fft.fftshift(np.fft.fft2(transmission))
psi_r = np.fft.fftshift(np.fft.ifft2((Q_k*objective_lens)))
image = np.real(psi_r*np.conjugate(psi_r))
fig, ax = plt.subplots(1, 2, figsize=(8, 4))
ax[0].imshow(objective_lens.real, extent=extent_r)
ax[0].set_xlabel('reciprocal distance (1/$\AA$)')
ax[0].set_xlim(-1,1)
ax[0].set_ylim(-1,1)
ax[1].imshow(image.real, extent=[0,nx*pixel_size, ny*pixel_size, 0 ])
ax[1].set_xlabel('distance ($\AA$)')
Text(0.5, 0, 'distance ($\\AA$)')
3.4.8. Summary#
The weak phase object allows for a fast check on image parameters. For a quantitative image simulation we need to do dynamic scattering theory. Please go to the Defocus-Thickness notebook