7. Transmission Electron Microscopy#

7.1. Introduction#

Transmission electron microscopy (TEM) lets us see the internal architecture of matter at the molecular scale. By transmitting a beam of electrons through a thin specimen, TEM produces images that reflect differences in how strongly different parts of the sample scatter electrons. In biological applications, this means we can directly visualise macromolecular assemblies, membrane organization, and organelle architecture with a resolution measured in Ångströms rather than micrometers.

The key advantage of TEM over scanning electron microscopy (SEM) is access to internal structure: while SEM probes the surface of a sample, TEM images the interior. The key advantage over X-ray crystallography is that specimens do not need to be crystalline — TEM can image single, non-repeating objects embedded in vitrified ice. This has made TEM central to modern structural biology, enabling structure determination of proteins, viruses, ribosomes, and entire organelles in their near-native state.

This chapter covers how TEM works: how electrons are produced, accelerated, and focused into an image; how image contrast arises from the wave nature of electrons; how the contrast transfer function (CTF) governs which spatial frequencies reach the detector; and why cryogenic conditions are essential for biological specimens.

7.2. The TEM column#

A transmission electron microscope is organized as a tall vertical column kept under high vacuum (\(\sim 10^{-7}\)\(10^{-9}\) mbar). The vacuum is essential for two reasons: electrons scatter strongly from gas molecules, and many components (especially the electron gun) are degraded by oxidation. The column can be divided into three functional zones: the illumination system, the specimen stage, and the imaging system.

7.2.1. Electron gun#

Electrons are produced at the top of the column by an electron gun. Modern biological TEMs typically use a field emission gun (FEG), in which electrons are extracted from a sharp tungsten or ZrO₂-coated tip by a strong electric field. FEGs produce a highly coherent, bright beam with a small energy spread (\(\Delta E \approx 0.3\)\(0.8\) eV), which is important for avoiding chromatic aberrations. The electrons are then accelerated through a voltage of typically 80–300 kV. The accelerating voltage determines the electron wavelength via the relativistic de Broglie relation:

(7.1)#\[ \lambda = \frac{h}{\sqrt{2m_e eV\left(1 + \frac{eV}{2m_e c^2}\right)}} \]

At 300 kV, \(\lambda \approx 1.97\) pm — roughly \(10^5\) times shorter than visible light, which is why electrons can in principle resolve features below 1 Å.

7.2.2. Illumination system#

Below the gun, a series of condenser lenses (C1 and C2, sometimes C3) shape the beam before it reaches the specimen. The first condenser lens (C1) demagnifies the gun crossover to produce a small virtual source. The second condenser lens (C2) controls the convergence angle and the illuminated area on the specimen. A condenser aperture between C2 and the specimen removes electrons at large angles, reducing the beam current and limiting chromatic aberration contributions from beam divergence.

For high-resolution cryo-EM, the illumination is set to near-parallel: a large, coherent patch of the specimen is illuminated simultaneously (wide-field mode), forming a bright-field TEM image on the detector.

7.2.3. Objective lens#

The objective lens is the most critical optical element in the TEM. It is a strong magnetic lens that surrounds the specimen and forms the first, most-magnified intermediate image. Because this lens governs the final image quality, minimising its aberrations (especially spherical aberration \(C_s\)) is the central engineering challenge of TEM design.

Biological TEMs typically use an objective aperture positioned in the back focal plane of the objective. This aperture blocks electrons scattered to large angles, which are incoherent relative to the direct beam and would otherwise add noise and reduce contrast. The aperture size controls the effective resolution and the contribution of amplitude contrast.

7.2.4. Projector system and detection#

Below the objective, a series of intermediate and projector lenses magnify and relay the image to the detector at the bottom of the column. Total magnifications of 10,000–500,000× are routinely available. Modern biological TEMs are equipped with direct electron detectors (DEDs) — chips in which electrons generate signal directly, without an intermediate phosphor screen. DEDs offer high quantum efficiency (DQE \(> 0.8\)), fast readout rates that enable movie-mode acquisition (essential for correcting beam-induced motion), and single-electron counting at low doses.

7.3. Ray diagrams and paraxial optics#

Electron trajectories through the TEM column can be understood using paraxial ray tracing: the approximation that all rays travel close to the optical axis and at small angles, so that lenses can be represented as thin lenses characterised by a single focal length \(f\). Under this approximation, a thin lens transforms ray slope \(u = dr/dz\) according to:

(7.2)#\[ u' = u - \frac{r}{f} \]

where \(r\) is the ray height at the lens plane and \(f\) is the focal length. Between lenses, rays travel in straight lines: \(r' = r + u \cdot \Delta z\).

The simulation below traces rays through a simplified TEM illumination system with two condenser lenses and an objective. You can adjust focal lengths and aperture sizes to explore how the beam is shaped before reaching the specimen.

Interactive element

Click Live Code to activate, expand the Show code toggle, and click ▶ to run. Adjust the sliders to see how condenser focal lengths and apertures shape the electron beam.

Hide code cell source

import io
import numpy as np
import matplotlib
matplotlib.use('agg')
import matplotlib.pyplot as plt
from ipywidgets import FloatSlider, VBox, HBox, Layout, Output, Box
from IPython.display import display, Image, clear_output

def electro_optical_components(focal_lengths, aperture_radii):
    f1, f2, f_obj = focal_lengths
    a1, a2 = aperture_radii
    return [
        {"type": "lens",     "name": "C1",         "axial_position": 0.225,  "focal_length": f1,   "color": "#1f77b4"},
        {"type": "aperture", "name": "spray ap.",  "axial_position": 0.5125, "aperture_radius": a1, "color": "#1f77b4"},
        {"type": "lens",     "name": "C2",         "axial_position": 0.7125, "focal_length": f2,   "color": "#ff7f0e"},
        {"type": "lens",     "name": "obj. upper", "axial_position": 0.95,   "focal_length": f_obj, "color": "#2ca02c"},
        {"type": "sample",   "name": "sample",     "axial_position": 1.00},
        {"type": "lens",     "name": "obj. lower", "axial_position": 1.05,   "focal_length": f_obj, "color": "#2ca02c"},
        {"type": "aperture", "name": "obj ap.",    "axial_position": 1.20,   "aperture_radius": a2, "color": "#2ca02c"},
    ]

def trace_rays(components, z_start=0.0, z_end=1.7, r_max=0.15, n_rays=61, n_steps=1200):
    """Paraxial ray tracing: free propagation + thin-lens kick + aperture blocking."""
    z_grid = np.linspace(z_start, z_end, n_steps)
    comp_sorted = sorted(components, key=lambda c: c["axial_position"])
    rays = []
    for r0 in np.linspace(-r_max, r_max, n_rays):
        r, u, alive, traj, ci, prev_z = r0, 0.0, True, [], 0, z_start
        for z in z_grid:
            if not alive: break
            while ci < len(comp_sorted) and comp_sorted[ci]["axial_position"] <= z:
                cz = comp_sorted[ci]["axial_position"]
                r = r + u * (cz - prev_z); prev_z = cz; traj.append((cz, r))
                c = comp_sorted[ci]
                if c["type"] == "lens":
                    u = u - r / c["focal_length"]
                elif c["type"] == "aperture":
                    if abs(r) > c["aperture_radius"]: alive = False; break
                ci += 1
            if not alive: break
            traj.append((z, r + u * (z - prev_z)))
        rays.append(traj)
    return rays

def draw_ray_diagram(condenser01=0.23, aperture01=0.05,
                     condenser02=0.15, aperture02=0.05,
                     objective=0.35):
    components = electro_optical_components(
        [condenser01, condenser02, objective],
        [aperture01, aperture02]
    )
    rays = trace_rays(components)
    t = np.linspace(0, 2*np.pi, 100)

    fig, ax = plt.subplots(figsize=(3.2, 7.0))
    for traj in rays:
        if len(traj) < 2: continue
        ax.plot([p[1] for p in traj], [p[0] for p in traj],
                color="green", alpha=0.5, linewidth=0.8)
    for c in components:
        if c["type"] == "lens":
            z0, col = c["axial_position"], c.get("color", "black")
            ax.plot([-0.18, 0.18], [z0, z0], color=col, alpha=0.5, linewidth=1.0)
            ax.plot(0.18*np.cos(t), z0 + 0.018*np.sin(t), color=col, linewidth=1.5)
        elif c["type"] == "aperture":
            z0, a, col = c["axial_position"], c["aperture_radius"], c.get("color", "0.6")
            ax.fill_betweenx([z0-0.005, z0+0.005], -0.2, -a,  color=col, alpha=0.35)
            ax.fill_betweenx([z0-0.005, z0+0.005],  a,  0.2,  color=col, alpha=0.35)
        elif c["type"] == "sample":
            z0 = c["axial_position"]
            ax.fill_betweenx([z0-0.005, z0+0.005], -0.1, 0.1, color="#26a7df", alpha=0.5)
    ax.set_xlim(-0.25, 0.25); ax.set_ylim(1.75, -0.05)
    ax.axis("off")
    buf = io.BytesIO()
    fig.savefig(buf, format='png', dpi=96, bbox_inches='tight')
    buf.seek(0); plt.close(fig)
    display(Image(data=buf.read()))

# ------- aligned slider layout -------
style = {"description_width": "140px"}
slider_layout = Layout(width="300px")

condenser01_slider = FloatSlider(value=0.23, min=0.025, max=0.5,  step=0.005,
    description="CL1 focal length", style=style, layout=slider_layout)
aperture01_slider  = FloatSlider(value=0.05, min=0.025, max=0.2,  step=0.005,
    description="CL1 aperture",     style=style, layout=slider_layout)
condenser02_slider = FloatSlider(value=0.15, min=0.025, max=0.5,  step=0.005,
    description="CL2 focal length", style=style, layout=slider_layout)
aperture02_slider  = FloatSlider(value=0.05, min=0.025, max=0.2,  step=0.005,
    description="OL aperture",      style=style, layout=slider_layout)
objective_slider   = FloatSlider(value=0.35, min=0.025, max=0.5,  step=0.005,
    description="OL strength",      style=style, layout=slider_layout)

plot_height = 550
z_start, z_end = 0.0, 1.7
def z_to_y(z):
    return 10 + ((z - z_start) / (z_end - z_start)) * (plot_height - 20)

positions = {
    "condenser01": 0.225,
    "aperture01":  0.5125,
    "condenser02": 0.7125,
    "objective":   1.00,   # centre on sample / objective gap
    "aperture02":  1.20,
}
sliders = [
    ("condenser01", condenser01_slider),
    ("aperture01",  aperture01_slider),
    ("condenser02", condenser02_slider),
    ("objective",   objective_slider),
    ("aperture02",  aperture02_slider),
]

children, cur_y, sh = [], 0, 36
for key, slider in sliders:
    ty = int(z_to_y(positions[key]) - sh / 2)
    sp = max(0, ty - cur_y)
    if sp > 0:
        children.append(Box(layout=Layout(height=f"{sp}px")))
    children.append(slider)
    cur_y = ty + sh
children.append(Box(layout=Layout(height=f"{max(0, plot_height - cur_y)}px")))

out = Output()
def update_ray(_=None):
    with out:
        clear_output(wait=True)
        draw_ray_diagram(
            condenser01_slider.value, aperture01_slider.value,
            condenser02_slider.value, aperture02_slider.value,
            objective_slider.value
        )
for w in [condenser01_slider, aperture01_slider, condenser02_slider,
          aperture02_slider, objective_slider]:
    w.observe(update_ray, names='value')

ui = VBox(children, layout=Layout(width="320px", height=f"{plot_height}px"))
display(HBox([ui, out], layout=Layout(align_items="flex-start")))
update_ray()

7.4. Image formation and contrast#

7.4.1. The Weak Phase Object approximation#

Biological macromolecules embedded in vitrified ice scatter electrons primarily through electrostatic interaction with atomic nuclei and electrons. For thin biological specimens, most electrons pass through the sample with their amplitude unchanged but with a small phase shift \(\phi(\mathbf{r})\) proportional to the projected electrostatic potential:

(7.3)#\[ \psi(\mathbf{r}) = e^{i\phi(\mathbf{r})} \approx 1 + i\phi(\mathbf{r}) \]

This is the weak phase object (WPO) approximation. It holds for thin specimens (\(< 50\) nm) at typical cryo-EM doses. Crucially, a pure phase shift produces no image contrast in a perfectly focused image, because the detector records intensity \(|\psi|^2 = 1\) — the phase information is invisible to a square-law detector.

Contrast arises from two mechanisms that convert phase into amplitude:

  1. Phase contrast: Defocusing the objective lens introduces an additional phase shift that partially converts the scattered phase into amplitude. This is described by the contrast transfer function (CTF).

  2. Amplitude contrast: A fraction of electrons (typically 7–10% for biological specimens) scatter inelastically or at large angles and are removed by the objective aperture, contributing genuine amplitude contrast. This fraction is called the amplitude contrast ratio, \(A\).

7.4.2. Origin of phase contrast: wave propagation below the specimen#

The mechanism by which defocus creates contrast follows directly from the wave picture. A periodic phase grating with spacing \(d\) diffracts the incident wave into two symmetric beams at angles \(\pm\theta\) satisfying \(\sin\theta = \lambda/d\). At the specimen exit plane (\(z=0\)) these diffracted beams carry the WPO factor \(i\phi\) — they are exactly 90° out of phase with the undiffracted beam — and contribute zero contrast. As the wave propagates to depth \(z\) below the specimen, the diffracted beams accumulate extra path length relative to the direct beam. When that path difference equals a quarter wavelength (90° phase retardation), the scattered amplitude rotates into phase with the direct beam and contrast is maximum. The direct beam acts as a reference wave for the diffracted beams — the geometry of inline holography.

For a single grating frequency \(\nu = 1/d\), the CTF evaluated at that frequency with defocus \(\Delta f = z\) is:

\[ \mathrm{CTF}\!\left(\tfrac{1}{d},\, \Delta f\right) = -\sin\!\left(\frac{\pi\lambda\,\Delta f}{d^2}\right) \]

Contrast is zero at \(\Delta f = 0\); the first magnitude maximum is at \(\Delta f = d^2/(2\lambda)\) (the negative sign means the specimen appears dark at this defocus, consistent with (7.5)). The first CTF zero falls at \(\Delta f = d^2/\lambda\). For \(d = 10\) Å at 200 keV (\(\lambda = 0.025\) Å): first maximum at \(z = 2000\) Å \(= 0.2\,\mu\text{m}\); for \(d = 7\) Å at \(z \approx 0.1\,\mu\text{m}\). Setting the objective lens underfocus is directly equivalent to choosing this propagation distance.

Interactive element

Click Live Code to activate, expand the Show code toggle, and click ▶ to run. Panel A shows the Fresnel carpet \(|\psi(x,z)|^2\). Panel B shows \(\Delta I = |\psi|^2 - 1\) at the grating centre (blue) together with the WPO sinusoidal prediction (red dashed). For small phase amplitude \(\varepsilon\) the two agree perfectly; at larger \(\varepsilon\) higher diffraction harmonics superimpose faster oscillations on the fundamental — the WPO breaks down.

Hide code cell source

import io
import numpy as np
import matplotlib
matplotlib.use('agg')
import matplotlib.pyplot as plt
from ipywidgets import FloatSlider, Dropdown, VBox, HBox, Output
from IPython.display import display, Image, clear_output

def _sim(d_nm, kV=200, eps=0.45, nx=256, dx_nm=0.1, nlines=201, dz_nm=10.0,
         width_nm=12.8, pad=8):
    V = kV * 1e3
    m0, ec, c, h = 9.109e-31, 1.602e-19, 2.998e8, 6.626e-34
    lam_nm = h / np.sqrt(2*m0*ec*V*(1+ec*V/(2*m0*c**2))) * 1e9
    x = (np.arange(nx) - (nx-1)/2) * dx_nm
    psi0 = np.ones(nx, dtype=complex)
    inside = np.abs(x) <= width_nm/2
    psi0[inside] = np.exp(1j * eps * np.cos(2*np.pi*x[inside]/d_nm))
    nfft = pad * nx; start = (nfft - nx) // 2
    padded = np.zeros(nfft, dtype=complex)
    padded[start:start+nx] = psi0 - 1.0
    spec = np.fft.fft(padded)
    k = 2*np.pi / lam_nm
    kx = 2*np.pi * np.fft.fftfreq(nfft, d=dx_nm)
    kz = np.sqrt(np.maximum(0.0, k**2 - kx**2))
    z_nm = np.arange(nlines, dtype=float) * dz_nm
    transfer = np.exp(1j * (kz[None,:] - k) * z_nm[:,None])
    prop = np.fft.ifft(spec[None,:] * transfer, axis=1)[:,start:start+nx]
    return np.abs(1.0 + prop)**2, x, z_nm, lam_nm

def draw(d_A, eps, kV):
    d_nm = d_A / 10
    image, x, z_nm, lam_nm = _sim(d_nm, kV=kV, eps=eps)
    z_max_nm  = d_nm**2 / (2*lam_nm)
    z_zero_nm = d_nm**2 / lam_nm
    clip = np.percentile(np.abs(image - 1.0), 99)

    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13, 5))

    # Panel A — Fresnel carpet
    ax1.imshow(image, cmap='gray', origin='upper', aspect='auto',
               vmin=1-clip, vmax=1+clip, interpolation='nearest',
               extent=[x[0]*10, x[-1]*10, z_nm[-1]/100, 0])
    if z_max_nm < z_nm[-1]:
        ax1.axhline(z_max_nm/100,  color='cyan',   lw=1.5, ls='--',
                    label=f'first max  {z_max_nm:.0f} nm')
    if z_zero_nm < z_nm[-1]:
        ax1.axhline(z_zero_nm/100, color='orange', lw=1.5, ls=':',
                    label=f'first zero  {z_zero_nm:.0f} nm')
    ax1.set_xlabel('x (Å)', fontsize=10)
    ax1.set_ylabel('z (μm)  [compressed ~50×]', fontsize=9)
    ax1.set_title(f'(A) |ψ(x,z)|²   d={d_A:.1f} Å, {kV} keV,  ε={eps:.2f} rad',
                  fontsize=10, fontweight='bold')
    ax1.legend(fontsize=9, loc='lower right')

    # Panel B — ΔI vs z at grating centre, with WPO prediction
    cx = np.argmin(np.abs(x))
    x0 = x[cx]
    phi0 = eps * np.cos(2*np.pi*x0/d_nm)
    z_th = np.linspace(0, z_nm[-1], 600)
    wpo = 2 * phi0 * np.sin(np.pi * lam_nm * z_th / d_nm**2)

    ax2.plot(z_nm/100, image[:, cx] - 1, color='steelblue', lw=1.8,
             label=f'simulation  x={x0*10:.1f} Å')
    ax2.plot(z_th/100, wpo, color='tomato', lw=1.5, ls='--',
             label=f'WPO: 2φ(x)·sin(πλz/d²)  [φ={phi0:.2f} rad]')
    ax2.axhline(0, color='k', lw=0.5)
    if z_max_nm < z_nm[-1]:
        ax2.axvline(z_max_nm/100,  color='cyan',   lw=1.2, ls='--', alpha=0.7)
    if z_zero_nm < z_nm[-1]:
        ax2.axvline(z_zero_nm/100, color='orange', lw=1.2, ls=':',  alpha=0.7)
    ax2.set_xlabel('z (μm)', fontsize=10)
    ax2.set_ylabel('ΔI = |ψ|² − 1', fontsize=10)
    ax2.set_title('(B) Contrast at grating centre\n'
                  '(faster wiggles at large ε = higher harmonics beyond WPO)',
                  fontsize=10, fontweight='bold')
    ax2.legend(fontsize=9)

    fig.tight_layout()
    buf = io.BytesIO(); fig.savefig(buf, format='png', dpi=96); buf.seek(0); plt.close(fig)
    display(Image(data=buf.read()))

style = {'description_width': '100px'}
d_sl  = FloatSlider(value=10, min=4, max=20, step=0.5,
                    description='Period d (Å)', style=style, layout={'width': '340px'})
eps_sl = FloatSlider(value=0.45, min=0.05, max=1.5, step=0.05,
                     description='ε (rad)', style=style, layout={'width': '300px'})
kv_dd = Dropdown(options=[120, 200, 300], value=200, description='Voltage (keV)',
                 style=style, layout={'width': '220px'})

out_cp = Output()
def update_cp(_=None):
    with out_cp:
        clear_output(wait=True)
        draw(d_sl.value, eps_sl.value, kv_dd.value)
for w in [d_sl, eps_sl, kv_dd]:
    w.observe(update_cp, names='value')
display(VBox([HBox([d_sl, eps_sl, kv_dd]), out_cp]))
update_cp()

7.4.3. The Contrast Transfer Function#

Defocusing the objective lens by an amount \(\Delta f\) introduces a phase shift that depends on the spatial frequency \(s\) (in Å⁻¹) of each Fourier component of the image. Combined with spherical aberration \(C_s\), the total phase error is:

(7.4)#\[ \chi(s) = \pi \lambda s^2 \left( -\Delta f + \frac{1}{2} C_s \lambda^2 s^2 \right) \]

