# ShowComplex2D — All Features Comprehensive demo of every ShowComplex2D feature using realistic electron microscopy synthetic data: ptychographic exit waves, holographic reconstructions, aberration functions, and more. Features demonstrated:
5 display modes: amplitude, phase, HSV, real, imaginary
Phase colorwheel inset
FFT of display data
Log scale, auto-contrast, percentile clipping
Scale bar with pixel size calibration
Colormaps for non-HSV modes
Zoom/pan, keyboard shortcuts
Figure export (publication-quality PNG)
State persistence (save/load)
(real, imag) tuple input
[1]:
# Install in Google Colab
try:
import google.colab
!pip install -q -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ quantem-widget
except ImportError:
pass # Not in Colab, skip
[2]:
try:
%load_ext autoreload
%autoreload 2
%env ANYWIDGET_HMR=1
except Exception:
pass # autoreload unavailable (Colab Python 3.12+)
env: ANYWIDGET_HMR=1
[3]:
import torch
import numpy as np
import quantem.widget
from quantem.widget import ShowComplex2D
from IPython.display import display
device = torch.device("mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")
print(f"quantem.widget {quantem.widget.__version__}")
Using device: mps
quantem.widget 0.4.0a3
1. Ptychographic Reconstruction — SrTiO₃#
Simulated SSB/ptychography result of SrTiO₃ [001] zone axis. Phase shows the projected electrostatic potential: Sr (bright), Ti (medium), O (faint). Amplitude ≈ 1 for a thin specimen with slight absorption at heavy atom columns.
[4]:
def make_ptycho_object(size=256):
"""SrTiO3 [001] ptychographic object: phase = projected potential, amp ≈ 1."""
y = torch.linspace(0, 1, size, device=device).unsqueeze(1).expand(size, size)
x = torch.linspace(0, 1, size, device=device).unsqueeze(0).expand(size, size)
n_cells = 8
phase = torch.zeros(size, size, device=device)
for i in range(n_cells + 1):
for j in range(n_cells + 1):
cx, cy = i / n_cells, j / n_cells
r2 = (x - cx) ** 2 + (y - cy) ** 2
# Sr columns (corners): Z=38
phase += 0.8 * torch.exp(-r2 / (2 * (0.012) ** 2))
# Ti columns (body center): Z=22
tcx, tcy = (i + 0.5) / n_cells, (j + 0.5) / n_cells
r2_ti = (x - tcx) ** 2 + (y - tcy) ** 2
phase += 0.5 * torch.exp(-r2_ti / (2 * (0.010) ** 2))
# O columns (face centers): Z=8
for ox, oy in [(cx + 0.5 / n_cells, cy), (cx, cy + 0.5 / n_cells)]:
if ox <= 1.0 and oy <= 1.0:
r2_o = (x - ox) ** 2 + (y - oy) ** 2
phase += 0.15 * torch.exp(-r2_o / (2 * (0.008) ** 2))
amp = 1.0 - 0.08 * phase / phase.max()
noise = 0.02 * torch.randn(size, size, device=device)
phase = phase + noise
obj = amp * torch.exp(1j * phase)
return obj.cpu().numpy()
obj = make_ptycho_object(256)
ShowComplex2D(obj, title="SrTiO₃ Ptycho — Phase", display_mode="phase", pixel_size_angstrom=0.5)
[4]:
2. All Five Display Modes#
The same ptychographic reconstruction visualized in each mode:
Phase: arg(obj) — projected electrostatic potential, atomic columns visible
Amplitude: |obj| — object transmission, ≈1 for thin specimens
HSV: phase→hue, amplitude→brightness — simultaneous view
Real: Re(obj) — real component
Imaginary: Im(obj) — imaginary component
[5]:
for mode in ["phase", "amplitude", "hsv", "real", "imag"]:
display(ShowComplex2D(obj, title=f"SrTiO₃ — {mode.capitalize()}", display_mode=mode, pixel_size_angstrom=0.5))
3. Holographic Reconstruction#
Simulated off-axis electron hologram reconstruction: magnetic domain structure encoded in phase with slowly varying amplitude from thickness changes.
[6]:
def make_hologram(size=256):
"""Holographic reconstruction with magnetic domain-like phase structure."""
y = torch.linspace(-1, 1, size, device=device).unsqueeze(1).expand(size, size)
x = torch.linspace(-1, 1, size, device=device).unsqueeze(0).expand(size, size)
# Smooth amplitude: thickness fringes
amp = 0.6 + 0.4 * torch.exp(-(x**2 + y**2) / 0.5)
# Phase: magnetic domain walls (tanh transitions)
domain1 = torch.tanh(5 * (x + 0.3 * torch.sin(3 * y)))
domain2 = torch.tanh(5 * (y - 0.2 * torch.sin(4 * x)))
phase = 1.5 * domain1 + 0.8 * domain2
wave = amp * torch.exp(1j * phase)
return wave.cpu().numpy()
holo = make_hologram(256)
display(ShowComplex2D(holo, title="Hologram — HSV (domains)", display_mode="hsv", pixel_size_angstrom=2.0))
display(ShowComplex2D(holo, title="Hologram — Phase (domains)", display_mode="phase", pixel_size_angstrom=2.0))
4. Aberration Function (Lens Transfer)#
CTF-like aberration function in reciprocal space: rotationally symmetric defocus + astigmatism.
[7]:
def make_aberration(size=256):
"""Complex transfer function with defocus and astigmatism."""
ky = torch.linspace(-1, 1, size, device=device).unsqueeze(1).expand(size, size)
kx = torch.linspace(-1, 1, size, device=device).unsqueeze(0).expand(size, size)
k2 = kx**2 + ky**2
# Aberration phase: defocus + 2-fold astigmatism
defocus = 40.0
astig = 8.0
theta = torch.atan2(ky, kx)
chi = defocus * k2 + astig * k2 * torch.cos(2 * theta)
# Spatial coherence envelope
envelope = torch.exp(-k2 / 0.3)
ctf = envelope * torch.exp(1j * chi)
return ctf.cpu().numpy()
aberr = make_aberration(256)
display(ShowComplex2D(aberr, title="Aberration Function — HSV", display_mode="hsv"))
display(ShowComplex2D(aberr, title="Aberration Function — Phase", display_mode="phase"))
5. Colormaps#
In amplitude/real/imag modes, any colormap can be applied. In phase mode, the cyclic HSV colormap is always used. In HSV mode, no colormap selector is needed (phase→hue is intrinsic).
[8]:
for cmap in ["inferno", "viridis", "magma", "plasma", "gray"]:
display(ShowComplex2D(obj, title=f"Phase — {cmap}", display_mode="amplitude", cmap=cmap, pixel_size_angstrom=0.5))
6. Log Scale + Auto Contrast#
Useful for amplitude data spanning orders of magnitude, e.g. the Fourier transform of a periodic structure.
[9]:
# Amplitude mode with log scale
display(ShowComplex2D(obj, title="Amplitude — Linear", display_mode="amplitude", pixel_size_angstrom=0.5))
display(ShowComplex2D(obj, title="Amplitude — Log Scale", display_mode="amplitude", log_scale=True, pixel_size_angstrom=0.5))
display(ShowComplex2D(obj, title="Amplitude — Auto Contrast", display_mode="amplitude", auto_contrast=True, pixel_size_angstrom=0.5))
7. FFT#
FFT of the currently displayed data (amplitude, phase, real, or imag). For an exit wave, the amplitude FFT shows reciprocal lattice spots.
[10]:
ShowComplex2D(obj, title="SrTiO₃ + FFT", display_mode="phase", show_fft=True, pixel_size_angstrom=0.5)
[10]:
8. ROI + ROI FFT#
Place a circle, square, or rectangle ROI on the image. When FFT is also active, the FFT panel shows only the cropped ROI region — useful for inspecting local crystal structure in a specific area of the reconstruction.
[11]:
# ROI circle on ptychographic reconstruction with FFT
w_roi = ShowComplex2D(obj, title="SrTiO₃ — ROI FFT", display_mode="phase", show_fft=True, pixel_size_angstrom=0.5)
w_roi.roi_circle(row=128, col=128, radius=50)
w_roi
[11]:
[12]:
# Rectangle ROI on one side of the ptychographic reconstruction
w_rect = ShowComplex2D(obj, title="SrTiO₃ — Rect ROI FFT", display_mode="phase", show_fft=True, pixel_size_angstrom=0.5)
w_rect.roi_rect(row=128, col=80, width=80, height=120)
w_rect
[12]:
9. Scale Bar#
When pixel_size_angstrom is set, a physical scale bar appears. Automatic unit conversion: Å → nm at ≥10 Å.
[13]:
display(ShowComplex2D(obj, title="Scale Bar — 0.5 Å/px", display_mode="phase", pixel_size_angstrom=0.5))
display(ShowComplex2D(obj, title="Scale Bar — 2.0 Å/px", display_mode="phase", pixel_size_angstrom=2.0))
10. (Real, Imag) Tuple Input#
When real and imaginary parts come from separate sources (e.g. two detector channels), pass them as a tuple.
[14]:
real_part = np.cos(np.linspace(0, 4 * np.pi, 256)).reshape(1, -1) * np.ones((256, 1))
imag_part = np.sin(np.linspace(0, 4 * np.pi, 256)).reshape(1, -1) * np.ones((256, 1))
real_part = real_part.astype(np.float32)
imag_part = imag_part.astype(np.float32)
ShowComplex2D((real_part, imag_part), title="Tuple Input (real, imag)", display_mode="hsv")
[14]:
11. SSB Reconstruction — Grain Boundary#
Simulated single-sideband (SSB) ptychography result: a crystalline specimen with a grain boundary. Two crystal grains with different orientations meet, creating a distinct phase contrast at the boundary.
[15]:
def make_ssb_grain_boundary(size=256):
"""SSB ptychography of a grain boundary: two crystal orientations meeting."""
y = torch.linspace(0, 1, size, device=device).unsqueeze(1).expand(size, size)
x = torch.linspace(0, 1, size, device=device).unsqueeze(0).expand(size, size)
# Grain boundary at x ≈ 0.5 with slight curvature
boundary = 0.5 + 0.03 * torch.sin(6 * torch.pi * y)
grain1 = (x < boundary).float()
grain2 = 1.0 - grain1
# Grain 1: cubic lattice along [100]
freq1 = 14.0
phase1 = 0.6 * (torch.cos(2 * torch.pi * freq1 * x) * torch.cos(2 * torch.pi * freq1 * y))
# Grain 2: rotated by ~15 degrees
angle = 0.26 # ~15 degrees
xr = x * torch.cos(torch.tensor(angle)) - y * torch.sin(torch.tensor(angle))
yr = x * torch.sin(torch.tensor(angle)) + y * torch.cos(torch.tensor(angle))
phase2 = 0.6 * (torch.cos(2 * torch.pi * freq1 * xr) * torch.cos(2 * torch.pi * freq1 * yr))
# Combine with smooth boundary transition
sigma = 0.005
blend = torch.sigmoid((x - boundary) / sigma)
phase = (1 - blend) * phase1 + blend * phase2
# Add boundary strain (extra phase at the interface)
boundary_dist = torch.abs(x - boundary)
phase += 0.3 * torch.exp(-boundary_dist ** 2 / (2 * 0.01 ** 2))
# Normalize to [0, max_phase] range
phase = phase - phase.min()
amp = 1.0 - 0.05 * phase / phase.max()
phase = phase + 0.015 * torch.randn(size, size, device=device)
obj = amp * torch.exp(1j * phase)
return obj.cpu().numpy()
ssb = make_ssb_grain_boundary(256)
display(ShowComplex2D(ssb, title="SSB — Grain Boundary (Phase)", display_mode="phase", pixel_size_angstrom=0.4))
display(ShowComplex2D(ssb, title="SSB — Grain Boundary (HSV)", display_mode="hsv", pixel_size_angstrom=0.4))
12. Minimal View#
No controls, no stats — just the image. Useful for publications or embedding.
[16]:
ShowComplex2D(obj, title="SrTiO₃ — Minimal", display_mode="phase", show_controls=False, show_stats=False, pixel_size_angstrom=0.5)
[16]:
13. State Persistence#
Save and restore all display settings (display mode, colormap, log scale, etc.) to a JSON file for reproducible analysis.
[17]:
w = ShowComplex2D(obj, title="SrTiO₃ Persistent", display_mode="phase", cmap="viridis", log_scale=False, pixel_size_angstrom=0.5)
w.summary()
w
SrTiO₃ Persistent
════════════════════════════════
Image: 256×256 (complex)
Amp: min=0.92 max=1 mean=0.9925
Phase: min=-0.07278 max=0.8486 mean=0.07529
Display: phase | hsv (cyclic) | manual | linear
[17]:
[18]:
# Save state
w.save("showcomplex_state.json")
print("Saved to showcomplex_state.json")
import json
print(json.dumps(w.state_dict(), indent=2))
Saved to showcomplex_state.json
{
"display_mode": "phase",
"title": "SrTiO\u2083 Persistent",
"cmap": "viridis",
"log_scale": false,
"auto_contrast": false,
"percentile_low": 1.0,
"percentile_high": 99.0,
"pixel_size": 0.0,
"scale_bar_visible": true,
"show_fft": false,
"show_stats": true,
"show_controls": true,
"image_width_px": 0,
"roi_mode": "off",
"roi_center_row": 128.0,
"roi_center_col": 128.0,
"roi_radius": 42.0,
"roi_width": 84.0,
"roi_height": 42.0,
"disabled_tools": [],
"hidden_tools": []
}
[19]:
# Restore from file
w2 = ShowComplex2D(obj, state="showcomplex_state.json")
print(f"Restored: mode={w2.display_mode}, cmap={w2.cmap}")
w2
Restored: mode=phase, cmap=viridis
[19]:
[20]:
# Clean up
from pathlib import Path
Path("showcomplex_state.json").unlink(missing_ok=True)
14. Figure Export#
Click the Figure button in the header to export a publication-quality PNG with title, scale bar, and colorbar baked in.
[21]:
ShowComplex2D(
ssb,
title="SSB Grain Boundary",
display_mode="phase",
pixel_size_angstrom=0.4,
)
[21]: