arrayview 0.15.0__tar.gz → 0.16.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. {arrayview-0.15.0 → arrayview-0.16.0}/.gitignore +2 -1
  2. {arrayview-0.15.0 → arrayview-0.16.0}/.mex/ROUTER.md +1 -1
  3. {arrayview-0.15.0 → arrayview-0.16.0}/.mex/context/architecture.md +5 -5
  4. {arrayview-0.15.0 → arrayview-0.16.0}/.mex/context/decisions.md +9 -1
  5. {arrayview-0.15.0 → arrayview-0.16.0}/.mex/context/frontend.md +9 -3
  6. {arrayview-0.15.0 → arrayview-0.16.0}/.mex/context/project-state.md +13 -2
  7. {arrayview-0.15.0 → arrayview-0.16.0}/IMMERSIVE_ANIMATION.md +1 -1
  8. {arrayview-0.15.0 → arrayview-0.16.0}/PKG-INFO +2 -1
  9. {arrayview-0.15.0 → arrayview-0.16.0}/docs/loading.md +1 -1
  10. {arrayview-0.15.0 → arrayview-0.16.0}/docs/remote.md +3 -3
  11. {arrayview-0.15.0 → arrayview-0.16.0}/pyproject.toml +2 -1
  12. {arrayview-0.15.0 → arrayview-0.16.0}/src/arrayview/ARCHITECTURE.md +3 -3
  13. {arrayview-0.15.0 → arrayview-0.16.0}/src/arrayview/_launcher.py +180 -152
  14. {arrayview-0.15.0 → arrayview-0.16.0}/src/arrayview/_platform.py +23 -1
  15. {arrayview-0.15.0 → arrayview-0.16.0}/src/arrayview/_server.py +161 -7
  16. {arrayview-0.15.0 → arrayview-0.16.0}/src/arrayview/_viewer.html +1694 -263
  17. {arrayview-0.15.0 → arrayview-0.16.0}/src/arrayview/_vscode.py +8 -8
  18. {arrayview-0.15.0 → arrayview-0.16.0}/tests/test_api.py +90 -0
  19. {arrayview-0.15.0 → arrayview-0.16.0}/tests/test_browser.py +8 -5
  20. {arrayview-0.15.0 → arrayview-0.16.0}/tests/test_interactions.py +4 -8
  21. {arrayview-0.15.0 → arrayview-0.16.0}/tests/test_mode_roundtrip.py +0 -28
  22. {arrayview-0.15.0 → arrayview-0.16.0}/uv.lock +1103 -1
  23. {arrayview-0.15.0 → arrayview-0.16.0}/vscode-extension/extension.js +2 -123
  24. {arrayview-0.15.0 → arrayview-0.16.0}/.claude/skills/invocation-consistency/SKILL.md +0 -0
  25. {arrayview-0.15.0 → arrayview-0.16.0}/.claude/skills/modes-consistency/SKILL.md +0 -0
  26. {arrayview-0.15.0 → arrayview-0.16.0}/.claude/skills/ui-consistency-audit/SKILL.md +0 -0
  27. {arrayview-0.15.0 → arrayview-0.16.0}/.claude/skills/viewer-ui-checklist/SKILL.md +0 -0
  28. {arrayview-0.15.0 → arrayview-0.16.0}/.claude/skills/visual-bug-fixing/SKILL.md +0 -0
  29. {arrayview-0.15.0 → arrayview-0.16.0}/.github/copilot-instructions.md +0 -0
  30. {arrayview-0.15.0 → arrayview-0.16.0}/.github/workflows/docs.yml +0 -0
  31. {arrayview-0.15.0 → arrayview-0.16.0}/.github/workflows/python-publish.yml +0 -0
  32. {arrayview-0.15.0 → arrayview-0.16.0}/.ignore +0 -0
  33. {arrayview-0.15.0 → arrayview-0.16.0}/.mex/AGENTS.md +0 -0
  34. {arrayview-0.15.0 → arrayview-0.16.0}/.mex/SETUP.md +0 -0
  35. {arrayview-0.15.0 → arrayview-0.16.0}/.mex/SYNC.md +0 -0
  36. {arrayview-0.15.0 → arrayview-0.16.0}/.mex/context/conventions.md +0 -0
  37. {arrayview-0.15.0 → arrayview-0.16.0}/.mex/context/render-pipeline.md +0 -0
  38. {arrayview-0.15.0 → arrayview-0.16.0}/.mex/context/setup.md +0 -0
  39. {arrayview-0.15.0 → arrayview-0.16.0}/.mex/context/stack.md +0 -0
  40. {arrayview-0.15.0 → arrayview-0.16.0}/.mex/patterns/INDEX.md +0 -0
  41. {arrayview-0.15.0 → arrayview-0.16.0}/.mex/patterns/README.md +0 -0
  42. {arrayview-0.15.0 → arrayview-0.16.0}/.mex/patterns/add-file-format.md +0 -0
  43. {arrayview-0.15.0 → arrayview-0.16.0}/.mex/patterns/add-server-endpoint.md +0 -0
  44. {arrayview-0.15.0 → arrayview-0.16.0}/.mex/patterns/debug-render.md +0 -0
  45. {arrayview-0.15.0 → arrayview-0.16.0}/.mex/patterns/frontend-change.md +0 -0
  46. {arrayview-0.15.0 → arrayview-0.16.0}/.opencode/opencode.json +0 -0
  47. {arrayview-0.15.0 → arrayview-0.16.0}/.python-version +0 -0
  48. {arrayview-0.15.0 → arrayview-0.16.0}/.vscode/settings.json +0 -0
  49. {arrayview-0.15.0 → arrayview-0.16.0}/AGENTS.md +0 -0
  50. {arrayview-0.15.0 → arrayview-0.16.0}/CONTRIBUTING.md +0 -0
  51. {arrayview-0.15.0 → arrayview-0.16.0}/LICENSE +0 -0
  52. {arrayview-0.15.0 → arrayview-0.16.0}/README.md +0 -0
  53. {arrayview-0.15.0 → arrayview-0.16.0}/docs/comparing.md +0 -0
  54. {arrayview-0.15.0 → arrayview-0.16.0}/docs/configuration.md +0 -0
  55. {arrayview-0.15.0 → arrayview-0.16.0}/docs/display.md +0 -0
  56. {arrayview-0.15.0 → arrayview-0.16.0}/docs/index.md +0 -0
  57. {arrayview-0.15.0 → arrayview-0.16.0}/docs/logo.png +0 -0
  58. {arrayview-0.15.0 → arrayview-0.16.0}/docs/measurement.md +0 -0
  59. {arrayview-0.15.0 → arrayview-0.16.0}/docs/stylesheets/extra.css +0 -0
  60. {arrayview-0.15.0 → arrayview-0.16.0}/docs/viewing.md +0 -0
  61. {arrayview-0.15.0 → arrayview-0.16.0}/matlab/arrayview.m +0 -0
  62. {arrayview-0.15.0 → arrayview-0.16.0}/mkdocs.yml +0 -0
  63. {arrayview-0.15.0 → arrayview-0.16.0}/plans/2026-04-14-immersive-animation.md +0 -0
  64. {arrayview-0.15.0 → arrayview-0.16.0}/plans/webview/LOG.md +0 -0
  65. {arrayview-0.15.0 → arrayview-0.16.0}/scripts/demo.py +0 -0
  66. {arrayview-0.15.0 → arrayview-0.16.0}/scripts/release.sh +0 -0
  67. {arrayview-0.15.0 → arrayview-0.16.0}/src/arrayview/__init__.py +0 -0
  68. {arrayview-0.15.0 → arrayview-0.16.0}/src/arrayview/__main__.py +0 -0
  69. {arrayview-0.15.0 → arrayview-0.16.0}/src/arrayview/_app.py +0 -0
  70. {arrayview-0.15.0 → arrayview-0.16.0}/src/arrayview/_config.py +0 -0
  71. {arrayview-0.15.0 → arrayview-0.16.0}/src/arrayview/_icon.png +0 -0
  72. {arrayview-0.15.0 → arrayview-0.16.0}/src/arrayview/_io.py +0 -0
  73. {arrayview-0.15.0 → arrayview-0.16.0}/src/arrayview/_render.py +0 -0
  74. {arrayview-0.15.0 → arrayview-0.16.0}/src/arrayview/_segmentation.py +0 -0
  75. {arrayview-0.15.0 → arrayview-0.16.0}/src/arrayview/_session.py +0 -0
  76. {arrayview-0.15.0 → arrayview-0.16.0}/src/arrayview/_shell.html +0 -0
  77. {arrayview-0.15.0 → arrayview-0.16.0}/src/arrayview/_stdio_server.py +0 -0
  78. {arrayview-0.15.0 → arrayview-0.16.0}/src/arrayview/_torch.py +0 -0
  79. {arrayview-0.15.0 → arrayview-0.16.0}/src/arrayview/arrayview-opener.vsix +0 -0
  80. {arrayview-0.15.0 → arrayview-0.16.0}/src/arrayview/gsap.min.js +0 -0
  81. {arrayview-0.15.0 → arrayview-0.16.0}/tests/conftest.py +0 -0
  82. {arrayview-0.15.0 → arrayview-0.16.0}/tests/make_vectorfield_test_arrays.py +0 -0
  83. {arrayview-0.15.0 → arrayview-0.16.0}/tests/test_cli.py +0 -0
  84. {arrayview-0.15.0 → arrayview-0.16.0}/tests/test_command_reachability.py +0 -0
  85. {arrayview-0.15.0 → arrayview-0.16.0}/tests/test_config.py +0 -0
  86. {arrayview-0.15.0 → arrayview-0.16.0}/tests/test_cross_mode_parametrized.py +0 -0
  87. {arrayview-0.15.0 → arrayview-0.16.0}/tests/test_large_arrays.py +0 -0
  88. {arrayview-0.15.0 → arrayview-0.16.0}/tests/test_loading_server.py +0 -0
  89. {arrayview-0.15.0 → arrayview-0.16.0}/tests/test_mode_consistency.py +0 -0
  90. {arrayview-0.15.0 → arrayview-0.16.0}/tests/test_mode_matrix.py +0 -0
  91. {arrayview-0.15.0 → arrayview-0.16.0}/tests/test_nifti_meta.py +0 -0
  92. {arrayview-0.15.0 → arrayview-0.16.0}/tests/test_rgb_pixel_art.py +0 -0
  93. {arrayview-0.15.0 → arrayview-0.16.0}/tests/test_torch.py +0 -0
  94. {arrayview-0.15.0 → arrayview-0.16.0}/tests/test_view_component_integration.py +0 -0
  95. {arrayview-0.15.0 → arrayview-0.16.0}/tests/test_view_component_unit.py +0 -0
  96. {arrayview-0.15.0 → arrayview-0.16.0}/tests/ui_audit.py +0 -0
  97. {arrayview-0.15.0 → arrayview-0.16.0}/tests/visual_smoke.py +0 -0
  98. {arrayview-0.15.0 → arrayview-0.16.0}/vscode-extension/AGENTS.md +0 -0
  99. {arrayview-0.15.0 → arrayview-0.16.0}/vscode-extension/LICENSE +0 -0
  100. {arrayview-0.15.0 → arrayview-0.16.0}/vscode-extension/package.json +0 -0