where \(\lambda\) is the electron wavelength, \(\Delta f > 0\) for underfocus (the convention used in most cryo-EM software). The contrast transfer function then describes how each spatial frequency is transferred to the image:

(7.5)#\[ \text{CTF}(s) = -\left[ \sqrt{1-A^2}\,\sin\chi(s) + A\cos\chi(s) \right] \cdot E(s) \]

where \(E(s)\) is an envelope function that damps high-frequency transfer due to temporal (chromatic) and spatial (source size) coherence:

(7.6)#\[ E(s) = \exp\!\left(-\tfrac{1}{2} B s^2\right) \]

The \(B\)-factor (related to the microscope’s coherence parameters) causes an exponential fall-off of the CTF at high spatial frequencies, ultimately setting the information limit of the microscope regardless of aberrations.

Key insight: defocus and contrast

At exact focus (\(\Delta f = 0\)), the CTF is nearly flat and weak for biological specimens — there is almost no contrast. Introducing underfocus causes the CTF to oscillate: some frequency bands pass with positive contrast, others with negative contrast (phase reversal), and some are nulled entirely at the CTF zeros. Cryo-EM data processing includes a CTF correction step that identifies these zeros and phase-flips the negative bands before combining information from many images.

The interactive figure below shows how the CTF depends on defocus and accelerating voltage. Pay attention to how the first CTF zero moves to lower spatial frequency (coarser features) as defocus increases, and how the envelope function limits information at high resolution.

