Show3D

Contents

Show3D#

A 3D volume scrubbed slice by slice, with playback and an interactive-HTML export. See the Show3D tutorial.

Reference#

class quantem.widget.show3d.Show3D(*args: Any, **kwargs: Any)#

Bases: WatchedImageFolderMixin, StaticFallbackMixin, AnyWidget

Interactive 3D stack viewer for sequential 2D images.

Renders an (N, H, W) stack through a WebGPU canvas (CPU fallback when WebGPU is unavailable) with a sliding prefetch buffer for smooth playback. The full stack is held once on the Python side; scrubbing and playback ship individual frames or chunks over the Jupyter Comm channel. Common use cases: defocus sweep, time series, depth stack, in-situ movie, side-by-side trial comparison.

Features#

  • Interactive scrubber + play / pause / stop / goto with bookmark and loop range

  • Per-frame statistics, log scale, percentile auto-contrast, manual vmin/vmax

  • Diff mode (vs first frame or vs previous frame) for delta visualization

  • ROI tools (circle / square / rectangle / annular) with per-frame timeseries

  • Line profiles sampled across the full stack

  • Page-aware depth/time kymographs for matched single-panel comparisons

  • FFT panel with compact quality labels and optional Hann window

  • Side-by-side multi-panel mode with linked or independent zoom / pan / contrast

  • Panel visibility controls by index or title without removing data

  • Frame hiding (hide, show, set_hidden, show_all) without rebuilding

  • Python-side PNG / PDF / TIFF single-frame save via save_image

  • JSON state save/load via state_dict / load_state_dict / save

  • Explicit free to release VRAM/RAM held by traitlets observers

param data:

3D array of shape (N, height, width) where N is the stack dimension. Also accepts a 2D array (treated as a single-frame stack), a torch tensor (CPU or GPU, any dtype), or a quantem Dataset3d. Complex input is rejected; cast to magnitude or phase first.

type data:

array_like

param labels:

Labels for each slice (e.g., ["C10=-500nm", "C10=-400nm", ...]). If None, uses string slice indices.

type labels:

list[str] | None, optional

param frame_metadata:

Generic metadata for each frame. If labels is not provided, entries are formatted into labels using frame_label_format or a compact key=value default. This is repository-neutral: upstream packages can pass fields such as iteration, defocus_nm, loss, dose, or any other JSON-like values.

type frame_metadata:

sequence of mapping, optional

param panel_frame_metadata:

Per-panel frame metadata, shaped [panel][frame]. If panel_frame_labels is not provided, entries are formatted into the per-panel overlay labels using frame_label_format.

type panel_frame_metadata:

sequence of sequence of mapping, optional

param frame_label_format:

Formatter for metadata-derived labels. A string uses Python format fields, for example "iter {iteration} · df={defocus_nm:.1f} nm". A callable may accept (metadata), (metadata, frame_idx), or (metadata, frame_idx, panel_idx) and is resolved to strings before HTML export.

type frame_label_format:

str or callable, optional

param title:

Title to display above the image.

type title:

str, optional

param cmap:

Colormap name. Use Colormap enum (Colormap.MAGMA, Colormap.VIRIDIS, etc.) or string (“magma”, “viridis”, “gray”, “inferno”, “plasma”).

type cmap:

str or Colormap, default Colormap.MAGMA

param vmin:

Minimum value for colormap. If None, uses data min.

type vmin:

float, optional

param vmax:

Maximum value for colormap. If None, uses data max.

type vmax:

float, optional

param sampling:

Real-space sampling for the scale bar (lateral). Pairs with units.

type sampling:

float or tuple of float, optional

param units:

Unit for sampling (e.g. "A" for Angstrom, "nm"). Matches the quantem Dataset / Show2D / Show4DSTEM convention.

type units:

str or list of str, optional

param log_scale:

Use log scale for intensity mapping.

type log_scale:

bool, default False

param auto_contrast:

Use percentile-based contrast (ignores vmin/vmax).

type auto_contrast:

bool, default True

param link_contrast:

Share one contrast range across panels. Paged data defaults to False so each reconstruction uses its own automatic percentile range; ordinary multi-panel data defaults to True. Pass True for a matched physical scale across every page and panel.

type link_contrast:

bool, optional

param percentile_low:

Lower percentile for auto-contrast.

type percentile_low:

float, default 0.5

param percentile_high:

Upper percentile for auto-contrast.

type percentile_high:

float, default 99.5

param fps:

Frames per second for playback, capped at 60.

type fps:

float, default 30.0

param timestamps:

Timestamps for each frame (e.g., seconds or dose values).

type timestamps:

list of float, optional

param timestamp_unit:

Unit for timestamps (e.g., “s”, “ms”, “e/A2”).

type timestamp_unit:

str, default “s”

param show_fft:

Show the FFT for the current frame and visible panels.

type show_fft:

bool, default False

param fft_layout:

Place the FFT below the real-space grid, beside it, or as one inset in every visible panel.

type fft_layout:

{“bottom”, “right”, “overlay”}, default “bottom”

param fft_overlay_position:

Initial corner for inset FFTs. Inset overlays can be dragged and snap to the nearest corner.

type fft_overlay_position:

{“top-left”, “top-right”, “bottom-left”, “bottom-right”}, default “top-left”

param fft_overlay_size:

Inset FFT size as a fraction of its real-space panel, from 0.2 to 0.7.

type fft_overlay_size:

float, default 0.35

param fft_overlay_zoom:

Initial shared FFT magnification for bottom, right, and overlay layouts. Every FFT tile or inset displays this live value as an N.N× badge while show_zoom_indicator is enabled.

type fft_overlay_zoom:

float, default 1.0

param fft_window:

Apply a Hann window before computing the FFT.

type fft_window:

bool, default True

param fft_metrics:

Show cached FFT sharpness, peak-count, and peak-SNR labels.

type fft_metrics:

bool, default True

param size:

Canvas rendering size in CSS pixels (the on-screen width of the main viewport). 0 uses the frontend default (500 px). Pass e.g. size=800 to enlarge for a presentation, or size=300 to compress alongside a control panel. This controls display only - the underlying stack resolution is never resampled; scrubbing and zoom still see every pixel of the full-resolution frame.

type size:

int, default 0

param panel_width_px:

Display width for each panel in CSS pixels. When >0, this wins over size and the frontend derives the total grid canvas from the active column count. This is display-only; the synced panel_width_px trait still records source panel geometry for multi-panel frame slicing.

type panel_width_px:

int, default 0

param padding:

Zero-valued spatial border added to every frame before display. Use this when aligned movies need extra field of view so shifted frames do not crop at the canvas edge. An int pads rows and columns equally; (rows, cols) pads each axis independently.

type padding:

int or tuple[int, int], default 0

param crop:

Spatial crop applied before padding. Use an int to crop all sides, (rows, cols) for symmetric row/column cropping, or (top, bottom, left, right) for side-specific cropping.

type crop:

int or tuple[int, int] or tuple[int, int, int, int], default 0

param max_cols:

Multi-panel grid wrap. 0 = single row (no wrap), N>0 = wrap into rows of at most N panels. Default 4 is a good fit for a 13”–16” laptop screen; bump to 6 on wide monitors or drop to 3 for a portrait split layout. Empty trailing cells in a partial last row are not rendered (transparent, non-interactive).

type max_cols:

int, default 4

param panel_gap:

Gap in CSS pixels between adjacent panels. 0 = flush (panels share an edge - useful for tiled montages), 20 = roomy (clear separation for slides). Single-panel widgets ignore this.

type panel_gap:

int, default 0

param inter_panel_gap_px:

Explicit replacement for panel_gap when the layer between panels needs a chosen color. The gap is real layout space between panel slots, not a stroke drawn inside a panel.

type inter_panel_gap_px:

int, str, optional

param inter_panel_gap_color:

Explicit replacement for panel_gap when the layer between panels needs a chosen color. The gap is real layout space between panel slots, not a stroke drawn inside a panel.

type inter_panel_gap_color:

int, str, optional

param gallery_outer_border_px:

Frame around the entire multi-panel gallery. This is separate from the inter-panel gap and from per-panel strokes.

type gallery_outer_border_px:

int, str, optional

param gallery_outer_border_color:

Frame around the entire multi-panel gallery. This is separate from the inter-panel gap and from per-panel strokes.

type gallery_outer_border_color:

int, str, optional

param panel_inner_border_px:

Stroke drawn inside each panel slot over the image edge. Defaults to 0 in Show3D to preserve the existing no-stroke canvas look.

type panel_inner_border_px:

float, str, optional

param panel_inner_border_color:

Stroke drawn inside each panel slot over the image edge. Defaults to 0 in Show3D to preserve the existing no-stroke canvas look.

type panel_inner_border_color:

float, str, optional

param panel_title_font_size:

Font size in CSS pixels for the per-panel title drawn at the top of each multi-panel slot. Bump to 14–16 for slide-projection clarity; drop to 9 to fit titles inside narrow panels on a small screen.

type panel_title_font_size:

int, default 11

param hidden_panels:

Multi-panel entries to collapse from the initial view. Integers are zero-based panel indices; strings match panel_titles exactly. Hidden panels stay in the widget state and exported HTML, and can be restored later with show_panel or show_all_panels.

type hidden_panels:

sequence of int or str, optional

param panel_order:

Multi-panel display order. Integers are zero-based panel indices; strings match panel_titles exactly. Reordering is display-only: source data, titles, stars, and hidden-panel state stay keyed by their original panel indices.

type panel_order:

sequence of int or str, optional

param panel_groups:

Rectangular grouping overlays for related panels. Each dict accepts {"panels": [0, 1, 2], "color": "#22c55e", "label": "raw"} or contiguous {"start": 0, "end": 2, ...} panel indices.

type panel_groups:

sequence of dict, optional

param panel_overlays:

Reproducible per-panel circle, rectangle, and square overlays. A mapping keyed by panel index or panel_titles value targets specific panels; a per-panel list aligns with panel order. Coordinates are data pixels by default using (row, col) conventions, and coords="relative" switches to normalized 0-1 panel coordinates. Style keys include stroke, stroke_width, line_style, dash, stroke_opacity, fill, fill_opacity, opacity, and z_order.

type panel_overlays:

mapping or sequence, optional

param overlays:

Convenience alias for shared geometric overlays. A single overlay or a flat list without panel= is broadcast to every panel. Use either overlays or panel_overlays, not both.

type overlays:

mapping or sequence, optional

param show_panel_titles:

Draw the top-center per-panel title and frame counter on multi-panel canvases. Set False for clean GIF/video exports.

type show_panel_titles:

bool, default True

param show_resize_handles:

Render the bottom-right corner triangle on every real panel. Dragging any handle resizes the entire multi-panel canvas (linked). Set False to declutter a screenshot or printed figure where the operator already has the layout they want.

type show_resize_handles:

bool, default True

param show_zoom_indicator:

Draw the live 1.0×-style zoom readout at the bottom-left of every real-space panel and every FFT tile or inset. Set False for clean static layouts or when the scale bar alone is enough to communicate scale.

type show_zoom_indicator:

bool, default True

param show_scale_bar:

Draw the bottom-right scale bar. When pixel_size is provided the label uses physical units; otherwise it shows pixel units. Set False for GIF/video exports or uncluttered figure captures.

type show_scale_bar:

bool, default True

param show_controls:

Show the live control UI. Set False for a permanently clean display.

type show_controls:

bool, default True

param controls_collapsed:

Start with the live control UI collapsed behind a small GUI toggle. Unlike show_controls=False, users can expand the controls in the frontend and Python can call expand_controls() later.

type controls_collapsed:

bool, default False

param debug:

Show a compact frontend FPS/debug badge in the widget title row.

type debug:

bool, default False

param save_state:

When False, saved notebooks omit heavy stack buffers and keep a compact static preview for cold reopen. Set True only for small widgets that must reopen interactively without rerunning the kernel.

type save_state:

bool, default False

param notebook_preview_format:

Static preview format used when save_state=False. "jpeg" is the most portable notebook default, "webp" is smaller for local/report workflows, "png" is lossless but larger, and None disables the preview.

type notebook_preview_format:

{“jpeg”, “webp”, “png”} or None, default “jpeg”

param notebook_preview_quality:

Lossy preview quality for JPEG/WebP, from 1 to 100. Ignored for PNG.

type notebook_preview_quality:

int, default 88

param notebook_preview_max_px:

Longest panel side for the saved-notebook preview. Lower values make notebooks smaller; higher values make the static fallback sharper.

type notebook_preview_max_px:

int, default 512

param notebook_preview_frames:

Zero-based frame indices to render into the saved-notebook static preview for single-panel Show3D. When omitted, the preview shows the current slice_idx only. Use this for compact cold-reopen contact sheets such as frames [0, 12, 25, 80].

type notebook_preview_frames:

sequence of int, optional

param notebook_preview_ncols:

Number of columns for notebook_preview_frames. None or 0 uses the widget column setting, capped by the number of preview frames.

type notebook_preview_ncols:

int, optional

param denoise:

Display-only denoise method for sparse map stacks (EDS, low dose), applied to every playback frame and to the FFT view. Three orthogonal choices: "none", "gaussian", or "anscombe" (Poisson count-respecting smoothing); binning is the separate denoise_bin knob. Recommendation ladder: sparse EDS -> "anscombe" with denoise_bin=2 and sigma 6-10; very sparse -> "anscombe" with denoise_bin=4 and sigma 8-12; decent-dose HAADF -> "gaussian" sigma 1-2 or "none"; anything quantitative -> "none". The compound spellings "bin2", "bin2_anscombe" and "bin4_anscombe" stay accepted as aliases that fold into (mode, bin); "tv" remains available from Python (not in the UI menu). Pure view transform: the stored stack, the stats row, and every export of raw data keep the original counts, and the lossless default is "none". When active, a one-line banner announces the reduction and how to get raw counts back. An active filter also reshapes the FFT view, so set denoise="none" for quantitative FFT work.

type denoise:

str, default “none”

param denoise_sigma:

Smoothing scale in pixels for the Gaussian/Anscombe display filters.

type denoise_sigma:

float, default 4.0

param denoise_bin:

Display-side 2x bin passes for SNR, combined with denoise. 1 (the default) is lossless. Independent of display_bin (the GPU display budget knob).

type denoise_bin:

{1, 2, 4}, default 1

param show_denoise:

Show the denoise controls row. Hidden by default to keep the widget clean; auto-enabled when the stack starts with an active denoise. An active reduction always shows its banner, even with the row hidden. The deprecated aliases display_filter, display_sigma and spatial_bin are still accepted for one release and map onto denoise, denoise_sigma and denoise_bin.

type show_denoise:

bool, default False

render_total_ms#

End-to-end wall clock from constructor start to first browser paint, populated by a JS→Python round-trip after the first canvas render. None until the browser has actually painted; also printed to stdout when it fires. Use to triage “is it Python, wire, or the browser?” during live acquisitions.

Type:

int or None

render_python_build_ms#

Subset of render_total_ms covering Python __init__ only.

Type:

int or None

render_wire_js_ms#

Subset covering everything after Python returns: Comm transfer, JS decode, colormap, and canvas paint.

Type:

int or None

Example

Instantiate Show3D with a stack of 2D images, drive playback, and persist the display state to disk:

>>> import numpy as np
>>> from quantem.widget import Show3D
>>> stack = np.random.rand(12, 256, 256).astype(np.float32)
>>> labels = [f"C10={c:.0f}nm" for c in np.linspace(-500, -200, 12)]
>>> w = Show3D(stack, labels=labels, title="Defocus Sweep", sampling=0.5, units="A")
>>> w.play()  
>>> w.goto(3)  
>>> w.save("show3d_state.json")  

Notes

  • The stack is loaded once into self._data (float32); set_image replaces the data without rebuilding the widget.

  • For live acquisitions or reconstruction loops where the stack grows over time, construct the widget with offline=False before calling set_image. Small initial stacks can otherwise use the saved/offline notebook representation, which is meant for static notebook state and standalone exports rather than streaming new frames.

  • display_bin="auto" keeps Show3D source pixels unchanged. For heavy multi-panel movies or standalone exports, pass an explicit display_bin=N to build a smaller display stack. This is a display tradeoff: it improves first load and playback, while native-pixel viewing requires display_bin=1 or a separate high-resolution workflow.

  • Multi-panel mode is auto-detected from input shape and configured via n_panels, panel_titles, and link_panels.

  • Call free() before discarding the widget; del alone will not release VRAM because traitlets observers pin the refcount.

classmethod from_rgb(stack, **kwargs) Self#

Play a true-color stack, bypassing grayscale/color auto-detection.

The plain constructor treats a trailing axis of 3 or 4 as color only when it is unambiguous (N > 4 frames). from_rgb is the explicit path for the cases it cannot call: a short color stack of 4 or fewer frames, or an (N, H, W, 3) array the heuristic might read as a scalar field. Color ships to the browser in full; a Rec. 709 luminance plane drives stats, FFT, and ROI.

Parameters:
  • stack (np.ndarray) – Color frames as (N, H, W, 3), (N, H, W, 4), or a single (H, W, 3) frame. uint8 or float in [0, 1].

  • **kwargs – Any other Show3D option (title, fps, sampling, …).

Returns:

A viewer painting the stack in true color.

Return type:

Show3D

Examples

>>> import numpy as np  
>>> Show3D.from_rgb(np.stack([frame_a, frame_b]))  # 2-frame color  

Browse a set of named result figures as one labeled, scrubbable stack.

Built for figure selection - hand it the candidate figures (a lambda sweep, competing reconstructions, before/after variants) and scrub or keyboard through them to pick the best, with each figure’s name in the frame label. Grayscale and true-color figures can be mixed: if any figure is color, all are promoted to RGB so one viewer shows them together. Every figure must share the same height and width.

Parameters:
  • figures (mapping or sequence of np.ndarray) – A {label: image} mapping (labels come from the keys), or a sequence of images. Each image is (H, W) grayscale or (H, W, 3) / (H, W, 4) color.

  • labels (sequence of str, optional) – Frame labels when figures is a sequence. Defaults to figure 1, figure 2, … .

  • **kwargs – Any other Show3D option (title, fps, cmap, …).

Returns:

A viewer scrubbing through the labeled figures.

Return type:

Show3D

Raises:

ValueError – If figures is empty, or the figures do not all share one (H, W).

Examples

