Show4DSTEM

Contents

Show4DSTEM#

Public import:

from quantem.widget import load, Show4DSTEM

Show4DSTEM is a dispatcher/factory with one operator-facing API. It picks the viewer from what load(...) returns and from the requested widget backend: CUDA/Torch on Linux, raw Metal on Apple Silicon MPS loads, CPU fallback, or browser WebGPU.

The MPS code is intentionally a backend implementation, not a separate public viewer to choose in notebooks. Direct names such as Show4DSTEMMPS, Show4DSTEM_MACBOOK, and show_4dstem_mps(...) stay importable for old notebooks and backend tests, but new code should use the single factory: Show4DSTEM(load(path, backend="mps", det_bin=...)).

Canonical forms:

# Auto-pick CUDA / MPS / CPU from the loaded data.
w = Show4DSTEM(load(path))

# Apple Silicon raw-Metal path, with sampling read from metadata when present.
w = Show4DSTEM(load(path, backend="mps", det_bin=4))

# Multi-dataset stack: one viewer, one Dataset slider.
w = Show4DSTEM(load([path1, path2, path3], det_bin=4))

# Multi-dataset comparison: one shared diffraction ROI, many virtual images.
w = Show4DSTEM(
    load([path1, path2, path3], det_bin=4),
    view_mode="multiple",
    compare_cols=3,
)

# Dynamic folder browse: first page paints now; the rest preload if they fit.
w = Show4DSTEM.from_folder(
    "/data/session",
    gpus=[0, 1],
    det_bin=1,
    columns=5,
    page_size=5,
    compare_dp_mode="selected",
    preview_cache="auto",
    preview_cache_max_bytes=4 << 30,
    warm_cache=True,
    watch=True,
)

# Apple Silicon live acquisition folder: dataset 0 appears first, then newly
# completed *_master.h5 files append into the same Dataset slider.
from quantem.widget.multidataset_mps import load_macbook_datasets

live = load_macbook_datasets("/data/live-scope-session", det_bin=4, scan_size=512)
w = Show4DSTEM(live)
live.watch_master_folder("/data/live-scope-session", interval=2.0, scan_size=512)

# Live-kernel WebGPU: the browser owns virtual-detector compute.
w = Show4DSTEM(load(path), backend="web")

# Standalone backendless export for large data: HTML + companion data folder.
w = Show4DSTEM(load(path), backend="web", offline_codec="bslz4",
               data_url="show4dstem-data")
w.export_html("show4dstem.html")

backend="browser", backend="webgpu", and offline=True are compatibility aliases for backend="web".

Backend ownership#

Show4DSTEM has two different acceleration surfaces:

  • Live Python-backed viewers use the data object returned by load(...). Depending on hardware this may be CUDA/Torch, raw Metal/MPS on Apple Silicon, Torch-MPS for specific paths, or CPU fallback.

  • Exported/offline browser viewers use the packed HTML/folder payload and browser WebGPU when available. After export, interaction should not depend on Python, Torch, CUDA, or MPS.

On Apple Silicon, prefer the raw Metal/MPS loading path for large first-pass browsing because it can control chunking, detector binning, and dtype more tightly than a generic Torch-MPS tensor path. Torch-MPS remains useful for some tensor workflows, but reports should say which path was used.

MPS loading also has an automatic preflight memory guard. If a no-bin or large Metal allocation would exceed the Mac’s conservative working-set budget, load(..., backend="mps") fails before allocating and recommends a safer det_bin value. This is intentional: it protects laptop sessions from unresponsive unified-memory pressure while keeping the MPS backend automatic.

Routing lives in quantem.widget.show4dstem_factory: chunked MPS payloads and lazy MacBook multi-dataset handles go to the raw-Metal backend, while CUDA/CPU arrays and CUDA 5D dataset wrappers stay on the universal base viewer. This keeps the user-facing API stable while backend-specific code stays isolated.

Live scope folders#

For real-time processing on a microscope or acquisition workstation, prefer the direct folder-backed API when you want ready masters to become available without materializing a full 5D stack:

from quantem.widget import Show4DSTEM

widget = Show4DSTEM.from_folder(
    "/data/live-scope-session",
    gpus=[0, 1],             # selects CUDA and distributes lazy frames
    det_bin=1,
    columns=5,
    page_size=5,
    compare_dp_mode="selected",
    preview_cache="auto",
    preview_cache_max_bytes=4 << 30,
    warm_cache=True,
    watch_interval=2.0,
)
widget

from_folder(...) keeps the folder as lazy slots instead of materializing a full 5D stack before first paint. The initial visible page is scheduled first; valid persistent previews may paint before its raw masters finish loading. The default preload_all_if_fits=True policy then calculates the complete raw footprint from the known frame shape and dtype. If that footprint fits the selected GPUs, every unhidden dataset loads in the background across those GPUs. If it does not fit, the viewer keeps full-resolution lazy paging; it does not silently detector-bin, real-space-bin, or narrow the dtype. Set preload_all_if_fits=False to keep the page-on-demand policy even when the series would fit.

The title row reports both GPU allocation and raw residency, for example raw 20/20 resident or raw 4/20 resident. New ready masters can be appended manually with widget.poll_folder() or by the default folder watcher. Each append re-evaluates whether the complete unhidden series still fits. Use watch=False for a fixed folder or a script that calls poll_folder() explicitly. A compact title-area badge distinguishes a live Watching worker, Updating, incomplete/stability probation as Waiting for file completion, a corrective Watch error, and Stopped; a fixed snapshot has no badge. Hidden multiple-grid panels are released from the raw resident cache and skipped by compare computes until unhidden.

