Chapter 3: Imaging


3.10. Z-Contrast Imaging#

Download

Open In Colab

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
The University of Tennessee, Knoxville

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:

J. Madsen & T. Susi, “The abTEM code: transmission electron microscopy from first principles”, Open Research Europe 1: 24 (2021)

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