>>> Show3D.from_figure_gallery({"lambda 0.3": fig_a, "lambda 3": fig_b})  
>>> Show3D.from_figure_gallery([recon_1, recon_2], labels=["adam", "lbfgs"])  
classmethod from_gif(path: str | Path, *, fps: float | None = None, title: str | None = None, frame_labels: list[str] | bool | None = None, **kwargs) Self#

Open a static or animated GIF as a playable stack.

The GIF is decoded through quantem.widget.io.read_gif into a grayscale Dataset3d. Pass normal Show3D keyword arguments such as cmap, show_controls, max_cols, or panel_width_px.

classmethod from_folder(path: str | Path, *, pattern: str = '*', recursive: bool = False, watch: bool = True, watch_interval: float = 1.0, **kwargs) Self#

Play naturally ordered, full-resolution folder images as one stack.

Stable additions update this widget in place. Every file is decoded through quantem.widget.io.read_image(); failed or partially written files remain pending for a later poll. Folder size never creates pages: every matching file extends the frame axis of this one stack.

classmethod from_panel_folders(panel_paths: Sequence[str | Path], *, pattern: str | None = None, file_type: str | None = None, frame_count: int | None = None, labels: Sequence[str] | None = None, panel_frame_labels: Sequence[Sequence[str]] | None = None, panel_titles: Sequence[str] | None = None, **kwargs) Self#

Open same-shape image folders as lazy full-resolution panels.

Only the first frame from each panel is decoded during construction so remote notebooks can mount promptly. The frame server decodes requested native frames on demand; source pixels remain full resolution.

to_show2d(frame: int | None = None, panel: Sequence[int | str] | int | str | None = None, *, title: str | None = None, copy: bool = True, include_hidden: bool = False)#

Create a Show2D view from the current Show3D frame.

By default this converts the current frame for all visible panels. Pass panel=... to choose a panel by index or title. Hidden panels are skipped unless include_hidden=True.

set_image(data, labels: list[str] | None = None, *, rgb: bool | None = None, display_bin: int | None = None) None#

Replace the stack data in place without rebuilding the widget.

Swaps in a new stack while preserving display settings (cmap, contrast, log scale, pixel size, FFT toggle, playback config) so the operator can cycle through datasets in one widget cell. Resets state that is tied to the previous frame dimensions: ROIs and the line profile are cleared if (height, width) changes; bookmarks and the loop range are clamped to the new n_slices; the playback prefetch buffer is invalidated; cached display data and any in-flight ROI debounce timer are dropped.

Parameters:
  • data (array_like) – 3D stack (N, H, W), a 2D image (promoted to a single-frame stack), a torch tensor, or a quantem Dataset3d. Complex data is rejected (cast to magnitude or phase first); non-finite data is rejected with a hint to np.nan_to_num.

  • labels (list[str] | None, optional) – New per-frame labels. If None, string indices "0"..."N-1" are used.

  • display_bin (int | None, optional) – Explicit display-only binning for the replacement stack. None keeps the historical set_image behavior of native-pixel display. Folder-backed updates pass their existing factor so a watched append cannot silently switch resolution.

Returns:

Mutates the widget in place. The browser canvas re-renders automatically via traitlet sync.

Return type:

None

Example

>>> from quantem.widget import Show3D
>>> w = Show3D(stack_a, title="A")  
>>> w.set_image(stack_b, labels=[f"frame {i}" for i in range(len(stack_b))])  

Notes

  • Prefer set_image over constructing a new Show3D when iterating through datasets in the same cell: it avoids re-creating the canvas and preserves the operator’s contrast / zoom / cmap state.

  • For live stack growth, instantiate the widget with offline=False: w = Show3D(first_frame[None], offline=False). Then append frames with w.set_image(np.stack(frames)) and set w.slice_idx = len(frames) - 1 to jump to the newest frame.

  • The new stack is cast to float32. If float64 input contains values outside float32 range, an error is raised (silent overflow to inf would corrupt stats).

state_dict() dict#

Return a JSON-serializable snapshot of every user-tunable trait.

Captures display config (cmap, contrast, log scale, FFT, diff mode), playback config (fps, loop, range, bookmarks, playback path), per-frame labels and timestamps, scale-bar settings, and any active ROIs and line profile. The raw stack data is NOT included; pair the snapshot with the original data argument (or re-attach via set_image) on restore.

Key order in the returned dict is deliberate: cross-validating pairs (percentile_high before percentile_low, loop_end before loop_start) are emitted in the order the validators expect so a round-trip through load_state_dict cannot wedge in an intermediate state.

Returns:

Mapping of trait name -> serializable value. Suitable for json.dump or use with save / load_state_dict.

Return type:

dict

Example

>>> from quantem.widget import Show3D
>>> w = Show3D(stack)  
>>> state = w.state_dict()  
>>> w2 = Show3D(stack)  
>>> w2.load_state_dict(state)  

Notes

  • Schema versioning is handled inside save; the dict returned here is the unversioned inner payload.

  • To save directly to disk in one step, use save(path) instead.

save(path: str)#

Write the current widget state to a versioned JSON file.

Wraps state_dict in a small envelope that records the widget type ("Show3D") and a schema version so load_state_dict can refuse states that belong to a different widget. The raw stack data is NOT written; only the display / playback / ROI / profile configuration.

Parameters:

path (str) – Destination JSON file path. Parent directories must already exist.

Return type:

None

Example

>>> from quantem.widget import Show3D
>>> w = Show3D(stack, cmap="viridis", sampling=0.5, units="A")  
>>> w.save("show3d_state.json")  

Notes

  • To restore, instantiate Show3D with the (possibly different) data and call w.load_state_dict(json.loads(open(path).read())).

  • Use state_dict directly if you want to embed widget state in a larger document instead of a standalone file.

export_html(path: str | Path | None = None, *, title: str | None = None, mode: str = 'single', encoding: str = 'full', downsample: int | None = None, quantized: bool | None = None, max_mb: float | None = 80.0) Path#

Write a standalone HTML viewer for sharing.

The exact export embeds the current float32 stack bytes and preserves numerical precision. The quantized export writes the existing offline uint8 representation plus global min/max metadata, making a smaller single-file report for visual sharing. Preferred export options are mode="single", encoding="full" or encoding="uint8". Use downsample=2 / 4 / 8 with encoding="uint8" for compact report HTML from heavy multi-panel movies. quantized is kept as a compatibility alias for encoding="uint8". If the widget is playing when exported, the standalone HTML starts playback on load.

Parameters:
  • path (str or pathlib.Path, optional) – Destination HTML path. Defaults to the current working directory with the widget title, stack shape, and export mode in the name.

  • quantized (bool, default False) – If True, write the uint8 offline pack. If False, write exact float32 bytes.

  • downsample (int, optional) – Spatial integer binning factor for the exported HTML only. The source widget data and live notebook view are unchanged. Binned exports multiply the scale-bar pixel size by this factor.

  • title (str, optional) – Browser page title. Defaults to the widget title or class name.

Returns:

The written HTML file.

Return type:

pathlib.Path

export_sidecar(out_dir: str | Path, *, title: str | None = None, stack_filename: str = 'offline_stack.u8', html_filename: str = 'index.html', manifest_filename: str = 'manifest.json') Path#

Write a thin HTML viewer + on-disk offline stack (no single-file embed).

Layout:

out_dir/
  index.html          # small: widget shell only
  offline_stack.u8    # raw uint8 stack bytes (sidecar)
  manifest.json       # shape / titles / paths

