diff --git a/README.md b/README.md index 25d03cd0..c429e190 100644 --- a/README.md +++ b/README.md @@ -2,279 +2,78 @@ [![TestPyPI](https://img.shields.io/pypi/v/quantem-widget?pypiBaseUrl=https://test.pypi.org&label=TestPyPI)](https://test.pypi.org/project/quantem-widget/) -Interactive WebGPU visualization widgets for 4D-STEM and electron microscopy - in -Jupyter, or straight from the [command line](#command-line). Works with NumPy, PyTorch, -or CuPy arrays. +Interactive, GPU-accelerated visualization widgets for 4D-STEM and electron +microscopy. Use them in Jupyter or open data directly from the command line; +NumPy, PyTorch, CuPy, CUDA, Apple Silicon, and browser WebGPU workflows are +supported. -> Prototype on [TestPyPI](https://test.pypi.org/project/quantem-widget/). Built on +> `quantem.widget` is currently a prototype on +> [TestPyPI](https://test.pypi.org/project/quantem-widget/) and is built on the > [`quantem`](https://github.com/electronmicroscopy/quantem) core. ## Install ```bash -pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ quantem.widget +pip install -i https://test.pypi.org/simple/ \ + --extra-index-url https://pypi.org/simple/ quantem.widget ``` -Verify: +See the [installation guide](https://electronmicroscopy.github.io/quantem.widget/install.html) +for backend setup, Colab instructions, and verification. + +## Quick start + +Open an image or microscopy dataset without writing a notebook: ```bash -python -c "import quantem.widget; print(quantem.widget.__version__)" +quantem show image.tif +quantem show3d ./frames/ +quantem show4dstem ./masters/ ``` -## Widgets - -| Widget | Input | Shows | -|---|---|---| -| `Show1D` | 1D trace / stack / live monitor with optional linked images | scientific traces, stats, jump markers, snapshots, playback, and live append | -| `Show2D` | 2D image or stack | image + contrast, FFT, line profiles, scale bar, display-side denoise, view-only low/high/band-pass frequency filters | -| `Show3D` | 3D stack | scrub / play through frames | -| `Show3DSlices` | 3D volume | orthogonal-slice viewer | -| `Show4DSTEM` | 4D-STEM array, or 5D stack | live virtual detectors (BF / ABF / ADF), CoM / iCoM / DPC, dataset slider + compare grid, offline WebGPU export | -| `ShowPtycho` | 4D-STEM master / SSB result | interactive SSB phase review: C10/C12/phi12/rotation sliders re-reconstruct in ~5-18 ms on-GPU; kernel-less WebGPU folder export for laptops | -| `ShowDiffraction` | 2D pattern or 3D stack | d-spacing, g-vector, and angle measurement on Bragg spots and rings | -| `ChooseLattice` | 2D image | ordered origin, a1, and a2 point selection for lattice-vector measurements | -| `ShowEDS` | EDS/EELS spectrum image | linked element map, spectrum, energy band, real-space ROI, and automatic element identification | -| `ShowFolder` | microscopy session folder | fast thumbnail browser, grouping, and file selection | +Or construct widgets directly in Python: ```python import numpy as np -from quantem.widget import ( - ChooseLattice, Show1D, Show2D, Show3D, Show3DSlices, - Show4DSTEM, ShowDiffraction, ShowEDS, ShowFolder, -) +from quantem.widget import Show2D, Show4DSTEM -ChooseLattice(np.random.rand(512, 512)) -Show1D(np.random.rand(100), x_label="frame", y_label="defocus", y_unit="nm") -Show2D(np.random.rand(512, 512)) -Show4DSTEM(np.random.rand(64, 64, 128, 128)) -ShowEDS(np.random.poisson(2, (64, 64, 256)).astype("uint16")) -ShowFolder("/data/session") +Show2D(np.random.random((512, 512))) +Show4DSTEM(np.random.random((64, 64, 128, 128))) ``` -## Load data +For real 4D-STEM data, load a master file onto the available GPU: ```python from quantem.gpu.io import load -from quantem.widget import ShowEDS, Show4DSTEM, load_eds - -data = load("scan_master.h5") # Arina 4D-STEM .h5 -> GPU -Show4DSTEM(data) - -eds = load_eds("spectrum_image.emd") # Velox/RSCIIO EDS/EELS -> energy-last SpectrumImage -ShowEDS(eds, energy=8.04, width=0.24) -``` - -For reconstruction or denoise pipelines that need a scan ROI rather than a -full field of view, `load("scan_master.h5", scan_region=(r0, r1, c0, c1))` -loads only that HDF5 scan patch to CUDA before downstream sampling. - -No data at hand? The tutorial datasets download and cache themselves: - -```python -from quantem.widget.datasets import show2d_gold, show4dstem_gold - -Show4DSTEM(show4dstem_gold()) -``` - -`quantem.widget.io` also provides `read_image`, `bin`, `download`, and more - -see the docs. - -## Command line - -Point `quantem` at a file or folder and it renders the right viewer - no notebook, no -Python. Installing the package adds the `quantem` command. - -```bash -quantem show ./anything/ # auto-detect content, pick the viewer -quantem show2d scan.png # an image -> Show2D -quantem show3d ./frames/ # a folder of frames -> Show3D scrub -quantem show2d ./frames/ --watch # live folder -> append new images -quantem show4dstem ./masters/ # *_master.h5 -> live Show4DSTEM -quantem show4dstem a_master.h5 b_master.h5 # several masters -> one 5D multi-tilt viewer -quantem show4dstem ./masters/ --html # 4D-STEM -> shareable offline HTML -quantem showptycho scan_master.h5 # raw 4D-STEM master -> user-owned review project -quantem showptycho ./ptycho-export/ # existing project -> WebGPU browser review -quantem showfolder ./session/ # microscopy folder -> ShowFolder notebook/HTML -quantem html tutorial.ipynb # a notebook -> standalone offline HTML -``` - -| Command | Input | Output | -|---|---|---| -| `quantem show ` | anything | auto-detects and dispatches to one of the below | -| `quantem show2d ` | one image, or a folder | Show2D HTML (a folder becomes a gallery); with `--watch`, a live ShowFolder notebook | -| `quantem show3d ` | a folder of same-size frames | Show3D scrub HTML; with `--watch`, a live ShowFolder notebook | -| `quantem show4dstem ` | one or more `*_master.h5` | live Show4DSTEM notebook (or `--html`) | -| `quantem showptycho ` | one or more `*_master.h5`, or an existing project | one index with direct ShowPtycho and Show4DSTEM browser viewers | -| `quantem showfolder ` | microscopy session folder | ShowFolder notebook (or `--html`) | -| `quantem html ` | a notebook you wrote | runs it, bakes outputs into one offline HTML | -| `quantem github ` | a notebook copy for GitHub | strips widget state, embeds compressed pictures for GitHub's preview | - -**Images** save a standalone HTML and open in your browser. **4D-STEM** opens a live, -kernel-backed notebook by default (full detector sampling and real-time interaction); -`--html` instead writes an offline WebGPU browser folder - drag detectors, switch -BF/ABF/ADF, pan diffraction, all with no kernel. Use `--bin N` only when you -explicitly want a detector-binned preview. Full-detector WebGPU exports keep the -compressed HDF5 files next to the viewer; open `index.html` and grant the data -folder, or double-click `Show4DSTEM.command` to serve the same folder locally. -Several masters (a folder, or listed explicitly) stack into one 5D viewer with a -dataset slider (the multi-tilt case). - -For live microscope sessions, keep the Show4DSTEM viewer mounted and append new -completed `*_master.h5` acquisitions into the same dataset slider. On Apple -Silicon, use `load_macbook_datasets(...)` and `live.watch_master_folder(...)`; -see the [Show4DSTEM API](docs/api/show4dstem.md#live-scope-folders) and -[Show4DSTEM storyboard](docs/maintainer/storyboard-show4dstem.md#s4d-14-append-live-scope-acquisitions). - -**Notebooks**: `quantem html notebook.ipynb` is the share path for a tutorial or report -you wrote. It runs every cell, then bakes the outputs (Show2D/Show3D widgets included, as -static images) into one self-contained HTML that opens in any browser with no Python or -kernel. Use `--no-execute` to wrap the already-saved outputs as-is. The command prints the -file size so you know how heavy the share artifact is. - -For GitHub notebook previews, make a copy and run -`quantem github notebook_github.ipynb --no-execute`. GitHub cannot run live widgets, so -this command keeps compressed pictures of each widget UI and removes heavy widget state. -See the HTML export docs for the widget capability table and folder-export guidance. - -Image and Show4DSTEM outputs land in `~/Downloads` by default. ShowPtycho -projects land in `~/QuantEM/showptycho/` so a shared or read-only -acquisition is never used as an implicit output directory. Pass `--out` for a -specific project location or `--in-place` to opt into -`SOURCE/quantem/showptycho`. - -| Option | Effect | -|---|---| -| `--bin N` | detector mean-bin factor for Show4DSTEM (default 1: full detector sampling); ShowPtycho always uses native detector sampling | -| `--dtype uint8/uint16` | 4D-STEM HTML export dtype; `uint8` is compact browse, `uint16` keeps the wider detector-count range | -| `--html` | 4D-STEM: write the offline-WebGPU HTML instead of a notebook | -| `--backend auto/cuda/mps/webgpu` | Show4DSTEM backend; use `webgpu` with `--html` | -| `--count N` | Show4DSTEM: require and load this many compatible masters from the input | -| `--devices 0,1` | Show4DSTEM CUDA placement; alias of `--gpus` | -| `--watch` | show2d/show3d/show4dstem folders: keep appending new files to a live notebook | -| `--combined` | many masters -> one 5D HTML viewer (served locally) | -| `--out PATH` | output file or directory (default `~/Downloads`) | -| `--no-open` | write the file(s) without launching a browser or Jupyter | -| `--title`, `-v/--verbose` | page title; verbose progress | - -Runs on CUDA, Apple Silicon (MPS), or browser WebGPU. On a MacBook, -`quantem show4dstem ./masters/ --backend webgpu --html --count 1` writes a -double-clickable HDF5-backed WebGPU browser folder without copying raw data. - -## Show4DSTEM export quick reference - -Most users want one of these paths: - -| Goal | Use this | Result | -|---|---|---| -| Keep working interactively in Python | `quantem show4dstem ./masters/` or `Show4DSTEM(load(...))` | Live notebook, kernel-backed CUDA/MPS interaction | -| Share a compact review result | `widget.export_html(..., export_kind="report")` | One self-contained HTML report; PNG virtual-image pages; no raw 4D payload | -| Share a small offline raw-4D widget | `widget.export_html(..., export_kind="interactive", dtype="uint8", scan_bin=2, det_bin=4)` | WebGPU HTML that can still drag detector ROIs, but embeds an explicitly reduced raw 4D payload | -| Open directly from the terminal | `quantem show4dstem ./masters/ --backend webgpu --html --count 1` | Browser WebGPU export built from source H5 masters | -| Open full native detector sampling from the terminal | `quantem show4dstem ./masters/ --backend webgpu --html --count 7 --bin 1 --dtype uint8` | No-notebook WebGPU export with native detector sampling | - -The default recommendation for large folders is a **report export** first: - -```python from quantem.widget import Show4DSTEM -viewer = Show4DSTEM.from_folder( - "/data/session", - gpus=[0, 1], - det_bin=1, - dtype="u8", - view_mode="multiple", - page_size=12, -) - -viewer.export_html( - "show4dstem_report.html", - export_kind="report", - dataset_scope="unhidden", # "current_page", "starred", or "all" also work - scan_bin=2, # real-space mean bin for smaller PNG pages - det_bin=8, # detector mean bin for representative DP thumbnails - dtype="uint8", -) +Show4DSTEM(load("scan_master.h5")) ``` -Use **interactive raw 4D export** only when the recipient must keep dragging the -detector ROI offline and the embedded raw payload is intentionally small enough -to share: +The [command-line guide](https://electronmicroscopy.github.io/quantem.widget/cli.html) +and [tutorials](https://electronmicroscopy.github.io/quantem.widget/tutorials/download_data.html) +cover data loading, HTML export, public example datasets, and complete workflows. -```python -viewer.export_html( - "show4dstem_interactive.html", - export_kind="interactive", - dtype="uint8", # use "uint16" only when the larger exact-count file is intended - scan_bin=2, - det_bin=4, -) -``` - -For users who do not want a notebook and want native detector sampling, use the -CLI full path: - -```bash -quantem show4dstem /data/session --backend webgpu --html --bin 1 --dtype uint16 --out ~/Downloads -``` - -Both `scan_bin` and `det_bin` are explicit mean-binning choices for the export. -They are not hidden performance shortcuts. See the LLM-friendly -[Show4DSTEM export recipes](docs/tutorials/show4dstem_export.md) and the -[Show4DSTEM API export section](docs/api/show4dstem.md#exporting-reports-and-raw-4d-viewers). - -## GPU SSB backend coverage - -SSB phase reconstruction runs on three backends. Headline numbers (real -512x512x192x192 experimental data, Apple M5 browser): one aberration-slider move -re-reconstructs the full 512x512 phase in **16 ms** in Exact mode (default) or -**13 ms** in Fast preview mode, at **2x / 4x less GPU memory** than the old -n x n implementation. Exact mode is **bit-exact** at every supported scan -size (128-1024); Fast preview stays below 0.2 % phase error up to 512 -(1.35 % at 1024 - preview quality). Details, full sweep table, and the port recipe: -[docs/maintainer/2026-07-16-showptycho-gqk-memory-modes.md](docs/maintainer/2026-07-16-showptycho-gqk-memory-modes.md) - -Implementation status: - -| Capability | WebGPU (browser / folder export) | CUDA (`quantem.gpu`) | MPS (`quantem.gpu`) | -|---|---|---|---| -| Exact `G(q,k)` storage — Hermitian half-plane, 2x less memory, bit-exact, faster | Done (default) | Todo | Todo | -| Fast preview `G(q,k)` storage — snorm16 half-plane, 4x less memory, ~1e-4 rad error | Done (opt-in `?gqk=preview` or `?gqk=herm16`) | Todo | Todo | -| VRAM budget clamp on BF count | Done (4.5 GB default, mode-aware; `__QUANTEM_SHOWPTYCHO_GQK_BUDGET_GB__` override) | n/a | Todo | -| Streamed initial build (bounded peak) | Todo | n/a | Todo | - -WebGPU scan-size coverage: - -| Scan size | Exact complex64 Hermitian `G(q,k)` | Fast preview `herm16` | Notes | -|---|---|---|---| -| 128x128 | Done | Done | Synthetic parity sweep passed; launch overhead dominates timing. | -| 256x256 | Done | Done | Synthetic parity sweep passed. | -| 512x512 | Done | Done | Real experimental parity and headed-browser timing measured. | -| 1024x1024 | Done | Done | Exact is bit-exact; Fast preview is preview-quality because measured error was larger than at 512. | - -Resident `G(q,k)` memory planning: - -These numbers are the resident reducer memory, not total browser or driver -memory. Peak first-load memory can be higher because temporary build chunks, -phase/loss images, and browser overhead also exist. `Active BF` means -nonzero-aperture BF pixels after the BF policy is applied. - -| Scan | Active BF | Typical use | Old n x n baseline (not runtime) | Exact default | Fast preview | -|---|---:|---|---:|---:|---:| -| 512x512 | 12 | Small smoke test | 25 MB | 13 MB | 6.3 MB | -| 512x512 | 408 | 0.30 BF preview in the reference report | 856 MB | 429 MB | 215 MB | -| 512x512 | 1360 | Full-BF estimate for the sparse experimental 512 dataset | 2.85 GB | 1.43 GB | 0.72 GB | -| 512x512 | 9070 | Dense experimental full active BF | 19.0 GB | 9.55 GB | 4.77 GB | -| 1024x1024 | 12 | Small smoke test | 101 MB | 50 MB | 25 MB | -| 1024x1024 | 1382 | Reference full active BF | 11.6 GB | 5.81 GB | 2.90 GB | -| 1024x1024 | 9070 | Workstation stress projection | 76.1 GB | 38.1 GB | 19.1 GB | - -## Docs - -https://electronmicroscopy.github.io/quantem.widget/ +## Widgets -The source tutorial notebooks live in [`docs/tutorials`](docs/tutorials). They -can be opened directly in Colab. To make a GitHub-readable preview copy, see -[`docs/github-preview.md`](docs/github-preview.md). +| Widget | Use it for | Learn more | +|---|---|---| +| `Show1D` | Scientific traces, reconstruction metrics, and live monitors | [API](https://electronmicroscopy.github.io/quantem.widget/api/show1d.html) | +| `Show2D` | Images, contrast, FFTs, ROIs, profiles, and scale bars | [tutorial](https://electronmicroscopy.github.io/quantem.widget/tutorials/show2d.html) · [API](https://electronmicroscopy.github.io/quantem.widget/api/show2d.html) | +| `Show3D` | Scrub and play through image or volume stacks | [tutorial](https://electronmicroscopy.github.io/quantem.widget/tutorials/show3d.html) · [API](https://electronmicroscopy.github.io/quantem.widget/api/show3d.html) | +| `Show3DSlices` | Inspect orthogonal slices through a 3D volume | [tutorial](https://electronmicroscopy.github.io/quantem.widget/tutorials/show3dslices.html) · [API](https://electronmicroscopy.github.io/quantem.widget/api/show3dslices.html) | +| `Show4DSTEM` | Live virtual detectors, multi-dataset review, and WebGPU HTML export | [tutorial](https://electronmicroscopy.github.io/quantem.widget/tutorials/show4dstem.html) · [export guide](https://electronmicroscopy.github.io/quantem.widget/tutorials/show4dstem_export.html) · [API](https://electronmicroscopy.github.io/quantem.widget/api/show4dstem.html) | +| `ShowPtycho` | Interactive SSB phase and aberration review | [API](https://electronmicroscopy.github.io/quantem.widget/api/showptycho.html) | +| `ShowDiffraction` | Measure diffraction spots, rings, spacing, and angles | [tutorial](https://electronmicroscopy.github.io/quantem.widget/tutorials/showdiffraction.html) · [API](https://electronmicroscopy.github.io/quantem.widget/api/showdiffraction.html) | +| `ChooseLattice` | Select an origin and lattice vectors | [API](https://electronmicroscopy.github.io/quantem.widget/api/choose-lattice.html) | +| `ShowEDS` | Explore linked EDS/EELS maps and spectra | — | +| `ShowFolder` | Browse, group, and select microscopy session files | [tutorial](https://electronmicroscopy.github.io/quantem.widget/tutorials/showfolder.html) · [API](https://electronmicroscopy.github.io/quantem.widget/api/showfolder.html) | + +## Documentation + +Visit the **[quantem.widget documentation](https://electronmicroscopy.github.io/quantem.widget/)** +for installation, tutorials, API references, command-line workflows, data I/O, +HTML sharing, and WebGPU export guidance. ## Citing quantem.widget @@ -287,204 +86,10 @@ https://doi.org/10.1093/mam/ozag053.941. ## Contributing -See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, checks, widget export -expectations, agent signoff, and release-candidate guidance. - -Pull request workflows follow the -[scikit-package](https://scikit-package.github.io/scikit-package/) procedures -for reproducible scientific software: issue-first development (a PR closes a -GitHub issue that states the problem), one small themed PR per issue, -Conventional Commit-style messages, NumPy-style docstrings, self-reviewed -PRs, and no force-pushing a branch under active review (rewritten commits -detach the reviewer's inline comments and destroy the incremental -"changes since last review" diff — append commits and `git revert` instead; -we squash-merge, so branch tidiness costs nothing). Human contributors -and coding agents/LLMs alike should apply these standards when writing code, -commits, issues, and PRs. This README is the canonical policy source; the -[pull request template](.github/PULL_REQUEST_TEMPLATE.md) turns the policy -into an operational checklist. For Git/GitHub tutorials and workflow onboarding, see -[ophusgroup/dev](https://github.com/ophusgroup/dev). - -### Policy map - -This README defines the project policy. The following documents apply that -policy to specific workflows without creating separate agent-specific rules: - -- [Contributing](CONTRIBUTING.md): setup, notebook hygiene, checks, commits, - and pull requests. -- [Pull request template](.github/PULL_REQUEST_TEMPLATE.md): the review - checklist used for each change. -- [Widget UI protocol](docs/maintainer/widget-ui-protocol.md): control wording, - layout, hover behavior, and visual verification. -- [Performance](docs/maintainer/widget-performance.md) and - [automation](docs/maintainer/automation.md): timing, browser, real-data, and - hardware-specific evidence. -- [HTML export](docs/api/html-export.md): public export terminology and API. -- [Release](docs/maintainer/widget-release.md): release-candidate gates and - TestPyPI publication. - -### Commit messages - -Use a short Conventional Commit-style first line: - -```text -type: short imperative summary -``` - -Common types are `feat`, `fix`, `docs`, `test`, `refactor`, `perf`, `build`, -`ci`, and `chore`. Examples: - -```text -feat: add ShowFolder thumbnail cache -fix: stabilize Show2D resize handle -docs: update HTML export protocol -``` - -Keep commit messages single-line unless the change genuinely needs a body. Do -not add `Co-authored-by` trailers unless requested. - -### Widget PR checklist - -Use this before opening a widget PR. It is intentionally explicit so human -contributors and coding agents can both work through it line by line. GitHub -pre-fills new PR descriptions from -[.github/PULL_REQUEST_TEMPLATE.md](.github/PULL_REQUEST_TEMPLATE.md), which -groups these items into sections (API/docs, UI/theming, performance, data, -export) — keep the small core checklist, complete only the sections your -change touches, and delete the rest so reviewers see only relevant boxes. -Verify each box yourself or have a coding agent verify and check it off. If -you edit the checklist here, update the template too. The PR workflow follows -the packaging standards for reproducible scientific software described in -[scikit-package](https://doi.org/10.1039/d6dd00121a) (S. Lee, C. Myers, -A. Yang, T. Zhang, Y. Xiao, and S. J. L. Billinge, *Digital Discovery*, 2026). - -- [ ] The widget has a small, stable Python API with NumPy-style docs, helpful - errors, and `(row, col)` coordinate wording where positions are shown. -- [ ] Every new public widget or API is exported from `quantem.widget`, listed - in the README widget catalog and import example, linked from the API index - and documentation sidebar, and recorded under **Unreleased** in - `CHANGELOG.md`. -- [ ] The frontend follows the local viewer patterns instead of inventing a new - design system; compare against [Show2D](docs/tutorials/show2d.ipynb), - [Show3D](docs/tutorials/show3d.ipynb), - [Show3DSlices](docs/tutorials/show3dslices.ipynb), - [Show4DSTEM](docs/tutorials/show4dstem.ipynb), and - [ShowEDS](docs/tutorials/showeds.ipynb). Follow the - [widget UI protocol](docs/maintainer/widget-ui-protocol.md). -- [ ] Controls are compact and content-sized: use icon/text buttons for - commands, switches for binary options, sliders for numeric values, menus for - option sets, and avoid stretched empty control bars. -- [ ] Compact labels stay grouped with the control they name on mobile and - narrow layouts. A row may wrap, but `Auto`, `Smooth`, `Zoom`, `Pan`, - `Contrast`, `fps`, `avg`, and similar labels must not separate from their - switch, menu, slider, or button. -- [ ] Compact widget control labels do not use decorative colons. Prefer - `Scale`, `Color`, `Auto`, `Link`, `Zoom`, `Pan`, and `ROI` in dense toolbar - rows; keep colons for explanatory prose and tooltips. -- [ ] Command buttons use Title Case, for example `Copy`, `Export`, `Reset`, - `Add`, `Clear`, and `Undo`. Keep scientific acronyms and file formats - uppercase, for example `FFT`, `ROI`, `BF`, `ADF`, `HTML`, `PNG`, and `MP4`. -- [ ] The widget supports both light and dark notebook/docs themes: all labels, - borders, controls, plots, histograms, ROI handles, status text, and export UI - remain readable. -- [ ] The widget has no hardcoded dark-only or light-only assumptions in plots, - canvas backgrounds, tooltips, menus, or exported HTML. -- [ ] Histogram UI matches the existing Show2D-style interaction: compact panel, - no extra whitespace, draggable min/max handles, fast center drag, and no - visible lag. -- [ ] Hover inspection is independent of selection. In Show2D, Show3D, - Show4DSTEM, and other multi-target widgets, hover at least two unselected - panels/regions and verify coordinates, value/readout, labels, detector/ROI - context, and stats follow the hovered target while edit controls remain - scoped to the explicitly selected target. -- [ ] New or changed widget interactions have a matching storyboard story in - [docs/maintainer/storyboard-\.md](docs/maintainer/storyboard.md) - (add stories for new behavior, update stale ones), and the storyboard - drive-test was run for the affected widget with the driven story IDs - reported. -- [ ] Any draggable selector has live preview separate from committed widget - state; use refs/CSS transforms or an equivalent fast path during drag. See - [performance notes](docs/maintainer/widget-performance.md). -- [ ] Use [Show4DSTEM](docs/tutorials/show4dstem.ipynb) detector dragging and - [ShowEDS](docs/tutorials/showeds.ipynb) energy-band dragging as the real-time - UX benchmark: aim for 60 FPS when feasible and keep live controls at 30 FPS - or better. -- [ ] Real-time interactions are browser-driven and verified by actually - dragging controls in JupyterLab or exported HTML, not only by reading code or - unit tests. -- [ ] Show4DSTEM live-scope workflows append new ready `*_master.h5` - acquisitions into the same live viewer without rebuilding the notebook. Verify - new masters appear in the Dataset slider, partial files are skipped until - ready, and detector/scan interactions remain real time after append. See - [Show4DSTEM live scope folders](docs/api/show4dstem.md#live-scope-folders). -- [ ] Performance reports separate load time, widget build time, first browser - paint, and interaction FPS/latency. Include data shape, dtype, raw size, - backend, and any crop/bin/downsample/quantization. Prefer `verbose=True` - output that users and agents can copy; use `quantem.widget.profile_widget` - for profiling notebooks when possible. See - [performance notes](docs/maintainer/widget-performance.md). -- [ ] For interaction-sensitive changes, run - `scripts/widget_local_signoff.sh --quick --browser` for exported HTML/UI - paths, fix issues immediately, rebuild, refresh, and redrive before claiming - the widget is ready. See - [Automation](docs/maintainer/automation.md) and - [Agent signoff](docs/maintainer/widget-agent-signoff.md). -- [ ] Expensive work avoids Python/kernel round trips during pointer movement; - use WebGPU, typed arrays, cached indexes, workers, or throttled schedulers - where the widget interaction requires live feedback. -- [ ] Large scientific data stays honest about precision and size: do not - silently crop, bin, downsample, quantize, or materialize sparse zeros. -- [ ] Keep `main` lightweight: small real rendered examples are fine, but large - tutorial arrays or HTML payloads should be generated during docs builds or - downloaded from public data hosting only when the size justifies it. -- [ ] Keep clone and install size small for microscope PCs. Real tutorial data - belongs in public data hosting such as Hugging Face datasets, Zenodo, or - release assets, then gets downloaded and cached by tutorial helpers at run - time. Do not commit large real arrays, generated HTML, or rendered docs - branches to this repository just to make examples work. -- [ ] CI should test data-loading protocol with tiny deterministic fixtures or - monkeypatched downloads. Full real-data downloads are reserved for docs builds, - release signoff, or local performance checks that explicitly opt in. -- [ ] Any binning/downsampling is explicit in the API and documentation, with - the reducer named clearly, for example mean, sum, or display-scaled `uint8`. -- [ ] The widget exposes `export_html(path=None, title=None, mode="single", - encoding="full", downsample=None)` when it can be exported. Follow the - [HTML export protocol](docs/api/html-export.md). -- [ ] If the widget has an in-widget **Export** button, it uses the standard - export traits and reports filename, mode, encoding/downsample choice, and - output size. -- [ ] Saved Jupyter widget state works: after interacting, Cmd+S, close/reopen - in JupyterLab, and confirm the view restores without rerunning cells when the - environment supports saved widget state. -- [ ] Standalone HTML works without a live Python kernel, and the exported page - preserves the intended theme, viewport, interaction state, and scale/contrast - state. -- [ ] GitHub sharing is treated separately from live HTML: GitHub notebook - previews should use static compressed widget pictures, never heavy live widget - state. See [GitHub preview](docs/github-preview.md). -- [ ] Documentation includes a minimal tutorial notebook under - [docs/tutorials](docs/tutorials) and an API page under [docs/api](docs/api) - when a public widget or loader is added. -- [ ] Tutorial notebooks avoid unnecessary `display(...)` and extra display - imports; let the returned widget render naturally. -- [ ] Synthetic-data generation cells in tutorial notebooks are collapsed with - the `hide-input` cell tag and a descriptive toggle label via cell metadata - `mystnb.code_prompt_show` (for example "Show synthetic data generation - code"), never the default "Show code cell source". Keep widget-construction - code and real-data loader calls visible; split a cell that mixes the two. -- [ ] The change includes focused tests for Python state/export behavior and - frontend build coverage where possible; start with `PYTHONPATH=src:. pytest -q` - and `npm run build`, or run `scripts/widget_local_signoff.sh`. -- [ ] Before committing, inspect `git status --short` and `git diff --stat`; - do not commit generated HTML, docs builds, screenshots, local notebooks, - private data, or machine-specific notes. -- [ ] Committed notebooks carry NO baked widget state (`metadata.widgets`) and - pass `scripts/check_notebook_sizes.py`. The docs CI executes tutorials at - build time (`execute_notebooks: force` in `docs/_config.yml`) and bakes - widget state into the published HTML only — never commit a re-executed - notebook with stored widget state, and never switch the docs build to - `cache` mode (it silently drops widget state and blanks every widget). - -## Issues +Contributions are welcome. Read [CONTRIBUTING.md](CONTRIBUTING.md) for setup, +tests, documentation standards, and the pull-request workflow. That workflow +follows the reproducible scientific-software procedures described by +[scikit-package](https://scikit-package.github.io/scikit-package/). -https://github.com/electronmicroscopy/quantem.widget/issues +Questions and bug reports belong in the +[issue tracker](https://github.com/electronmicroscopy/quantem.widget/issues). diff --git a/docs/maintainer/2026-07-16-showptycho-gqk-memory-modes.md b/docs/maintainer/2026-07-16-showptycho-gqk-memory-modes.md index e036beb3..f86ab978 100644 --- a/docs/maintainer/2026-07-16-showptycho-gqk-memory-modes.md +++ b/docs/maintainer/2026-07-16-showptycho-gqk-memory-modes.md @@ -1,334 +1,16 @@ -# 2026-07-16 — ShowPtycho WebGPU resident-memory experiment: Exact and compact preview storage - -## Question - -The ShowPtycho WebGPU folder viewer keeps the per-BF-pixel `G(q,k)` reducers -resident on the GPU for the whole review session (that residency is what makes -aberration-slider drags ~15 ms instead of a full HDF5 re-decode). Resident cost -is `active_BF_pixels x scan_pixels x 8 bytes` (complex64). On a 512x512 scan at -full BF that projects to tens of GB — workstation-only. Question: **can the -resident footprint shrink without losing precision or drag speed**, so -collaborators can open these folders on ordinary laptops? - -## Setup - -- Dataset: a real experimental 4D-STEM acquisition - (512x512 scan, 192x192 Arina, 19.3 GB raw, max pixel 17 counts). - Calibration: fresh SSB fit, 200 Optuna trials + Nelder-Mead refine - (`Show4DSTEM.compute_ssb`, rotation seeded from a prior batch fit). -- Current export command: `quantem showptycho --calibration ` - with the historical BF-column companion enabled - (WebGPU folder, compressed HDF5 + `bf_columns.u8` companion, 5.9 GB on disk). - Current exports use the compressed-HDF5 WebGPU source directly; the - BF-column companion transport was later removed. -- Viewer host: reference Apple Silicon laptop (M5 MacBook, 24 GB unified, - Chrome, adapter `apple/metal-3` - — real hardware confirmed via `adapter.info`, not SwiftShader). -- Harness: folder served by `scripts/serve_sidecar_range.py` (plain - `python -m http.server` FAILS — no HTTP Range support, viewer dies with - "Failed to fetch"). Page driven over CDP; public viewer choices are Exact - (default, or `?gqk=exact`) and compact preview (`?gqk=preview` or - `?gqk=herm16`). Earlier parity runs also measured the old n x n storage - baseline before that runtime path was removed. The engine stashes - `globalThis.__quantemSsbLast = {gqkMode, residentGqkBytes, gpuMs, phase, ...}` - after every reconstruct, and the harness pulls the raw `Float32Array` phase - out over CDP for offline numpy comparison. Same aberrations, same BF set - (preview 0.30 => 3941 requested, 408 active aperture pixels) in all runs. - -## Raw numbers - -| G(q,k) mode | resident VRAM | reconstruct (gpuMs) | max abs dphase vs old n x n baseline | rms dphase | phase corr | -|---|---|---|---|---|---| -| old n x n baseline (removed runtime path) | 0.856 GB | 18.2 ms | — | — | — | -| Exact / `herm` (complex64 Hermitian half-plane) | 0.429 GB (2.0x) | 16.4 ms | **0.0 (bit-exact)** | 0.0 | 1.0 | -| Compact preview / `herm16` (half-plane + snorm16, one f32 scale per BF px) | 0.215 GB (4.0x) | 12.6 ms | 1.204e-4 rad | 2.60e-5 rad | 0.9999954 | - -Phase image span was 0.0748 rad, so compact preview worst-case error is 0.16 % of -span (rms 0.03 %) — far below shot noise on 17-count data. - -Projection for this dataset at **full BF (13137 px, ~1360 active est.)**: -old n x n baseline ~2.9 GB, Exact ~1.4 GB, compact preview ~0.7 GB. -Upper-bound projection if every selected BF pixel were aperture-active: -27.6 / 13.9 / 6.9 GB. Per-BF-pixel cost by scan size: 2 MB at 512^2, -0.5 MB at 256^2, 0.125 MB at 128^2 for the old n x n baseline; -divide by 2 for Exact and by 4 for compact preview. - -## Memory planning table - -These are resident `G(q,k)` reducer sizes only. First load may need additional -temporary chunk, phase/loss, canvas, and browser memory. `Active BF` means -nonzero-aperture BF pixels after the BF policy is applied; it is often smaller -than the raw BF label shown in the UI. - -| Scan | Active BF | Typical use | Old n x n baseline (not runtime) | Exact default | Compact preview | -|---|---:|---|---:|---:|---:| -| 512x512 | 12 | Small smoke test | 25 MB | 13 MB | 6.3 MB | -| 512x512 | 408 | 0.30 BF preview in the reference report | 856 MB | 429 MB | 215 MB | -| 512x512 | 1360 | Full-BF estimate for the sparse experimental 512 dataset | 2.85 GB | 1.43 GB | 0.72 GB | -| 512x512 | 9070 | Dense experimental full active BF | 19.0 GB | 9.55 GB | 4.77 GB | -| 1024x1024 | 12 | Small smoke test | 101 MB | 50 MB | 25 MB | -| 1024x1024 | 1382 | Reference full active BF | 11.6 GB | 5.81 GB | 2.90 GB | -| 1024x1024 | 9070 | Workstation stress projection | 76.1 GB | 38.1 GB | 19.1 GB | - -Formula: - -- Old n x n baseline: `active_BF * N * N * 8` bytes. -- Exact default: `active_BF * N * (N/2 + 1) * 8` bytes. -- Compact preview: `active_BF * N * (N/2 + 1) * 4 + active_BF * 4` bytes. - -## Why herm is exactly lossless — and faster - -The stored `G(q,k)` is the scan-space FFT of each BF pixel's intensity trace, -and intensities are real, so `G(-q,k) = conj(G(q,k))`. Storing the -`n x (n/2+1)` half-plane and mirror-conjugating on fetch is algebra, not -approximation. Measured **bit-exact** (max diff literally 0.0): the radix-2 -FFT's rounding errors are themselves conjugate-symmetric for real input, so -the discarded half was a bitwise mirror all along. It is *faster* because the -per-drag reduce is bandwidth-bound (re-reads all of G every slider move) and -now reads half the bytes. Strictly better on every axis => **`herm` is the new -default Exact path**; the old n x n storage branch has been removed from the -runtime and now exists only as historical baseline data in this report. - -## Implementation (`quantem.gpu.ssb.compute.webgpu/backend.ts`) - -- `GqkMode` = `herm | herm16`, resolved from `?gqk=` URL param or - `globalThis.__QUANTEM_SHOWPTYCHO_GQK_MODE__`; default `herm`. Public aliases: - `exact` -> `herm`, `preview` -> `herm16`. -- `makeSsbShader(n, mode)` templates a `fetch_g(local_bf, bf_global, row, x)` - WGSL helper: direct read for `x <= n/2`, mirror `(r,c) = ((n-row) % n, n-x)` - + conjugate otherwise; `herm16` additionally - `unpack2x16snorm(word) * gqkScale[bf_global]`. -- Build path unchanged (temporary n x n chunks, gather + in-place FFT), then a new - GPU post-pass `transformGqkChunks` runs `scaleMax` (per-BF-pixel max |G| over - the half-plane, workgroup tree reduce) and `compact` (copy or - `pack2x16snorm(clamp(v/scale))`) per chunk, destroying each temporary chunk - immediately — build peak only briefly exceeds the old peak by one compacted - chunk; the *resident* session footprint is what shrinks. -- `__quantemSsbLast` debug hook on every reconstruct for harnesses. - -## Rejected / deferred ideas - -- **float16 storage**: 2x, but real mantissa loss on FFT accumulations - (10-bit mantissa vs values spanning ~4.5e6 dynamic range). Rejected — - strictly worse than compact preview, which spends its 16 bits after per-pixel - scaling. -- **Band-limit crop in q**: exact only when the scan oversamples the 2-alpha - double-overlap disk. This dataset (10.4 A scan sampling, 30 mrad, 300 kV; - q_Nyquist 0.048 1/A << 2-alpha/lambda 3.05 1/A) is in-band across the whole - q-plane — zero win here, dataset-dependent in general. Not implemented. -- **Store raw counts, rebuild G per drag**: unbounded memory win but turns - every slider move into a full FFT rebuild — kills the 15 ms interactivity. - Only sensible as a future explicit "final full-BF render" button. -- **Aberration-basis factorization of the k-sum**: the gamma weight is - nonlinear in the coefficients (`e^{i chi}`), no exact low-rank split. Rejected. -- **Streaming the initial build** (bounded peak, not just bounded resident): - requires either re-decoding all HDF5 chunks per BF chunk (N_chunks x slower - load) or a compact real-u16 time-domain gather buffer. Deferred; noted as - the remaining lever for laptop-friendly *first load*. - -## Gotchas recorded - -- `python -m http.server` cannot serve ShowPtycho folders (no Range support); - use `scripts/serve_sidecar_range.py --dir --port

`. -- `collectActiveBfIndices` drops zero-aperture-weight BF pixels: the - "3941/13137 BF" UI label overstates the resident set (408 active here). - Memory projections must use *active* BF counts. -- `FULL_STACK_GPU_BUDGET_BYTES` (4.5 GB) in the synced ShowPtycho WebGPU SSB - source is still dead code - — the VRAM clamp for the BF slider remains unimplemented. With `herm` default - the pressure is halved but a 512^2 full-BF drag can still device-lost a small - GPU. Follow-up: cap effective BF by `budget / (storedPlane x bytesPer)`. - -## Backend coverage checklist (for follow-up agents) - -Status as of 2026-07-16. The math is backend-independent: every SSB backend -builds `G(q,k)` as the scan-space FFT of real intensity traces, so the -Hermitian identity `G(-q,k) = conj(G(q,k))` holds everywhere, and the -per-BF-pixel snorm16/int16 block quantization transfers directly. - -| Optimization | WebGPU (`quantem.gpu/ssb/compute/webgpu/backend.ts`, generated under the matching widget engine tree) | CUDA (`quantem.gpu/ssb/compute/cuda`) | MPS (`quantem.gpu/ssb/compute/mps`) | -|---|---|---|---| -| Exact Hermitian half-plane G(q,k) (2x, bit-exact, faster) | **DONE — default** | TODO — `self.G_qk` is n x n complex64; also the streaming `result_buffer`/staging buffers (batch x bf x scan^2 x c64) would halve | TODO — `mx.complex64` n x n storage; gamma kernels at mps.py:430-440 already compute conj explicitly, mirror fetch slots in there | -| Compact preview snorm16/int16 block storage (4x, ~1e-4 rad error) | **DONE — opt-in** `?gqk=preview` (`?gqk=herm16` alias) | TODO — cupy int16 pairs + per-BF f32 scale; dequant inside the variance/correction kernels | TODO — mx int16 + scale; check MLX gather perf before committing | -| VRAM budget clamp on BF count | TODO — `FULL_STACK_GPU_BUDGET_BYTES` still dead code | n/a (96 GB workstation assumption baked in; revisit for L40S) | TODO — unified memory, clamp matters most on 8-16 GB Macs | -| Streamed initial build (bounded peak, not just resident) | TODO — needs real-u16 time-domain gather or per-chunk re-decode | n/a today | TODO | - -WebGPU scan-size coverage: - -| Scan size | Exact complex64 Hermitian `G(q,k)` | Compact preview `herm16` | Verification status | -|---|---|---|---| -| 128x128 | Done | Done | Synthetic parity sweep passed. | -| 256x256 | Done | Done | Synthetic parity sweep passed. | -| 512x512 | Done | Done | Real experimental parity and headed-browser timing measured. | -| 1024x1024 | Done | Done | Synthetic parity sweep passed; compact preview is preview-quality because error was larger than at 512. | - -Verification recipe for a port (what was used here): compute the same -reconstruction with the optimization off and on (same aberrations, same BF -set), assert `max|dphase|` is 0.0 for Hermitian and < ~1e-3 of the phase span -for int16; then compare per-drag wall time — Hermitian must not be slower -(it reads half the bytes; if it is slower, the mirror fetch broke coalescing). -Raw parity harness for the WebGPU case: CDP + `globalThis.__quantemSsbLast` -(this doc's Setup section). - -## Scan-size sweep (added same day) - -Reference kernel sweep across all supported scan sizes, three modes each, on the reference laptop -(`apple/metal-3`). 128/256/1024 are synthetic Arina-style masters written with -`quantem.gpu.io.save` (uint16, 48x48 detector, disk + gradient-shift phase -object, semiangle 8 mrad, det sampling 1 mrad/px); 512 is the real -experimental row from the table above. Preview BF (0.30); activeBf ~60 for -the synthetic sets, 408 for real 512. gpuMs is the first reconstruct (launch- -dominated at small BF counts; the 512-real row is the bandwidth-relevant one). - -| scan | mode | resident | gpuMs | max abs dphase vs old n x n baseline | rms | phase span | -|---|---|---|---|---|---|---| -| 128 | old n x n baseline | 8.0 MB | 4.7 | — | — | 1.002 rad | -| 128 | Exact / herm | 4.1 MB | 4.5 | **0.0** | 0.0 | | -| 128 | Compact preview / herm16 | 2.0 MB | 4.8 | 7.0e-4 (0.07 % span) | 1.5e-4 | | -| 256 | old n x n baseline | 31.5 MB | 8.5 | — | — | 0.813 rad | -| 256 | Exact / herm | 15.9 MB | 5.8 | **0.0** | 0.0 | | -| 256 | Compact preview / herm16 | 7.9 MB | 5.3 | 1.3e-3 (0.16 % span) | 3.1e-4 | | -| 512 (real) | old n x n baseline | 856 MB | 18.2 | — | — | 0.075 rad | -| 512 (real) | Exact / herm | 429 MB | 16.4 | **0.0** | 0.0 | | -| 512 (real) | Compact preview / herm16 | 215 MB | 12.6 | 1.2e-4 (0.16 % span) | 2.6e-5 | | -| 1024 | old n x n baseline | 495 MB | 13.1 | — | — | 0.459 rad | -| 1024 | Exact / herm | 248 MB | 12.5 | **0.0** | 0.0 | | -| 1024 | Compact preview / herm16 | 124 MB | 11.6 | 6.2e-3 (1.35 % span) | 1.3e-3 | | - -Conclusions: - -- **Exact / herm is bit-exact at every supported size** (128/256/512/1024, - synthetic and real data) and never slower. Safe as the unconditional default. -- **Compact preview / herm16 error grows with scan size**: 0.07 % of span at 128 up to 1.35 % - at 1024. Cause: one snorm16 scale per BF pixel spans the whole q-plane, and - the dynamic range inside G(q,k) (DC-dominated peak vs weak high-q tail) - widens with n, so a single per-pixel scale under-resolves the tail. - Recommendation: compact preview is comfortably below shot noise up to 512; at - 1024 treat it as preview-only, or implement **per-q-row block scales** - (n scales per BF pixel instead of 1, +0.4 % memory) to pull the error back - down — noted as the follow-up for whoever extends the quantization. -- Repro: masters under a local private SSB sweep directory, harness - `sweep_run.py` in the session scratchpad, per-mode viewer selection via - `?gqk=`. - -## Lessons learned (process, not just numbers) - -1. **Ask the symmetry question before the hardware question.** The 2x win here - did not come from a faster kernel — it came from noticing the input is real, - so half the stored spectrum was a mathematical mirror. Physics/math - equivalences (Hermitian symmetry, band limits, separability, known output - realness) reduce the PROBLEM; occupancy and coalescing only speed up - whatever problem is left. Always ask first: what symmetry, invariance, or - physical constraint makes part of this data or compute redundant? -2. **Bandwidth-bound loops convert memory wins into speed wins for free.** The - per-drag reduce re-reads all of G(q,k) every slider move, so halving the - bytes halved the traffic — herm was faster, not merely smaller. When a loop - is bandwidth-bound, compression IS optimization. -3. **Quantize after per-block scaling, in the domain with bounded dynamic - range.** snorm16 works because each BF pixel gets its own scale. The same - 16 bits as raw float16 would have failed (mantissa loss across ~1e6 dynamic - range). And the residual error law is set by the dynamic range INSIDE each - block - which grows with scan size - hence the 1024^2 degradation and the - per-q-row-scale follow-up. -4. **Bit-exactness is testable and worth demanding.** Expected ~1e-7 rounding - differences from the mirror fetch; measured literally 0.0 because radix-2 - FFT rounding is itself conjugate-symmetric for real input. A tolerance-free - `array_equal` assertion is a far stronger regression net than atol=1e-6. -5. **Measure the active set, not the labeled set.** UI said 3941 BF pixels; - only 408 carried nonzero aperture weight. Memory projections from labels - were 10x off. Instrument the engine (resident-bytes counter, `__quantemSsbLast` - hook) instead of computing footprints from UI numbers. -6. **A parity harness is one page of code.** URL-param mode switch + a - globalThis hook holding the raw Float32Array + CDP pull + numpy compare. - Built once, it validated the default flip, the sweep, and will validate the - CUDA/MPS ports. -7. **Verify the adapter before believing any GPU number** (`adapter.info` must - not be SwiftShader), and serve folder exports with a Range-capable server - - two silent failure modes that produce plausible-looking nonsense. - -## Follow-ups landed 2026-07-17 - -- **VRAM budget clamp wired** (was dead code): the active BF set is capped at - `budget / (storedPlane x bytesPerValue)` with uniform stride, default 4.5 GB - (`__QUANTEM_SHOWPTYCHO_GQK_BUDGET_GB__` override), status line announces the - clamp. Mode-aware: herm16 admits 4x more BF pixels under the same budget. -- **rfft half-plane calibrations accepted**: the CUDA backend now stores - Hermitian-half `G_qk` (n x n/2+1) and exports that `g_shape`; the viewer - derives n from either layout (it rebuilds its own G from the folder source). -- **Flattened scan input**: verified `(N, det, det)` works end to end - - square scan inferred (or `scan_shape=` explicit) before `g_shape` is written. -- **Corrected-calibration visual A/B** (scan sampling fixed to 10.4 A, - refit C10 383 nm / C12 79 nm / phi12 80 deg): old n x n baseline vs compact - preview phase images indistinguishable; difference map is structureless - noise, max 1.24e-4 rad (0.16 % of span), rms 2.7e-5 rad (0.035 %). - Old n x n baseline 856 MB / 13.8 ms vs compact preview 215 MB / 14.0 ms at - 408 BF. Caution from the fit: C10 and C12 landed near the +-400 / 100 nm - search bounds - widen the search when refitting. - -## Object-redraw fast path ported from CUDA (2026-07-17 late) - -The CUDA team's overnight identity - `mean_bf(ifft2(corrected_bf)) == -ifft2(mean_bf(corrected_bf))` - ported to the WebGPU drag path: sum the -gamma-corrected G(q,k) over BF pixels in Fourier space (one bandwidth-bound -pass per chunk), then ONE 2D inverse FFT + one atan2 pass, instead of two FFT -passes + atan2 per BF pixel. - -Measured (512x512 experimental folder, M-series, herm storage, 408 active BF): - -| path | per-reconstruct | estimator | -|---|---|---| -| exact per-BF (before / loss commits) | 18 ms | mean(angle(object)) | -| object fast path (drags, default now) | **7-8 ms (2.5x)** | angle(mean(object)) - same as Python `SSB.result()` | - -Correctness: fast vs exact at identical state corr 0.9965; fast vs the Python -CUDA `result()` object phase corr 0.997 (better than the exact mean-phase -path's 0.9916, because the estimator now MATCHES the backend reference). -Loss commits (slider release at full BF) always use the exact per-BF path - -`mean(angle)` variance needs per-BF phases. - -Debug war story worth keeping: an initial "the fast path is broken" -conclusion (corr 0.3) was a BASELINE ERROR - comparing a 408-BF preview -subset against the full-13137-BF reference; the exact path scored the same -0.379 against that wrong baseline. Rule: when validating an estimator change, -hold BF subset, aberrations, and estimator definition fixed and compare -apples-to-apples first. Opt-out: `globalThis.__QUANTEM_SSB_OBJ_FAST__ = false`. -Also gained: `__QUANTEM_SSB_OBJ_ONLY_CHUNK__` chunk-isolation debug toggle. - -### Object fast path across all sizes (same session) - -Initial reconstruct, herm storage, preview BF, M-series (synthetic folders for -128/256/1024 carry only ~60 active BF; the real 512 row carries 408): - -| scan | exact path | object fast path | note | -|---|---|---|---| -| 128 (61 BF) | 4.5 ms | 7.1 ms | tiny-BF regime: fixed FFT/dispatch overhead dominates, no win | -| 256 (60 BF) | 5.8 ms | 6.6 ms | parity | -| 512 real (408 BF) | 18 ms | **7.8 ms (2.3x)** | the win scales with BF count | -| 1024 (59 BF) | 12.5 ms | 12.4 ms | parity at synthetic BF; expect large win at real BF counts | - -The speedup comes from removing two FFT passes + atan2 PER BF PIXEL, so it -grows linearly with active BF count. At <100 BF both paths are launch-bound -and equal; potential refinement: auto-select exact path below ~100 active BF -(both are instant there, so not urgent). - -### Real-data full-BF result (the number that matters) - -Re-measured with REAL pointer drags on the 512x512x192x192 experimental -dataset (19.3 GB), drag-BF at 1.0 = all 13137 BF (3418 aperture-active), -recorder capturing every reconstruct (24 in one drag): - -| path | per-reconstruct | FPS | -|---|---|---| -| exact per-BF + loss (commits) | 112-127 ms | ~8 | -| **object fast path (drags)** | **18-23 ms (median ~20)** | **~50** | - -**5.6x at real full BF.** 20 ms for 3.6 GB of herm G reads = ~180 GB/s - -right at M-class unified-memory bandwidth, i.e. the fast path is now -bandwidth-floor-limited, which is as fast as this storage layout can go. -Earlier suspect 4-8 ms "full-BF" readings were stale-stash sampling -artifacts; only synthetic input events miss the drag handler - real pointer -drags hit the fast path correctly, so there is NO routing gap for users. -Rule reinforced: never quote FPS from small/synthetic BF counts - the -per-size table above with ~60-BF synthetic folders shows path parity only -because both are launch-bound there; real BF counts are where the identity -pays. +# WebGPU G(q,k) memory experiment moved to quantem.gpu + +The dated WebGPU SSB memory experiment formerly stored here belongs to the +compute engine that owns the SSB CUDA, MPS, and WebGPU implementations. Its +full historical record—including the original measurements, formulas, +rejected ideas, and a prominent re-verification checklist—has moved to +`quantem.gpu`. + +Use the current +[quantem.gpu SSB performance documentation](https://github.com/bobleesj/quantem.gpu/blob/main/docs/maintainer/ssb-performance.md) +for supported backend status and newer evidence. Historical measurements must +be re-run against the current backend before they are presented as current +performance claims. + +`quantem.widget` retains only the scientist-facing ShowPtycho and Show4DSTEM +interaction, visualization, and export documentation.