Interactive element

Click Live Code to activate, expand the Show code toggle, and click ▶ to run. Drag the defocus slider to see how the CTF oscillations and the first zero shift. Higher defocus gives more low-frequency contrast but moves the first zero to lower resolution.

Hide code cell source

import io
import numpy as np
import matplotlib
matplotlib.use('agg')
import matplotlib.pyplot as plt
from ipywidgets import FloatSlider, IntSlider, VBox, HBox, Layout, Output
from IPython.display import display, Image, clear_output

def compute_ctf(s, defocus_nm, Cs_mm=2.7, voltage_kV=300, B=50.0, A=0.07):
    lam = 12.264 / np.sqrt(voltage_kV*1e3 * (1 + voltage_kV*1e3 / (2*511e3)))
    Cs  = Cs_mm * 1e7
    dz  = defocus_nm * 10
    chi = np.pi * lam * s**2 * (-dz + 0.5 * Cs * lam**2 * s**2)
    env = np.exp(-0.5 * B * s**2)
    return -(np.sqrt(1-A**2) * np.sin(chi) + A * np.cos(chi)) * env

def draw_ctf(defocus_nm=1000, voltage_kV=300, B=50):
    s = np.linspace(0.005, 0.45, 600)
    ctf = compute_ctf(s, defocus_nm, voltage_kV=voltage_kV, B=B)
    env = np.exp(-0.5 * B * s**2)

    fig, axes = plt.subplots(1, 2, figsize=(11, 4))

    ax = axes[0]
    ax.fill_between(1/s, -env, env, alpha=0.12, color='gray', label='Envelope')
    ax.plot(1/s, ctf, color='steelblue', linewidth=2)
    ax.axhline(0, color='k', linewidth=0.5)
    zeros_mask = np.diff(np.sign(ctf)) != 0
    zero_res = (1/s[:-1][zeros_mask])
    for zr in zero_res:
        ax.axvline(zr, color='red', linewidth=0.5, alpha=0.5)
    ax.set_xlabel("Resolution (Å)"); ax.set_ylabel("CTF")
    ax.set_title(f"CTF  |  Δf = {defocus_nm:.0f} nm, {voltage_kV:.0f} kV")
    ax.set_xlim(80, 2); ax.set_ylim(-1.1, 1.1)

    ax = axes[1]
    ax.plot(1/s, ctf**2, color='tomato', linewidth=1.5)
    ax.axhline(0, color='k', linewidth=0.5)
    ax.set_xlabel("Resolution (Å)"); ax.set_ylabel("CTF²")
    ax.set_title("Power spectrum (Thon rings pattern)")
    ax.set_xlim(80, 2)

    fig.tight_layout()
    buf = io.BytesIO(); fig.savefig(buf, format='png', dpi=96); buf.seek(0)
    display(Image(data=buf.read())); plt.close(fig)