Serve out_dir over HTTP (not file://) so the browser can fetch the sidecar. Full spatial resolution is preserved (display_bin of the live widget is used; no export downsample).

load_state_dict(state: dict) None#

Apply a saved state_dict snapshot to this widget.

Restores display, playback, ROI, and profile configuration from a dict produced by state_dict. Unknown keys (typically from a newer widget version or a different widget type) are dropped with a UserWarning instead of raising, so partial / forward-compatible loads succeed. Cross-validated trait pairs are applied atomically:

  • percentile_low / percentile_high are validated together and applied in whichever order keeps the invariant low < high.

  • vmin / vmax are cleared first so either bound can be set regardless of the current contrast limits.

  • loop_start / loop_end are clamped to [0, n_slices) and applied in safe order via the -1 sentinel.

Parameters:

state (dict) – Mapping previously returned by state_dict (or its on-disk equivalent). Old canvas_size aliases are migrated to size; the constructor-derived display_bin key is ignored.

Returns:

Mutates the widget in place.

Return type:

None

Example

>>> import json
>>> from quantem.widget import Show3D
>>> w = Show3D(stack)  
>>> w.load_state_dict(json.load(open("show3d_state.json")))  

Notes

  • Unknown keys raise warnings.warn rather than an exception so forward-compatible files from a future widget still load.

  • The new contrast (vmin / vmax) is also stored on the private user-override slots so subsequent set_image calls keep the loaded contrast pinned.

summary() None#

Print a one-screen status report for the current widget.

Sections include: title and stack shape (with pixel-size readout when set), current frame index and label, raw data min/max/mean, display config (cmap, contrast, log/linear, FFT, diff mode), playback config (fps, loop, reverse, boomerang) and loop range, active ROI count, line profile endpoints if set, and render timing once the first browser paint has fired. Useful for notebook reproducibility and bug reports.

Returns:

Prints to stdout.

Return type:

None

Example

>>> from quantem.widget import Show3D
>>> w = Show3D(stack, title="defocus")  
>>> w.summary()  

Notes

  • Rendered: reads (pending first browser paint) until the JS side has round-tripped its first paint timestamp.

play() Self#

Start playback from the current frame.

Sets the playing trait to True, which triggers the JS playback loop and the Python-side sliding-prefetch buffer that ships chunks of frames ahead of the scrubber position.

Returns:

The widget, for chaining (w.play().goto(0)).

Return type:

Self

Example

>>> from quantem.widget import Show3D
>>> w = Show3D(stack, fps=10)  
>>> w.play()  
pause() Self#

Pause playback at the current frame.

Sets playing to False without resetting slice_idx. Per-frame statistics are refreshed for the current frame on pause.

Returns:

The widget, for chaining.

Return type:

Self

Example

>>> from quantem.widget import Show3D
>>> w = Show3D(stack)  
>>> w.play().pause()  
stop() Self#

Stop playback and jump back to frame 0.

Returns:

The widget, for chaining.

Return type:

Self

Example

>>> from quantem.widget import Show3D
>>> w = Show3D(stack)  
>>> w.play().stop()  

Notes

  • Unlike pause, stop resets slice_idx to 0. Use pause to keep the current frame visible.

goto(index: int) Self#

Jump to a specific frame index.

The index is taken modulo n_slices, so negative or out-of-range values wrap rather than raise.

Parameters:

index (int) – Target frame index. Wrapped into [0, n_slices).

Returns:

The widget, for chaining.

Return type:

Self

Example

>>> from quantem.widget import Show3D
>>> w = Show3D(stack)  
>>> w.goto(5)  
>>> w.goto(-1)  # last frame  
star_panel(panel: int = 0, frame: int | None = None) Self#

Mark a “best frame” star on a panel.

Parameters:
  • panel (int, default 0) – Panel index (0-based). For single-panel widgets, panel=0 is the only valid value.

  • frame (int | None, default None) – Frame index to star. None stars the currently displayed frame (slice_idx).

Returns:

The widget, for chaining.

Return type:

Self

Example

>>> from quantem.widget import Show3D
>>> w = Show3D(stack_a, stack_b)  
>>> w.goto(50)  
>>> w.star_panel(0)            # mark frame 50 as best on panel 0
>>> w.star_panel(1, frame=80)  # mark frame 80 as best on panel 1
>>> w.starred_frames           # → {0: 50, 1: 80}  
unstar_panel(panel: int) Self#

Clear the star on a panel (sets starred[panel] = -1).

property starred_frames: dict[int, int]#

Mapping of panel index → starred frame index, only for panels that have a star set. Returns {} if no panel is starred. Useful for downstream code like best_iters = {trial: w.starred_frames.get(i)}.

property starred_pages: list[int]#

Zero-based page indices marked with a star.

property ordered_panels: list[int]#

Zero-based movie panel indices in the current display order.

property visible_panels: list[int]#

Zero-based panel indices currently visible in the canvas grid.

star_page(page: int) Self#

Mark a page with a star.

unstar_page(page: int) Self#

Clear the star on a page.

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

Replace the hidden panel set by index or exact panel title.

Hidden panels are collapsed from the canvas grid and skipped by panel-scoped frontend computations, but remain available in state and standalone HTML export. At least one panel must stay visible.

Parameters:

panels (sequence of int or str, int, or str) – Panels to hide. Integers are zero-based panel indices; strings must match a panel title exactly.

Returns:

The widget, for chaining.

Return type:

Show3D

Examples

>>> w = Show3D(a, b, panel_titles=["SSB", "Mean DP"])  
>>> w.set_hidden_panels(["Mean DP"])  
>>> w.set_hidden_panels([1])  
hide_panel(*panels: int | str) Self#

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

Hidden panels are not removed from the widget; they can be restored with show_panel or show_all_panels and are preserved in HTML export.

show_panel(*panels: int | str) Self#

Restore one or more hidden panels by zero-based index or exact title.

show_all_panels() Self#

Restore every panel in the canvas grid.

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

Set the multi-panel display order by panel index or exact title.

The order is display-only: source data, titles, hidden state, stars, and per-panel contrast stay keyed by their original panel indices.

reset_panel_order() Self#

Restore the natural source-panel order.

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

Move one panel to a zero-based display position.

collapse_controls() Self#

Collapse the live control UI while leaving the GUI toggle available.

expand_controls() Self#

Expand the live control UI.

toggle_controls() Self#

Toggle the collapsed state of the live control UI.

property visible_indices: list[int]#

Live list of frame indices NOT in hidden_indices. Read-only; mutate via set_hidden() / show_all() / hide().

hide(*indices: int) Self#

Mark one or more frames as hidden from the scrubber.

Hidden frames are excluded from the scrubber UI and from playback but kept in memory; restore them with show or show_all. Idempotent: hiding an already-hidden index is a no-op. At least one frame always stays visible, so a call that would hide every frame is silently rejected.

Parameters:

*indices (int) – One or more frame indices to hide. Values outside [0, n_slices) are accepted but never become visible-or-hidden in the UI.

Returns:

The widget, for chaining (w.hide(0, 1).hide(5)).

Return type:

Show3D

Example

>>> from quantem.widget import Show3D
>>> w = Show3D(stack)  
>>> w.hide(0, 1, 2)  

Notes

  • The hideable trait must be True for the JS scrubber to show the hide overlay; the underlying hidden_indices trait is always honored regardless.

show(*indices: int) Self#

Restore one or more previously hidden frames.

Idempotent: indices that are not currently hidden are silently ignored.

Parameters:

*indices (int) – Frame indices to make visible again.

Returns:

The widget, for chaining.

Return type:

Show3D

Example

>>> from quantem.widget import Show3D
>>> w = Show3D(stack)  
>>> w.hide(2).show(2)  
set_hidden(indices: list[int]) Self#

Replace the hidden set wholesale.

Discards any current hidden_indices and installs indices clamped to [0, n_slices). At least one frame is always visible; if the input would hide every frame, the largest index is dropped.

Parameters:

indices (list[int]) – Full replacement set of hidden frame indices. Order and duplicates are normalized internally.

Returns:

The widget, for chaining.

Return type:

Show3D

Example

>>> from quantem.widget import Show3D
>>> w = Show3D(stack)  
>>> w.set_hidden([0, 1, 2, 7])  
show_all() Self#

Clear the hidden set so every frame is visible.

Returns:

The widget, for chaining.

Return type:

Show3D

Example

>>> from quantem.widget import Show3D
>>> w = Show3D(stack)  
>>> w.hide(0, 1, 2).show_all()  
property roi: dict#

The selected ROI dict (or the first ROI if none is selected).

delete_selected_roi() Self#

Delete the currently selected ROI.

get_roi_geometries(*, visible_only: bool = True) list[dict[str, Any]]#

Return normalized ROI geometry in image (row, col) coordinates.

The raw roi_list trait remains synced for widget state, while this helper gives notebooks, reports, and agents a stable public shape for downstream measurements across the scrubbed stack. Circle ROIs include center and radius. Rectangles and squares include clockwise corners. Annular ROIs include radius_inner and radius_outer.

roi_geometries(*, visible_only: bool = True) list[dict[str, Any]]#

Return normalized ROI geometry in image (row, col) coordinates.

The raw roi_list trait remains synced for widget state, while this helper gives notebooks, reports, and agents a stable public shape for downstream measurements across the scrubbed stack. Circle ROIs include center and radius. Rectangles and squares include clockwise corners. Annular ROIs include radius_inner and radius_outer.

set_notebook_preview_frames(frames: Sequence[int] | int | None, *, ncols: int | None = None) Self#

Choose Show3D frames for the saved-notebook static preview.

Parameters:
  • frames (sequence of int, int, or None) – Zero-based frame indices to show when a lightweight saved notebook is reopened without a running kernel. None or an empty sequence restores the default current-frame preview.

  • ncols (int, optional) – Contact-sheet column count. None leaves the current setting unchanged; 0 uses the widget’s column setting.

Returns:

The widget, for chaining.

Return type:

Show3D

clear_notebook_preview_frames() Self#

Restore the default saved preview: the current frame only.

set_roi(row: int, col: int, radius: int = 10) Self#

Set selected ROI position and size (creates one if needed).

roi_circle(radius: int = 10) Self#

Set selected ROI shape to circle.

roi_square(half_size: int = 10) Self#

Set selected ROI shape to square.

roi_rectangle(width: int = 20, height: int = 10) Self#

Set selected ROI shape to rectangle.

roi_annular(inner: int = 5, outer: int = 10) Self#

Set selected ROI shape to annular (donut).

property profile#

Get profile line endpoints as [(row0, col0), (row1, col1)] or [].

property profile_values#

Get profile values for the current frame and active single-panel page.

property profile_distance#

Get total distance of the profile line in calibrated units (Å or px).

set_profile(start: tuple[float, float], end: tuple[float, float]) Self#

Set a line profile between two points (image pixel coordinates).

Parameters:
  • start (tuple of (row, col)) – Start point in pixel coordinates.

  • end (tuple of (row, col)) – End point in pixel coordinates.

clear_profile() Self#

Clear the current line profile.

profile_all_frames(start: tuple[float, float] | None = None, end: tuple[float, float] | None = None, *, panel: int | None = None, page: int | None = None) ndarray#

Extract the line profile from every frame, returning (n_slices, n_points).

Uses the current profile_line unless start/end are provided. Always samples raw data (ignores diff_mode).

Parameters:
  • start (tuple of (row, col), optional) – Start point. Overrides current profile_line.

  • end (tuple of (row, col), optional) – End point. Overrides current profile_line.

  • panel (int, optional) – Absolute source-panel index. Defaults to the first panel on the active page, or panel 0 for a non-paged widget.

  • page (int, optional) – Page index for a paged widget. The first panel on that page is sampled. Pass panel instead when a page contains several panels.

Returns:

Shape (n_slices, n_points) float32 array.

Return type:

np.ndarray

profile_all_pages(start: tuple[float, float] | None = None, end: tuple[float, float] | None = None, *, panel_slot: int = 0) ndarray#

Extract one spatially matched profile stack from every page.

Returns an array shaped (n_pages, n_slices, n_points). The same profile endpoints and panel slot are used on every page, which makes raw/corrected or method-to-method depth comparisons directly aligned.

save_image(path: str | Path, *, frame_idx: int | None = None, format: str | None = None, dpi: int = 150) Path#

Save a single frame as a PNG, PDF, or TIFF file.

The saved image is colorized with the current cmap and contrast (vmin / vmax or percentile auto-contrast), so the saved file matches what the browser shows for that frame. diff_mode is respected: "previous" saves frame - frame[idx-1] and "first" saves frame - frame[0].

Parameters:
  • path (str or pathlib.Path) – Output file path. Parent directories are created if needed.

  • frame_idx (int | None, optional) – Frame index to save. Defaults to the current slice_idx.

  • format (str | None, optional) – One of "png", "pdf", "tiff". If omitted, inferred from the file extension; defaults to "png" if no extension.

  • dpi (int, default 150) – DPI metadata written into the file.

Returns:

The written file path.

Return type:

pathlib.Path

Example

>>> from quantem.widget import Show3D
>>> w = Show3D(stack, cmap="viridis")  
>>> w.save_image("frame_5.png", frame_idx=5)  
>>> w.save_image("frame.pdf", dpi=300)  

Notes

  • PDF output is converted to RGB internally (no alpha channel).

  • Frame indices outside [0, n_slices) raise IndexError; unsupported extensions raise ValueError.

  • For multi-frame output, call save_image in a loop.

save_gif(path: str | Path, *, quality: str = 'high', fps: float | None = None, playback: str = 'forward', show_frame_labels: bool = False, background: str | tuple[int, int, int] = 'dark', frame_start: int | None = None, frame_stop: int | None = None, every_n: int = 1, max_frames: int | None = None, downsample: int = 1, max_edge_px: int | None = None, show_panel_titles: bool | None = None, show_scale_bar: bool | None = None, show_zoom: bool | None = None, slides_preset: bool = False, ppt_preset: bool = False) Path#

Save the z-stack panels as an animated GIF matching the live image view.

Each frame is colorized with the current cmap and contrast (vmin / vmax or percentile auto-contrast, log_scale honored), carries per-panel scale bars when enabled, respects hidden panels and panel titles, and excludes FFT/profiles/controls from the export.

Parameters:
  • path (str or pathlib.Path) – Output .gif path. Parent directories are created.

  • quality ({"high", "medium", "low"}, default "high") – Spatial resolution tier (1.0 / 0.6 / 0.35). GIF is always a 256-color palette, so quality trades resolution for file size.

  • fps (float, optional) – Playback rate. Defaults to the widget’s fps.

  • playback ({"forward", "bounce"}, default "forward") – Frame order. "forward" is best for time series; "bounce" plays forward then backward without duplicating endpoint frames.

  • show_frame_labels (bool, default False) – Draw per-panel dynamic frame labels from panel_frame_labels. When enabled and no labels were provided, draw "i/n".

  • background ({"dark", "black", "white"} or RGB tuple, default "dark") – Grid gutter/background color for multi-panel exports.

  • frame_start (int, optional) – Zero-based Python slice bounds for the exported frame range. The default exports every visible frame.

  • frame_stop (int, optional) – Zero-based Python slice bounds for the exported frame range. The default exports every visible frame.

  • every_n (int, default 1) – Export every Nth frame from the selected range.

  • max_frames (int, optional) – Evenly sample at most this many frames from the selected range.

  • downsample ({1, 2, 4, 8}, default 1) – Display-only spatial downsample for the animation file.

  • max_edge_px (int, optional) – Cap each source panel’s exported edge length in pixels.

  • show_panel_titles (bool, optional) – Override the corresponding live-view overlay settings for the exported animation.

  • show_scale_bar (bool, optional) – Override the corresponding live-view overlay settings for the exported animation.

  • show_zoom (bool, optional) – Override the corresponding live-view overlay settings for the exported animation.

  • slides_preset (bool, default False) – Convenience preset for slide decks. Unless explicitly overridden, exports at most 40 frames with max_edge_px=512.

  • ppt_preset (bool, default False) – Deprecated alias for slides_preset.

Returns:

The written GIF path.

Return type:

pathlib.Path

Notes

Browser zoom/pan is a view-only transform and is not reflected (the full frame is exported). FFT overlays and analysis panels are intentionally omitted so the GIF contains only the scientific image panels.

save_mp4(path: str | Path, *, quality: str = 'high', fps: float | None = None, playback: str = 'forward', crf: int = 18, show_frame_labels: bool = False, background: str | tuple[int, int, int] = 'dark', frame_start: int | None = None, frame_stop: int | None = None, every_n: int = 1, max_frames: int | None = None, downsample: int = 1, max_edge_px: int | None = None, show_panel_titles: bool | None = None, show_scale_bar: bool | None = None, show_zoom: bool | None = None, slides_preset: bool = False, ppt_preset: bool = False) Path#

Save the z-stack panels as an H.264 MP4.

The rendered content matches save_gif(): image panels only, with current colormap/contrast, panel titles, frame labels, hidden panels, and scale bars. FFT/profiles/controls are omitted.

Parameters:
  • path (str or pathlib.Path) – Output .mp4 path. Parent directories are created.

  • quality ({"high", "medium", "low"}, default "high") – Spatial resolution tier (1.0 / 0.6 / 0.35).

  • fps (float, optional) – Playback rate. Defaults to the widget’s fps.

  • playback ({"forward", "bounce"}, default "forward") – Frame order.

  • crf (int, default 18) – x264 constant-rate-factor. Lower values are larger and higher quality; 18 is visually high quality.

  • show_frame_labels (bool, default False) – Draw per-panel dynamic frame labels from panel_frame_labels. When enabled and no labels were provided, draw "i/n".

  • background ({"dark", "black", "white"} or RGB tuple, default "dark") – Grid gutter/background color for multi-panel exports.

  • frame_start (int, optional) – Zero-based Python slice bounds for the exported frame range.

  • frame_stop (int, optional) – Zero-based Python slice bounds for the exported frame range.

  • every_n (int, default 1) – Export every Nth frame from the selected range.

  • max_frames (int, optional) – Evenly sample at most this many frames from the selected range.

  • downsample ({1, 2, 4, 8}, default 1) – Display-only spatial downsample for the animation file.

  • max_edge_px (int, optional) – Cap each source panel’s exported edge length in pixels.

  • show_panel_titles (bool, optional) – Override the corresponding live-view overlay settings.

  • show_scale_bar (bool, optional) – Override the corresponding live-view overlay settings.

  • show_zoom (bool, optional) – Override the corresponding live-view overlay settings.

  • slides_preset (bool, default False) – Convenience preset for slide decks. Unless explicitly overridden, exports at most 40 frames with max_edge_px=512.

  • ppt_preset (bool, default False) – Deprecated alias for slides_preset.

Returns:

The written MP4 path.

Return type:

pathlib.Path

save_animation_preview(directory: str | Path, *, stem: str | None = None, formats: Sequence[str] = ('gif', 'mp4'), quality: str = 'medium', fps: float | None = None, playback: str = 'forward', show_frame_labels: bool = False, background: str | tuple[int, int, int] = 'dark', frame_start: int | None = None, frame_stop: int | None = None, every_n: int = 1, max_frames: int | None = None, downsample: int = 1, max_edge_px: int | None = None, show_panel_titles: bool | None = None, show_scale_bar: bool | None = None, show_zoom: bool | None = None, slides_preset: bool = False, ppt_preset: bool = False) AnimationExportPreview#

Save GIF/MP4 animation exports and return a notebook preview.

This is a convenience wrapper around save_gif() and save_mp4() for notebooks. It writes one or more animation files and returns an object with a rich HTML representation showing the media previews and file sizes.

Parameters:
  • directory (str or pathlib.Path) – Output directory. Created if missing.

  • stem (str, optional) – Filename stem. Defaults to a slug derived from title.

  • formats (sequence of {"gif", "mp4"}, default ("gif", "mp4")) – Animation formats to write.

  • quality ({"low", "medium", "high"}, default "medium") – Export quality. MP4 also maps this to the same CRF values used by the GUI export menu.

  • fps (float, optional) – Playback rate. Defaults to the widget’s fps.

  • playback ({"forward", "bounce"}, default "forward") – Frame order.

  • show_frame_labels (bool, default False) – Draw per-panel dynamic frame labels from panel_frame_labels.

  • background ({"dark", "black", "white"} or RGB tuple, default "dark") – Grid gutter/background color for multi-panel exports.

  • frame_start (int, optional) – Zero-based Python slice bounds for the exported frame range.

  • frame_stop (int, optional) – Zero-based Python slice bounds for the exported frame range.

  • every_n (int, default 1) – Export every Nth frame from the selected range.

  • max_frames (int, optional) – Evenly sample at most this many frames from the selected range.

  • downsample ({1, 2, 4, 8}, default 1) – Display-only spatial downsample for the animation files.

  • max_edge_px (int, optional) – Cap each source panel’s exported edge length in pixels.

  • show_panel_titles (bool, optional) – Override the corresponding live-view overlay settings.

  • show_scale_bar (bool, optional) – Override the corresponding live-view overlay settings.

  • show_zoom (bool, optional) – Override the corresponding live-view overlay settings.

  • slides_preset (bool, default False) – Convenience preset for slide decks.

  • ppt_preset (bool, default False) – Deprecated alias for slides_preset.

Returns:

Notebook-displayable preview object keyed by format label.

Return type:

AnimationExportPreview

free() None#

Release VRAM and RAM held by this widget.

Drops the numpy stack, the torch view (if any), the binned display copy, synced frame / ROI / prefetch bytes, and cancels any in-flight ROI debounce timer. When applicable, also flushes the CuPy memory pool and the FFT plan cache (in case a torch tensor was a view into CuPy memory), and releases the active torch device cache (mps or cuda).

del widget alone does NOT free memory: traitlets installs strong observer references that pin the widget’s refcount until free is called.

Returns:

Mutates the widget in place. After this call, frame data is gone and rendering will be blank; rebuild a new widget for further use.

Return type:

None

Example

>>> from quantem.widget import Show3D
>>> w = Show3D(stack)  
>>> w.free()  

Notes

  • Idempotent: calling free twice is a no-op.

  • After free, set_image cannot restore the widget (the observers are still pinned but _data is None); construct a new Show3D if you need to re-display something.

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 frame buffers from that snapshot, so a plain Show3D does not bake a hundreds-of-MB z-stack into the .ipynb. Targeted syncs (key is a name or set, used by hold_sync / send_state during live streaming) are untouched, so the frontend still receives frame_bytes / _buffer_bytes normally. save_state=True embeds everything so a reopened notebook restores the interactive widget without a kernel.

Interactive controls#

Control

Trait

Expected effect

Slice slider

slice_idx

Canvas shows that depth slice

Arrow keys

slice_idx

Step one slice per press

Play / pause

playing

Auto-advances slices at fps

Reverse

reverse

Playback direction flips

Boomerang

boomerang

Ping-pongs at the ends instead of looping

FPS field

fps

Playback rate changes, capped at 60 fps

Loop range

loop_start, loop_end

Playback confined to the sub-range

Playback dynamics

fps, loop, boomerang, playback_path; future playback_preset

More-menu presets can play a time series linearly, bounce at ends, slow down around key frames, or follow a custom frame path without changing the underlying stack

Colormap dropdown

cmap, panel_cmaps

Shared by default. More → Color shared can unlock per-panel colormaps for advanced comparisons

Panel identity markers

marker_colors, identity_colors, marker_style, row_markers, col_markers, panel_groups

Optional panel colors plus row, column, or rectangular group frames make panels easy to reference in notebooks, reports, and agent instructions

Local panel annotations

panel_annotations

Optional multiple in-image labels per panel, placed by corner, normalized point, or normalized region box

Geometric panel overlays

panel_overlays (overlays shorthand)

Optional circle, rectangle, and square overlays in data or relative coordinates, with stroke/fill opacity, dashed/dotted line styles, and z-order, rendered while scrubbing and in exported HTML; More -> Overlay Edit allows live select, move, resize, delete, and reset

Contrast preset dropdown

contrast_preset

Applies percentile ranges such as 1-99, 2-98, and 3-97 while keeping the main histogram UI compact

Advanced histogram toggle

show_histogram_advanced, histogram_advanced

Exposes detailed histogram controls only when a user asks for them

Export button

export_request, export_status

Writes standalone HTML from live widgets; live Show3D can also request GIF/MP4 animation exports

Page controls (paged galleries)

page_idx, n_pages, panels_per_page, page_starred; star_page(), unstar_page()

Shows, stars, or plays through one page of panels at a time

Panel layout (multi-panel)

n_panels, link_panels, max_cols

Panels arrange; linked scrub moves all

Gallery gap and borders

inter_panel_gap_px, inter_panel_gap_color, gallery_outer_border_px, gallery_outer_border_color, panel_inner_border_px, panel_inner_border_color

Separately controls the layer between Show3D panels, the outside gallery frame, and each panel’s own inner stroke for live display, HTML, and GIF/MP4 exports

Panel visibility (multi-panel)

hidden_panels

Panels collapse from view without deleting data

Panel reorder (multi-panel)

panel_order; set_panel_order(), move_panel(), reset_panel_order()

Reorders panel display without changing source data, labels, stars, or hidden state

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 temporarily collapse them behind the top GUI toggle

Title visibility

show_title

Top title row shows/hides

Statistics

show_stats

Optional mean/min/max/std readout

Panel title visibility

show_panel_titles, panel_title_font_size, panel_title_style

Per-panel labels show/hide, resize, and optionally get title chrome such as background, border, and padding

Rich panel title spans

panel_title_spans

Optional structured text / math / color spans for symbols such as λ and χ² in panel titles, panel menus, stats, animation labels, saved state, and exported HTML

Scale bar visibility

show_scale_bar (scale_bar_visible in saved state)

Scale bar shows/hides

Saved notebook preview frames

notebook_preview_frames, notebook_preview_ncols; set_notebook_preview_frames()

Single-panel saved notebooks can reopen as a compact contact sheet of selected frame indices instead of only the current frame

ROI add / drag

roi_active, roi_list, roi_selected_idx; get_roi_geometries()

Single-panel stack ROI overlays stay visible while scrubbing; saved notebook previews include all visible ROI overlays and right-side zoom crops; Python can read circle centers/radii and rectangle/square corners in (row, col) coordinates

FFT toggle

show_fft

Shows the FFT view for the current frame or visible panel grid

FFT quality labels

fft_metrics

Compact in-panel label reports FFT sharpness, peak count, and peak SNR from the cached FFT magnitude

FFT window toggle

fft_window

Apodization on/off before FFT rendering

Resize / zoom chrome

show_resize_handles, show_zoom_indicator

Resize handles and zoom readouts show/hide; the zoom setting covers every real-space panel and FFT tile/inset

FFT layout and initial view

fft_layout, fft_overlay_position, fft_overlay_size, fft_overlay_zoom

Places FFTs below, right, or inside every panel and initializes their shared zoom

Denoise

denoise_enabled, denoise, denoise_sigma, denoise_bin, show_denoise

The master swaps raw/denoised frames without losing settings; Settings expands the Method/σ/bin editor; an active filter also reshapes FFT

Filter

frequency_filter_enabled, frequency_filter, frequency_filter_cutoff, frequency_filter_center, frequency_filter_width, show_frequency_filter

View-only low/high/band-pass filtering with a draggable FFT ring; stored frames, statistics, and raw exports remain unchanged

Sub-pixel alignment

subpixel_align_enabled, subpixel_align_reference

More-menu display alignment for a drifting single-panel stack; the browser estimates row/column shifts against the reference frame and keeps raw data unchanged

More menu: Flip

flip_horizontal, flip_vertical, flip_rows, flip_cols

Display-only orientation checks for row/column or horizontal/vertical review

More menu: Rotate

image_rotation, rotation_scope, frame_rotations; rotation=, rotations=

Display-only 0/90/180/270° rotation for the whole stack or the selected frame

More menu: Compare

compare_mode, compare_pair, blink_fps, diff_cmap, compare_background

Blink, difference, or overlay two frames for point-defect and time-series change detection

Rich panel labels and math#

Use rich labels when panel titles are scientific variables rather than plain names. Show3D accepts inline math strings or structured spans in panel_titles / panel_title_spans:

from quantem.widget import Show3D

Show3D(
    raw_stack,
    residual_stack,
    panel_titles=[
        [{"math": r"\lambda=0.03"}, {"text": " raw"}],
        [{"math": r"\chi^2"}, {"text": "/pixel residual"}],
    ],
    show_stats=True,
)

Plain strings with $...$ also work:

Show3D(
    raw_stack,
    residual_stack,
    panel_titles=[r"$\lambda=0.03$ raw", r"$\chi^2$/pixel residual"],
)

The frontend renders common Greek symbols plus simple superscripts/subscripts without MathJax or KaTeX. It also normalizes doubled backslashes from JSON/state files, so \\lambda displays as λ. Panel titles, the Panels menu, stats rows, animation labels, saved notebook state, and exported HTML all use the same rich rendering path.

Use panel_annotations when a label belongs to a local feature or region inside a panel. Each annotation can use text, math, or spans:

Show3D(
    raw_stack,
    residual_stack,
    panel_titles=[
        [{"math": r"\lambda=0.03"}, {"text": " raw"}],
        [{"math": r"\chi^2"}, {"text": "/pixel"}],
    ],
    panel_annotations=[
        {"panel": 0, "math": r"\lambda", "position": "top-left", "variant": "pill"},
        {
            "panel": 1,
            "spans": [{"math": r"\chi^2"}, {"text": " high", "color": "#f87171"}],
            "box": [0.56, 0.48, 0.32, 0.16],
            "variant": "callout",
        },
    ],
)

Circle and rectangle overlays#

Show3D accepts the same panel_overlays API as Show2D. Use it for reproducible geometry that should stay fixed while a stack scrubs or plays. Coordinates are data pixels by default and follow (row, col) ordering.

from quantem.widget import Show3D

Show3D(
    raw_stack,
    denoised_stack,
    residual_stack,
    panel_titles=["raw", "denoised", "residual"],
    panel_overlays={
        "raw": {
            "shape": "circle",
            "center": (96, 88),
            "radius": 14,
            "stroke": "#60a5fa",
            "stroke_width": 3,
            "line_style": "dashed",
        },
        "denoised": [
            {
                "shape": "rect",
                "box": (48, 58, 126, 146),
                "stroke": "#facc15",
                "fill": "#facc15",
                "fill_opacity": 0.12,
                "line_style": "dotted",
            },
            {
                "shape": "square",
                "center": (96, 88),
                "size": 42,
                "stroke": "#34d399",
                "dash": [8, 3, 2, 3],
                "z_order": 1,
            },
        ],
    },
)

Pass overlays=[...] to broadcast the same geometry to every panel, or add panel="raw" / panel=0 to each flat-list entry when building the overlay list programmatically. Set coords="relative" for normalized 0-1 geometry that follows panels with different pixel shapes. Overlay strokes are solid by default. Set line_style="dashed", line_style="dotted", or line_style="dashdot", or pass a custom dash=[on, off, ...] pattern.

When overlays are present, the live widget and exported HTML show More -> Overlay Edit. Turn it on to select an overlay, drag inside the shape to move it, drag an edge to resize it, press Delete to remove the selected overlay, or choose Reset Overlays to restore the constructor state. Use ROIs instead when the geometry should drive statistics, FFT crops, or Python readback through get_roi_geometries().

Presentation and export chrome#

ui_mode="presentation" starts Show3D with controls collapsed while preserving the top Controls button. The Export button also remains visible in that collapsed title chrome:

w = Show3D(
    raw_stack,
    residual_stack,
    panel_titles=[r"$\lambda=0.03$ raw", r"$\chi^2$/pixel residual"],
    ui_mode="presentation",
)
w.export_html("lambda_movie.html", encoding="uint8")

In a live Python-backed widget, the Export menu can write HTML and request GIF/MP4 animation exports through the Python backend. In standalone HTML, the page can download itself as HTML; GIF/MP4 entries remain visible but disabled with a backend-required explanation until browser-side animation encoding is implemented.

The denoise family matches Show2D. See Which denoise filter should I use? for the recommendation ladder (sparse EDS, very sparse, decent-dose HAADF, quantitative). An active denoise reshapes the FFT magnitude too, so set denoise="none" for quantitative FFT work.

Frequency Filter follows the same scientist-facing contract as Show2D while remaining a separate control from Denoise. For example, a reconstruction stack with a slow background can start as Show3D(stack, show_fft=True, frequency_filter="highpass", frequency_filter_cutoff=0.08). A lattice stack can use "bandpass" with frequency_filter_center and frequency_filter_width. Values are fractions of Nyquist from 0 to 1, and the cyan FFT ring can be dragged during scrubbing or playback. The browser applies Denoise first and Filter second; the raw stack and quantitative exports are never replaced by the view. Filter lives under More and is off by default. Its FFT overlay dims rejected frequencies and labels the clear region as Inside kept, Outside kept, or Band kept.

Sub-pixel alignment for drifting stacks#

For a single-panel stack that is already available in the browser, use subpixel_align=True or open More → Sub-pixel align. The first version aligns the displayed frames to subpixel_align_reference (default frame 0) with phase-correlation registration and bilinear display shifts. It is intentionally view-only: the original stack, raw exports, and quantitative data remain unchanged.

The More menu reports the reference frame, the approximate row/column padding implied by the shifts, and whether WebGPU or CPU FFT was used. Multi-panel, RGB, or streamed-only stacks currently show an explanatory status instead of silently aligning the wrong data.

Playback dynamics for time-series review#

Show3D playback is not only a convenience for movies. For experimental time-series data, a scientist often wants to probe temporal behavior: a defect appears slowly, a reconstruction accelerates, a relaxation comes back, or only a short sub-range matters. The lightweight API already exposes the core pieces: fps, loop, boomerang, loop_start, loop_end, and playback_path.

The UI path is a compact More → Playback Dynamics section, not another crowded toolbar row. The presets write those existing traits, so the same behavior remains scriptable and serializable:

Preset

Intended user behavior

Linear

Step forward through the loop range at constant fps

Slow

Lower fps for careful inspection of subtle frame-to-frame changes

Bounce

Ping-pong with boomerang=True so reversible dynamics are easy to see

Focus range

Set loop_start / loop_end around an event and play only that interval

Hold key frames

Populate playback_path with repeated important frames so the eye can settle

Custom path

Let a notebook or agent provide exact frame indices for non-uniform timing

The stored frame stack is never resampled or duplicated. These controls only change the order and cadence of frame display. Exported HTML and saved widget state should preserve the selected playback dynamics, while a reopened notebook should not unexpectedly start playing unless the user explicitly requests that behavior.

FFT quality labels#

Pass show_fft=True to show the FFT view. By default, fft_metrics=True adds a small white label inside the FFT panel with sharpness, peak count, and peak SNR. In multi-panel FFT views, Show3D summarizes the visible FFT tiles. The metrics reuse the cached FFT magnitude used for rendering, so frame playback, zoom, and pan do not trigger an extra FFT for the label. Set fft_metrics=False for a clean FFT image.

The first FFT for a frame or ROI may take a moment on large data. After that, Show3D reuses the cached FFT magnitude when you return to the same frame and when you redraw, zoom, pan, scrub, or show metric labels.

Every visible FFT tile or overlay inset shows the shared live magnification as an N.N× badge, even for uncalibrated arrays. Wheel or pinch zoom updates it; double-click, double-tap, or Reset returns to 1.0×. Pass fft_overlay_zoom=2.0 to initialize any FFT layout at 2.0×, and set show_zoom_indicator=False to hide both real-space and FFT zoom badges.

Reuse ROI coordinates across a stack#

Single-panel Show3D uses the same ROI state contract as Show2D. ROIs are synced in roi_list, saved by state_dict(), and exposed through get_roi_geometries() for analysis code or agents:

w = Show3D(stack).set_roi(row=72, col=65, radius=12)
roi = w.get_roi_geometries()[0]
roi["center"]  # {"row": 72.0, "col": 65.0}
roi["radius"]  # 12.0

Rectangle and square ROIs include clockwise corners; annular ROIs include radius_inner and radius_outer. Saved notebook previews of a single-panel Show3D frame show all visible ROI overlays and one zoom crop per visible ROI, so a report reader can compare the marked sites without rerunning the notebook.

Save a contact sheet of selected frames#

By default, a lightweight saved notebook preview shows the current Show3D frame. For single-panel stacks, pass explicit zero-based frame indices when a cold-reopen report should show several representative frames:

w = Show3D(
    stack,
    notebook_preview_frames=[0, 12, 25, 80],
    notebook_preview_ncols=3,
)

A notebook can also decide this interactively before saving:

w.set_notebook_preview_frames([0, w.slice_idx, stack.shape[0] - 1], ncols=3)

Multi-panel Show3D intentionally keeps the saved preview to the current frame of each visible panel; combining many movie panels with many saved frames is better handled as an explicit report or animation export.

Live stack updates#

Use set_image() to replace the stack in an already displayed widget while a notebook kernel is still running. Keep a reference to the widget, display it once, then call set_image(...) whenever new stack data should be rendered. For live acquisitions or reconstruction loops, construct the widget with offline=False so frames travel over the live Jupyter Comm channel instead of the saved/offline notebook-data path:

import numpy as np
from quantem.widget import Show3D

frames = [first_frame]
w = Show3D(first_frame[None], labels=["frame 1"], offline=False)
w

for next_frame in acquisition:
    frames.append(next_frame)
    w.set_image(
        np.stack(frames),
        labels=[f"frame {i + 1}" for i in range(len(frames))],
    )
    w.slice_idx = len(frames) - 1

In a real JupyterLab browser session this updates the displayed frame as each set_image() call is processed. A background thread is optional for UI ergonomics, but is not required for the widget update itself.

set_image() is the re-render trigger. Mutating the original NumPy array in place does not notify the frontend. The method writes a fresh current-frame transfer, bumps frame_seq, invalidates playback buffers, clamps the current slice and loop range, and resets stale panel-specific state when replacing a multi-panel view with a single stack.

Important

Do not use the default tiny-stack constructor path for acquisition-style live updates. Small stacks may auto-enable the offline notebook representation, which is intended for saved notebooks and static exports. Pass offline=False when the stack will grow over time.

Watch a growing frame folder#

Use Show3D.from_folder(...) when each matching image file is the next frame in one time series, focal series, or reconstruction history. New files append to the single displayed stack; they do not create additional gallery panels or replace frames that are already loaded.

from quantem.widget import Show3D

w = Show3D.from_folder(
    "/data/session/reconstruction",
    pattern="frame_*.tif",
    watch_interval=2.0,
    title="Live reconstruction",
)
w

watch=True is the default. The first folder scan establishes deterministic frame order, then the watcher appends newly readable files without rebuilding the widget or rereading unchanged source files. Use Show2D instead when each file should be a separate comparison panel.

Folder size never creates Show3D pages. Whether the folder has 2, 20, or 200 files, each file extends the frame axis of one stack and the frame slider/playback controls remain its navigation. page_size= and page_labels= are therefore rejected by Show3D.from_folder(...); use Show2D.from_folder(..., page_size=20) for independent paged images. Explicit 5-D or list-of-page Show3D comparison data remain a separate constructor mode.

An empty watched folder stays mounted and changes into the real stack in the same widget model after the first stable frame. The compact title-area badge reports Watching, Updating, Waiting for file completion, Watch error, or Stopped; fixed watch=False snapshots do not show it.

new_frames = w.poll_folder()       # scan now; return newly appended indices
w.stop_folder_watch()             # pause background scans
w.watch_folder(interval=1.0)      # resume with a different interval
w.close()                         # stop watching and close the widget

Folder watching is append-only. Files already represented in the stack are not duplicated, incomplete files wait for a later poll, and source removals or rewrites do not alter existing frames silently. An incompatible shape is reported without blocking a later compatible frame. Pass watch=False when a fixed folder must remain fixed.

Show3D.from_folder(...) reads full-resolution source frames. ShowFolder uses cached thumbnails to browse and select a session quickly; those thumbnails are not the data used by the folder-backed Show3D stack.

Maintainer real-time signoff follows S3D-17: append genuine EMD frames after the stack is visibly mounted and verify the same browser canvas, playback state, and frame controls update.

Panel visibility#

Use panel visibility when a secondary panel is useful for validation but should not take space in the first view. For example, an SSB reconstruction can keep the mean diffraction pattern in the widget while hiding it from the canvas:

w = Show3D(
    ssb_stack,
    mean_dp_stack,
    panel_titles=["SSB reconstruction", "Mean DP"],
    hidden_panels=["Mean DP"],
)

Panel references can be zero-based indices or exact panel titles:

w.hide_panel("Mean DP")
w.hide_panel(1)
w.show_panel("Mean DP")
w.show_all_panels()

Hidden panels stay in the widget state and standalone HTML export. They are not removed from the data, and readers can restore them from the Panels menu.

Use panel reordering when the comparison order should change without copying or rebuilding the source stacks:

w.set_panel_order(["Probe", "SSB reconstruction", "Mean DP"])
w.move_panel("SSB reconstruction", 0)
w.reset_panel_order()

Panel order is saved in widget state and standalone HTML. It is display-only: hidden panels, stars, titles, and per-panel contrast remain keyed by the original source panel index.

Use panel_groups when several panels should be read as one comparison block. The group box follows the displayed panel positions, so it still works after panel hiding or reordering:

w = Show3D(
    bf_raw,
    df_raw,
    bf_denoised,
    df_denoised,
    panel_titles=["BF raw", "DF raw", "BF denoised", "DF denoised"],
    max_cols=2,
    panel_groups=[
        {"panels": [0, 1], "color": "#2563eb", "label": "raw"},
        {"start": 2, "end": 3, "color": "#16a34a", "label": "denoised"},
    ],
)

The statistics readout is off by default. Turn on show_stats=True in Python, or use the Stats switch in the widget, when mean/min/max/std values are useful.

Paged galleries#

Use pages when each view is itself a small multi-panel movie. This is useful for reconstruction sweeps, denoising parameters, or iteration checkpoints where each page should show the same panel layout while the user scrubs pages:

# Shape: pages, panels_per_page, frames, rows, cols
w = Show3D(
    stacks_5d,
    panel_titles=["raw", "filtered", "residual", "probe"],
    page_labels=["lambda 0.01", "lambda 0.03", "lambda 0.10"],
)

You can also pass explicit page dictionaries:

w = Show3D([
    {"title": "iteration 10", "stacks": [raw_10, filtered_10, residual_10]},
    {"title": "iteration 20", "stacks": [raw_20, filtered_20, residual_20]},
])

Paged Show3D keeps the data in the normal multi-panel transport internally, so HTML export, notebook state, panel hiding, panel stars, playback, FFT, and GIF/MP4 export use the same paths as ordinary Show3D. In page mode, visible_panels returns only panels from the active page, and to_show2d() converts the current visible page into a Show2D gallery. The page row has its own play/pause button and FPS menu; the lower frame playback controls still scrub time or depth inside the active page. Manual page scrubbing pauses page playback, and Show3D keeps rendering work scoped to the active visible page.

Paged views use independent automatic percentile clipping by default because separate reconstructions often have different numerical ranges. This preserves structure on every page instead of letting one high-amplitude reconstruction set the contrast for all others. Pass link_contrast=True when identical color limits are scientifically required for direct amplitude comparison. Ordinary non-paged multi-panel views keep linked contrast by default.

w.page_idx = 2
w.star_page(2)
show2d_page = w.to_show2d(frame=w.slice_idx)

Compare depth profiles across pages#

When every page contains one panel, the line-profile kymograph follows the active page. Draw the profile once, enable Kymograph, and scrub the page row to compare the same spatial line through raw/corrected volumes, reconstruction methods, or experimental conditions. The kymograph title includes the active page label; its horizontal line coordinate and depth/time axis stay matched.

# Shape: pages, 1 panel per page, depth, rows, cols
comparison = np.stack([single_slice, multislice], axis=0)[:, None]
w = Show3D(
    comparison,
    page_labels=["single-slice", "multislice"],
    panel_titles=["object phase"],
    dim_label="Depth",
)
w.set_profile((row0, col0), (row1, col1))

# Numerical form: page, depth, distance along the line
matched_profiles = w.profile_all_pages()

For pages containing several panels, pass panel_slot= to profile_all_pages() or panel= to profile_all_frames() when extracting a specific numerical profile. The interactive kymograph intentionally remains a single-panel-page tool so its line and depth axes are unambiguous.

Full-resolution folder export (advanced)#

Use the folder export when a microscopist needs to scrub a large stack at the native detector/reconstruction size instead of opening a reduced one-file HTML. This writes a small index.html beside an offline_stack.u8 data file and a manifest.json:

from quantem.widget import Show3D

w = Show3D(stack, title="800C 1.3Mx full-resolution review", debug=True)
w.export_sidecar("/data/reports/800C_1.3Mx_fullres")

Serve the folder over Range-capable local HTTP; do not open the HTML with file:// because the browser must fetch the data file:

# Use your project or lab helper that supports HTTP Range requests.
python scripts/serve_sidecar_range.py \
  --dir /data/reports/800C_1.3Mx_fullres \
  --port 8803 --bind 127.0.0.1

Then open http://127.0.0.1:8803/index.html. The viewer shell should appear immediately. The browser then loads the full stack into memory, shows the load status banner, and swaps to the cached playback path when the stack is ready. Changing Color or the histogram range repaints the current microscope view immediately, marks the playback cache as updating, and rebuilds the remaining frames in the background. Scrubbing during that rebuild should still advance the current frame; once the banner clears, playback uses the cached full-stack path again.

This workflow preserves the source spatial shape used to construct the widget. If you intentionally want a smaller browse artifact, make that explicit with a separate downsampled/single-file export rather than treating it as the full-resolution review copy. For the end-to-end browser checklist and example timing report, see the advanced tutorial.

When sharing this export, send or copy the whole folder. index.html is not a standalone result for this mode; it needs offline_stack.u8 and manifest.json beside it. The colleague should serve the received folder with the same Range helper and open the local URL.

Animation exports#

Use HTML when collaborators should keep scrubbing, zooming, and changing contrast. Use GIF when the result needs to drop into PowerPoint, email, or a static report. MP4 is available when a video file is specifically required:

w.save_gif("movie.gif", quality="medium", fps=6)
w.save_mp4("movie.mp4", quality="high", fps=12)

For array-first workflows that do not need to construct a widget, use quantem.widget.movie.save_gif(...) or quantem.widget.movie.save_mp4(...).

quality="low", "medium", and "high" control the exported spatial resolution. GIF is always palette-limited, so medium is usually the practical slide/email choice; high is sharper but larger. Pass show_frame_labels=True when panel titles should include the same live-style frame label and count that the widget canvas shows. GIF/MP4 exports keep the panel labels, scale bar, and zoom readout styling consistent with the static/offline widget image output. The widget Export menu keeps the common path explicit: choose GIF, Interactive HTML, or secondary MP4 video, then set frame range, maximum frame count, fps, spatial size, and quality before exporting. The size shown in that menu is estimated render size before compression, so the final GIF/MP4 file can vary with image texture and palette/video compression.

w.save_gif("movie_slides.gif", quality="medium", fps=8, slides_preset=True)

The GIF/MP4 path exports the full panel frames. Browser-only zoom and pan gestures are view state, so use HTML export when collaborators need to continue zooming, panning, or changing contrast interactively.

Use the Python API for frame labels, background color, bounce playback, and other presentation-specific choices. frame_start and frame_stop follow normal zero-based Python slice bounds, while max_frames evenly samples the selected range:

w.save_gif(
    "movie.gif",
    quality="medium",
    fps=6,
    frame_start=0,
    frame_stop=48,
    every_n=2,
    max_frames=20,
    max_edge_px=768,
    playback="bounce",
    show_frame_labels=True,
    background="black",
)

Note

export_html(quantized=True) writes the smaller single-file uint8 pack; the default writes exact float32 into one HTML file. For multi-GB Show3D reviews, use the folder export above instead of forcing one huge HTML file. See the widget export tutorial.