Open In Colab # 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]: