Chapter 3: Imaging
3.10. Z-Contrast Imaging#
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.
Note: This notebook needs a Linux environment and is most easily run in Colab
3.10.1. Load important packages#
In Colab the next code cell must be run first
3.10.1.1. Check Installed Packages#
import sys
from pkg_resources import get_distribution, DistributionNotFound
def test_package(package_name):
"""Test if package exists and returns version or -1"""
try:
version = get_distribution(package_name).version
except (DistributionNotFound, ImportError) as err:
version = '-1'
return version
# Colab setup ------------------
if 'google.colab' in sys.modules:
!pip install pyTEMlib -q
!pip install ase -q
!pip install ipympl -q
!pip install abtem -q
# pyTEMlib setup ------------------
else:
if test_package('pyTEMlib') < '0.2import 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
if test_package('abtem') < '1.0.0b17':
print('installing abtem')
!{sys.executable} -m pip install --upgrade abtem -q
print('done')023.3.0':
print('installing pyTEMlib')
!{sys.executable} -m pip install --upgrade pyTEMlib -q
if test_package('abtem') < '1.0.0b17':
print('installing abtem')
!{sys.executable} -m pip install --upgrade abtem -q
# ------------------------------
print('done')
done
3.10.1.2. Load Packages#
We will use
numpy and matplotlib
physical constants from scipy
The pyTEMlib kinematic scattering librarty is only used to determine the wavelength.
abTEM
please cite abTEM methods article:
# import matplotlib and numpy
# use "inline" instead of "notebook" for non-interactive plots
import sys
%matplotlib ipympl
if 'google.colab' in sys.modules:
from google.colab import output
output.enable_custom_widget_manager()
import matplotlib.pyplot as plt
import numpy as np
# import atomic simulation environment
import ase
import ase.spacegroup
import ase.visualize
# import abintio-tem library
import abtem
__notebook__ = 'CH3_09-Z_Contrast'
__notebook_version__ = '2021_03_29'
3.10.2. Z-contrast imaging#
A Z-contrast image is acquired by scanning a convergent
beam accross the sample and collecting signals with an annular
(ring-like) detector. The detector sits in the convergent beam electron diffraction pattern plane and so it integrates over a ring-like part of the convergent beam diffraction (CBED) pattern.
More generally a scanning transmisison electron microscopy (STEM) image is still scanning the same probe but integrates over different portions of the CBED pattern. A bright field detector
for instance integrates over the inner part of the CBED pattern and is disk-like
.
3.10.3. Make Structure and Potential with Frozen Phonons#
As in the Dynamic Diffraction with Frozen Phonons part in the Thermal Diffuse Scattering notebook, we first define the potential of the slices.
Again we use the abtem
and ase
packages to do this
3.10.3.1. Defining the structure#
Here we make a SrTiO\(_3\) crystal again
# ------- Input -----------#
thickness =30 # in nm
number_of_layers = 2 # per unit cell
# -------------------------#
atom_pos = [(0.0, 0.0, 0.0), (0.5, 0.5, 0.5), (0.5, 0.5, 0.0)]
srtio3 = ase.spacegroup.crystal(['Sr','Ti','O'], atom_pos, spacegroup=221, cellpar=3.905, size=(8, 8, 60))
srtio3.center()
print(f"Simulation cell: {srtio3.cell}")
abtem.show_atoms(srtio3);
Simulation cell: Cell([31.24, 31.24, 234.29999999999998])
3.10.3.2. Make the potential#
with frozen phonon approximation
# ------ Input ------ #
number_of_frozen_phonon_runs = 12
# --------------------#
frozen_phonons = abtem.FrozenPhonons(srtio3, number_of_frozen_phonon_runs, {'Sr' : .1, 'Ti' : .1, 'O' : .1}, seed=1)
tds_potential = abtem.Potential(frozen_phonons, gpts=512, slice_thickness=3.905/2,
projection='infinite', parametrization='kirkland')
print(f"Real space sampling: {tds_potential.sampling} Angstrom ")
Real space sampling: (0.061015625, 0.061015625) Angstrom
3.10.3.3. Make the probe#
The probe has to be on the same grid (matrix, pixels) as the potential, which is ensured with the grid.match
function.
# ---- Input ----- #
convergence_angle = 20 # in mrad of half angle
acceleration_voltage = 80e3 # in V
defocus = 40 # in nm
C_s = .5 # in mm conversion to ase and Angstrom *1-6 * 1e10 = 1e4
# -----------------#
probe = abtem.Probe(energy=acceleration_voltage, semiangle_cutoff=convergence_angle,
defocus=defocus, Cs=C_s*1e4)
probe.grid.match(tds_potential)
3.10.4. Calculate CBED pattern#
As discussed before, in STEM
we integrate over portions of the CBED pattern
so we first calculate that.
Because thermal diffuse scattering
is so important we are using the thermal diffuse potential in the frozen phonon approximation
.
Takes about 2 min on my laptop
detector = abtem.PixelatedDetector(max_angle='limit')
probe.grid.match(tds_potential)
tds_cbed = probe.multislice(tds_potential)
tds_cbed.mean(0).show(power=0.25)
[########################################] | 100% Completed | 24.66 ss
<abtem.visualize.MeasurementVisualization2D at 0x1e68b187010>
3.10.5. Detectors#
The detectors are definded by their angles and ususally in mrad.
The detector are overlayed over the CBED pattern to indicate which part of the CBED is integrated to get the intensity of one pixel.
Well, integration is may be a strong word for multiplying with a mask (1 where detector and 0 elsewhere) and sum over all pixels.
bright = abtem.AnnularDetector(inner=0, outer=20)
haadf = abtem.AnnularDetector(inner=90, outer=200)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8,4), sharex=True, sharey=True)
tds_cbed.mean(0).show(power=0.25, ax=ax1, cmap='viridis')
tds_cbed.mean(0).show(power=0.25, ax=ax2, cmap='viridis')
bright.show(probe, ax=ax1, title='Bright field', alpha=.4)
haadf.show(probe, ax=ax2, title='HAADF', alpha=.4);
ax1.set_xlim(-210,210)
ax1.set_ylim(-210,210)
ax2.set_xlim(-210,210)
ax2.set_ylim(-210,210)
[########################################] | 100% Completed | 24.99 ss
[########################################] | 100% Completed | 25.23 ss
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[42], line 8
6 tds_cbed.mean(0).show(power=0.25, ax=ax1, cmap='viridis')
7 tds_cbed.mean(0).show(power=0.25, ax=ax2, cmap='viridis')
----> 8 bright.show(probe, ax=ax1, title='Bright field', alpha=.4)
9 haadf.show(probe, ax=ax2, title='HAADF', alpha=.4);
10 ax1.set_xlim(-210,210)
AttributeError: 'AnnularDetector' object has no attribute 'show'
We see that the HAADF detector is dominated by the features of thermal diffuse scattering.
While that part is not terribly important in the bright field image.
Please note:
The detectors have to be well aligned on the optical axis or the simulation here is not valid.
3.10.6. Scanning the Probe#
3.10.6.1. Get data directory#
data_directory = './'
if 'google.colab' in sys.modules:
from google.colab import drive
drive.mount('/content/drive')
!mkdir '/content/drive/My Drive/abtem'
data_directory = '/content/drive/My Drive/abtem/'
3.10.6.2. Define scanning area#
grid_scan = abtem.GridScan(
start=(0, 0),
end=[1/8, 1/8],
sampling=probe.aperture.nyquist_sampling,
fractional=True,
potential=tds_potential,
)
fig, ax = abtem.show_atoms(srtio3)
grid_scan.add_to_plot(ax)
detector = abtem.FlexibleAnnularDetector()
gridscan = abtem.GridScan(start=[0, 0], end=[1/8,1/8], sampling=probe.ctf.nyquist_sampling * .9)
ax, im = tds_potential.project().show();
gridscan.add_to_mpl_plot(ax)
[########################################] | 100% Completed | 14.83 s
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[45], line 5
1 detector = abtem.FlexibleAnnularDetector()
3 gridscan = abtem.GridScan(start=[0, 0], end=[1/8,1/8], sampling=probe.ctf.nyquist_sampling * .9)
----> 5 ax, im = tds_potential.project().show();
7 gridscan.add_to_mpl_plot(ax)
TypeError: cannot unpack non-iterable MeasurementVisualization2D object
3.10.6.3. Now we are scanning#
The results are going to be stored to file.
This takes about 20 min on my laptop
flexible_measurement = probe.scan(tds_potential, scan=grid_scan, detectors=detector)
flexible_measurement.compute()
[########################################] | 100% Completed | 211.95 s
<abtem.measurements.PolarMeasurements at 0x1e68d081c50>
3.10.6.4. Integrate measurements#
The measurements are integrated to obtain the bright field, medium-angle annular dark field and high-angle annular dark field signals.
bf_measurement = flexible_measurement.integrate_radial(0, probe.semiangle_cutoff)
maadf_measurement = flexible_measurement.integrate_radial(50, 150)
haadf_measurement = flexible_measurement.integrate_radial(90, 200)
3.10.6.5. Plot images#
measurements = abtem.stack(
[bf_measurement, maadf_measurement, haadf_measurement], ("BF", "MAADF", "HAADF")
)
measurements.show(
explode=True,
figsize=(14, 5),
cbar=True,
);
3.10.6.6. Plotting result#
haadf_image = haadf_measurement.tile((4, 4))
haadf_image = haadf_image.interpolate(.04)
bf_image = bf_measurement.tile((4, 4))
bf_image = bf_image.interpolate(.04)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8,4), sharex=True, sharey=True)
_, im1= haadf_image.show(ax=ax1, cmap = 'viridis');
fig.colorbar(im1, ax=ax1, orientation='vertical')
_, im2 = bf_image.show(ax=ax2, cmap = 'viridis');
fig.colorbar(im2, ax=ax2, orientation='vertical')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[37], line 9
5 bf_image = bf_image.interpolate(.04)
7 fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8,4), sharex=True, sharey=True)
----> 9 _, im1= haadf_image.show(ax=ax1, cmap = 'viridis');
10 fig.colorbar(im1, ax=ax1, orientation='vertical')
11 _, im2 = bf_image.show(ax=ax2, cmap = 'viridis');
TypeError: cannot unpack non-iterable MeasurementVisualization2D object
3.10.7. Experimental Consideration#
We need an as small a probe as possible.
This will depend on the instrument especially everything before the sample!
This will depend on the defocus.
This will depend on the aperture, which will depend on the instrument and the largest coherent area.
This will depend on the aberrations and how well you corrected them
We need to be tilted in the relevant zone axis
We need a relatively thin specimen location
3.10.8. Summary#
For a quantitative image simulation we need to do dynamic scattering theory.
The dynamic scattering theory is done within the multislice algorithm that treats each slice like a weak phase object.
Thermal defuse scattering needs to be included into the multislice calculations for a good simulation
The thermal diffuse scattering can be approximated by the frozen phonon approximation but it is computationally intensive.