Show2D

Contents

Show2D#

One or many 2D images, including gallery panels with independent local slice stacks, plus contrast control, FFT, ROIs, line profiles, and a calibrated scale bar. See the Show2D tutorial for worked examples and Advanced Show2D for report-ready galleries, rich labels, local annotations, overlays, inset plots, and export patterns.

Reference#

class quantem.widget.show2d.Show2D(*args: Any, **kwargs: Any)#

Bases: WatchedImageFolderMixin, StaticFallbackMixin, AnyWidget

2D image comparison viewer with optional local panel stacks and analysis.

Display a single image or multiple images in a gallery layout. A 3-D item inside a list is an independent local stack for that panel, with its own in-panel slider and playback. A bare 3-D array remains a static gallery; use Show3D when every panel should share one global frame axis.

Parameters:
  • data (array_like) – 2D array (height, width) for single image, or 3D array (N, height, width) for multiple images displayed as gallery. A Python list may mix 2D images with 3D (frames, height, width) arrays. Each grayscale 3D list item is one gallery panel with an independent slider, playback state, frame count, and current frame; local frame counts do not need to match between panels. RGB images are first-class: inside a LIST input, any item shaped (H, W, 3) / (H, W, 4) is treated as an RGB(A) color image (uint8 or float in [0, 1]; alpha is dropped). A bare 3-D ARRAY keeps the historical (N, H, W) stack semantics unless its trailing dim is 3/4 AND its leading dim is > 4, in which case it is a single RGB image. Mixed galleries (grayscale + RGB side by side) are supported: RGB panels are display-ready and bypass the colormap, contrast, auto/log pipeline; their stats row, histogram, FFT, and line profile read the Rec. 709 luminance; the hover readout shows the (r, g, b) triplet. offline=True, export_html and rotate are not supported when RGB panels are present.

  • labels (list of str, optional) – Labels for each image in gallery mode.

  • panel_overlays (mapping or sequence, optional) – Reproducible per-panel geometric overlays. Each overlay is a mapping with shape equal to "circle", "rect"/"rectangle", or "square". Circle geometry uses center=(row, col) plus radius; rectangles use box=(row0, col0, row1, col1), xyxy=(col0, row0, col1, row1), or xywh=(col, row, width, height); squares may use center plus size. A dictionary keyed by panel index or label targets specific panels. Coordinates are data pixels by default; pass coords="relative" for normalized 0-1 panel coordinates. Style keys include stroke, stroke_width, line_style, dash, stroke_opacity, fill, fill_opacity, opacity, and z_order.

  • overlays (mapping or sequence, optional) – 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.

  • title (str, optional) – Title to display above the image(s).

  • cmap (str or sequence of str, default "inferno") – Colormap name (“magma”, “viridis”, “gray”, “inferno”, “plasma”). A sequence assigns one colormap per panel while preserving the first entry as the fallback/global colormap for older saved states.

  • sampling (float or tuple of float, optional) – Pixel size per axis (row, col). Scalar broadcasts to both axes. Used for scale bar display. Defaults to (1, 1).

  • units (str or list of str, optional) – Unit string per axis. Scalar broadcasts to both. Common: "A", "nm", "pixels". Defaults to ["pixels", "pixels"].

  • show_fft (bool, default False) – Show FFT and histogram panels. Every interactive FFT panel includes its current browser-side zoom multiplier (for example 2.0× or 5.0×), including independently zoomed gallery FFTs.

  • fft_metrics (bool, default True) – Show compact FFT quality labels inside FFT panels when FFT is visible. The frontend computes these labels from the cached FFT magnitude and does not trigger another FFT.

  • 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 behind a small GUI toggle. Unlike show_controls=False, users can expand the controls in the frontend and Python can call expand_controls() later.

  • show_stats (bool, default True) – Show statistics (mean, min, max, std).

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

  • log_scale (bool, default False) – Use log scale for intensity mapping.

  • auto_contrast (bool, default False) – Use percentile-based contrast.

  • vmin (float, optional) – Absolute minimum intensity for color mapping. When both vmin and vmax are set, all gallery images share the same intensity scale: essential for A/B visual comparison.

  • vmax (float, optional) – Absolute maximum intensity for color mapping.

  • diff_mode (bool, default False) – Append a live signed-difference panel to a grayscale gallery (mirrors overlay): the gallery becomes [a, b, a - b] for a pair, using a symmetric diverging colormap centered on zero so positive and negative residuals read with equal weight. The reference frame defaults to panel 0 (change it with the diff_reference trait); the diff recomputes whenever either frame or the reference changes, so it tracks scrubbing stacks live. With more than two frames one diff panel is appended per non-reference grayscale frame (#ref - #other). RGB panels never get a diff panel. Combine with overlay=True for [a, b, overlay] plus the diff panel.

  • overlay (bool or str, default False) – Compose an alignment overlay panel from a 2-image grayscale gallery (mirrors diff_mode): the gallery becomes [a, b, overlay] with image 0 -> magenta, image 1 -> green, aligned -> white (the colorblind-safe default; same math as quantem drift’s overlay_pair: one shared 1/99-percentile scale across both images). overlay="rgb" gives classic red/green (aligned -> yellow). Requires exactly 2 grayscale images. Combine with diff_mode=True for [a, b, overlay] plus the dynamic signed diff panel of the pair.

  • underlay (bool or str, default False) – Compose chemistry-on-structure from exactly two grayscale inputs [haadf, map]: the gallery becomes [haadf, map, map on HAADF], blending the element map (magenta) onto the HAADF (gray structure) as a third RGB panel so sparse EDS signal reads against the lattice without washing bright columns to white. Pass True or "haadf". Requires exactly two single-frame grayscale images (no per-panel frame stacks) and is mutually exclusive with overlay. The two sources are never modified; only the composed panel is added. See underlay_alpha and underlay_haadf_gain to tune the blend.

  • underlay_alpha (float, default 0.95) – Opacity of the element map over the HAADF in the underlay blend, in [0, 1]. Higher means more chemistry, less structure; the slider re-blends live without touching the sources.

  • underlay_haadf_gain (float, default 0.35) – Brightness of the HAADF structure showing through the underlay blend, in [0, 1]. Lower keeps the map colors saturated over a dim lattice; higher lets more of the gray structure through.

  • underlay_mode ({"haadf", "dual"}, default "haadf") – Composite recipe for the third underlay panel. "haadf" blends a single element map (magenta) onto the HAADF lattice. "dual" takes two grayscale maps [map A, map B] (no HAADF) and composes a colorblind-safe magenta+green panel: map A -> magenta, map B -> green, co-located signal -> white. Both modes need exactly two single-frame grayscale inputs.

  • stretch_percentiles (sequence of float, default (4.0, 99.0)) – Low/high display-stretch percentiles applied to the element map(s) before colorizing, matching the drift-paper Fig4 sweep. The slider re-stretches live without touching the stored counts; must satisfy 0 <= low < high <= 100.

  • display_gamma (float, default 0.75) – Presence gamma inside the "haadf" blend, > 0. Below 1 lifts mid-count columns into color; above 1 keeps only the brightest lit. Ignored in "dual" mode (the magenta+green composite has no ghost).

  • dual_gain (sequence of float, default (1.0, 1.0)) – Per-channel brightness [gain A, gain B] for the "dual" composite, each >= 0. Raise one channel to balance a weak element against a strong one; ignored in "haadf" mode.

  • ncols (int, default 3) – Number of columns in gallery mode.

  • panel_frame_indices (sequence of int, optional) – Initial frame for every panel when data is a list containing local 3-D stacks. Each value is resolved against that panel’s own frame count. Static 2-D panels accept only 0. Negative indices follow Python indexing, so -1 starts a stack on its final frame.

  • panel_playback_fps (float, default 10) – Playback speed shared by the independent local-stack play buttons. Configure it at construction without adding another toolbar control. Values above 30 are capped at the browser playback budget.

  • size (int, default 0) – Canvas rendering size in CSS pixels (the on-screen width of each image). 0 uses the frontend default: 500 px for a single image, 300 px per image in gallery mode. Pass e.g. size=800 to enlarge for a presentation, or size=200 to compress alongside a control panel. This controls display only: the underlying image resolution is never resampled; zooming into a 4K image preserves every pixel.

  • save_state (bool, default False) – When False, saved notebooks omit heavy image 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. "jpeg" is the most portable notebook default, "webp" is smaller for local/report workflows, "png" is lossless but larger, and None disables the preview.

  • 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. Lower values make notebooks smaller; higher values make the static fallback sharper.

  • denoise (str or sequence of str, default "none") – Display-only denoise method for sparse maps (EDS, low dose). 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). A scalar applies to every panel; a sequence (one entry per panel) gives each panel its own method, e.g. ["none", "anscombe"] for a raw vs denoised A/B gallery. Pure view transform: the stored array, 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. RGB panels are never filtered. Independent of display_bin (the GPU display budget knob). The per-panel denoise_modes / denoise_sigmas / denoise_bins lists are the source of truth; the scalar denoise / denoise_sigma / denoise_bin traits are the UI editor and, while denoise_scope == "panel", mirror only the selected panel. Set per-panel values imperatively with set_denoise().

  • denoise_sigma (float or sequence of float, default 4.0) – Smoothing scale in pixels for the Gaussian/Anscombe display filters. A sequence sets one sigma per panel.

  • denoise_bin ({1, 2, 4} or sequence, default 1) – Display-side 2x bin passes for SNR, combined with denoise. 1 (the default) is lossless. A sequence sets one bin factor per panel. This is the SNR knob for sparse maps (EDS, low dose): it trades resolution for counts. It is orthogonal to display_bin, which is a performance-only downsample to fit the GPU display budget and does not change reported intensities. Reach for denoise_bin to see faint chemistry, display_bin only to render a huge gallery faster.

  • show_denoise (bool, default False) – Shows the denoise controls row; does not itself denoise - use denoise= for that. Hidden by default to keep the widget clean; auto-enabled when any panel starts with an active denoise. An active reduction always shows its banner, even with the row hidden.

  • denoise_scope ({"all", "panel"}, default "all") –

    UI knob scope: “all” applies Denoise/σ/Bin edits to every panel, “panel” edits only the selected panel. Passing any per-panel sequence switches to the “panel” scope automatically. In gallery mode the toggle lives in the Link group (Link Zoom / Pan / Contrast / Denoise): checked means linked (“all”), unchecked means per panel.

    Deprecated since version The: display_filter-era kwargs display_filter, display_sigma, spatial_bin and filter_per_panel are still accepted for one release and map onto denoise, denoise_sigma, denoise_bin and denoise_scope respectively. Passing any of them emits a DeprecationWarning; if both a new kwarg and its deprecated alias are given, the new kwarg wins. In particular spatial_bin maps onto denoise_bin (the SNR knob), not display_bin (the performance downsample).

  • pad_ratio (float, default 0.0) – Ratio-based border added on each side of the displayed frame, as a fraction of the image’s max(rows, cols). Valid range 0 to 1. The border value is the frame minimum, which keeps the colormap floor. Display-only and reversible (single-panel widgets only): combine with crop_to_view() and undo with reset_view_ops(). When active, a one-line view: banner announces the reduction.

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

Examples

>>> import numpy as np
>>> from quantem.widget import Show2D

Single 2D NumPy array:

>>> Show2D(np.random.rand(512, 512))

PyTorch tensor (CPU or GPU, any dtype):

>>> import torch
>>> Show2D(torch.rand(512, 512))

3D NumPy stack (N, H, W) rendered as a gallery:

>>> Show2D(np.random.rand(6, 256, 256), ncols=3)

List of arrays with different shapes (center-padded to a common canvas):

>>> Show2D([np.random.rand(256, 256), np.random.rand(300, 400)])

One static map beside an independently scrubbed HAADF stack:

>>> haadf = np.random.rand(26, 256, 256)
>>> Show2D([np.random.rand(256, 256), haadf],
...        labels=["EDS map", "HAADF"], panel_frame_indices=[0, -1])

Four tomography reconstructions with independent slice counts and sliders:

>>> reconstructions = [
...     np.random.rand(n_slices, 64, 64)
...     for n_slices in (8, 12, 16, 20)
... ]
>>> labels = ["baseline", "regularized", "fine z", "alternative"]
>>> w = Show2D(reconstructions, labels=labels,
...            panel_frame_indices=[3, 5, 7, -1],
...            panel_playback_fps=4, ncols=2, show_fft=True)
>>> _ = w.set_panel_frame("fine z", 8)

quantem Dataset2d: title, sampling, units auto-extracted:

>>> from quantem.core.datastructures import Dataset2d
>>> ds = Dataset2d.from_array(np.random.rand(512, 512))
>>> Show2D(ds)

quantem Dataset3d: gallery view of N frames with calibration:

>>> from quantem.core.datastructures import Dataset3d
>>> ds = Dataset3d.from_array(np.random.rand(6, 256, 256))
>>> Show2D(ds, ncols=3)

A/B comparison with shared contrast and linked zoom/pan:

>>> a, b = np.random.rand(512, 512), np.random.rand(512, 512)
>>> Show2D([a, b], vmin=0, vmax=1, link_zoom=True, link_pan=True)

Per-image absolute contrast (one vmin/vmax per image):

>>> Show2D([a, b], vmin=[0.0, 0.2], vmax=[1.0, 0.8])

Drift comparison: diff mode adds a A - B panel alongside the originals (gallery becomes [A, B, A - B]):

>>> Show2D([a, b], diff_mode=True, link_zoom=True, link_pan=True)

Alignment overlay: append a composed green-magenta RGB panel (gallery becomes [a, b, overlay]; aligned regions read white):

>>> Show2D([a, b], overlay=True)

RGB images are first-class gallery items (e.g. drift’s green-magenta overlay_pair composite next to the grayscale merge):

>>> rgb_overlay = np.stack([a, b, a], axis=-1)  # (H, W, 3) in [0, 1]
>>> Show2D([a, rgb_overlay], labels=["merged", "overlay (RGB)"])

Large image: display-only canvas size (full resolution preserved):

>>> Show2D(np.random.rand(4096, 4096), size=800)

Per-panel display width for galleries. Use ncols to choose an intentional gallery shape, for example ncols=2 for a 2×2 comparison of four images or ncols=4 for a single row:

>>> Show2D(np.random.rand(13, 128, 128), ncols=13, panel_width_px=70)

Static export to PDF or PNG (vector PDF for publication figures):

>>> w = Show2D(np.random.rand(512, 512), sampling=0.5, units="nm")
>>> w.save_image("figure.pdf", dpi=150)

Denoise a sparse EDS map for display (raw counts stay untouched):

>>> eds_map = np.random.poisson(0.3, (256, 256)).astype(np.float32)
>>> Show2D(eds_map, denoise="anscombe", denoise_bin=2, denoise_sigma=8)

Raw vs denoised A/B gallery from one call (per-panel denoise list):

>>> Show2D([eds_map, eds_map], denoise=["none", "anscombe"], denoise_sigma=8)

Chemistry on structure: blend an element map onto HAADF (magenta on gray):

>>> haadf = np.random.rand(256, 256).astype(np.float32)
>>> Show2D([haadf, eds_map], underlay=True)

Zoom into a feature, commit that window as the display, then undo it:

>>> w = Show2D(eds_map, view_box=(64, 64, 96))  # zoom to a 96x96 region
>>> w.crop_to_view()      # the window becomes the whole displayed frame
>>> w.reset_view_ops()    # back to the full frame, bit-identical
classmethod from_gif(path: str | Path, *, ncols: int | None = None, labels: list[str | None] | bool | None = None, title: str | None = None, **kwargs) Self#

Open a GIF as a static frame gallery.

Animated GIFs become an (N, H, W) stack displayed as a contact sheet. Use ncols to choose an intentional layout, for example ncols=2 for a 2x2 denoising comparison or ncols=1 for a vertical strip.

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

Display readable folder images as a paged full-resolution gallery.

Files are ordered naturally (image_2 before image_10) and read through quantem.widget.io.read_image(), including EMD, TIFF, PNG, NPY, and DM calibration paths. The same widget is updated when stable files are added. Unreadable files remain retryable. At most page_size panels are visible at once; pass None to keep one unpaged gallery.

Parameters:
  • path (str or pathlib.Path) – Folder containing independent 2D image files.

  • pattern (str, default "*") – Glob selecting files within path.

  • recursive (bool, default False) – Search matching files below subdirectories as well.

  • watch (bool, default True) – Start background polling immediately.

  • watch_interval (float, default 1.0) – Seconds between background polls.

  • page_size (int or None, default 20) – Maximum visible panels per page. Paging appears only after the ready image count exceeds this value. Pass None to disable automatic folder paging.

  • **kwargs – Normal Show2D options. File-derived labels and data are managed by the folder source.

property folder_page_size: int | None#

Maximum visible panels per page for this folder-backed gallery.

set_folder_page_size(page_size: int | None) Self#

Change automatic folder pagination without rebuilding the widget.

Parameters:

page_size (int or None) – Maximum visible panels per page, or None for one unpaged gallery.

Returns:

This widget, for method chaining.

Return type:

Show2D

set_image(data, labels: list[str | None] | None = None, *, panel_frame_indices: Sequence[int] | None = None) None#

Replace the displayed image stack without rebuilding the widget.

This is the light-weight live-update path used by ShowFolder watched selections. It preserves display controls such as colormap, contrast, FFT/profile toggles, and gallery layout, while resetting per-panel state tied to the previous image count or dimensions.

save_image(path: str | Path, *, idx: int | None = None, format: str | None = None, dpi: int = 150, title: bool | str = False, colorbar: bool = False, scalebar: bool = False) Path#

Save current image as PNG, PDF, or TIFF.

When title, colorbar, or scalebar are enabled, the output is a publication-quality figure rendered via matplotlib. Otherwise a raw colormapped image is saved directly (faster, exact pixel output).

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

  • idx (int, optional) – Image index in gallery mode. Defaults to current selected_idx.

  • format (str, optional) – ‘png’, ‘pdf’, or ‘tiff’. If omitted, inferred from file extension.

  • dpi (int, default 150) – Output DPI.

  • title (bool or str, default False) – True uses the widget title, a string sets a custom title.

  • colorbar (bool, default False) – Include a colorbar showing the intensity mapping.

  • scalebar (bool, default False) – Include a scale bar (requires pixel_size > 0).

Returns:

The written file path.

Return type:

pathlib.Path

export_svg(path: str | Path | None = None, *, scale: float = 3, include_scale_bar: bool = True, include_colorbar: bool = False, title: str | None = None) Path#

Export the current Show2D gallery as a hybrid SVG figure.

The SVG keeps figure chrome editable as vector elements: panel frames, marker bars, panel labels, title, and scale-bar text/line. The scientific image panels are embedded as PNG images at scale times the widget display size, which preserves the measured pixels while giving Illustrator or Inkscape sharp panels to place in a manuscript.

Parameters:
  • path (str or pathlib.Path, optional) – Output SVG path. Defaults to a descriptive filename in the current working directory.

  • scale (float, default 3) – Embedded image scale relative to the widget display panel size. Values below 1 are clamped to 1; use 3 for the default high-resolution export, or a smaller value when file size matters.

  • include_scale_bar (bool, default True) – Include the current scale bar when scale bars are visible on the widget. Set False to omit scale-bar chrome from the SVG.

  • include_colorbar (bool, default False) – Include an editable SVG colorbar for single-panel exports. The live browser export uses the current Color switch state instead.

  • title (str, optional) – Figure title override. Defaults to the widget title.

Returns:

The written SVG path.

Return type:

pathlib.Path

property view_corner#

Current view as (row0, col0, size) - paste straight into view_box=.

view_box live-syncs from JS on every pan/zoom, so after interacting this reads the region on screen. Restore next session with Show2D(..., view_box=w.view_corner).

property view_center#

Current view as dict(zoom=..., zoom_row=..., zoom_col=...) - paste as kwargs.

The center/zoom flavor of view_corner: restore with Show2D(..., **w.view_center).

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 image buffers from that snapshot, so a plain Show2D does not bake ~1 GB of pixels into the .ipynb. Targeted syncs (key is a name or set, used by hold_sync / send_state during live rendering) are untouched, so the frontend still receives frame_bytes normally. save_state=True embeds everything so a reopened notebook restores the interactive widget without a kernel.

save_view_state(name: str | None = None, *, update: bool = False) dict[str, Any]#

Save the current lightweight Show2D inspection state.

This is the programmatic version of the More → Save State button. It stores widget/view settings such as ROI, zoom/view box, padding, denoise/filter, FFT, selected panel, hidden panels, contrast, and frame indices. It never stores raw image arrays.

load_view_state(name_or_index: str | int) Self#

Restore a named saved inspection state on the current data.

delete_view_state(name_or_index: str | int) Self#

Delete one saved inspection state.

clear_view_states() Self#

Delete all saved inspection states.

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) Path#

Write a standalone HTML viewer for this widget.

The exported file mounts the live anywidget JS bundle with the current widget state (data, labels, cmap, vmin/vmax, log_scale, sampling, …). Opens in any browser without a Jupyter kernel. Preferred export options are mode="single", encoding="full" or encoding="uint8", and downsample=None. quantized is kept as a compatibility alias for encoding="uint8".

Parameters:
  • path (str or pathlib.Path, optional) – Destination HTML path.

  • quantized (bool, default False) – Store the displayed image stack as uint8 with min/max metadata. This is smaller and visually equivalent after colormapping. The default stores exact float32 display values.

  • title (str, optional) – Browser page title. Defaults to widget title or “Show2D”.

property current_view: dict#

The field of view currently on screen, in image and calibrated coordinates.

Every pan/zoom in the browser syncs the visible region back to Python (debounced ~100 ms), so after zooming into a feature you can capture exactly where you are for figure-making: view = w.current_view. Feed view["box"] back as Show2D(data, view_box=view["box"]) to reproduce the same crop later. The box is axis-aligned, so the two corners (row0, col0) and (row1, col1) define all four.

Returns a dict with row0/col0/row1/col1 (image pixel coordinates), height/width (visible extent in pixels), zoom, box (the (row0, row1, col0, col1) tuple accepted by view_box=), and when pixel_size > 0 the calibrated *_cal extents plus unit.

crop_to_view() Self#

Commit the current browser viewport as the display extent.

Zoom into a feature (mouse wheel, or view_box= at construction), then call this to make that window the whole displayed frame: the widget repacks only the committed region, so an active denoise operates on the cropped view. Display-only and reversible: the stored array is never modified, the stats row keeps reporting the full raw data, and cursor coordinates stay full-image (row, col). reset_view_ops() restores the full frame bit-identically.

Returns:

The widget, for chaining.

