Performance UI Testing#
Use this protocol when a change can affect load speed, FPS, large-data interaction, notebook save/reopen, or export size. It is intentionally separate from the storyboards: storyboards describe scientific behavior; this page defines the real-data performance gate that agents must measure.
The default production topology is an HPC/workstation with an NVIDIA GPU or other lab compute backend and the local browser as the frontend. Python/Jupyter owns file I/O, large arrays, CUDA/MPS/CPU preprocessing, and export packing. The browser owns canvas drawing, WebGPU, pointer events, playback, menus, and exported HTML. Reports must not mix those timings.
Maintainer Map#
Use this table when you only need the high-level view of what each performance or visual automation file proves. The point is to keep routine CI small, while still having stronger browser, real-data, phone, and release gates when the change needs them.
File |
What it proves |
Main checks |
When to run |
Output |
|---|---|---|---|---|
|
The repository is generally ready. |
Size guards, frontend build, Python tests, HTML export smoke, optional browser/mobile/performance gates; when |
Default before saying a widget change is ready; |
Top-level |
|
The generated evidence can be reviewed from one page. |
Pass/fail/skipped gate summary, failed gate list, next recommended runs, extracted FPS/load/export metrics, and links to raw reports. |
Runs through local signoff/CI; rerun manually after dropping additional reports into the same artifact directory. |
Top-level |
|
CI can repeat the normal local signoff on clean Linux. |
Same default local signoff path: build, tests, export smoke, docs build when not quick. |
Automatically on PRs and pushes touching widget code/docs/tests. |
GitHub Actions logs. |
|
Every export-capable widget can write standalone HTML with state. |
|
CI/default signoff; update when a widget gains or changes HTML export. |
|
|
ShowFolder live-folder handoff stays centralized and functional. |
Activates simultaneous all-image Show2D/Show3D through the public dual-view path, adds image files and |
CI/default signoff; update when ShowFolder watcher or selection handoff changes. |
|
|
Show3D GIF exports are presentation-ready. |
Dry-run size plan, multi-panel low/medium/high GIF previews, panel-gap control, live-style labels, scale bar, zoom readout, export seconds, file sizes, dimensions, frame count, frame-delta metric, optional local Caitlyn time-series source. |
When GIF/MP4 animation export, PowerPoint sharing, or Show3D movie quality changes. |
|
|
Exported HTML actually renders and responds in Chromium. |
Nonblank canvases, wheel/drag interaction, switches, sliders, console/page/HTTP errors, |
|
|
|
Backend export packing and small real-data Show2D/Show3D payloads are measurable. |
Real-data discovery, export time, output size, browser-drive plan. |
|
|
|
A standalone exported HTML report that already exists outside the repo remains interactive and fast. |
Opens a provided URL, checks nonblank canvases, samples FPS, drives common Show3D page/play/hide controls when present, captures screenshots and console/page errors. |
Local-only review of Tailscale-served or hosted real-data reports, especially when a user points to an existing exported HTML file. |
|
|
Heavy Show2D/Show3D real-data browser performance is acceptable on lab data. |
Local real-data discovery, paged heavy exports, browser FPS, nonblank render, screenshots, page-scrub latency, hidden-panel persistence across page swaps, Show3D offline frame-cache/prewarm counters, Show3D FFT overlay idle-cache guard, and FFT metric stats-toggle cache guard. |
Local-only HPC/workstation performance claims; never normal CI. |
|
|
Heavy Show4DSTEM real-data loading, NVIDIA/CUDA backend memory, append/stack-growth, export, and browser interaction are acceptable on lab data. |
Local 4D-STEM master discovery, CUDA first-load timing, backend memory report, append/stack-growth timing, dataset/frame flip FPS, virtual-detector drag FPS, scan-position FPS, browser WebGPU/backend split, GPU memory before/after. |
Local-only Show4DSTEM performance claims; never normal CI. |
|
|
A human can verify physical phone Safari behavior with shared logs. |
Serves report on |
Physical iPhone/iPad checks after browser smoke, especially WebGPU or touch changes. |
Served report, |
|
Visual stories can be driven in Jupyter/browser before release. |
Story-oriented widget drive packets, screenshots, selected release gates. |
Broad UI or release-candidate work when a human/agent must drive real workflows. |
Signoff packet/report under |
|
Agent-driven story redrive is structured and auditable. |
Story IDs, issue observed, fix made, redriven evidence. |
Before release candidates or after interaction-heavy fixes. |
Agent signoff report. |
|
The package can ship. |
TypeScript typecheck/tests/build, standalone/offline browser build, Python compile, wheel build/content check. |
Before tagging |
Console logs and local |
|
Tutorial data loaders work without heavy network downloads in CI. |
Tiny monkeypatched Show2D/Show3D/Show4DSTEM datasets, ShowFolder fallback. |
Normal pytest/CI. |
Pytest result. |
|
Notebook state stays compact and visible instead of embedding huge arrays. |
Saved widget MIME output, screenshot fallback, export state size behavior. |
Normal pytest; important after save/export changes. |
Pytest result. |
|
Live Jupyter widgets can render nonblank canvases in browser-driven tests. |
Jupyter display, nonblank canvas, browser interaction/FPS when enabled. |
Opt-in browser/Jupyter visual verification. |
Pytest/browser artifacts. |
Read the table left to right: unit tests and HTML export smoke protect protocol; browser smoke protects rendered exported HTML; heavy signoff protects real-data performance; phone handoff protects physical Safari/touch behavior; release check protects packaging. Do not replace a stronger gate with a weaker one when the user story depends on the stronger surface.
The Five Gates#
1. Real Dataset Matrix#
Do not sign off performance with synthetic data alone. Use real or real-derived data and record the exact path, shape, dtype, native bytes, and backend host.
Preferred heavy datasets on the lab machines:
Gate |
Widget |
Minimum real-data target |
Preferred source |
|---|---|---|---|
PUI-2D-4K |
Show2D |
8 panels, 4096 x 4096 |
Drift/denoise/ptycho real-space outputs on an HPC/workstation with NVIDIA GPU |
PUI-2D-BATCH |
Show2D |
30 panels, 4096 x 4096 |
High-throughput denoise or drift batch |
PUI-2D-STRESS |
Show2D |
45 to 85 panels, 4096 x 4096 |
Optional stress pass when backend memory allows |
PUI-3D-SINGLE |
Show3D |
1 panel, at least 512 x 512 x 100 frames |
Real time series, focal stack, or SSB iteration stack |
PUI-3D-MULTI |
Show3D |
12 panels x 32 frames x 2048 x 2048 source |
Real-derived drift/ptycho/gold stack |
PUI-3D-EXPORT |
Show3D |
same as PUI-3D-MULTI |
Exact/quantized/binned HTML export paths |
PUI-EDS |
ShowEDS |
native sparse EDS stream, no hidden crop/bin |
DGGG 0039 or equivalent Velox EDS stream |
PUI-4DSTEM |
Show4DSTEM |
real scan with diffraction and virtual images |
4D-STEM tutorial or paper data on an HPC/workstation or hosted dataset |
PUI-4DSTEM-NOBIN |
Show4DSTEM |
30-40 ready real masters at |
Private lab 4D-STEM masters on an NVIDIA workstation; never commit data or reports |
PUI-FOLDER |
ShowFolder |
folder with many microscopy files |
Real screening folder with cache reuse |
If a preferred source is unavailable, use the closest local real source and
mark the report PASS WITH LIMITATION. The report must say why the preferred
source was not used.
2. Backend And Frontend Topology#
Every run must identify both sides:
Backend: host, conda/uv environment,
quantem.widgetcommit, Jupyter URL, data path, data shape, data construction time, export packing time.Frontend: Mac/browser, WebGPU adapter when relevant, page URL, viewport size, first visible paint, console errors, and whether the Jupyter kernel becomes busy during pointer-only interaction.
The normal heavy signoff is:
Build or install the widget package on the HPC/workstation backend.
Launch Jupyter on the HPC/workstation backend or the active local workstation.
Open the notebook or exported HTML from the Mac in the Codex in-app browser.
Drive the real UI from the Mac.
Store screenshots, timing JSON, and the final report outside the repo unless the user explicitly asks to commit them.
Real-data performance signoff is local-only. Do not upload lab workstation data, absolute local paths, screenshots of private data, or heavy generated HTML to GitHub. Normal CI must not depend on local lab data. CI owns lightweight protocol and synthetic smoke coverage; the local heavy gate owns real data and browser performance proof.
Standalone exported HTML is a separate surface. It must be tested in the browser without assuming a Python kernel exists.
Rendered tutorial HTML is also a separate surface. A screenshot is not enough: for interactive tutorials, execute the notebook, open the rendered HTML in a browser, and drive the widget. For the Show4DSTEM tutorial, use:
PYTHONPATH=src:. python scripts/widget_tutorial_interactivity_smoke.py \
docs/tutorials/show4dstem.ipynb \
--artifact-dir /tmp/quantem-widget-tutorial-interactivity-smoke
The smoke fails if the rendered multi-panel widget does not update its
DP at (...) coordinate after a real drag. Keep these rendered HTML files and
screenshots under /tmp unless a sanitized artifact is explicitly requested.
When the real-data export already exists, profile the URL directly instead of regenerating data:
PYTHONPATH=src:. python scripts/widget_external_html_profile.py \
--url http://127.0.0.1:8779/path/to/exported-widget.html \
--artifact-dir /tmp/quantem-widget-external-html-profile
Use this for Tailscale-served reports, overnight studies, and other external HTML evidence. The report is local-only by default; do not commit private screenshots, generated HTML, or metrics containing private paths unless the user explicitly asks for a sanitized artifact.
3. Browser FPS And Latency Measurements#
Measure the interactions users feel. Unit tests and Python timers are not enough.
Show2D, Show3D, and Show4DSTEM support an internal debug overlay with
debug=True. It shows a compact Debug UI FPS badge in the widget title row
and is meant for live diagnosis in Jupyter and exported HTML. Agents should
turn it on when they need a quick, visible performance signal while driving a
widget:
Show3D(stack, debug=True)
Keep it off by default, keep the sampler browser-local, and do not update Python traits from the animation loop. The badge is one common metric: browser paint-loop responsiveness. It does not measure Python load, GPU kernels, FFT work, data decode, export packing, or cache behavior. A performance claim still needs browser smoke, external HTML profile, or heavy signoff evidence with the report path recorded in the final handoff.
Debug should scale as a telemetry protocol, not as one global number. New widget-specific debug metrics should fit one of these buckets:
UI: paint-loop FPS, dropped-frame count, pointer-to-preview latency.
Decode: bytes read, transfer/decode time, frame fetch time, cache hit/miss.
Draw: colormap, histogram, canvas/WebGL/WebGPU draw, overlay/layout time.
Compute: FFT, virtual detector, EDS map/spectrum, Show1D profile, or other scientific computation time; separate CPU worker and GPU/WebGPU paths.
Cache and memory: prewarm status, cache size, hit/miss counters, GPU memory label when available.
Export/notebook: export packing time, HTML size, saved-output preview format, and notebook state size when relevant.
Expose these metrics in stable browser-local debug objects so agents can read
them without scraping text. Existing examples include
window.__quantemShow3DPerf, window.__quantemShowEDSPerf, and
window.__quantemShow3DSlicesPerf. Keep payloads small: latest value, rolling
average or last N samples, max, and counters are enough. The visible HUD should
show only the highest-signal summary; detailed values belong in the browser
object and the generated report.
FFT caching is part of the interaction contract for Show2D and Show3D. The
first FFT for a frame, panel set, ROI, and windowing state can be slow, but
returning to that same scientific input must reuse the cached magnitude so
slider scrub, page playback, zoom, pan, and metric labels stay real-time. Cache
keys should describe the FFT input (frame, data version, dimensions, visible
panels, layout, ROI, and windowing) and must not include delivery counters such
as traitlet frame-byte sequence numbers. Data-source changes should invalidate
the cache explicitly.
For Show2D, drive and record:
first paint for PUI-2D-4K and PUI-2D-BATCH,
wheel zoom and drag pan at native/detail zoom,
histogram min/max drag and center drag,
linked zoom/pan/contrast across panels,
per-panel colormaps: pass a list of cmaps at construction, select/hover each panel, change one Color control, and verify saved state plus standalone HTML preserve independent panel maps,
column reflow through 1, 2, 4, 6, 8, and 12 columns,
paged panel sweeps: dragging or keyboard-stepping the Page slider should update the visible page from local browser state immediately, then commit the trait on the next animation frame,
hidden panels in paged views should be page-slot based, so hiding panel slot 2 keeps slot 2 hidden after moving to another page,
FFT toggle, FFT pan/zoom, and FFT reflow,
narrow mobile control wrapping: label/control pairs stay grouped for
Auto,Smooth,Zoom,Pan,Contrast, and FFT controls, with no overlap or orphaned switch/menu controls,panel hide/restore,
export menu open and exported HTML reopen,
Cmd+Ssave/reopen with compact visible output.
For Show3D, drive and record:
first paint for PUI-3D-SINGLE and PUI-3D-MULTI,
play at 30 FPS and verify image, frame label, histogram, and slider stay synchronized,
high-FPS playback stress and slider lag,
frame slider scrub, keyboard frame step, loop, bounce, and averaging,
remote Jupyter tunnel scrub for S3D-20: measure Python prepare/wire/encode/trait-set, browser receive/decode/paint, and UI latency over the real laptop-to-backend ssh tunnel; verify any drag-time preview announces its factor and that release restores native full-resolution pixels,
paged panel sweeps: Page slider scrubs and Page play should not wait for a Python trait round trip before the rendered page changes,
hidden panels in paged views should be page-slot based and must remain hidden after Page slider scrubs, Page play, and page-label changes,
wheel zoom and drag pan with linked zoom on/off,
per-panel colormaps: pass a list of cmaps at construction, hover/select each panel, change one Color control, scrub frames, and verify saved state plus standalone HTML preserve independent panel maps without recoloring neighbors,
column reflow through 1, 2, 4, 6, 8, and 12 columns,
FFT bottom/right/overlay layouts,
FFT overlay drag, corner snap, independent zoom, and pan,
FFT cache behavior during scroll, resize, playback, and frame scrub,
FFT metric labels while toggling Stats, Profile, page playback, and panel visibility. These UI controls must not recompute FFTs or metric summaries unless the displayed scientific input changed,
narrow mobile control wrapping: label/control pairs stay grouped for
Scale,Color,Auto,Smooth,Diff,fps,avg,Loop,Bounce, FFT controls, and any page/column controls, with no overlap or orphaned switch/menu controls,export exact, quantized, GIF, MP4, and binned quantized HTML where supported,
Cmd+Ssave/reopen with compact visible output.
Targets:
First useful paint: about 1 s for normal data; within a few seconds for heavy data. Over 10 s is a release blocker unless explicitly accepted.
Pointer interactions: target 30 FPS or better. If a path cannot hit 30 FPS, the report must say which interaction, dataset, browser, and likely cause.
Playback/sliders: target 30 FPS for heavy practical views. Slider and image must stay synchronized at the selected FPS.
Remote tunnel scrub: target immediate visual feedback during active drag. Native pixels must remain available after release or zoom/detail inspection. If a reduced preview is used during drag, the report must include the factor, preview bytes, native bytes, announcement text, and native-restoration check.
Page sliders: target a visible page update within one animation frame. In heavy local signoff, page-scrub latency above 500 ms is treated as a failure unless the report documents a hardware or browser limitation.
Mobile controls: labels and their switches, menus, sliders, or buttons must wrap as grouped pairs. A dense row may wrap onto another line, but it must not separate the label from the control it names.
Export: menu labels must say the format and size class. Heavy standalone exports should offer compact choices rather than silently creating unusable hundreds-of-MB HTML.
Save/reopen: notebook output must be visible after reload and must not embed heavy frame/detail/export buffers when compact state is requested.
4. Native Pixel And Preview Contract#
Load fast first, then expose the highest resolution the widget contract supports.
Show2D:
display_bin="auto"may show a fast preview first.Native arrays stay on the backend.
Zoomed inspection should stream a visible native/detail tile when available.
Cursor readout reports native
(row, col)and labels whether the value came from preview/detail/native data.Stale detail tiles must never draw after the view changes.
Show3D:
Current heavy Show3D single-file export does not stream native detail tiles like Show2D.
A Show3D folder export can preserve the native panel shape while keeping the large frame stack beside the HTML. Treat this as the full-shape browser review path, not as exact float32 source preservation unless the encoding says so.
A binned/downsampled Show3D HTML export is a compact visual report, not a promise that zooming returns exact native pixels.
If exact backend arrays, fresh computation, or non-exported precision are required, test a live workflow or focused view that can afford the native transfer.
Any display bin, downsample, or export encoding must be explicit in the UI, report, and final handoff.
Show4DSTEM and ShowEDS:
Native data should stay queryable or sparse where possible.
Multi-master Show4DSTEM sessions must load quickly enough to become useful, then let the user flip through loaded datasets/frames from the browser at the target FPS. Timing first load without testing the dataset slider is not a complete signoff.
A 30-40 master no-bin Show4DSTEM request is a memory-capacity gate first and a browser FPS gate only if the backend can actually keep that many masters available. Reports must state the per-master bytes, maximum loaded count, devices used, append failure if any, and cleanup result. Do not replace this with MPS when the requested backend is NVIDIA/CUDA.
U8 no-bin browse timing and full Show4DSTEM UI signoff are different claims. It is valid to benchmark the direct HDF5 loader with
scripts/widget_load_bench_matrix.pyorscripts/widget_load_bench_sharded.py, but a release claim still needs the real Show4DSTEM widget/export/browser path. Ifimport quantem.widgetfails in the workstation environment, report the UI gate as blocked even when the direct HDF5 loader benchmarks pass.Multi-disk loading must be proven on a real multi-disk layout. A report where
group_by_disk(masters)is{'nvme2n1': N}validates GPU sharding and capacity, but it does not validate aggregate disk bandwidth.Browser interaction should not require a Python round trip during drag unless the report calls out the limitation.
WebGPU/MPS/CUDA usage must be recorded by surface: browser WebGPU is not the same as backend MPS/CUDA preprocessing.
5. Report And Release Decision#
Every performance UI run writes a short report with this format:
Performance UI report
Backend:
- Host:
- quantem.widget commit:
- Jupyter URL:
- Data path:
- Shape/dtype/native bytes:
- Backend build/export timings:
Frontend:
- Browser:
- URL/export file:
- Viewport:
- WebGPU adapter:
- Console errors:
Measured gates:
- First paint:
- Zoom/pan FPS:
- Histogram drag FPS:
- Slider/playback FPS:
- FFT FPS/cache notes:
- Export size/time/reopen:
- Save/reopen:
Result:
- PASS / PASS WITH LIMITATION / BLOCKED
- Story IDs covered:
- Screenshots/videos:
- Remaining risk:
For any automated run that writes multiple reports, open the top-level everything dashboard first. It is the decision surface: failed gates, skipped stronger gates, extracted metrics, and links to raw artifacts. The raw report JSON remains the source of truth for exact numbers, but the dashboard is where a human or agent should decide whether the claim is adequately proven.
Release rule:
PASS: all affected gates were driven after the last code change.PASS WITH LIMITATION: the limitation is specific, documented, and accepted.BLOCKED: first paint is too slow, 30 FPS interaction fails on practical heavy data, export produces an unusable artifact without a compact option, saved notebook output is blank, FFT is incorrect, or the test used synthetic data for a real-data claim.
Performance Evidence Registry#
Do not commit raw heavy reports by default. Keep generated HTML, screenshots,
private file paths, timing JSON, and local browser artifacts in /tmp or CI
artifacts. Commit only distilled metadata when the result is useful as a future
baseline or protocol example.
Each evidence row should record:
date and purpose,
widget and scenario,
dataset alias, not private path,
shape, dtype, and native size,
export mode, exported size, and export time,
browser surface, FPS, and key interaction checks,
cache counters or correctness checks that mattered,
command family and local report path pattern,
result and limitation.
Use three dataset tiers:
Tier |
Purpose |
Storage policy |
|---|---|---|
CI tiny |
Protocol coverage, export matrix, nonblank canvas, API behavior. |
Commit only tiny generated fixtures or create them at test time. |
Public real |
Reproducible docs/tutorial/performance examples. |
Prefer hosted datasets or small tracked examples when clone size stays reasonable. |
Private heavy |
Release-grade workstation/HPC proof. |
Keep data, reports, screenshots, and private paths out of git; commit only alias-level summary. |
Current distilled evidence:
Date |
Evidence ID |
Widget/scenario |
Dataset alias |
Shape/native size |
Export |
Browser/cache result |
Command family |
Result |
|---|---|---|---|---|---|---|---|---|
2026-07-07 |
|
Show3D overlay FFT metric label, Stats toggle cache guard |
|
3 panels x 16 frames x 384 x 384, 27.0 MB native float32 |
|
Browser smoke 121.3 FPS; FFT idle 120.7 FPS; idle FFT recompute growth 0; Stats-toggle FFT recompute growth 0; Stats-toggle FFT metric growth 0; label stayed |
|
PASS as quick automation evidence; not a full release-grade heavy baseline because it used |
2026-07-07 |
|
Show2D/Show3D heavy export, browser render, Show3D overlay FFT metric label, Stats toggle cache guard |
|
Show2D: 8 panels x 4096 x 4096, 512 MB native float32; Show3D: 12 panels x 32 frames x 2048 x 2048, 6144 MB native float32 |
Show2D |
Browser smoke: Show2D |
|
PASS as local full heavy signoff evidence; raw report, screenshots, and private paths remain outside git. |
Keep quick rows only as automation debugging examples. A release or performance claim should reference the strongest matching full local heavy signoff row.
Minimal Agent Run#
For the common Show2D + Show3D heavy gate, an agent should do the following:
Start from
main, rebuild the frontend, and launch the HPC/workstation Jupyter backend with the patched checkout first onPYTHONPATH.Run
PYTHONPATH=src:. python scripts/widget_performance_smoke.pywith real-data size options appropriate for the release gate. The script writes standalone exports,index.html,report.json, andbrowser-plan.json.Serve the artifact directory and open the generated pages in the in-app browser.
Drive the in-app browser through all controls listed in Gate 3, including repeated fast gestures.
Save screenshots and browser timing JSON under
/tmp/quantem-widget-perf-ui.Run
PYTHONPATH=src pytest -qandnpm run build, then attach the browser report before claiming the release is ready.
The browser report is the proof. Passing tests without a browser report is
Not verified for UI performance.
Local Heavy Signoff Command#
Use this command on the lab workstation that can see the real data. It is the preferred one-command signoff when a change touches Show2D/Show3D heavy rendering, FFT overlays, exported HTML performance, or browser interaction latency:
PYTHONPATH=src:. python scripts/widget_heavy_perf_signoff.py
By default it writes to:
/tmp/quantem-widget-heavy-signoff/<timestamp>/
The heavy signoff:
discovers local real microscopy images from common HPC/workstation data roots,
builds paged Show2D real 4K exports and a paged Show3D real-derived heavy FFT overlay export,
runs
scripts/widget_browser_smoke.pyagainst the generated standalone HTML,checks browser FPS against the configured threshold,
checks that paged Show2D and Show3D keep hidden-panel state after page scrubs,
checks that standalone Show3D exports expose bounded offline frame-cache and prewarm counters in
window.__quantemShow3DPerf,checks that Show3D FFT cache counters do not grow while the page is idle,
checks that a new Show3D FFT frame increments misses/computes once, then a return scrub to a previously computed frame increments hits while misses and computes stay unchanged,
checks that Show3D FFT and FFT metric counters do not grow while toggling the Stats UI. Stats are display chrome; they must not invalidate cached FFT magnitudes or metric summaries,
saves screenshots, command logs,
browser-smoke-report.json, andheavy-signoff-report.json,writes
index.htmlas the visual handoff report.
Use explicit roots when the default local data paths are not the active dataset:
PYTHONPATH=src:. python scripts/widget_heavy_perf_signoff.py \
--search-root /path/to/local/real/microscopy/data
For repeated lab runs, set QUANTEM_WIDGET_REAL_DATA_ROOTS to one or more
local data roots separated by the platform path separator, then run the same
signoff command without hardcoding private paths in the repository.
Use --quick only while debugging the automation itself. A release or
performance claim needs the full local real-data run. Use --skip-browser only
to debug export generation; it intentionally reports that UI performance was
not fully verified.
This script is intentionally excluded from normal CI because it requires local
real data and can produce large private artifacts. Keep those artifacts under
/tmp or another ignored local directory.
Heavy Compute UI Protocol#
Use this protocol before adding any expensive browser-side computation such as FFT metrics, peak finding, segmentation overlays, or live denoise previews. The goal is to keep scientific feedback visible without dropping browser FPS.
Separate the work into four phases:
Scientific input: frame index, visible panel IDs, FFT window, ROI, sampling, units, display bin, and data version. These are the only values that should invalidate the expensive cache.
Expensive compute: FFT, peak finding, metric summaries, or GPU kernels. Cache these by a stable key derived from the scientific input. Do not put
show_stats, labels, toolbar visibility, hover text, or layout chrome into that key unless they change the math.Display transform: colormap, log/linear view, clipping, and annotations. This should reuse the cached numeric result whenever possible.
Pointer UI: hover labels, sliders, drag overlays, and toolbar state. Keep this path cheap: use refs,
requestAnimationFrame, stable DOM nodes, and opacity/transform updates instead of recomputing arrays or forcing large React rerenders on every pointer event.
Paged Show2D/Show3D sliders are in the pointer/UI layer. Render the requested
page from local browser state immediately, then batch the synced trait write
with requestAnimationFrame. Hidden panels in paged viewers are layout slots,
not one absolute source index, so a hidden slot remains hidden as the page
changes. Standalone Show3D HTML should render the current page/frame first,
then prewarm neighboring frames and later frames in the background using a
bounded cache with debug counters.
Every heavy compute feature needs both math proof and cache proof:
Math proof: compare against NumPy, PyTorch, or another trusted reference on a deterministic microscopy-like fixture. For FFT metrics, the parity test is
js/fftMetrics.numpy.test.ts.Cache proof: expose local debug counters for expensive work, for example
fftComputes,fftMetricComputes,lastFftMetricMs, and the cache key. The browser signoff must assert those counters do not grow during cosmetic UI changes such as Stats toggles.Visual proof: open the rendered HTML or notebook in the browser, confirm the canvas is nonblank, and capture a report screenshot with the label/overlay visible.
Optimization order:
Cache first. Reuse display-sized arrays and cached FFT magnitudes before moving work to another thread or GPU path.
Downsample or summarize for labels. A quality label should not scan a full native 4K frame on every redraw when a sampled cached magnitude is enough.
Move real heavy kernels to WebGPU or a worker only when cached CPU work still shows up in the browser report.
Keep microbenchmarks local-only. Use them to compare algorithm cost, but do not make normal CI depend on workstation hardware, private data, or browser timing variance.
For the current FFT metric label, the accepted behavior is:
Compute the label from the cached FFT magnitude, not from a second FFT.
Recompute only when the FFT magnitude version, FFT crop dimensions, sampling, units, or visible FFT panel grid changes.
Do not recompute when users toggle Stats, Profile, panel menu visibility, page playback controls, or other toolbar chrome.
Verify the math with
npm test -- --run js/fftMetrics.numpy.test.ts js/fftMetrics.test.ts.Verify the browser cache behavior with
scripts/widget_heavy_perf_signoff.py; the report must includeshow3d_fft_stats_togglewith zero FFT and zero FFT metric growth.
Local Show4DSTEM Heavy Signoff#
Use this command when a change touches Show4DSTEM loading, chunking, lazy multi-master append, detector interaction, scan-position browsing, WebGPU browser drawing, or standalone HTML export:
PYTHONPATH=src:. python scripts/widget_show4dstem_heavy_signoff.py \
--search-root /path/to/local/real/4dstem/data \
--backend cuda \
--max-masters 2 \
--det-bin 4 \
--export-det-bin 4 \
--min-fps 30
By default it writes to:
/tmp/quantem-widget-show4dstem-heavy-signoff/<timestamp>/
The Show4DSTEM signoff:
discovers local ready
*_master.h5files without committing those paths,measures CUDA first-master load time and widget build time on NVIDIA backends,
records backend shape, dtype, device, resident memory, and memory before/after, and Python/GPU memory before and after each stage,
measures additional masters through the active backend’s append strategy: CUDA records eager stack-growth/reload timing, while MPS records live lazy append timing,
exports standalone Show4DSTEM HTML with explicit
uint8/uint16and detector binning labels,opens the export in Chromium, records browser WebGPU adapter information, and measures virtual-detector drag FPS, scan-position movement FPS, wheel-zoom FPS, and recompute latency,
writes
show4dstem-heavy-signoff-report.jsonandindex.html.
Run two Show4DSTEM modes when memory allows:
# Practical browse path: detector-binned live data on NVIDIA/CUDA.
PYTHONPATH=src:. python scripts/widget_show4dstem_heavy_signoff.py \
--search-root /path/to/local/real/4dstem/data \
--backend cuda \
--max-masters 2 \
--det-bin 4 \
--export-det-bin 4 \
--min-fps 30
# No-bin backend path: full detector data in NVIDIA memory, compact export for sharing.
PYTHONPATH=src:. python scripts/widget_show4dstem_heavy_signoff.py \
--search-root /path/to/local/real/4dstem/data \
--backend cuda \
--max-masters 1 \
--det-bin 1 \
--export-det-bin 8 \
--min-fps 30
The no-bin pass is important because it exposes real resident memory pressure, full-detector backend behavior, and virtual-detector latency. Use a compact export bin for that pass unless the explicit goal is to measure a large private standalone HTML payload.
Use --backend mps only for local MacBook fallback checks. It is not the
primary heavy signoff when an NVIDIA backend is available.
For the high-risk capacity test the user cares about, run the backend-only stress first so a too-large request fails cleanly and releases memory:
PYTHONPATH=src:. python scripts/widget_show4dstem_heavy_signoff.py \
--search-root /path/to/local/real/4dstem/data \
--backend cuda \
--devices 0,1 \
--max-masters 30 \
--det-bin 1 \
--export-det-bin 8 \
--skip-browser \
--min-fps 30
This intentionally records CUDA memory before load, after load or OOM, and
after free_gpu() cleanup. A 20-30 file no-bin stack can exceed even a
two-GPU workstation because a single 512 x 512 x 192 x 192 uint16 master is
about 18 GiB resident before transient decompression overhead. If this stress
does not fit, the report should say where it failed and prove GPU memory was
returned before the next run. After the capacity pass, run a smaller no-bin
browser pass without --skip-browser to verify user interaction remains smooth.
Use QUANTEM_WIDGET_4DSTEM_ROOTS or QUANTEM_WIDGET_REAL_DATA_ROOTS to avoid
hardcoding private data roots in commands. Use --quick only while iterating on
the signoff script itself. Use --skip-browser only for backend/export
debugging; it intentionally reports that UI performance was not fully verified.