Discovery and metadata do not copy raw 4D arrays to a GPU. A newly appended master starts lazy, then joins the background full-series preload only when the new total still fits. Otherwise, selecting it or including it in a visible page loads it on demand. page_budget bounds raw GPU residency and evicts older raw pages as needed; reduced virtual-image cache entries use separate host-memory limits. Appending a master invalidates or warms only affected comparison pages, so unrelated cached pages remain fast.

The folder lifecycle matches Show2D and Show3D:

new_datasets = widget.poll_folder()       # append newly ready masters now
widget.stop_folder_watch()                # pause background discovery
widget.watch_folder(interval=1.0)         # resume discovery
widget.close()                            # stop watchers/workers and close

Folder watching is append-only. Known masters are not duplicated, incomplete or externally linked masters wait until they are readable, and removing a file does not silently delete a dataset from an active scientific view.

Maintainer real-time signoff follows S4D-14: introduce genuine master/chunk files while one Jupyter widget is mounted and measure both discovery/control paint and requested virtual-image/diffraction paint.

warm_cache=True preserves the original detector data. It loads raw masters in memory-aware batches, computes the standard BF/ABF/ADF/HAADF virtual images, keeps only those small 2D results in host memory and the configured persistent preview cache, and releases raw pages as the worker advances when full residency is unavailable. Cold pages still pay real disk/decompression cost; warmed page, preset, and matching future-process opens reuse cached results. compare_dp_mode="selected" keeps scan-position movement responsive without loading every master just to average the diffraction panel.

This path uses the original master data at the requested det_bin and dtype. It does not use ShowFolder’s cached thumbnails. Set det_bin=1 and keep the count-preserving dtype when full detector resolution is required.

For folders with tens or hundreds of masters, page_size is the number of datasets shown together. It is deliberately independent from raw GPU residency: the loader divides a visible page into safe progressive waves. columns controls the grid width:

widget = Show4DSTEM.from_folder(
    "/data/session",
    gpus=[0, 1],
    det_bin=1,
    columns=3,
    page_size=12,
    page_budget=4,          # resident lazy/GPU cache
    compare_group_mode="paged",
    compare_cache_pages=16, # reduced VI page cache, not raw 4D VRAM
)

widget.set_compare_page(1)       # second zero-based page
widget.next_compare_page()
widget.previous_compare_page()
widget.show_compare_all_groups() # collapse pages into one dense grid
widget.show_compare_paged_groups()
widget.preload_all_datasets()    # re-run the fit check in the background
widget.wait_for_dataset_preload(timeout=120)  # deterministic scripts/tests

The page control appears in the multiple-grid header whenever the visible dataset count exceeds page_size. compare_group_mode="paged" shows one group at a time with precise group buttons plus a compact play/pause control. compare_group_mode="all" collapses all visible groups into one dense grid for screening tens or hundreds of reduced virtual images.

For a cold lazy page, the grid reserves every requested panel slot immediately. Each selected GPU loads at most one new master in a wave, different GPUs can make progress together, and each virtual image fades into its stable slot as soon as it is ready. A newer page request cancels obsolete work after its current safe wave; late results from an older page cannot overwrite the new page. Once the visible page is complete, the current detector preset is prefetched for the next and previous pages while foreground work is idle. Hidden panels remain hidden and are not recomputed.

Use page_budget for the raw resident-cache policy and page_size for the display grouping. Existing code may continue to use compare_cols and compare_max_panels; new folder-browse code should use the shorter names.

Automatic residency uses 98% as an upper data fraction. It then reserves one largest processed master plus bounded reduction/allocator workspace before deciding that the complete series fits. This prevents a nominally full resident set from consuming the transient memory needed to decode the next master. Pass page_reserve_vram_bytes= or page_max_vram_bytes= for an explicit policy.

Pass gpus=[0, 1] to use specific cards, or gpus="all" to use every CUDA device visible to the process. Lazy masters are placed according to each card’s safe byte budget, while already resident CUDA frames stay on their owning card. Equal budgets naturally alternate; a larger or freer card receives a larger share instead of leaving usable memory stranded behind fixed round-robin placement. Per-device decoding remains serialized, and separate cards may load and reduce their wave concurrently.

The multiple-grid BF/ABF/ADF/HAADF previews are cached as reduced float32 virtual-image pages. This lets page 1 -> page 2 -> page 1 return the already computed thumbnails without keeping page 1’s raw 4D tensors in VRAM. Tune compare_cache_pages for how many reduced pages to keep and compare_cache_max_bytes for the host-memory cap. This cache is separate from page_budget: page_budget controls raw 4D GPU residency, while compare_cache_pages controls small rendered page previews.

Persistent folder preview cache#

The bounded host-memory cache above lasts only for the current widget. Folder viewers also keep a persistent cache of standard scientific previews so a new widget or Python process can show prior BF/ABF/ADF/HAADF results while raw data loads:

widget = Show4DSTEM.from_folder(
    "/data/session",
    gpus=[0],
    page_size=12,
    preview_cache="auto",
    preview_cache_dir=None,
    preview_cache_max_bytes=4 << 30,
    rebuild_preview_cache=False,
)

widget.preview_cache_info
# {'enabled': True, 'hits': ..., 'misses': ..., 'current_bytes': ...,
#  'max_bytes': ..., 'bytes_read': ..., 'bytes_written': ..., 'path': ...}

preview_cache="auto" uses the QuantEM user cache, honoring QUANTEM_WIDGET_CACHE when it is set. True is equivalent to automatic user caching, "folder" selects a project-local .quantem cache, and False disables persistent reads and writes. preview_cache_dir="/fast/ssd/cache" overrides the location. Use rebuild_preview_cache=True to ignore entries from an earlier run and repopulate them. The default disk limit is 4 GiB; least recently used complete entries are evicted when preview_cache_max_bytes is exceeded.

This cache contains reduced float32 virtual images for the recognized BF, ABF, ADF, and HAADF presets only. It never stores raw 4D tensors, CUDA allocations, or diffraction patterns. Standard presets computed during normal browsing are written on demand; warm_cache=True proactively computes them in memory-aware batches. The three relevant limits remain independent:

  • page_budget and the VRAM options bound authoritative raw 4D CUDA residency;

  • compare_cache_max_bytes bounds reduced pages in host memory for this widget;

  • preview_cache_max_bytes bounds persistent reduced previews on disk.

Entries are keyed per source master rather than per display page, so page-size, order, star, and hidden-panel changes can reuse the same result. Validation includes the master and all linked detector chunks (path, size, nanosecond modification/change time, device, and inode), processing/cache version, requested dtype and detector bin, processed shape, scan override, detector center, and preset mask geometry. A changed or new master/chunk therefore invalidates only the affected master’s previews. Unreadable source chunks and corrupt or partial cache files are misses, never unverified scientific hits.

On a matching reopen, valid panels appear in their stable slots with an honest status such as Cached preview · loading raw data. The normal capacity-aware CUDA scheduler continues loading authoritative raw data, and fresh panels replace cached pixels in place. Partial pages mix immediate cache hits with loading placeholders. A failed refresh leaves the valid cached image visible with a refresh error; cached pixels are never silently called fresh. Custom detector ROIs and diffraction inspection wait for raw data and continue to use the requested source dtype and resolution.

The current CUDA-first implementation still loads one raw master before the widget is ready so it can establish detector shape, calibration, and the selected diffraction pattern. Persistent previews remove the black wait for the rest of the page and accelerate later page/preset returns; they do not yet make initial construction metadata-only. Performance reports therefore split API-call-to-model-ready from model-ready-to-cached-canvas paint.

Inspect or clear the persistent cache explicitly:

info = widget.preview_cache_info  # read-only snapshot
widget.clear_preview_cache()

clear_preview_cache() removes this folder/configuration’s persistent preview namespace. It does not clear ShowFolder’s thumbnail cache and does not free raw GPU memory. An active widget may repopulate the namespace when another standard preset or page is computed; construct it with preview_cache=False when the namespace must stay disabled. Maintainer verification follows S4D-19 and records cached-first, fresh-first, visible-page, complete-page, and prefetch timing separately.

For folder-backed multi-master browsing, dtype="auto" is resolved to a stable u16 load dtype. A lazy series needs every page to share shape and dtype; using per-master auto-narrowing could otherwise make one page uint16 and a later page uint8. Pass dtype="u8" explicitly only when lossy clipping is acceptable for browsing.

If a folder contains mixed scan shapes, from_folder(...) chooses the largest metadata-compatible group and warns with the skipped count. This prevents a mixed folder from failing halfway through page loading. Use scan_size= or a narrower pattern= when you intentionally want a smaller group.

On the Apple Silicon raw-Metal path, load_macbook_datasets(...) remains the backend-specific live handle:

from quantem.widget import Show4DSTEM
from quantem.widget.multidataset_mps import load_macbook_datasets

live = load_macbook_datasets("/data/live-scope-session", det_bin=4, scan_size=512)
widget = Show4DSTEM(live, title="Live 4D-STEM")
live.watch_master_folder("/data/live-scope-session", interval=2.0, scan_size=512)
widget

watch_master_folder(...) polls for *_master.h5 files, ignores masters whose linked data files are not present yet, and appends only new acquisitions. The notebook cell and viewer stay stable; the dataset slider grows as files become ready. Use live.stop_watch() before switching to a different folder.

GPU memory is owned by the loaded data object and the Python session, not by the visual widget alone. The live widget shows a compact GPU memory label in its title row when CUDA or MPS memory is visible. To release all memory, remove or replace the backend data object, clear references, use backend-specific cleanup utilities when provided, or restart the kernel/session. Exported HTML has no live Python GPU allocation, so it should not expose a “free GPU memory” control.

Multiple grid#

Use view_mode="multiple" when the extra frame axis represents multiple acquisitions that should be inspected side by side. The viewer keeps the standard diffraction-panel workflow: one shared detector ROI, one shared scan cursor, and one Dataset slider. The virtual-image side becomes a grid of ready frames or datasets. Older notebooks that pass view_mode="compare" still load as the same multiple-grid mode. Older view_mode="temporal" inputs are treated as view_mode="single" because the one-at-a-time dataset browser is the single view.

from quantem.widget import load, Show4DSTEM

widget = Show4DSTEM(
    load([path1, path2, path3, path4], det_bin=4),
    view_mode="multiple",
    compare_cols=2,
    compare_panel_gap_px=0,
    compare_max_panels=4,
    compare_dp_mode="average",
)
widget

compare_cols=0 lets the frontend pick a responsive layout. compare_layout accepts "side" and "top" for placing the shared diffraction panel next to or above the multiple grid. Positive compare_cols values are treated as the maximum grid columns on desktop; narrow/mobile viewports cap the grid at two columns so the tiles remain touch-friendly. On lazy MPS multi-dataset loads, the grid starts with the first decoded dataset and appends tiles as the background loader marks additional datasets ready; it does not materialize a full 5D stack just to build the comparison.

When the visible set is larger than compare_max_panels, the multiple grid is paged like Show2D/Show3D galleries. compare_page_idx is zero-based and compare_page_count is synced widget state, so notebooks can drive pages programmatically or save/restore the current page with the rest of the widget state.

Set compare_group_mode="all" or call widget.show_compare_all_groups() to collapse all visible pages into one dense comparison grid. Call widget.show_compare_paged_groups() to restore page-by-page browsing. The shared diffraction panel keeps using the active page for compare_dp_mode="average" so scan-position drags stay responsive even when the virtual-image grid is showing every reduced panel.

compare_panel_gap_px=0 renders the virtual-image grid edge-to-edge for dense screening. Increase it when a report or presentation needs visible gutters between panels. Mouse-wheel or trackpad scroll over a multiple tile zooms the shared virtual-image grid instead of scrolling the page; double-click a tile to reset the compare zoom. The single-panel diffraction and virtual-image canvases use the same scroll-to-zoom behavior.

The shared diffraction panel defaults to compare_dp_mode="average", which shows the mean diffraction pattern at the current scan position across visible ready multiple panels. Use compare_dp_mode="selected" when the diffraction panel should follow the clicked/active dataset instead.

Multiple panel curation is stored on the widget, so a notebook can reuse the same state in a later cell or saved HTML export:

widget.set_compare_panel_order(["scan-3", "scan-0", "scan-1", "scan-2"])
widget.hide_compare_panel("scan-4")
widget.star_compare_panel("scan-3")

state = widget.state_dict()
another_widget.load_state_dict(state)

The GUI exposes the same state: the star and hide icons live on each multiple tile, the reorder button enables drag-and-drop or click-then-click ordering, and the multiple toolbar can restore hidden panels or reset the saved panel state.

Exporting reports and raw 4D viewers#

Show4DSTEM has two HTML export modes with different goals:

Export kind

Use when

Data included

Memory behavior

export_kind="report"

Sharing a curated folder/multiple-grid result or saving a compact screening report

Static PNG virtual-image pages plus a representative diffraction pattern

Page-aware; lazy folder data is rendered page by page and raw 4D tensors are not embedded

export_kind="interactive"

The recipient must drive the actual 4D dataset offline in the browser

Raw 4D payload, optionally quantized/binned

Can be large; use binning deliberately before sending

Report exports are the safe default for large lazy folders:

widget.export_html(
    "show4dstem_report.html",
    export_kind="report",
    dataset_scope="unhidden",  # "current_page", "starred", or "all" also work
    scan_bin=2,                # mean-bin real space for smaller PNG pages
    det_bin=8,                 # mean-bin the representative DP thumbnail
    dtype="uint8",
)

Interactive raw exports remain available when the exported HTML needs the backendless Show4DSTEM widget, not just a report:

widget.export_html(
    "show4dstem_interactive.html",
    export_kind="interactive",
    dtype="uint8",       # "uint16" keeps the exact integer range but is larger
    scan_bin=2,          # real-space mean bin before embedding raw 4D
    det_bin=4,           # detector mean bin before embedding raw 4D
)

Both scan_bin and det_bin use mean binning, not summing. This keeps display exports from saturating uint8 and makes the file-size estimate in the GUI match the binned payload shape. The GUI export menu labels the same distinction as HTML report: static PNG, no raw 4D and HTML interactive raw 4D. The interactive section offers a size-sorted ladder of uint8/uint16, real-space-bin, and detector-bin presets so users can choose between a quick preview, a practical offline browser file, and exact raw 4D HTML deliberately.

Reference#

class quantem.widget.show4dstem.Show4DSTEM(*args: Any, **kwargs: Any)#

Bases: StaticFallbackMixin, AnyWidget

Fast interactive 4D-STEM viewer with advanced features.

Optimized for speed with binary transfer and pre-normalization. Works with NumPy and PyTorch arrays.

Parameters:
  • data (Dataset4dstem or array_like) – Dataset4dstem object (calibration auto-extracted), 4D array of shape (scan_rows, scan_cols, det_rows, det_cols), or 5D array of shape (n_frames, scan_rows, scan_cols, det_rows, det_cols) for time-series or tilt-series data.

  • scan_shape (tuple, optional) – If data is flattened (N, det_rows, det_cols), provide scan dimensions.

  • sampling (tuple of 4 floats, optional) – Pixel size per axis (scan_row, scan_col, k_row, k_col). Scalar broadcasts to all four axes. Defaults to (1, 1, 1, 1). Auto-extracted from Dataset4dstem if not provided.

  • units (list of 4 str, optional) – Unit string per axis. Common: ["A", "A", "mrad", "mrad"]. Defaults to ["pixels"] * 4. Auto-extracted from Dataset4dstem if not provided.

  • center (tuple[float, float], optional) – (center_row, center_col) of the diffraction pattern in pixels. If not provided, defaults to detector center.

  • bf_radius (float, optional) – Bright field disk radius in pixels. If not provided, estimated as 1/8 of detector size.

  • precompute_virtual_images (bool, default True) – Precompute BF/ABF/LAADF/HAADF virtual images for preset switching.

  • DPC_row (array_like, optional) – Precomputed real-space maps to expose in the image panel alongside the ROI-derived virtual image. Each map must be real-valued with shape (scan_rows, scan_cols) or (n_frames, scan_rows, scan_cols). SSB is the phase map; complex SSB inputs are rejected so amplitude is not selected silently.

  • DPC_col (array_like, optional) – Precomputed real-space maps to expose in the image panel alongside the ROI-derived virtual image. Each map must be real-valued with shape (scan_rows, scan_cols) or (n_frames, scan_rows, scan_cols). SSB is the phase map; complex SSB inputs are rejected so amplitude is not selected silently.

  • SSB (array_like, optional) – Precomputed real-space maps to expose in the image panel alongside the ROI-derived virtual image. Each map must be real-valued with shape (scan_rows, scan_cols) or (n_frames, scan_rows, scan_cols). SSB is the phase map; complex SSB inputs are rejected so amplitude is not selected silently.

  • vi_source ({"roi", "DPC_row", "DPC_col", "SSB"}, optional) – Initial image-panel source. Defaults to the ROI-derived virtual image.

  • frame_dim_label (str, optional) – Label for the frame dimension when 5D data is provided. Defaults to “Frame”. Common values: “Tilt”, “Time”, “Focus”.

  • view_mode ({"single", "multiple"}, default "single") – Scientific layout mode. "single" shows one selected frame/dataset. "multiple" shows a grid of virtual images for the first ready frames/datasets while sharing the detector ROI and scan cursor with the existing diffraction panel. Legacy aliases "temporal" and "compare" are accepted as "single" and "multiple".

  • compare_layout ({"side", "top"}, default "side") – Frontend layout hint for view_mode="multiple". The current widget renders "side" as the default shared-DP plus virtual-image grid.

  • compare_cols (int, default 0) – Number of columns in the compare virtual-image grid. 0 selects a responsive automatic layout.

  • compare_grid_width_px (int, default 0) – Desktop width of the compare virtual-image grid in CSS pixels. 0 uses the default responsive width. The frontend resize handle updates this value without changing the diffraction-panel size.

  • compare_panel_gap_px (int, default 0) – Gap between compare virtual-image panels in CSS pixels. 0 gives a dense, edge-to-edge grid for browsing many datasets; larger values can be useful in reports.

  • compare_max_panels (int, default 12) – Maximum ready frames/datasets included in the compare grid.

  • compare_group_mode ({"paged", "all"}, default "paged") – Compare-grid grouping behavior. "paged" shows one group of up to compare_max_panels panels at a time. "all" collapses all visible groups into one dense grid while still computing lazy datasets in page-sized batches.

  • compare_dp_mode ({"average", "selected"}, default "average") – Diffraction panel source in compare mode. "average" displays the mean diffraction pattern at the current scan position across visible ready compare panels. "selected" displays the active frame/dataset.

  • compare_cache_pages (int, default 16) – Number of reduced compare-grid virtual-image pages to keep in host memory. This caches BF/ABF/ADF/HAADF thumbnails across page changes and is separate from raw 4D GPU residency.

  • compare_cache_max_bytes (int, optional) – Host-memory cap for the reduced compare-grid page cache. Defaults to 512 MiB. Set to 0 or compare_cache_pages=0 to disable.

  • ui_mode ({"interactive", "presentation", "report", "minimal"}, default "interactive") – Preset for viewer chrome. Explicit show_* keyword arguments override the preset.

  • show_title (bool, default True) – Show the top title row.

  • show_controls (bool, default True) – Show the live control UI. Set False for a permanently clean display.

  • controls_collapsed (bool, default False) – Start with the live control UI collapsed. Unlike show_controls=False, Python can call expand_controls() later.

  • show_stats (bool, default True) – Show mean/min/max/std readout bars under the DP, virtual image, and FFT.

  • show_scale_bar (bool, default True) – Draw scale bars on the diffraction and virtual-image canvases.

  • debug (bool, default False) – Show a compact frontend FPS/debug badge in the widget title row.

  • save_state (bool, default False) – When False, saved notebooks omit heavy 4D buffers and keep a compact static preview for cold reopen. Set True only for small widgets that must reopen interactively without rerunning the kernel.

  • notebook_preview_format ({"jpeg", "webp", "png"} or None, default "jpeg") – Static preview format used when save_state=False. Set to None for live-only notebooks that should publish only the interactive view.

  • notebook_preview_quality (int, default 88) – Lossy preview quality for JPEG/WebP, from 1 to 100. Ignored for PNG.

  • notebook_preview_max_px (int, default 512) – Longest panel side for the saved-notebook preview.

Examples

>>> import numpy as np
>>> from quantem.widget.show4dstem import Show4DSTEM

4D NumPy array (scan_rows, scan_cols, det_rows, det_cols):

>>> Show4DSTEM(np.random.rand(64, 64, 128, 128))

PyTorch tensor (CPU or GPU):

>>> import torch
>>> Show4DSTEM(torch.rand(64, 64, 128, 128))

With explicit calibration (real-space Å, k-space mrad):