Return type:

Self

Raises:

NotImplementedError – For galleries (crop-to-view is single panel only in this release) and for RGB panels.

Examples

>>> w = Show2D(image, view_box=(64, 64, 96))  # zoom into a feature
>>> w.crop_to_view()      # the 96x96 window becomes the display extent
>>> w.reset_view_ops()    # back to the full frame, bit-identical
reset_view_ops() Self#

Restore the uncropped, unpadded display.

Undoes crop_to_view() and pad_ratio in one call. Both ops are display-only view transforms, so resetting returns the exact original frame bytes; the stored data was never touched.

Returns:

The widget, for chaining.

Return type:

Self

Examples

>>> w = Show2D(image, pad_ratio=0.1)
>>> w.reset_view_ops()  # full frame again, no border
set_padding(ratio: float, *, fill: str = 'min', panels: Sequence[int] | int | str | None = None) Self#

Set display padding for every panel, or a chosen panel subset.

Padding is a reversible display transform: the stored arrays are never modified, while frame bytes, histograms, saved state, and exports use the padded display frame. ratio is a fraction of the current display canvas size (0 to 1). fill chooses the constant border value from each panel: "min", "median", or "mean".

set_denoise(mode: str, *, sigma: float | None = None, bin: int | None = None, panels: int | Sequence[int] | None = None) Self#

Set the display denoise for every panel, or a chosen panel subset.

The imperative twin of the denoise= constructor kwarg, chainable like crop_to_view() and set_roi(). Denoise is a pure view transform: the stored array, the stats row, and every raw-data export keep the original counts, and an active reduction still announces itself with the one-line banner. The per-panel denoise_modes / denoise_sigmas / denoise_bins lists are the source of truth; the scalar denoise / denoise_sigma / denoise_bin traits mirror only the selected panel while denoise_scope == "panel".

Parameters:
  • mode (str) – Denoise method for the targeted panels: "none", "gaussian", or "anscombe". The compound spellings "bin2", "bin2_anscombe" and "bin4_anscombe" fold into a (mode, bin) pair; "tv" stays available from Python.

  • sigma (float or None, optional) – Smoothing scale in pixels. None leaves each targeted panel’s current sigma unchanged.

  • bin (int or None, optional) – Display-side SNR bin factor (1, 2, or 4). None leaves each targeted panel’s current bin unchanged, unless a compound mode (e.g. "bin2_anscombe") sets it.

  • panels (int, sequence of int, or None, optional) – Panel indices to change. None (the default) targets every panel and applies one uniform setting; passing a subset edits only those panels and switches denoise_scope to "panel" so later UI edits stay per panel.

Returns:

The widget, for chaining.

Return type:

Self

Raises:

ValueError – When a panels index is out of range for the widget.

Examples

>>> import numpy as np
>>> from quantem.widget import Show2D
>>> a = np.random.poisson(0.3, (256, 256)).astype("float32")
>>> b = np.random.poisson(0.3, (256, 256)).astype("float32")
>>> w = Show2D([a, b])                             # a raw A/B gallery
>>> _ = w.set_denoise("anscombe", sigma=8, bin=2)  # denoise both panels
>>> _ = w.set_denoise("none", panels=[0])          # keep panel 0 raw
to_show3d(panels: Sequence[int | str] | int | str | None = None, *, title: str | None = None, copy: bool = True, include_hidden: bool = False)#

Create a Show3D stack from this Show2D image gallery.

The default uses currently visible panels as frames, preserving labels, colormap, sampling, scale-bar units, and contrast settings. Use include_hidden=True when hidden panels should also become frames.

property ordered_panels: list[int]#

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

property visible_panels: list[int]#

Zero-based image panel indices currently visible in the gallery.

property starred_panels: list[int]#

Zero-based image panel indices marked with a star.

property starred_pages: list[int]#

Zero-based page indices marked with a star.

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

Replace the hidden panel set by index or exact label.

Hidden panels remain in the widget state and standalone HTML export, but they are collapsed from the gallery until restored. At least one panel must stay visible.

hide_panel(*panels: int | str) Self#

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

show_panel(*panels: int | str) Self#

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

show_all_panels() Self#

Restore every image panel in the gallery.

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

Set the gallery display order by panel index or exact label.

The order is display-only: source data, labels, hidden state, stars, and pixel sizes stay keyed by their original panel indices.

reset_panel_order() Self#

Restore the natural source-image order.

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

Move one panel to a zero-based display position.

set_panel_frame(panel: int | str, frame: int) Self#

Set the displayed frame for one local stack panel.

Static panels have one frame and therefore only accept frame 0. Negative indices follow normal Python indexing, so -1 selects the final frame (useful for Velox/EDS acquisitions whose exported HAADF is the last survey frame).

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.

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

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

star_panel(panel: int | str) Self#

Mark an image panel with a star.

unstar_panel(panel: int | str) Self#

Clear the star on an image panel.

star_page(page: int) Self#

Mark a page with a star.

unstar_page(page: int) Self#

Clear the star on a page.

summary()#

Print a human-readable snapshot of the widget’s current state.

Reports image dimensions and pixel size, data min/max/mean, display settings (colormap, contrast, scale, FFT), active ROIs and profile line, per-image rotations, and the most recent render timings.

rotate(idx: int, angle: int) Self#

Rotate image idx by angle degrees (CCW-positive, matches np.rot90).

Rotation convention follows np.rot90:

angle | image_rotations | np.rot90 k | direction
------+-----------------+------------+----------
  90  |        1        |     1      | 90° CCW
 180  |        2        |     2      | 180°
 -90  |        3        |     3      | 90° CW
 360  |        0        |     0      | identity
Parameters:
  • idx (int) – Image index in the gallery (0-based).

  • angle (int) – Rotation angle in degrees (must be a multiple of 90). Positive = counter-clockwise, negative = clockwise.

Return type:

Self

set_profile(start: tuple, end: tuple)#

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()#

Clear the current line profile.

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. Bounds are reported in the image coordinate system; bounds_clipped is clamped to the current image extent for code that wants to slice arrays safely.

Parameters:

visible_only (bool, default=True) – If True, omit ROIs whose synced state has visible=False.

Returns:

JSON-friendly ROI descriptions. Circle ROIs include center and radius. Rectangles and squares include corners in clockwise order from the top-left. Annular ROIs include radius_inner and radius_outer.

Return type:

list of dict

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. Bounds are reported in the image coordinate system; bounds_clipped is clamped to the current image extent for code that wants to slice arrays safely.

Parameters:

visible_only (bool, default=True) – If True, omit ROIs whose synced state has visible=False.

Returns:

JSON-friendly ROI descriptions. Circle ROIs include center and radius. Rectangles and squares include corners in clockwise order from the top-left. Annular ROIs include radius_inner and radius_outer.

Return type:

list of dict

property profile#

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

Returns:

Line endpoints in pixel coordinates, or empty list if no profile.

Return type:

list of tuple

property profile_values#

Get intensity values along the profile line as a numpy array.

Returns:

Float32 array of sampled intensities, or None if no profile.

Return type:

np.ndarray or None

property profile_distance#

Get total distance of the profile line in calibrated units.

Returns:

Distance in angstroms (if pixel_size > 0) or pixels. None if no profile line is set.

Return type:

float or None

Interactive controls#

Each control mutates the listed synced trait. A UI-test agent acts on the control, then asserts the trait changed and the canvas repainted (non-zero, no console error, no NaN frame).

Control

Trait

Expected effect

Colormap dropdown

cmap

Canvas recolors to the chosen map

More menu: Color shared

panel_cmaps, panel_cmaps_memory

Shared by default. Turn off to let the Color dropdown edit only the selected gallery panel; turn on for one shared colormap, then turn off again to restore the previous individual panel colormaps