style = {"description_width": "140px"}
sl = Layout(width="340px")
dz_sl  = FloatSlider(value=1000, min=100, max=5000, step=100,
                      description="Defocus (nm)", style=style, layout=sl)
kv_sl  = IntSlider(  value=300,  min=80,  max=300,  step=20,
                      description="Voltage (kV)", style=style, layout=sl)
b_sl   = FloatSlider(value=50,   min=0,   max=200,  step=10,
                      description="B-factor (Ų)", style=style, layout=sl)

out = Output()
def update_ctf(_=None):
    with out:
        clear_output(wait=True)
        draw_ctf(dz_sl.value, kv_sl.value, b_sl.value)
for sl_w in [dz_sl, kv_sl, b_sl]:
    sl_w.observe(update_ctf, names='value')

ui = VBox([dz_sl, kv_sl, b_sl])
display(HBox([ui, out]))
update_ctf()

7.5. Cryo-EM: Imaging biological specimens#

7.5.1. Why cryogenic conditions?#

Biological macromolecules are functional in aqueous solution but incompatible with the high vacuum of a TEM column. Two main strategies exist to overcome this: embedding in heavy-metal stains (negative stain EM), which provides high contrast but limits resolution to ~20 Å, or vitrification — rapid plunge-freezing into liquid ethane, which turns the aqueous buffer into amorphous (vitreous) ice without ice crystal formation. Cryo-EM in vitrified ice preserves specimens in their near-native hydrated state and, in conjunction with modern processing methods, enables structure determination at near-atomic resolution.