>>> Show4DSTEM(np.random.rand(64, 64, 128, 128),
...            sampling=(2.39, 2.39, 0.46, 0.46),
...            units=["A", "A", "mrad", "mrad"])

quantem Dataset4dstem — calibration + units auto-extracted:

>>> from quantem.core.datastructures import Dataset4dstem
>>> ds = Dataset4dstem.from_array(np.random.rand(64, 64, 128, 128))
>>> Show4DSTEM(ds)

Flattened scan (N, det_rows, det_cols) with explicit scan shape:

>>> Show4DSTEM(np.random.rand(4096, 128, 128), scan_shape=(64, 64))

Custom BF disk center and radius (overrides auto-detection):

>>> Show4DSTEM(np.random.rand(64, 64, 128, 128),
...            center=(64, 64), bf_radius=12)

5D time-series or tilt-series (n_frames, scan_r, scan_c, det_r, det_c):

>>> Show4DSTEM(np.random.rand(20, 64, 64, 128, 128), frame_dim_label="Tilt")

Raster animation (scan path through 4D dataset):

>>> w = Show4DSTEM(np.random.rand(64, 64, 128, 128))
>>> w.raster(step=2, interval_ms=50)

Static export to PDF or PNG (single panel or all four):

>>> w = Show4DSTEM(np.random.rand(64, 64, 128, 128))
>>> w.save_image("dp.pdf", view="diffraction")
>>> w.save_image("all.pdf", view="all")
set_vi_product_map(label: str, value: Any) Self#

Attach or replace a static virtual-image product map.

compute_ssb(*, set_source: bool = True, verbose: bool = False, **kwargs) ndarray#

Compute SSB phase in the live kernel and attach it as the SSB map.

export_html(path: str | Path | None = None, *, title: str | None = None, mode: str = 'single', encoding: str | None = None, downsample: int | None = None, dtype: str = 'uint8', det_bin: int = 1, scan_bin: int = 1, real_space_bin: int | None = None, export_kind: str = 'interactive', dataset_scope: str = 'unhidden') Path#

Write a standalone HTML viewer.

export_kind="interactive" packages raw 4D data into the standalone browser-compute widget. export_kind="report" writes a compact static virtual-image report with no raw 4D payload, which is the safe export path for lazy folder-backed viewers. det_bin bins detector pixels by mean over det_bin x det_bin blocks. scan_bin (or alias real_space_bin) bins scan pixels by mean over scan_bin x scan_bin blocks. dtype may be "uint8" or "uint16".

get_state(key=None, drop_defaults=False)#

Trait state for comm sync and notebook embedding.

ipywidgets calls this with key=None to snapshot the FULL state that gets written into the saved notebook’s metadata.widgets. When save_state is False we drop the heavy buffers from that snapshot so a plain Show4DSTEM does not bake the packed 4D stack into the .ipynb. Targeted syncs (key is a name or set, used by hold_sync / send_state during live rendering - e.g. the deferred virtual_image_bytes / frame_bytes re-send on mount) are untouched, so the frontend still receives every buffer normally. save_state=True embeds everything so a reopened notebook restores the interactive offline widget without a kernel.

collapse_controls() Self#

Collapse the live control UI programmatically.

expand_controls() Self#

Expand the live control UI.

toggle_controls() Self#

Toggle the collapsed state of the live control UI.

free()#

Free GPU memory held by this widget.

Drops EVERY reference to the data tensor and flushes the allocator pools, so the stack actually leaves VRAM (no kernel restart needed). The data is held in FOUR places, not one: self._data plus the compute backend’s cached _t / _4d / _flat views (self._compute_backend) plus the per-op self._compute_for cache - missing any one keeps the whole stack pinned. And the storage is cupy-owned (io.load decompresses with cupy; the widget wraps it via from_dlpack), so when the torch refs die the memory returns to the CUPY pool, which torch.empty_cache cannot release - the cupy pool is freed too. Call before loading a new dataset.

Examples

>>> w.free()          # release the full stack from VRAM
>>> del result        # free the source array
property position: tuple[int, int]#

Current scan position as (row, col) tuple.

property scan_shape: tuple[int, int]#

Scan dimensions as (rows, cols) tuple.

property detector_shape: tuple[int, int]#

Detector dimensions as (rows, cols) tuple.

set_path(points: list[tuple[int, int]], interval_ms: int = 100, loop: bool = True, autoplay: bool = True) Self#

Set a custom path of scan positions to animate through.

Parameters:
  • points (list[tuple[int, int]]) – List of (row, col) scan positions to visit.

  • interval_ms (int, default 100) – Time between frames in milliseconds.

  • loop (bool, default True) – Whether to loop when reaching end.

  • autoplay (bool, default True) – Start playing immediately.

Returns:

Self for method chaining.

Return type:

Show4DSTEM

Examples

>>> widget.set_path([(0, 0), (10, 10), (20, 20), (30, 30)])
>>> widget.set_path([(i, i) for i in range(48)], interval_ms=50)
play() Self#

Start playing the path animation.

pause() Self#

Pause the path animation.

stop() Self#

Stop and reset path animation to beginning.

goto(index: int) Self#

Jump to a specific index in the path.

raster(step: int = 1, bidirectional: bool = False, interval_ms: int = 100, loop: bool = True) Self#

Play a raster scan path (row by row, left to right).

This mimics real STEM scanning: left→right, step down, left→right, etc.