@@ -34,4 +34,5 @@ van_gogh/
34
34
  cig_presentation/
35
35
  docs/superpowers
36
36
  .playwright-mcp/
37
- dev/TODO.md
37
+ dev/TODO.md
38
+ .nninteractive_weights/
@@ -20,7 +20,7 @@ edges:
20
20
  condition: when working on rendering, colormaps, LUTs, caching, or the render thread
21
21
  - target: patterns/INDEX.md
22
22
  condition: when starting a task — check the pattern index for a matching pattern file
23
- last_updated: 2026-04-15
23
+ last_updated: 2026-04-17
24
24
  ---
25
25
 
26
26
  # arrayview — Router
@@ -43,18 +43,18 @@ Browser (_viewer.html — single self-contained HTML+JS+CSS file)
43
43
 
44
44
  A `view()` call creates a `Session`, starts the FastAPI server if not running,
45
45
  registers the session via HTTP POST `/load`, then opens a display for the detected
46
- environment (Jupyter inline, VS Code SimpleBrowser or direct webview, native
46
+ environment (Jupyter inline, VS Code webview panel or direct webview, native
47
47
  pywebview, or system browser).
48
48
 
49
49
  ## Key Components
50
50
 
51
- - **`_launcher.py`** — CLI parser, `view()` API, `ViewHandle`, server lifecycle, SSH relay, file watching. Heavy imports (`_session`, `_render`, `_io`, uvicorn) are all lazy to keep the CLI fast path near-zero cost.
51
+ - **`_launcher.py`** — CLI parser, `view()` API, `ViewHandle`, server lifecycle, reverse-tunnel relay (`--relay`), file watching. Heavy imports (`_session`, `_render`, `_io`, uvicorn) are all lazy to keep the CLI fast path near-zero cost.
52
52
  - **`_server.py`** — FastAPI app with all REST and WebSocket routes (`/meta/{sid}`, `/load`, `/slice`, `/ws/{sid}`, `/seg/*`, `/reload`, etc.). Dispatches render work to the render thread via `_render()` from `_session.py`.
53
53
  - **`_session.py`** — Single source of global mutable state: `SESSIONS`, `SERVER_LOOP`, `VIEWER_SOCKETS`, `VIEWER_SIDS`, `SHELL_SOCKETS`. Owns the render thread (`_RENDER_QUEUE`, `_RENDER_THREAD`), prefetch pool, and the `Session` class with its three LRU caches.
54
54
  - **`_render.py`** — Stateless rendering functions: `extract_slice()`, `apply_complex_mode()`, `render_rgba()`, `render_rgb_rgba()`, `render_mosaic()`, `extract_projection()`. Owns colormap LUTs (`LUTS` dict, lazy-initialized by `_init_luts()`).
55
55
  - **`_io.py`** — All file-format loading behind `load_data(filepath)`. Lazy nibabel import for NIfTI. Handles `.npy`, `.npz`, `.nii/.nii.gz`, `.zarr`, `.zarr.zip`, `.pt/.pth`, `.h5/.hdf5`, `.tif/.tiff`, `.mat`. Extensions registered in `_SUPPORTED_EXTS`.
56
56
  - **`_platform.py`** — Environment detection: checks jupyter → vscode → julia → ssh → terminal in priority order. Results cached. Never short-circuit this order.
57
- - **`_vscode.py`** — VS Code extension install/management, signal-file IPC, shared-memory IPC, SimpleBrowser and direct webview opening.
57
+ - **`_vscode.py`** — VS Code extension install/management, signal-file IPC, shared-memory IPC, webview panel and direct webview opening.
58
58
  - **`_stdio_server.py`** — Alternative to FastAPI for VS Code tunnel (direct webview): JSON on stdin, length-prefixed binary on stdout.
59
59
  - **`_viewer.html`** — The entire frontend (~15 600 lines). CSS + JS in one file, no build step. Canvas-based rendering, WebSocket binary protocol, all viewing modes, reconcilers, command registry. See `context/frontend.md`.
60
60
 
@@ -63,11 +63,11 @@ pywebview, or system browser).
63
63
  | Environment | Default display | Server mode |
64
64
  |---|---|---|
65
65
  | Jupyter | Inline iframe | network |
66
- | VS Code local | Simple Browser | network |
66
+ | VS Code local | Webview panel | network |
67
67
  | VS Code tunnel | Direct webview (stdio) | stdio |
68
68
  | Julia | System browser | network |
69
69
  | CLI / Python script | Native pywebview | network |
70
- | SSH terminal | VS Code ext via TCP relay (prints URL on failure) | network |
70
+ | SSH terminal | Prints URL user forwards port with `ssh -L` | network |
71
71
 
72
72
  Detection logic: `_platform.py`. Display opening: `_launcher.py` + `_vscode.py`.
73
73
 
@@ -14,7 +14,7 @@ edges:
14
14
  condition: when a decision relates to technology choice
15
15
  - target: context/frontend.md
16
16
  condition: when a decision relates to the frontend architecture or viewer modes
17
- last_updated: 2026-04-15
17
+ last_updated: 2026-04-16
18
18
  ---
19
19
 
20
20
  # Decisions
@@ -61,6 +61,14 @@ last_updated: 2026-04-15
61
61
  **Alternatives considered:** Port forwarding via VS Code tunnel (rejected — unreliable, depends on tunnel configuration); WebSocket over stdio tunnel (rejected — more complex than length-prefixed binary).