Grid preparation for single-particle cryo-EM involves pipetting a few microlitres of purified protein solution onto a grid covered with a thin carbon or gold foil with holes, blotting away excess liquid until a thin film of aqueous solution spans the holes, and rapidly plunging the grid into liquid ethane at \(-170\)°C. The entire blotting and plunging cycle takes less than 2 seconds. The resulting frozen specimen contains molecules embedded in a thin layer of vitreous ice, typically 20–200 nm thick.

7.5.2. Radiation damage and low-Ddose imaging#

Electrons interact strongly with matter. The same scattering interactions that generate image contrast also deposit energy in the specimen, causing radiation damage: bond breaking, mass loss, and structural changes that degrade the image at higher doses. The critical parameter is the electron dose (fluence), measured in electrons per Ų (e⁻/Ų). For biological specimens at liquid nitrogen temperature:

  • Noticeable radiation damage occurs above ~10–20 e⁻/Ų

  • Visible high-frequency information in single-particle cryo-EM is destroyed above ~40–80 e⁻/Ų

To minimise damage, cryo-EM uses low-dose imaging protocols: the area of interest is never illuminated before data collection, and total exposure is kept below ~50 e⁻/Ų per micrograph. This dramatically lowers the SNR of individual images — a single cryo-EM particle image has far less contrast than the noise that surrounds it — which is why averaging hundreds of thousands of particles is necessary to extract signal (see Section 10).

Modern direct electron detectors are read out at frame rates of 40–400 frames/second, allowing individual frames of a movie to be aligned and summed after correcting for beam-induced specimen motion (motion correction). This restores high-frequency signal that would otherwise be blurred by drift during exposure.

7.5.3. Phase plates#

One practical limitation of conventional phase-contrast TEM is that the CTF is nearly zero at very low spatial frequencies (large features), even with underfocus. To recover low-frequency information with a flat CTF, phase plates can be inserted in the back focal plane of the objective lens to introduce an additional \(\pi/2\) phase shift on the unscattered beam. This converts the sine-like CTF into a cosine-like CTF, producing strong contrast at all frequencies from the start. Phase plates are an active area of development for single-particle cryo-EM, particularly for small particles where low-frequency contrast is critical.

7.6. Resolution in TEM#

The theoretical resolution limit of TEM is set by the electron wavelength (\(\approx 2\) pm at 300 kV), far below any biological feature of interest. In practice, the achievable resolution is governed by:

Factor

Effect

Spherical aberration \(C_s\)

Limits phase-contrast image quality; corrected in aberration-corrected TEMs

Chromatic aberration \(C_c\)

Energy spread of gun and specimen; reduced by energy filters

CTF zeros and envelope

Set the information limit for a given microscope/dose

Radiation damage

Destroys high-frequency information above a critical dose

Specimen motion

Blurs high-resolution detail; corrected by movie-mode acquisition

Ice contamination

Scatters electrons incoherently, adds background

For single-particle cryo-EM, the ultimate limit is the particle orientation determination accuracy and the available number of particles, rather than the optics alone. State-of-the-art cryo-EM routinely reaches 2–3 Å resolution, and sub-2 Å structures are now reported for stable, well-behaved complexes.