Parameters:
  • step (int, default 1) – Step size between positions.

  • bidirectional (bool, default False) – If True, use snake/boustrophedon pattern (alternating direction). If False (default), always scan left→right like real STEM.

  • interval_ms (int, default 100) – Time between frames in milliseconds.

  • loop (bool, default True) – Whether to loop when reaching the end.

Returns:

Self for method chaining.

Return type:

Show4DSTEM

roi_circle(radius: float | None = None) Self#

Switch to circle ROI mode for virtual imaging.

In circle mode, the virtual image integrates over a circular region centered at the current ROI position (like a virtual bright field detector).

Parameters:

radius (float, optional) – Radius of the circle in pixels. If not provided, uses current value or defaults to half the BF radius.

Returns:

Self for method chaining.

Return type:

Show4DSTEM

Examples

>>> widget.roi_circle(20)  # 20px radius circle
>>> widget.roi_circle()    # Use default radius
roi_point() Self#

Switch to point ROI mode (single-pixel indexing).

In point mode, the virtual image shows intensity at the exact ROI position. This is the default mode.

Returns:

Self for method chaining.

Return type:

Show4DSTEM

roi_square(half_size: float | None = None) Self#

Switch to square ROI mode for virtual imaging.

In square mode, the virtual image integrates over a square region centered at the current ROI position.

Parameters:

half_size (float, optional) – Half-size of the square in pixels (distance from center to edge). A half_size of 15 creates a 30x30 pixel square. If not provided, uses current roi_radius value.

Returns:

Self for method chaining.

Return type:

Show4DSTEM

Examples

>>> widget.roi_square(15)  # 30x30 pixel square (half_size=15)
>>> widget.roi_square()    # Use default size
roi_annular(inner_radius: float | None = None, outer_radius: float | None = None) Self#

Set ROI mode to annular (donut-shaped) for ADF/HAADF imaging.

Parameters:
  • inner_radius (float, optional) – Inner radius in pixels. If not provided, uses current roi_radius_inner.

  • outer_radius (float, optional) – Outer radius in pixels. If not provided, uses current roi_radius.

Returns:

Self for method chaining.

Return type:

Show4DSTEM

Examples

>>> widget.roi_annular(20, 50)  # ADF: inner=20px, outer=50px
>>> widget.roi_annular(30, 80)  # HAADF: larger angles
roi_rect(width: float | None = None, height: float | None = None) Self#

Set ROI mode to rectangular.

Parameters:
  • width (float, optional) – Width in pixels. If not provided, uses current roi_width.

  • height (float, optional) – Height in pixels. If not provided, uses current roi_height.

Returns:

Self for method chaining.

Return type:

Show4DSTEM

Examples

>>> widget.roi_rect(30, 20)  # 30px wide, 20px tall
>>> widget.roi_rect(40, 40)  # 40x40 rectangle
auto_detect_center(update_roi: bool = True) Self#

Automatically detect BF disk center and radius using centroid.

This method analyzes the summed diffraction pattern to find the bright field disk center and estimate its radius. The detected values are applied to the widget’s calibration (center_row, center_col, bf_radius).

Parameters:

update_roi (bool, default True) – If True, also update ROI center and recompute cached virtual images. Set to False during __init__ when ROI is not yet initialized.

Returns:

Self for method chaining.

Return type:

Show4DSTEM

Examples

>>> widget = Show4DSTEM(data)
>>> widget.auto_detect_center()  # Auto-detect and apply
save_image(path: str | Path, view: str | None = None, position: tuple[int, int] | None = None, frame_idx: int | None = None, format: str | None = None, include_metadata: bool = True, metadata_path: str | Path | None = None, include_overlays: bool | None = None, include_scalebar: bool | None = None, restore_state: bool = True, dpi: int | None = None) Path#

Save the current visualization as PNG or PDF.

Parameters:
  • path (str or pathlib.Path) – Output image path.

  • view (str, optional) – One of: “diffraction”, “virtual”, “fft”, “all”.

  • position (tuple[int, int], optional) – Temporary scan position override as (row, col) for this export.

  • frame_idx (int, optional) – Temporary frame index override for 5D data.

  • format (str, optional) – “png” or “pdf”. If omitted, inferred from file extension.

  • include_metadata (bool, default True) – If True, writes JSON metadata next to the image.

  • metadata_path (str or pathlib.Path, optional) – Override metadata JSON path.

  • include_overlays (bool, default True) – Draw ROI/profile/crosshair overlays on exported panels.

  • include_scalebar (bool, default True) – Draw panel scale bars on exported panels.

  • restore_state (bool, default True) – If True, temporary position/frame overrides are reverted after export.

  • dpi (int, optional) – Export DPI metadata.

Returns:

The written image path.

Return type:

pathlib.Path

property compare_ordered_panels: list[int]#

Frame/dataset indices in the current compare display order.

property compare_visible_panels: list[int]#

Frame/dataset indices currently visible in compare mode.

set_compare_hidden_panels(panels: Sequence[int | str] | int | str) Self#

Replace the hidden compare panel set by index or exact label.

hide_compare_panel(*panels: int | str) Self#

Hide one or more compare panels by zero-based index or exact label.

show_compare_panel(*panels: int | str) Self#

Restore one or more hidden compare panels by index or exact label.

show_all_compare_panels() Self#

Restore every compare panel.

property preview_cache_info: dict[str, Any]#

Persistent reduced-preview cache status for this folder viewer.

clear_preview_cache() Self#