62
62
  **Consequences:** `_stdio_server.py` must mirror every route and feature of `_server.py`. New server features must be implemented in both. The `_vscode.py` signal-file and shared-memory IPC are part of this same display path.
63
63
 
64
+ ### ROI in qMRI uses per-pane overlay canvases, not a shared overlay
65
+ **Date:** 2026-04-16
66
+ **Status:** Active
67
+ **Decision:** Each qMRI pane gets its own `.qv-roi-overlay` canvas element. ROI shapes are drawn on all overlays simultaneously during drag, and stats are fetched per parameter map.
68
+ **Reasoning:** In qMRI mode, the single main canvas is hidden and replaced by N independent pane canvases, each with its own coordinate system and scale. A shared overlay (like the main `#roi-overlay`) cannot span multiple independently-positioned canvases. Per-pane overlays also allow the ROI mirroring UX where drawing on one pane instantly shows the same shape on all others.
69
+ **Alternatives considered:** A single overlay canvas spanning the entire `#qmri-view-wrap` (rejected — would need to track pane positions and clip per-pane, fragile across resize/mosaic layout changes); reusing the main `#roi-overlay` (rejected — it's inside `#canvas-viewport` which is hidden in qMRI mode).
70
+ **Consequences:** Overlay canvases must set `background: transparent` to override the global `canvas { background: var(--bg) }` rule. qMRI view objects carry `roiOverlay` and `roiCtx` properties. `_drawAllQvRois()` replaces `_drawAllRois()` in qMRI mode; `_redrawRoiOverlays()` dispatches to the correct function based on mode.
71
+
64
72
  ### UI visibility changes go through reconcilers, not ad hoc style/classList calls
65
73
  **Date:** 2025 (View Component System refactor)
66
74
  **Status:** Active
@@ -23,7 +23,7 @@ edges:
23
23
  condition: when writing new frontend code and need section separator conventions
24
24
  - target: patterns/frontend-change.md
25
25
  condition: when making a concrete change to _viewer.html
26
- last_updated: 2026-04-15
26
+ last_updated: 2026-04-17
27
27
  ---
28
28
 
29
29
  # Frontend (_viewer.html)
@@ -60,7 +60,7 @@ Section separators: `/* ── Section Name ── */` in CSS, `// ── Sectio
60
60
  | Info Bar and Pixel Display | Bottom info bar, hover pixel readout |
61
61
  | State Persistence and Restore | URL hash state, `sessionStorage` save/restore |
62
62
  | Rendering Pipeline | `updateView()`, play/animate, screenshot |
63
- | ROI and Selection Modes | Rectangle/ellipse ROI, statistics computation |
63
+ | ROI and Selection Modes | Rectangle/circle/freehand ROI, statistics computation, qMRI cross-pane mirroring |
64
64
  | nnInteractive Segmentation | Click-to-segment UI, mask overlay, undo stack |
65
65
  | Keyboard Shortcuts | Command registry + command palette |
66
66
  | Mode Transitions | Compare/multiview/qMRI enter/exit, crosshair animation |
@@ -122,11 +122,17 @@ Introduces `View`, `Slicer`, `Layer`, `LayoutStrategy`, `modeManager` alongside
122
122
 
123
123
  **Shim layer (Section 7):** `Object.defineProperty(window, 'manualVmin', …)` intercepts all bare writes to `manualVmin`/`manualVmax` (27 sites) and routes them to `modeManager.currentViews[0].displayState.vmin/vmax`.
124
124
 
125
+ ### Plugin Shelf (`/` menu)
126
+ `SPECIAL_MODE_TILES` array defines five plugins: qMRI, Segmentation, ROI, Overlay, Vector field. The shelf supports multi-select (spacebar toggles, Enter applies). Mutual exclusion is enforced via `tile.excludes` arrays — ROI ↔ Segmentation, and Overlay/Vector field are reciprocally exclusive with all other plugins. `_applyShelfSelection()` diffs current state against selection, exits removed plugins, then enters new ones wrapped in `crossfade()`. Overlay and Vector field auto-seed the shelf at init when arrays are present (`_overlaySids.length > 0`, `hasVectorfield`); their per-tile state lives in `_shelfSelection.has(id)` since there is no separate mode flag.
127
+
125
128
  ### Eggs
126
129
  Pill badges below the canvas showing active transforms: `FFT` `LOG` `MAGNITUDE` `PHASE` `REAL` `IMAG` `RGB` `ALPHA` `PROJECTION`. **ROI** and **SEGMENT** are NOT eggs — they are interaction modes with their own dynamic island UI.
127
130
 
128
131
  ### Dynamic Islands
129
- Floating UI panels: ROI statistics, segmentation controls, colorbar hover, dimension sliders. Use absolute/fixed positioning — must be tested across normal, immersive, multiview, and compare modes.
132
+ Floating UI panels. `renderIsland()` collects all active plugins (`qmriActive`, `rectRoiMode`, `_segMode`/`_segActivating`, `_shelfSelection.has('overlay')`, `_shelfSelection.has('vectorfield')`) and renders sections for each with dividers between them. ROI/Segmentation share the same shape-toolbar + rows + magnifier-action layout; segmentation also renders a pulsing "connecting" loading row during `_segActivating`. Overlay section renders per-overlay rows (swatch + editable label + eye + `×`) with a shared opacity slider and a `+ add overlay` button that opens the filesystem picker. Vector field section renders a visibility row plus density and length sliders wired to `vfDensityLevel` / `vfLengthLevel` (both directions — keyboard `[ ] { }` also re-renders the island). An inline `~` collapse button at the island's top-right triggers `_collapseIslandToHint()`. `renderIsland()` early-returns while `_islandSliderDragging` is true so a mid-drag DOM rebuild doesn't kill the grab. Use absolute/fixed positioning — must be tested across normal, immersive, multiview, and compare modes.
133
+
134
+ ### ROI in qMRI Mode
135
+ Each qMRI pane has a `.qv-roi-overlay` canvas (transparent, `position: absolute`, `pointer-events: none`). Drawing a ROI on any pane mirrors it to all panes in real-time via `_drawAllQvRois()`. On finalize, `_fetchQvRoiStats()` fetches per-parameter-map statistics in parallel. Results stored as `roi.qmriStats[]` and displayed as sub-rows in the island. Key functions: `_drawAllQvRois`, `_clearQvRoiOverlays`, `_redrawRoiOverlays`, `_finalizeQvRoi`, `_fetchQvRoiStats`. **Important:** the global `canvas { background: var(--bg) }` rule makes all canvas elements opaque — overlay canvases must set `background: transparent` to avoid covering the underlying content.
130
136
 
131
137
  ## CSS Architecture
132
138
 
@@ -7,7 +7,7 @@ triggers:
7
7
  - "recent work"
8
8
  - "active feature"
9
9
  - "shipped recently"
10
- last_updated: 2026-04-15
10
+ last_updated: 2026-04-17
11
11
  ---
12
12
 
13
13
  # Project State
@@ -15,7 +15,7 @@ last_updated: 2026-04-15
15
15
  ## Working
16
16
 
17
17
  - CLI (`uvx arrayview file.npy`) and Python API (`view(arr)`) — both stable
18
- - All six display environments: Jupyter inline, VS Code local, VS Code tunnel (stdio), Julia, native pywebview, SSH URL print
18
+ - Display environments: Jupyter inline, VS Code local, VS Code tunnel (stdio), Julia, native pywebview, SSH URL print (user forwards port with `ssh -L`)
19
19
  - File formats: `.npy`, `.npz`, `.nii`/`.nii.gz`, `.zarr`, `.h5`/`.hdf5`, `.mat`, `.tif`/`.tiff`, `.pt`/`.pth`
20
20
  - Rendering pipeline: colormaps, complex modes, mosaic, RGB/RGBA, projections, overlays
21
21
  - NIfTI spatial metadata, RAS resampling
@@ -23,10 +23,21 @@ last_updated: 2026-04-15
23
23
  - Colorbar refactor: `ColorBar` JS class partially migrated (in progress)
24
24
  - Colorbar island flip: `c` and `d` keys trigger 3D `rotateX` card flip (front=colorbar, back=cmap thumbnails/histogram)
25
25
  - Cold-start loading spinner in VS Code and native shell
26
+ - Plugin shelf (`/` menu) supports multi-select: spacebar toggles plugins, Enter applies selection. Mutual exclusion enforced (ROI ↔ Segmentation, and overlay/vectorfield ↔ everything else). Cursor indicator shows focused tile via yellow background + left accent bar
27
+ - Dynamic island renders sections for all active plugins simultaneously (qMRI pills + ROI shapes/stats separated by divider), replacing the old single-plugin priority chain
28
+ - ROI mode works alongside qMRI: drawing on any pane mirrors the ROI to all panes in real-time via per-pane overlay canvases; stats fetched per parameter map and shown as sub-rows in the island
29
+ - qMRI map toggle (`_islandToggleQmriMap`) fade animation now covers dimbar and array-name in addition to panes and colorbars
30
+ - Segmentation menu shares the ROI layout (yellow accent, magnifier action icon, common `#export-overlay` modal). Pre-activation shows a pulsing "nnInteractive · connecting" row that morphs into the normal shape toolbar once `/seg/activate` resolves
31
+ - Overlays are a plugin tile (`OV`): per-overlay row with colour swatch, editable label, eye toggle, × delete; shared opacity slider; `+ add overlay` opens a filesystem picker rooted at the launched file's directory
32
+ - Vector field is a plugin tile (`VF`): row with visibility toggle + density/length sliders bi-directionally wired to the `[ ] { }` keyboard commands
33
+ - CLI `--overlay FILE` can be repeated to load multiple overlays at launch
34
+ - Filesystem picker endpoint (`GET /fs/list`) clamped to `$HOME`. Accepts `base_sid` + `mode` (`overlay` | `vectorfield`) to filter entries by a cheap header-shape peek (`.npy`, `.nii`/`.nii.gz`, `.h5`, `.zarr`); overlay mode requires identical shape, vectorfield mode requires base shape plus one axis of size 3
35
+ - Island collapse affordance: inline `~` at the island's top-right animates the panel into the bottom-left `~` hint circle; external `~` hint only visible while the island is actually collapsed. New `/` hint circle at bottom-right opens the plugin shelf
26
36
 
27
37
  ## In Progress
28
38
 
29
39
  - Smooth immersive transition — stale scrub geometry handoff is fixed, immersive overlay fade-in is held until after the class switch, shared slim colorbar returns through `drawSlimColorbar()` on reverse, and active scrub suppresses minimap/overflow/drag side effects. Single-pane scrub now targets the actual centered immersive viewport rect instead of a hardcoded corner box, the dimbar stays above the pane during scrub, the shared colorbar sits behind the growing pane, and the phantom extra `av-view-wrap` footprint in normal mode was removed by rebinding `NormalLayout` to the real `#viewer` canvas. Cross-mode parity and deeper reverse-pinch validation still need manual verification.
40
+ - ROI + qMRI integration refinements: floodfill not yet supported on qMRI panes; ROI hover tooltip not yet wired for qMRI canvases; per-pane stats are re-fetched on each ROI draw but not updated on slice scroll
30
41
 
31
42
  ## Not Yet Built
32
43
 
@@ -16,7 +16,7 @@ The canvas grows smoothly, tied 1-to-1 with how much you've pinched. No spring,
16
16
 
17
17
  ### When the canvas reaches its final immersive size (progress = 1)
18
18
 
19
- The immersive layout class is applied. At this point the dimbar and colorbar are completely invisible, so they are silently repositioned to their overlay locations — dimbar near the top of the canvas, colorbar near the bottom. They teleport while invisible, not animate. Then they fade from invisible to visible at those positions. They do not move during this fade-in.
19
+ The immersive layout class is applied. A0t this point the dimbar and colorbar are completely invisible, so they are silently repositioned to their overlay locations — dimbar near the top of the canvas, colorbar near the bottom. They teleport while invisible, not animate. Then they fade from invisible to visible at those positions. They do not move during this fade-in.
20
20
 
21
21
  ### Pinching back out (progress 1 → 0)
22
22
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arrayview
3
- Version: 0.15.0
3
+ Version: 0.16.0
4
4
  Summary: Fast multi-dimensional array viewer
5
5
  Project-URL: Home, https://github.com/oscarvanderheide/arrayview
6
6
  Project-URL: Source, https://github.com/oscarvanderheide/arrayview
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.12
16
16
  Requires-Python: >=3.12
17
17
  Requires-Dist: fastapi>=0.129.0
18
18
  Requires-Dist: h5py>=3.0
19
+ Requires-Dist: jupyter-server-proxy>=4.0
19
20
  Requires-Dist: matplotlib>=3.9.0
20
21
  Requires-Dist: nibabel>=5.3.3
21
22
  Requires-Dist: numpy>=2.4.2
@@ -61,7 +61,7 @@ Key parameters:
61
61
  | `None` | Auto: native outside Jupyter, inline inside |
62
62
  | `"native"` | Native desktop window |
63
63
  | `"browser"` | System browser |
64
- | `"vscode"` | VS Code Simple Browser |
64
+ | `"vscode"` | VS Code tab |
65
65
  | `"inline"` | Inline IFrame (Jupyter / VS Code notebook) |
66
66
 
67
67
  ## File Picker
@@ -12,7 +12,7 @@ Then open `http://localhost:8000` in your local browser.
12
12
 
13
13
  ## VS Code
14
14
 
15
- Auto-detects VS Code terminals and opens in Simple Browser. Works automatically.
15
+ Auto-detects VS Code terminals and opens in a VS Code tab. Works automatically.
16
16
 
17
17
  ## VS Code tunnel
18
18
 
@@ -73,7 +73,7 @@ ssh -R 8000:localhost:8000 user@gpu-server
73
73
  arrayview array.npy
74
74
  ```
75
75
 
76
- The array is sent back to the remote machine and the viewer opens in Simple Browser locally.
76
+ The array is sent back to the remote machine and the viewer opens in a VS Code tab locally.
77
77
 
78
78
  If port 8000 is already taken on the GPU server:
79
79
 
@@ -88,5 +88,5 @@ arrayview array.npy --relay 8765
88
88
  |-------|----------|
89
89
  | `native` | Desktop window (default outside Jupyter) |
90
90
  | `browser` | System browser |
91
- | `vscode` | VS Code Simple Browser |
91
+ | `vscode` | VS Code tab |
92
92
  | `inline` | Inline IFrame (default in Jupyter) |
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "arrayview"
7
- version = "0.15.0"
7
+ version = "0.16.0"
8
8
  description = "Fast multi-dimensional array viewer"
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  requires-python = ">=3.12"
@@ -22,6 +22,7 @@ urls = { "Home" = "https://github.com/oscarvanderheide/arrayview", "Source" = "h
22
22
  dependencies = [
23
23
  "fastapi>=0.129.0",
24
24
  "h5py>=3.0",
25
+ "jupyter-server-proxy>=4.0",
25
26
  "matplotlib>=3.9.0",
26
27
  "nibabel>=5.3.3",
27
28
  "numpy>=2.4.2",
@@ -26,11 +26,11 @@ Frontend (_viewer.html — single self-contained HTML file)
26
26
  | Environment | Default display | Server mode |
27
27
  |--------------------|------------------------------------|-------------|
28
28
  | Jupyter | Inline iframe | network |
29
- | VS Code local | Simple Browser (network) | network |
29
+ | VS Code local | Webview panel (network) | network |
30
30
  | VS Code tunnel | Direct webview (stdio) | stdio |
31
31
  | Julia | System browser | network |
32
32
  | CLI / Python script | Native pywebview | network |
33
- | SSH terminal (ni) | VS Code ext via TCP relay (prints URL on relay failure) | network |
33
+ | SSH terminal | Prints URL user forwards port with `ssh -L` | network |
34
34
 
35
35
  Detection logic lives in `_platform.py`. Display opening logic lives in `_launcher.py` (section: ViewHandle and view() API) and `_vscode.py`.
36
36
 
@@ -43,7 +43,7 @@ Detection logic lives in `_platform.py`. Display opening logic lives in `_launch
43
43
  | `_app.py` | 179 | Backward-compat shim — re-exports everything from the split modules |
44
44
  | `_config.py` | 121 | `~/.arrayview/config.toml` read/write, valid window modes/env keys |
45
45
  | `_io.py` | 253 | Data loading: numpy, NIfTI (lazy nibabel), zarr, DICOM, raw files |
46
- | `_launcher.py` | 2817 | **Main entry.** CLI parser, `view()` API, `ViewHandle`, server lifecycle, SSH relay, demo arrays, file watching |
46
+ | `_launcher.py` | 2817 | **Main entry.** CLI parser, `view()` API, `ViewHandle`, server lifecycle, reverse-tunnel relay, demo arrays, file watching |
47
47
  | `_platform.py` | 396 | Environment detection: Jupyter, VS Code, SSH, tunnel, Julia, native-window capability |
48
48
  | `_render.py` | 834 | Rendering pipeline: colormap LUTs, slice extraction, RGBA/RGB/mosaic rendering, overlay compositing, preload |
49
49
  | `_segmentation.py` | 227 | nnInteractive segmentation client (pure HTTP, no nnInteractive dependency) |