Panel identity markers

marker_colors, identity_colors, marker_style, row_markers, col_markers

Optional panel colors plus row/column 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; More -> Overlay Edit allows live select, move, resize, delete, and reset

Contrast min / max sliders

vmin, vmax

Display clamp changes; histogram markers move

Auto-contrast toggle

auto_contrast

Re-fits vmin/vmax to the percentile range

More menu: Contrast

contrast_preset

Applies scientist-friendly percentile ranges such as 1-99, 2-98, and 3-97; the histogram stays visible below the image

Log-scale toggle

log_scale

Intensity mapped through log

FFT toggle

show_fft

Canvas shows the power spectrum; lattice spots appear

FFT window toggle

fft_window

Apodization on/off (ringing at edges differs)

FFT quality labels

fft_metrics

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

FFT zoom / pan

Browser-local per FFT panel; FFT Link Zoom and Link Pan controls

Wheel or pinch zoom updates an always-visible N.N× badge in that FFT panel; reset returns Show2D’s 2.0× FFT default

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

Stats visibility

show_stats

Mean/min/max/std readout shows/hides

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, saved state, and exported HTML

Scale bar toggle

show_scale_bar (scale_bar_visible in saved state)

Calibrated bar shows/hides (needs pixel_size > 0)

Scale bar style

scale_bar_panels, scale_bar_length, scale_bar_label, scale_bar_style

Restrict scale bars to selected panels and control exact publication text, font, outline, label spacing, offset, and bar thickness

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 panels, the outside gallery frame, and each panel’s own inner stroke for browser, SVG, and static previews

Pan (drag)

per-image pan

Image translates; with link_pan all panels move together

Zoom (wheel)

initial_zoom, zoom_row, zoom_col

Zooms about the cursor

Smooth toggle

smooth

Bilinear vs nearest sampling

ROI add / drag

roi_active, roi_list, roi_selected_idx; get_roi_geometries()

Region overlay; stats panel reports the ROI; saved notebook previews include all ROI overlays and one comparable right-side zoom crop per visible ROI; Python can read circle centers/radii and rectangle/square corners in (row, col) coordinates

Gallery select

selected_idx

Highlights the active panel

Local stack slider / play

panel_frame_indices, panel_frame_counts, panel_playback_fps; set_panel_frame()

Every grayscale 3D list item gets independent slider/play controls; changing one panel does not move another, and constructor-configured playback speed adds no toolbar clutter

Gallery page controls

page_idx, n_pages, panels_per_page, page_labels, page_starred

Switch, star, or play through panel pages without changing the source stack

Panel reorder

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

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

Diff mode

diff_mode, diff_reference

Panels render as difference vs the reference

Link Denoise switch (gallery)

denoise_scope

Linked (“all”): denoise edits apply to every panel; unlinked (“panel”): edits apply to the selected panel only

Denoise master

denoise_enabled

Off shows raw pixels and hides the banner without discarding method, sigma, bin, or per-panel settings

Denoise Settings

show_denoise

Expands/collapses the Method, sigma, and bin editor without changing whether the effect is active

Filter master

frequency_filter_enabled

Turns frequency filtering on/off without discarding cutoff or band settings

Filter Settings

show_frequency_filter

Chooses None, Low-pass, High-pass, or Band-pass and exposes the FFT-ring parameters

More menu: Save State

saved_view_states, saved_view_request, saved_view_status; save_view_state(), load_view_state(), delete_view_state(), clear_view_states()

Save named lightweight inspection bookmarks, then load/update/delete them without storing another copy of the image data

View menu: Crop to view

view_crop; crop_to_view()

Commits the current viewport as the display extent (single panel, display-only, reversible)

View menu: Padding

pad_ratio, pad_ratios, pad_fill_mode, pad_fill_modes, pad_scope; set_padding()

Adds a ratio-based display border with min/median/mean fill; gallery edits can apply to all panels or the selected panel

View menu: Reset view

reset_view_ops()

Restores the uncropped, unpadded display bit-identically

More menu: Flip

image_flips_horizontal, image_flips_vertical

Display-only horizontal/vertical flips help compare orientations and point-defect neighborhoods without changing stored arrays

More menu: Rotate

image_rotations, rotation_scope; rotation=, rotations=

Display-only 0/90/180/270° rotation for every panel or the selected panel; raw data and (row, col) coordinates stay unchanged

Panel inset plots

inset_plots

Optional per-panel mini line plots for calibration curves, ACF/R sweeps, or other scientific context; hover reports the nearest plotted coordinate

Scale bar placement

scale_bar_position, show_zoom_indicator

Move the scale bar between bottom corners and hide the zoom badge when an inset plot needs that space

Rich panel labels and math#

Panel labels can be plain strings, inline math strings, or structured rich spans. Use this for parameter sweeps where the compact panel chrome should say λ, χ², σ, or similar symbols instead of spelling out the word.

from quantem.widget import Show2D

Show2D(
    [raw, residual],
    labels=[
        r"$\lambda=0.03$ raw",
        r"$\chi^2$/pixel residual",
    ],
    show_stats=True,
)

The frontend renders common Greek symbols plus simple superscripts/subscripts without requiring MathJax or KaTeX. It also normalizes doubled backslashes from JSON/state files, so \\lambda is rendered as λ rather than as raw markup. The same rich label path is used by panel titles, the Panels menu, the stats row, and exported standalone HTML.

For mixed color or explicit math spans, use panel_title_spans. Each span can contain text, math, and optional color:

Show2D(
    [raw, filtered, residual],
    labels=["raw", "filtered", "residual"],
    panel_title_spans=[
        [{"math": r"\lambda=0.03"}, {"text": " raw"}],
        [{"text": "filtered", "color": "#34d399"}],
        [{"math": r"\chi^2"}, {"text": "/pixel residual"}],
    ],
)

Use panel_annotations when the label belongs to a local region inside a panel instead of the whole panel. An annotation can also use math or spans, so a panel can have both a rich title and multiple local callouts:

Show2D(
    [raw, residual],
    panel_title_spans=[
        [{"math": r"\lambda=0.03"}, {"text": " raw"}],
        [{"math": r"\chi^2"}, {"text": "/pixel"}],
    ],
    panel_annotations={
        0: [
            {"math": r"\lambda", "position": "top-left", "variant": "pill"},
            {
                "spans": [{"text": "ROI "}, {"text": "A", "color": "#60a5fa"}],
                "box": [0.18, 0.25, 0.30, 0.16],
                "variant": "callout",
            },
        ],
        1: {"math": r"\chi^2", "position": "top-right", "variant": "outline"},
    },
)

Circle and rectangle overlays#

Use panel_overlays when the figure needs reproducible geometric callouts in addition to analysis ROIs. Overlay coordinates use microscope image coordinates by default: (row, col) for centers and (row0, col0, row1, col1) for boxes. The same state is rendered in live Jupyter widgets, saved notebook state, and standalone HTML exports.

from quantem.widget import Show2D

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

Overlay strokes are solid by default. Set line_style="dashed", line_style="dotted", or line_style="dashdot" for common scientific callouts, or pass dash=[on, off, ...] for an exact canvas dash pattern.

For a shared overlay on every panel, pass overlays=[...] instead of panel_overlays. Use coords="relative" when the geometry should follow normalized panel coordinates from 0 to 1:

Show2D(
    [raw, denoised],
    labels=["raw", "denoised"],
    overlays=[
        {
            "shape": "circle",
            "center": (0.5, 0.5),
            "radius": 0.08,
            "coords": "relative",
            "stroke": "#f87171",
        }
    ],
)

The live widget and exported HTML also expose More -> Overlay Edit when overlays are present. In edit mode, click a circle or rectangle to select it, drag inside to move it, drag an edge to resize it, press Delete to remove the selected overlay, and choose Reset Overlays to restore the constructor state. Use ROIs when the shape should drive statistics, FFT crops, or Python readback through get_roi_geometries().

Presentation and export chrome#

ui_mode="presentation" starts with controls collapsed so the scientific image gets the first view. It still leaves a compact Controls button for adjusting the widget and an Export button for saving the current standalone HTML:

w = Show2D(
    [raw, residual],
    labels=[r"$\lambda=0.03$ raw", r"$\chi^2$/pixel"],
    ui_mode="presentation",
    show_stats=True,
)
w.export_html("lambda_sweep.html", encoding="uint8")

In live Jupyter, the Export menu can request exact or uint8 standalone HTML through the Python backend. In standalone HTML, the Export button downloads the current embedded page using the representation already present in that file.

For Illustrator or Inkscape, use the Export menu’s SVG item or call w.export_svg(...). Show2D writes a hybrid publication SVG: measured image panels remain embedded rasters, while panel titles, annotations, geometric overlays, inset plots, scale bars, colorbars, and group markers are editable SVG objects that match the current figure state.

Publication SVG typography and layout keys#

Use these keys when Show2D should produce a publication figure that can go directly to Illustrator or Inkscape.

Panel title fonts#

labels set the panel title text. panel_title_font_size sets the size, and panel_title_style controls typography and placement:

PUBLICATION_FONT = (
    "Nimbus Sans, Helvetica, Arial, "
    "Liberation Sans, DejaVu Sans, sans-serif"
)

Show2D(
    panels,
    labels=["a 0° scan", "b 90° scan", "c combined"],
    panel_title_font_size=16,
    panel_title_style={
        "font_family": PUBLICATION_FONT,
        "font_weight": 700,
        "fg": "#ffffff",
        "outline_color": "#000000",
        "outline_width": 2.2,
        "x": 0.035,
        "y": 0.035,
        "anchor": "top-left",
        "align": "left",
    },
)

To match a published PDF, inspect its embedded fonts and put the matching family first:

pdffonts path/to/published_figure.pdf
fc-match "Helvetica"

If the PDF text was outlined or rasterized, no editable text font may be reported; use the source figure script or the closest installed Helvetica-like family instead.

Accepted panel_title_style keys:

Key

Meaning

font_family

CSS/SVG font-family stack, for example "Helvetica, Arial, sans-serif"

font_weight

Numeric or named weight, for example 700 or "bold"

fg

Text fill color

outline_color, outline_width

Text stroke used for publication contrast

x, y

Relative panel coordinates from 0 to 1

anchor

top-left, top-center, top-right, center, bottom-left, etc.

offset

Pixel nudge (dx, dy) after relative placement

align

Text alignment fallback when x/y are not supplied

bg, border_color, border_width, pad_x, pad_y, radius, opacity, max_width

Optional title chrome

Local annotation fonts#

Use panel_annotations for multiple labels inside the same image panel. Each annotation can have its own font, color, outline, and position:

Show2D(
    panels,
    labels=["f composite on 0° HAADF"],
    panel_annotations={
        "f composite on 0° HAADF": [
            {
                "text": "Ba+Ti",
                "x": 0.20,
                "y": 0.15,
                "anchor": "center",
                "align": "center",
                "font_family": PUBLICATION_FONT,
                "font_size": 15,
                "font_weight": 800,
                "fg": "#ff4dff",
                "outline_color": "#000000",
                "outline_width": 1.8,
                "variant": "plain",
            },
            {
                "text": "Sr",
                "x": 0.80,
                "y": 0.15,
                "anchor": "center",
                "align": "center",
                "font_family": PUBLICATION_FONT,
                "font_size": 15,
                "font_weight": 800,
                "fg": "#58ff58",
                "outline_color": "#000000",
                "outline_width": 1.8,
                "variant": "plain",
            },
        ],
    },
)

Use x and y to attach a label to a physical region of the panel. Use box=[left, top, width, height] when the label describes an area. Use variant="plain" for text-only publication labels, or badge, pill, outline, and callout for report-style labels.

Scale bar fonts and placement#

scale_bar_panels limits the bar to one or more panels. Values can be panel indices or label strings. scale_bar_length is in the physical unit implied by sampling and units; scale_bar_label overrides the displayed text.

Show2D(
    panels,
    labels=labels,
    sampling=0.0105,
    units="nm",
    scale_bar_panels=["f corrected combined"],
    scale_bar_length=2.0,
    scale_bar_label="2 nm",
    scale_bar_style={
        "font_family": PUBLICATION_FONT,
        "font_size": 16,
        "font_weight": 700,
        "color": "#ffffff",
        "outline_color": "#000000",
        "outline_width": 1.3,
        "bar_height": 5,
        "label_gap": 5,
        "offset": (0, -8),
    },
    show_zoom_indicator=False,
)

Accepted scale_bar_style keys:

Key

Meaning

font_family, font_size, font_weight

Scale-bar label typography

color

Bar and label fill color

outline_color, outline_width

Label text stroke

shadow_color

Fallback label/bar shadow when no outline is used

bar_height

Bar thickness in SVG/browser CSS pixels

label_gap

Pixels between label baseline and bar

offset

Pixel nudge (dx, dy) for the whole scale bar

Pixel-perfect gutters#

Use explicit gallery chrome controls for manuscript grids:

Show2D(
    panels,
    labels=labels,
    ncols=3,
    inter_panel_gap_px=2,
    inter_panel_gap_color="#000000",
    gallery_outer_border_px=2,
    gallery_outer_border_color="#000000",
    panel_inner_border_px=1,
    panel_inner_border_color="#000000",
)

inter_panel_gap_* controls only the layer between panels. gallery_outer_border_* controls the frame around the whole grid. panel_inner_border_* controls the stroke drawn inside each image panel. The older gallery_gap_px / gallery_gap_color names remain as aliases for old notebooks; when used together they populate all three black-grid layers for the historical pixel-perfect SVG behavior.

Which denoise filter should I use?#

The Denoise controls are hidden behind their own toggle by default; everything here is display-only (the stored array, the stats row, and raw exports keep the original counts, and an active denoise always announces itself with a one-line banner). Three methods cover the space; binning is a separate knob, so there is no “bin2_anscombe” menu entry: pick Poisson (Anscombe) and set Bin 2.

Your data

Use

Why

Sparse EDS / counting maps

Poisson (Anscombe), Bin 2, sigma 6-10

Respects Poisson count statistics; the standard choice for element maps

Very sparse maps (single counts)

Poisson (Anscombe), Bin 4, sigma 8-12

More SNR from binning before smoothing

HAADF / decent-dose images

Gaussian, Bin 1, sigma 1-2 (or nothing)

The data is not count-starved; a light smooth is enough

Anything quantitative (FFT, intensities, stats)

None

Measure on raw counts; the stats row is always computed from raw data

From Python, the same ladder is denoise="anscombe", denoise_bin=2, denoise_sigma=8 (per-panel lists supported for A/B galleries); legacy spellings like display_filter="bin2_anscombe" keep working as aliases.

Reuse ROI coordinates in notebooks and agents#

ROIs are synced in roi_list and saved in state_dict(). For analysis code, prefer get_roi_geometries() so the shape-specific coordinates are explicit:

w = Show2D(img).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. Every ROI also includes bounds and bounds_clipped, which lets an agent crop an array safely while preserving the user’s exact visible selection.

Save microscope-stage view states#