Delete this viewer’s derived disk previews, never raw 4D data.

preload_all_datasets(*, background: bool = True) Self#

Keep every unhidden lazy dataset in VRAM when the full set fits.

The decision uses Dataset5dstem.residency_plan(), which knows each frame’s shape and dtype without reading it. If memory becomes unavailable after planning, Show4DSTEM retains full-resolution paging and reports the fallback without surfacing a raw backend allocation error.

stop_dataset_preload(*, wait: bool = False) Self#

Stop automatic all-dataset loading after the current file read.

wait_for_dataset_preload(timeout: float | None = None) Self#

Wait for an automatic all-dataset preload, primarily for verification.

poll_folder() list[int]#

Append newly ready folder masters as lazy frames.

Only widgets created by Show4DSTEM.from_folder(...) have a folder source attached. A newly discovered master must produce the same complete header/source signature on two consecutive polls before it is appended. New masters start as cold lazy slots, then join the complete series preload when the updated shape/dtype footprint still fits.

watch_folder(*, interval: float = 2.0) Self#

Poll the attached folder in the background and append ready masters.

stop_folder_watch() None#

Stop the background folder watcher, if one was started.

close() None#

Stop background work and close the widget comm.

set_compare_panel_order(panels: Sequence[int | str]) Self#

Set the compare-grid display order by panel index or exact label.

reset_compare_panel_order() Self#

Restore the natural compare panel order.

move_compare_panel(panel: int | str, position: int) Self#

Move one compare panel to a zero-based display position.

set_compare_page(page: int) Self#

Show a zero-based page of compare-grid panels.

next_compare_page() Self#

Advance the compare grid by one page.

previous_compare_page() Self#

Move the compare grid back by one page.

show_compare_paged_groups() Self#

Show one compare-grid group/page at a time.

show_compare_all_groups() Self#

Collapse all visible compare-grid groups into one dense grid.

set_compare_starred_panels(panels: Sequence[int | str] | int | str) Self#

Replace the set of starred compare panels by index or exact label.

star_compare_panel(panel: int | str) Self#

Mark a compare panel with a star.

unstar_compare_panel(panel: int | str) Self#

Clear the star on a compare panel.

stop_compare_maintenance(*, wait: bool = False) Self#

Stop and optionally join folder preload/cache maintenance.

stop_compare_page_load(*, wait: bool = False) Self#

Cancel progressive visible/prefetch work after its current GPU wave.

wait_for_compare_page(timeout: float | None = None) Self#

Wait for visible completion and neighbor prefetch, for verification.

warm_compare_cache(presets: Sequence[str] = ('bf', 'abf', 'adf', 'haadf'), *, background: bool = True) Self#

Cache standard detector views without retaining every raw 4D master.

Folder-backed data are loaded in memory-aware batches. All requested detector presets are reduced while each batch is resident, then only the small 2D virtual images remain in host memory.

stop_compare_cache_warm(*, wait: bool = False) None#

Stop background detector-preset caching after the current GPU batch.

Note

The generated reference above is the universal base viewer. The public quantem.widget.Show4DSTEM factory accepts the same viewer options plus dispatch options such as backend="web", offline_codec, data_url, and export_html(...).

Interactive controls#

With a running kernel these recompute on the GPU backend (CUDA / MPS / CPU). In backend="web" mode, the same controls run in the browser via WebGPU with no Python round trip - see Performance.

Control

Trait

Expected effect

Detector position (drag on diffraction)

pos_row, pos_col

Virtual image recomputes for that probe position

BF aperture radius

bf_radius

Bright-field disk grows/shrinks; virtual image updates

Aperture center

center_row, center_col

Recenters the detector on the unscattered beam

Detector ROI mode

roi_mode, roi_active

Switch BF / annular / rectangular detector

Annular inner / outer

roi_radius_inner, roi_radius

ADF annulus geometry

Virtual-image ROI

vi_roi_mode, vi_roi_center_row, vi_roi_center_col

Pick a real-space region to average its diffraction

FFT toggle

show_fft, fft_window

Power spectrum of the virtual image

Multiple grid

view_mode="multiple", compare_cols, compare_panel_gap_px, compare_max_panels, compare_group_mode, compare_layout

Shows ready frames/datasets as synchronized virtual images sharing the detector ROI and scan cursor; compare_group_mode="all" collapses pages into one dense grid

Multiple DP source

compare_dp_mode

Shows either the average DP across visible multiple panels or the selected panel’s DP

Multiple panel state

compare_panel_order, compare_hidden_panels, compare_starred_panels; set_compare_panel_order(), hide_compare_panel(), show_all_compare_panels(), star_compare_panel()

Saves/reuses panel order, hidden panels, and starred picks across cells, state files, and HTML export

Viewer chrome preset

ui_mode plus explicit show_* kwargs

Applies shared display presets; see Viewer UI controls

Control visibility

show_controls, controls_collapsed; collapse_controls(), expand_controls(), toggle_controls()

Permanently remove controls or programmatically collapse/expand them for clean exports

Title visibility

show_title

Top title row shows/hides

Stats visibility

show_stats

DP, virtual-image, and FFT stats bars show/hide

Scale bar visibility

show_scale_bar

DP and virtual-image scale bars show/hide

Scan-path playback

path_playing, path_index, path_interval_ms

Sweeps the probe across the scan

k-space calibration

k_pixel_size, k_calibrated

Diffraction axes read in mrad when calibrated