Show2D can store named lightweight inspection bookmarks. This is useful when a scientist explores a notebook interactively — for example drawing an ROI around a defect, turning on FFT, hiding panels, changing contrast, adding padding for a drift check, and then wanting to come back to exactly that view later without writing export code.

The GUI path is More → Save State. Existing states can be loaded, updated, deleted one at a time, or deleted all at once. The Python API uses the same state model:

w = Show2D([raw, corrected, residual], labels=["raw", "corrected", "residual"])
w.add_roi(row=72, col=65).set_padding(0.2, fill="median")
w.show_fft = True

w.save_view_state("defect A drift check")
w.hidden_panels = [2]
w.save_view_state("raw vs corrected")

w.load_view_state("defect A drift check")
w.delete_view_state("raw vs corrected")
w.clear_view_states()

Saved view states include UI/view traits such as ROI list, selected ROI, selected panel, hidden panels, panel order, local stack frame indices, contrast, colormap, FFT, denoise/filter, crop, padding, zoom/view box, and scale-bar settings. They do not store another copy of frame_bytes, panel_stack_bytes, or raw source arrays. state_dict() includes the named bookmarks so a saved notebook can reopen with the same list of microscope-stage positions.

Add scientific inset plots to panels#

Advanced figure-making sometimes needs a compact plot inside each image panel: for example an autocorrelation score versus an R ratio while reviewing automatic denoising calibration. Use inset_plots for that per-panel context. The top-level parameter stays singular and readable, while each dictionary keeps the plot data, placement, labels, and style together.

Show2D(
    denoised_panels,
    labels=["candidate A", "candidate B", "candidate C"],
    inset_plots=[
        {
            "x": r_values,
            "y": acf_values_for_panel,
            "point": (best_r, best_acf),
            "xlabel": "R",
            "ylabel": "ACF",
            "legend": "ACF/R",
            "annotation": f"R*={best_r:.2f}",
            "position": "bottom-right",
            "margin": 24,
            "size": 0.36,
            "height": 0.26,
            "show_ticks": True,
            "xticks": [0, 1],
            "yticks": [0, 1],
            "line_width": 2.6,
            "background_alpha": 0.58,
            "border_width": 0,
        }
        for acf_values_for_panel, best_r, best_acf in calibration_results
    ],
    scale_bar_position="bottom-left",
    show_zoom_indicator=False,
)

For normal notebooks, prefer position plus margin, size, and height. Use box=[left, top, width, height] only when a publication figure needs exact normalized panel coordinates. border_width=0 gives a clean no-frame look; increase background_alpha or use a subtle border_color when the underlying image is noisy. On hover, the live widget reports the nearest plotted coordinate using the axis labels, such as R 0.465 · ACF 0.48.

If inset_plots is present, the live widget shows an Inset Chart switch in the More menu. A scientist can hide the chart during image inspection, turn it back on for reporting, or drag the chart inside the panel. Dragging previews freely and then snaps to the closest corner on release, updating position and margin in the widget state.

Remove a background or isolate a periodicity#

Frequency Filter is deliberately separate from Denoise. Denoise promises a cleaner view of the measurement; High-pass and Band-pass deliberately remove real signal, so their displayed pixels must not be treated as measurable raw counts. The stored array, statistics row, and raw exports remain unchanged.

# Flatten a slow brightness gradient.
Show2D(eds_map, show_fft=True,
       frequency_filter="highpass", frequency_filter_cutoff=0.08)

# Isolate a lattice-frequency ring.
Show2D(lattice, show_fft=True,
       frequency_filter="bandpass",
       frequency_filter_center=0.32,
       frequency_filter_width=0.08)

Parameters are fractions of Nyquist from 0 to 1. Drag the cyan FFT ring to select the cutoff or band center by eye. The FFT overlay dims rejected frequencies: Low-pass keeps the clear area inside the ring, High-pass keeps the clear area outside, and Band-pass keeps only the clear annulus. Filter and its More-menu toggle are off by default; turning the master on with no remembered mode starts with Low-pass. Denoise and Filter are chainable in the fixed order Denoise then Filter; each active operation has its own banner. Turn the Filter master off or choose None to restore the unfiltered view without losing the settings.

For paged galleries, FFT work is demand-driven: only panels visible on the current page are transformed. Visiting another page computes that page once and retains the bounded cache for later return visits.

Crop and pad the view (advanced)#

Single-panel widgets can commit the current browser viewport as the display extent. Single-panel and gallery widgets can add a ratio-based display border, either from the toolbar’s View menu or from Python:

w = Show2D(image, view_box=(64, 64, 96))  # zoom into a feature
w.crop_to_view()          # the 96x96 window becomes the displayed frame
w.set_padding(0.1, fill="median")  # 10% border on each side
w.reset_view_ops()        # full frame again, bit-identical

For a drift-correction gallery, use per-panel padding to compare candidate margins without rebuilding another notebook cell:

w = Show2D([raw, corrected, residual], ncols=3)
w.set_padding(0.20, fill="mean", panels=[1])  # only corrected panel

Both ops honor the display-only contract:

  • The stored array is never modified; reset_view_ops() returns the exact original frame bytes.

  • The crop applies in the display pipeline before denoise, so an active denoise operates on the cropped region; the pad applies after it and uses the selected fill mode (min, median, or mean).

  • The stats row keeps reporting the full raw data, and cursor coordinates remain full-image (row, col) while a crop or pad is active: the crop is a display window, not a new coordinate system.

  • The histogram and canvas are repacked from the padded display frame, so the border contribution is visible while the raw data remain unchanged.

  • An active crop or pad is never silent: a one-line view: banner names the window and the ratio, e.g. view: cropped to (64,64)-(160,160) · pad 10% median (reset_view_ops() restores full frame).

  • Both persist through state_dict() / load_state_dict().

Crop-to-view remains single-panel in this release because it changes the coordinate origin. Padding is gallery-safe: panels share the largest padded canvas so comparisons stay aligned, while each panel can still have its own ratio/fill mode.

FFT quality labels#

Pass show_fft=True to show the FFT panel. By default, fft_metrics=True adds a small white label inside each FFT panel with three quick checks: sharpness, peak count, and peak SNR. These values are computed from the FFT magnitude already used for rendering, so the label does not trigger a second FFT. Set fft_metrics=False when a clean FFT image is more important than the readout.

The first FFT for an image, local panel frame, ROI, or window configuration may take a moment on large data. After that, Show2D reuses that bounded cache entry for redraws, zoom/pan, contrast changes, metric labels, and return visits to the same slice. During a first-time slice computation, the previous valid FFT stays visible instead of flashing to a dark loading panel.

Each interactive FFT panel also carries its own bottom-left zoom multiplier. It starts at 2.0×, follows wheel and touch-pinch zoom immediately, and returns to 2.0× on double-click, double-tap, or Reset. In a gallery, unlinked FFTs report independent values; enabling FFT Link Zoom makes their values move together without changing the real-space zoom.

w = Show2D(images, labels=["raw", "filtered", "residual"])
w.set_panel_order(["residual", "raw", "filtered"])
w.move_panel("raw", 0)
w.reset_panel_order()

See also

The deeper behavioral spec (invariants, per-feature pass criteria, isolation checks) lives alongside the integration test at widget/docs/show2d-test-spec.md.

Live image updates#

Use set_image() to trigger a new browser render in an already displayed Show2D widget. Keep a reference to the widget object, display it once, then replace the image or gallery data through that method:

import numpy as np
from quantem.widget import Show2D

w = Show2D(first_image, labels=["initial"], offline=False)
w

for step, next_image in enumerate(image_stream, start=1):
    w.set_image(next_image, labels=[f"step {step}"])

For a gallery, pass a 3D stack (N, H, W) and one label per panel:

w.set_image(
    np.stack([raw, filtered, residual]),
    labels=["raw", "filtered", "residual"],
)

Independent local stack panels#

A bare 3D array still means a static gallery of N images. To give gallery panels their own frame or slice axes, pass a Python list whose 3D items are each shaped (frames, rows, cols). Every grayscale 3D list item becomes one panel with its own slider, play/pause control, frame count, and current frame. Counts may differ between panels. RGB(A) list items keep the color-image behavior described in the constructor reference.

This is useful for tomography or multislice studies that produce several reconstruction volumes. The scientist can keep four methods, regularization settings, or slice thicknesses side by side, stop each one at the depth where a feature or artifact is clearest, and retain linked spatial context without forcing all panels onto one global slice index:

from quantem.widget import Show2D

reconstructions = [
    recon_8_slices_20A,
    recon_16_slices_10A,
    recon_regularized,
    recon_alternative,
]

w = Show2D(
    reconstructions,
    labels=[
        "8 slices x 20 A",
        "16 slices x 10 A",
        "regularized",
        "alternative",
    ],
    panel_frame_indices=[3, 7, -1, 0],
    panel_playback_fps=4,
    ncols=2,
    link_zoom=True,
    link_pan=True,
)
w.set_panel_frame("16 slices x 10 A", 8)
w

Use a list even when all reconstructions happen to have the same slice count; the outer list is what declares independent local stacks. Use Show3D instead when all panels should advance together on one global frame or slice axis.

The list may also mix stack panels with ordinary 2D images:

w = Show2D(
    [eds_sum_map, haadf_stack, ti_map, o_map],
    labels=["EDS sum", "HAADF", "Ti", "O"],
    panel_frame_indices=[0, -1, 0, 0],
    ncols=2,
    auto_contrast=True,
)
w

Only the HAADF panel gets an in-panel slider and play button. Each local frame index remains keyed to its source panel when panels are hidden or reordered. Click a stack panel and use the left or right arrow key to scrub it. From Python, use w.set_panel_frame("HAADF", -1).

Set panel_playback_fps when constructing the widget to choose the cadence for all local-stack play buttons without adding another compact control. The default is 10 fps; values above the 30 fps browser budget are capped. The setting is saved in widget state and carried into standalone HTML exports, while the per-panel playing/paused state remains browser-local so reopening a report does not start playback unexpectedly.

The active panel’s histogram, stats, and FFT follow its current local frame; scrubbing it does not change neighboring panels. state_dict() records panel_frame_indices, and single HTML exports using either encoding="full" or encoding="uint8" contain every local frame. For an interactively restored notebook output, construct with save_state=True; the default save_state=False deliberately stores only a compact static preview rather than embedding the stack payload in the notebook.

For single-image ROI review, that compact saved-notebook preview keeps the scientific marks visible: the fallback PNG draws every visible ROI on the full image and adds one right-side zoom crop per ROI. The zoom crops use a common crop size where image boundaries allow it, so multiple ROIs remain visually comparable. A reader who opens the notebook or report without rerunning the cell can still see the marked features.

set_image() accepts the same mixed list form:

w.set_image(
    [next_eds_map, next_haadf_stack, next_ti_map, next_o_map],
    labels=["EDS sum", "HAADF", "Ti", "O"],
    panel_frame_indices=[0, 0, 0, 0],
)

Watch a growing image folder#

Use Show2D.from_folder(...) when a microscope or reconstruction job writes new 2D images into one folder. Each newly readable file becomes another panel in the existing gallery. The widget object is not rebuilt, so the current selection and the state of panels already present remain stable.

from quantem.widget import Show2D

w = Show2D.from_folder(
    "/data/session/haadf",
    pattern="*.tif",
    watch_interval=2.0,
    page_size=20,
    title="Live HAADF images",
)
w

watch=True is the default. Pass watch=False for a fixed folder or a reproducible script that should update only when you call poll_folder(). Folder galleries default to page_size=20: page controls appear automatically when the 21st ready image arrives. Use another positive integer to change the visible limit, or page_size=None to keep one unpaged gallery. The final page contains only real files, so 45 images form pages of 20, 20, and 5 panels. Folder pages keep selection, stars, hidden state, and ordering keyed to each source file rather than sharing a hidden slot across pages.

Change the grouping later without rebuilding the widget:

w.set_folder_page_size(50)   # regroup the same source panels
w.set_folder_page_size(None) # show one unpaged gallery

Paging limits the panels, histograms, and FFT views rendered at one time. The current folder implementation still retains the complete full-resolution gallery in Python and transports its display previews to the browser; it is not yet a lazy, bounded-memory source-page cache. Use ShowFolder for lightweight thumbnail discovery when the full scientific arrays need not all be opened.

An empty watched folder remains mounted with a waiting view and becomes the real gallery in the same widget model when its first stable file arrives. The compact title-area badge reports Watching, Updating, Waiting for file completion, Watch error, or Stopped; fixed watch=False snapshots do not show the badge.

new_panels = 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. A file already represented in the gallery is not duplicated, an incomplete file is deferred until a later poll, and removing or rewriting a source file does not silently remove or replace an existing panel. An incompatible shape is reported without blocking a later compatible file. Close long-running widgets when the notebook no longer needs them.

Show2D.from_folder(...) reads the scientific image data at its source resolution. It is different from ShowFolder, which intentionally uses small cached thumbnails for fast folder discovery and selection. Use ShowFolder to decide what to open; use Show2D.from_folder(...) when the displayed pixel data and live panel append behavior matter.

Maintainer real-time signoff follows S2D-18: add genuine EMD files after the widget is visibly mounted and verify the same browser canvas paints each full-resolution panel.

Paged galleries#

Direct Show2D(...) pages are comparison pages. Use them when each view contains the same panel grid across several analysis settings, iterations, or parameter values. A common example is a 4-by-4 reconstruction sweep where each page is one iteration or one denoising parameter, and the panels within the page are the related output images.

These repeated-slot comparison pages are distinct from the sequential item pages created by Show2D.from_folder(..., page_size=20).

Pass a 4D array with shape (pages, panels_per_page, rows, cols):

from quantem.widget import Show2D

w = Show2D(
    reconstruction_sweep,
    labels=[
        "lambda 0.01", "lambda 0.03", "lambda 0.10", "lambda 0.30",
        "lambda 1", "lambda 3", "lambda 10", "lambda 30",
        "raw", "filtered", "residual", "score",
        "phase", "amplitude", "mask", "diagnostic",
    ],
    page_labels=["iteration 10", "iteration 20", "iteration 30"],
    ncols=4,
)
w

You can also pass explicit page dictionaries when the page titles naturally belong beside the image data:

w = Show2D(
    [
        {"title": "iteration 10", "images": iter10_panels, "labels": panel_labels},
        {"title": "iteration 20", "images": iter20_panels, "labels": panel_labels},
    ],
    ncols=4,
)

Paged galleries keep the Python data model simple: all pages use the same panel count and image shape, the browser renders only the active page, and page stars are stored separately from panel stars. Use star_page(page) and unstar_page(page) from Python, or the star button beside the page slider in the widget. The page row also includes play/pause and a small FPS menu so readers can step through iteration or parameter pages without touching the main image controls. Manual slider scrubbing pauses page playback. For very large 4K sweeps, start with a reduced or representative stack; a future lazy page transport can avoid sending every page to the browser at once.

set_image() is the re-render trigger. Mutating the original NumPy array in place does not notify the frontend. The method sends fresh synced frame_bytes and resets state tied to the old image count or dimensions, including stale hidden panels, stars, view box, ROIs, line profiles, detail tiles, and panel order. Use offline=False for acquisition-style updates so the live Jupyter Comm path carries each new frame instead of the saved/offline notebook path.