arrayview 0.6.0__tar.gz → 0.8.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.
- {arrayview-0.6.0 → arrayview-0.8.0}/.claude/skills/ui-consistency-audit/SKILL.md +32 -0
- arrayview-0.8.0/.claude/skills/visual-bug-fixing/SKILL.md +153 -0
- arrayview-0.8.0/.github/workflows/docs.yml +20 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/.gitignore +5 -1
- arrayview-0.8.0/AGENTS.md +52 -0
- arrayview-0.8.0/PKG-INFO +133 -0
- arrayview-0.8.0/README.md +98 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/docs/remote.md +26 -5
- arrayview-0.8.0/matlab/arrayview.m +74 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/mkdocs.yml +2 -0
- arrayview-0.8.0/plans/webview/LOG.md +130 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/pyproject.toml +1 -1
- {arrayview-0.6.0 → arrayview-0.8.0}/src/arrayview/__init__.py +2 -1
- arrayview-0.8.0/src/arrayview/__main__.py +4 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/src/arrayview/_app.py +0 -2
- {arrayview-0.6.0 → arrayview-0.8.0}/src/arrayview/_config.py +29 -0
- arrayview-0.8.0/src/arrayview/_icon.png +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/src/arrayview/_io.py +91 -4
- {arrayview-0.6.0 → arrayview-0.8.0}/src/arrayview/_launcher.py +516 -99
- {arrayview-0.6.0 → arrayview-0.8.0}/src/arrayview/_render.py +11 -37
- arrayview-0.8.0/src/arrayview/_segmentation.py +227 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/src/arrayview/_server.py +990 -128
- {arrayview-0.6.0 → arrayview-0.8.0}/src/arrayview/_session.py +8 -3
- arrayview-0.8.0/src/arrayview/_stdio_server.py +705 -0
- arrayview-0.8.0/src/arrayview/_torch.py +217 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/src/arrayview/_viewer.html +4245 -1743
- {arrayview-0.6.0 → arrayview-0.8.0}/src/arrayview/_vscode.py +365 -141
- arrayview-0.8.0/src/arrayview/arrayview-opener.vsix +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/tests/test_api.py +36 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/tests/test_cli.py +3 -3
- {arrayview-0.6.0 → arrayview-0.8.0}/tests/test_config.py +27 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/tests/test_interactions.py +23 -23
- {arrayview-0.6.0 → arrayview-0.8.0}/tests/test_mode_consistency.py +38 -22
- arrayview-0.8.0/tests/test_torch.py +223 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/tests/ui_audit.py +132 -5
- {arrayview-0.6.0 → arrayview-0.8.0}/tests/visual_smoke.py +85 -6
- {arrayview-0.6.0 → arrayview-0.8.0}/uv.lock +1 -1
- arrayview-0.8.0/vscode-extension/extension.js +895 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/vscode-extension/package.json +9 -2
- arrayview-0.6.0/.claude/skills/task-workflow/SKILL.md +0 -82
- arrayview-0.6.0/AGENTS.md +0 -137
- arrayview-0.6.0/PKG-INFO +0 -68
- arrayview-0.6.0/README.md +0 -33
- arrayview-0.6.0/docs/superpowers/plans/2026-03-27-colorbar-class-refactor.md +0 -1642
- arrayview-0.6.0/src/arrayview/arrayview-opener.vsix +0 -0
- arrayview-0.6.0/vscode-extension/extension.js +0 -395
- {arrayview-0.6.0 → arrayview-0.8.0}/.claude/skills/invocation-consistency/SKILL.md +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/.claude/skills/modes-consistency/SKILL.md +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/.claude/skills/viewer-ui-checklist/SKILL.md +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/.github/workflows/python-publish.yml +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/.python-version +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/.tmp-vsix/extension/extension.js +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/.tmp-vsix/extension/package.json +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/LICENSE +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/docs/comparing.md +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/docs/configuration.md +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/docs/display.md +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/docs/index.md +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/docs/loading.md +0 -0
- /arrayview-0.6.0/src/arrayview/_icon.png → /arrayview-0.8.0/docs/logo.png +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/docs/measurement.md +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/docs/stylesheets/extra.css +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/docs/viewing.md +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/scripts/demo.py +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/src/arrayview/_platform.py +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/src/arrayview/_shell.html +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/tests/conftest.py +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/tests/make_vectorfield_test_arrays.py +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/tests/test_browser.py +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/tests/test_large_arrays.py +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/tests/test_mode_matrix.py +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/tests/test_rgb_pixel_art.py +0 -0
- {arrayview-0.6.0 → arrayview-0.8.0}/vscode-extension/LICENSE +0 -0
|
@@ -222,6 +222,13 @@ These rules are codified as DOM assertions in `tests/ui_audit.py`. When a new in
|
|
|
222
222
|
| R26 | Shared colorbar width capped in compare mode | `#slim-cb-wrap` width ≤ 500px in compare mode |
|
|
223
223
|
| R27 | Mode eggs hidden during loading | `#mode-eggs` empty/hidden until at least one frame has been received |
|
|
224
224
|
| R28 | Compare+multiview crosshair fade works | Crosshair lines in compare+multiview fade in/out on hover, scoped to the hovered array's panes |
|
|
225
|
+
| R29 | Dimbar/colorbar height sync | `#info` and `#slim-cb-wrap` offsetHeight within 4px when both visible. Enforced by `_validateUIState()` (runtime) and `run_invariant_assertions()` (Playwright) |
|
|
226
|
+
| R30 | Drag positions cleared on immersive exit | `_infoDragPos`, `_cbDragPos`, `_islandDragPos` all null when `!_fullscreenActive`. Enforced by `_validateUIState()` and `run_immersive_exit_assertions()` |
|
|
227
|
+
| R31 | No .fs-overlay outside immersive | Zero `.fs-overlay` elements when `!_fullscreenActive && !_immersiveAnimating`. Enforced by `_validateUIState()` and `run_immersive_exit_assertions()` |
|
|
228
|
+
| R32 | Per-pane cbs only in immersive+center | `.compare-pane-cb-island.fs-overlay` count > 0 only when `_fullscreenActive && centerMode`. Enforced by `_validateUIState()` |
|
|
229
|
+
| R33 | Colorbar flex-direction always row | All visible `.cb-island` computed `flex-direction === 'row'`. Enforced by CSS (`!important`) and `_validateUIState()` and `run_invariant_assertions()` |
|
|
230
|
+
| R34 | Shared colorbar visible in compare non-center | `#slim-cb-wrap` not `display:none` when `compareActive && !centerMode`. Enforced by `_validateUIState()` |
|
|
231
|
+
| R35 | Islands fully inside or outside pane | Dynamic islands must be fully inside pane bounds (immersive) or fully outside in normal flow (non-immersive) — never partially overlapping |
|
|
225
232
|
|
|
226
233
|
---
|
|
227
234
|
|
|
@@ -240,6 +247,31 @@ All colorbars across ALL modes follow the same structure: `[vmin span] [gradient
|
|
|
240
247
|
|
|
241
248
|
---
|
|
242
249
|
|
|
250
|
+
## Section 5d: Enforcement Layers
|
|
251
|
+
|
|
252
|
+
Rules are enforced through multiple layers. When adding new rules, add enforcement at the appropriate layer(s):
|
|
253
|
+
|
|
254
|
+
| Layer | Mechanism | Where | When it runs |
|
|
255
|
+
|-------|-----------|-------|--------------|
|
|
256
|
+
| **CSS** | `!important` structural constraints | `_viewer.html` CSS (line ~156) | Always — structurally impossible to violate |
|
|
257
|
+
| **Runtime JS** | `_validateUIState()` | `_viewer.html` JS (after `_positionFullscreenChrome`) | After every layout change, gated behind `?debug_ui=1` |
|
|
258
|
+
| **Playwright** | `run_invariant_assertions()` + `run_immersive_exit_assertions()` | `tests/ui_audit.py` | During `ui_audit.py` runs (all tiers) |
|
|
259
|
+
| **Skill** | This document (Section 5b) | `.claude/skills/ui-consistency-audit/SKILL.md` | When AI agent invokes the skill |
|
|
260
|
+
|
|
261
|
+
**Cross-reference:**
|
|
262
|
+
|
|
263
|
+
| Rule | CSS | Runtime JS | Playwright | Notes |
|
|
264
|
+
|------|-----|-----------|------------|-------|
|
|
265
|
+
| R29 (height sync) | — | ✓ | ✓ | |
|
|
266
|
+
| R30 (drag cleanup) | — | ✓ | ✓ | Only after immersive exit |
|
|
267
|
+
| R31 (fs-overlay cleanup) | — | ✓ | ✓ | Only after immersive exit |
|
|
268
|
+
| R32 (per-pane cb visibility) | — | ✓ | — | |
|
|
269
|
+
| R33 (flex-direction row) | ✓ | ✓ | ✓ | CSS makes violation impossible |
|
|
270
|
+
| R34 (shared cb in compare) | — | ✓ | — | |
|
|
271
|
+
| R35 (island containment) | — | — | — | Design principle, enforced by code review |
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
243
275
|
## Section 6: Common Feature Categories
|
|
244
276
|
|
|
245
277
|
### Zoom / Canvas Sizing
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: visual-bug-fixing
|
|
3
|
+
description: Use when fixing any visual bug, layout glitch, rendering artifact, or UI regression in arrayview. Triggered by symptoms like overlapping elements, wrong positioning, missing components, flickering, or incorrect sizing in any view mode.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Visual Bug Fixing
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
**Fix visual bugs with screenshot evidence, not guesswork.** Capture before-state, diagnose root cause, fix, capture after-state, verify no regressions across all view modes. Never commit a visual fix without photographic proof it works and doesn't break anything else.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- User reports a visual bug (overlap, misalignment, wrong size, missing element, rendering artifact)
|
|
15
|
+
- You notice a visual inconsistency during other work
|
|
16
|
+
- A UI change causes unexpected layout shifts
|
|
17
|
+
- Screenshot diff failures in `ui_audit.py`
|
|
18
|
+
|
|
19
|
+
**When NOT to use:**
|
|
20
|
+
- Pure logic bugs with no visual impact — use superpowers:systematic-debugging
|
|
21
|
+
- Adding new UI features — use ui-consistency-audit + frontend-designer
|
|
22
|
+
- Updating test coverage — use viewer-ui-checklist
|
|
23
|
+
|
|
24
|
+
## Workflow
|
|
25
|
+
|
|
26
|
+
```dot
|
|
27
|
+
digraph visual_bug_fix {
|
|
28
|
+
rankdir=TB;
|
|
29
|
+
"Bug reported" -> "Capture baseline screenshots";
|
|
30
|
+
"Capture baseline screenshots" -> "Diagnose root cause";
|
|
31
|
+
"Diagnose root cause" -> "Implement fix";
|
|
32
|
+
"Implement fix" -> "Capture post-fix screenshots";
|
|
33
|
+
"Capture post-fix screenshots" -> "Compare before/after";
|
|
34
|
+
"Compare before/after" -> "Run full regression check";
|
|
35
|
+
"Run full regression check" -> "Regressions found?" [shape=diamond];
|
|
36
|
+
"Regressions found?" -> "Implement fix" [label="yes — iterate"];
|
|
37
|
+
"Regressions found?" -> "Show evidence & commit" [label="no"];
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Step 1: Capture Baseline Screenshots
|
|
42
|
+
|
|
43
|
+
Before touching any code, capture the current broken state. Use the Playwright MCP tools:
|
|
44
|
+
|
|
45
|
+
1. **Navigate** to the affected view using `browser_navigate` to the arrayview URL
|
|
46
|
+
2. **Set up the bug conditions** — press keys to enter the right mode, load the right array
|
|
47
|
+
3. **Screenshot the broken state** with `browser_take_screenshot` — save as `baseline_bug.png`
|
|
48
|
+
4. **Screenshot ALL other modes** that could be affected:
|
|
49
|
+
- Normal view (default)
|
|
50
|
+
- Immersive mode (`Shift+K`)
|
|
51
|
+
- Compare mode (navigate to compare URL)
|
|
52
|
+
- Diff modes (`Shift+X` to cycle)
|
|
53
|
+
- Multiview (`v`)
|
|
54
|
+
- Zen mode (`Shift+F`)
|
|
55
|
+
- Zoomed state (`+` keys)
|
|
56
|
+
|
|
57
|
+
**Key selectors to inspect** (via `browser_snapshot` or `browser_evaluate`):
|
|
58
|
+
- `#canvas-wrap` — main canvas container
|
|
59
|
+
- `#slim-cb-wrap` — colorbar wrapper
|
|
60
|
+
- `#info` — info bar
|
|
61
|
+
- `.mode-egg` — mode indicator badges
|
|
62
|
+
- `#miniview` — minimap overlay
|
|
63
|
+
- `#dimbar` — dimension navigation bar
|
|
64
|
+
|
|
65
|
+
### Step 2: Diagnose Root Cause
|
|
66
|
+
|
|
67
|
+
Read the relevant source files. The UI lives in:
|
|
68
|
+
|
|
69
|
+
| Layer | File |
|
|
70
|
+
|-------|------|
|
|
71
|
+
| HTML structure | `arrayview/_viewer.html` |
|
|
72
|
+
| Inline CSS | `<style>` blocks in `_viewer.html` |
|
|
73
|
+
| Canvas rendering | JavaScript in `_viewer.html` (search for `scaleCanvas`, `mvScaleAllCanvases`, `compareScaleCanvases`) |
|
|
74
|
+
| Python server | `arrayview/_app.py` |
|
|
75
|
+
| Colorbar logic | `ColorBar` class in `_viewer.html` |
|
|
76
|
+
|
|
77
|
+
**Diagnosis checklist:**
|
|
78
|
+
- Is this a CSS issue (positioning, overflow, z-index, flexbox)?
|
|
79
|
+
- Is this a JS layout calculation issue (wrong width/height, missing mode branch)?
|
|
80
|
+
- Does the bug only appear in certain modes? Which scale function is involved?
|
|
81
|
+
- Does zoom level matter? Check the zoom-related code paths.
|
|
82
|
+
- Is this a race condition (element rendered before data arrives)?
|
|
83
|
+
|
|
84
|
+
**Check hard rules:** Before implementing a fix, check if the bug is a violation of a hard UI rule (R1-R35 in `ui-consistency-audit` Section 5b). If so, the fix should restore the invariant rather than work around it. Enable `?debug_ui=1` to see runtime invariant warnings in the browser console — they may pinpoint the exact rule being violated.
|
|
85
|
+
|
|
86
|
+
**Use `browser_evaluate` to inspect live DOM state:**
|
|
87
|
+
```javascript
|
|
88
|
+
// Get bounding boxes of key elements
|
|
89
|
+
JSON.stringify({
|
|
90
|
+
canvas: document.querySelector('#canvas-wrap')?.getBoundingClientRect(),
|
|
91
|
+
colorbar: document.querySelector('#slim-cb-wrap')?.getBoundingClientRect(),
|
|
92
|
+
info: document.querySelector('#info')?.getBoundingClientRect(),
|
|
93
|
+
viewport: { width: window.innerWidth, height: window.innerHeight }
|
|
94
|
+
})
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Step 3: Implement the Fix
|
|
98
|
+
|
|
99
|
+
Apply the minimal fix. Reference the ui-consistency-audit design rules (R1-R35) to ensure the fix doesn't violate any existing constraints.
|
|
100
|
+
|
|
101
|
+
**Common fix patterns:**
|
|
102
|
+
- **Overlap bugs**: Check z-index hierarchy, adjust `position`/`top`/`left`/`right`
|
|
103
|
+
- **Sizing bugs**: Trace the calculation chain in the relevant scale function
|
|
104
|
+
- **Mode-specific bugs**: Ensure all mode branches handle the element (check `isCompare`, `isMultiview`, `isImmersive`, `isZen` flags)
|
|
105
|
+
- **Zoom bugs**: Check `zoomLevel` interactions with element positioning
|
|
106
|
+
|
|
107
|
+
### Step 4: Capture Post-Fix Screenshots
|
|
108
|
+
|
|
109
|
+
Repeat the exact same navigation and key presses from Step 1. Screenshot every view that was captured in the baseline.
|
|
110
|
+
|
|
111
|
+
### Step 5: Compare Before/After
|
|
112
|
+
|
|
113
|
+
Present the before and after screenshots to the user. Explain:
|
|
114
|
+
- What was broken (with baseline screenshot)
|
|
115
|
+
- What changed in the code
|
|
116
|
+
- What it looks like now (with post-fix screenshot)
|
|
117
|
+
|
|
118
|
+
### Step 6: Full Regression Check
|
|
119
|
+
|
|
120
|
+
Run the automated visual audit:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
uv run python tests/ui_audit.py --tier 1
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
This checks 14 core scenarios with DOM assertions and pixel diffs. If any fail:
|
|
127
|
+
- Read the failure output to identify which scenario regressed
|
|
128
|
+
- Screenshot that specific scenario manually to understand the regression
|
|
129
|
+
- Go back to Step 3 and iterate
|
|
130
|
+
|
|
131
|
+
For thorough validation (before committing):
|
|
132
|
+
```bash
|
|
133
|
+
uv run python tests/ui_audit.py --tier 2
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Only commit when Tier 1 passes clean.** Tier 2 failures should be investigated but may be pre-existing.
|
|
137
|
+
|
|
138
|
+
## Red Flags — STOP and Investigate
|
|
139
|
+
|
|
140
|
+
| Symptom | Likely Cause |
|
|
141
|
+
|---------|-------------|
|
|
142
|
+
| Fix works in normal mode but breaks compare | Missing branch in `compareScaleCanvases` |
|
|
143
|
+
| Element disappears on zoom | Absolute positioning without zoom-aware offsets |
|
|
144
|
+
| Fix works at 1440x900 but breaks at other sizes | Hardcoded pixel values instead of relative/calc |
|
|
145
|
+
| Colorbar overlaps canvas after fix | Forgot to account for colorbar width in canvas sizing |
|
|
146
|
+
| Mode eggs shift position | Changed a parent container's layout without updating egg anchoring |
|
|
147
|
+
|
|
148
|
+
## Common Mistakes
|
|
149
|
+
|
|
150
|
+
- **Fixing the symptom, not the cause** — A `display:none` hack hides the bug. Find why the element is in the wrong place.
|
|
151
|
+
- **Testing only the affected mode** — Visual bugs often have cross-mode impact. Always check at minimum: normal, immersive, compare, multiview.
|
|
152
|
+
- **Skipping the baseline screenshot** — Without before/after evidence, you can't prove the fix worked or detect subtle regressions.
|
|
153
|
+
- **Committing without running ui_audit.py** — DOM assertions catch things screenshots miss (off-by-1px overlaps, viewport overflow).
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Deploy docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths: [docs/**, mkdocs.yml]
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
deploy:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.x"
|
|
19
|
+
- run: pip install mkdocs-material
|
|
20
|
+
- run: mkdocs gh-deploy --force
|
|
@@ -11,6 +11,7 @@ wheels/
|
|
|
11
11
|
|
|
12
12
|
*.npy
|
|
13
13
|
.claude/
|
|
14
|
+
.worktrees/
|
|
14
15
|
!.claude/skills/
|
|
15
16
|
!.claude/skills/**
|
|
16
17
|
CLAUDE.md
|
|
@@ -24,7 +25,10 @@ tests/smoke_output/
|
|
|
24
25
|
dev/
|
|
25
26
|
*.png
|
|
26
27
|
!src/arrayview/_icon.png
|
|
28
|
+
!docs/logo.png
|
|
27
29
|
|
|
28
30
|
benchmarks/data
|
|
29
31
|
.superpowers/brainstorm
|
|
30
|
-
site/
|
|
32
|
+
site/
|
|
33
|
+
van_gogh/
|
|
34
|
+
docs/superpowers
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# ArrayView
|
|
2
|
+
|
|
3
|
+
Interactive viewer for multi-dimensional arrays and medical/scientific volumes. FastAPI server + single-file HTML/JS frontend (`_viewer.html`), displayed via native window, browser, VS Code Simple Browser, or Jupyter iframe.
|
|
4
|
+
|
|
5
|
+
## Core Files
|
|
6
|
+
|
|
7
|
+
| File | Role |
|
|
8
|
+
|------|------|
|
|
9
|
+
| `_launcher.py` | Entry points, process management, display routing |
|
|
10
|
+
| `_server.py` | FastAPI app, REST/WebSocket, HTML templates |
|
|
11
|
+
| `_session.py` | Sessions, state, caches, render thread |
|
|
12
|
+
| `_render.py` | Colormaps, LUTs, slice extraction, RGBA |
|
|
13
|
+
| `_viewer.html` | All frontend (JS/CSS embedded, single file) |
|
|
14
|
+
| `_vscode.py` | Extension management, signal-file IPC |
|
|
15
|
+
| `_stdio_server.py` | Stdio transport for direct webview mode |
|
|
16
|
+
| `_segmentation.py` | nnInteractive segmentation client |
|
|
17
|
+
|
|
18
|
+
## Skills
|
|
19
|
+
|
|
20
|
+
Load the relevant skill before touching the corresponding area.
|
|
21
|
+
|
|
22
|
+
| Skill | When |
|
|
23
|
+
|-------|------|
|
|
24
|
+
| `ui-consistency-audit` | Any visual/UI change |
|
|
25
|
+
| `frontend-designer` | Styling/layout changes to `_viewer.html` |
|
|
26
|
+
| `vscode-simplebrowser` | Extension, signal-file IPC, `_VSCODE_EXT_VERSION` |
|
|
27
|
+
| `invocation-consistency` | Server startup, display-opening, env detection |
|
|
28
|
+
| `docs-style` | README, help overlay, docstrings |
|
|
29
|
+
|
|
30
|
+
## Non-Negotiables
|
|
31
|
+
|
|
32
|
+
- Always use `localhost` (not `127.0.0.1`) -- required for VS Code port forwarding
|
|
33
|
+
- Never `--force` reinstall the extension if correct version is on disk
|
|
34
|
+
- Do not add logic to `_app.py` -- compat shim only
|
|
35
|
+
- Avoid orphan processes; shutdown must be automatic
|
|
36
|
+
- Do not regress working display paths when fixing another
|
|
37
|
+
- For visual/animation features, propose 2-3 options BEFORE implementing
|
|
38
|
+
- UI visibility changes go through reconcilers (`_reconcileUI`/`_reconcileLayout`/`_reconcileCompareState`/`_reconcileCbVisibility`), not inline `style.display` or `classList` toggles in mode functions
|
|
39
|
+
|
|
40
|
+
## Execution
|
|
41
|
+
|
|
42
|
+
Always use **subagent-driven development** for implementation. Commit completed work automatically.
|
|
43
|
+
|
|
44
|
+
## Testing
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
uv run pytest tests/test_api.py -v # HTTP API
|
|
48
|
+
uv run pytest tests/test_browser.py -v # Playwright
|
|
49
|
+
uv run python tests/visual_smoke.py # screenshots
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
After any UI change, use `/ui-consistency-audit` to verify across all modes.
|
arrayview-0.8.0/PKG-INFO
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: arrayview
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: Fast multi-dimensional array viewer
|
|
5
|
+
Project-URL: Home, https://github.com/oscarvanderheide/arrayview
|
|
6
|
+
Project-URL: Source, https://github.com/oscarvanderheide/arrayview
|
|
7
|
+
Author-email: Oscar <oscarvanderheide@example.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: array,mri,npy,viewer,visualization
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Python: >=3.12
|
|
17
|
+
Requires-Dist: fastapi>=0.129.0
|
|
18
|
+
Requires-Dist: h5py>=3.0
|
|
19
|
+
Requires-Dist: matplotlib>=3.9.0
|
|
20
|
+
Requires-Dist: nibabel>=5.3.3
|
|
21
|
+
Requires-Dist: numpy>=2.4.2
|
|
22
|
+
Requires-Dist: pillow>=12.1.1
|
|
23
|
+
Requires-Dist: pyqt5>=5.15; sys_platform == 'linux'
|
|
24
|
+
Requires-Dist: pyqtwebengine>=5.15; sys_platform == 'linux'
|
|
25
|
+
Requires-Dist: python-multipart>=0.0.22
|
|
26
|
+
Requires-Dist: pywebview>=6.1
|
|
27
|
+
Requires-Dist: qmricolors>=0.1.2
|
|
28
|
+
Requires-Dist: qtpy>=2.0; sys_platform == 'linux'
|
|
29
|
+
Requires-Dist: scipy>=1.10
|
|
30
|
+
Requires-Dist: tifffile>=2023.1.1
|
|
31
|
+
Requires-Dist: uvicorn>=0.41.0
|
|
32
|
+
Requires-Dist: websockets>=14.0
|
|
33
|
+
Requires-Dist: zarr>=2.17
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# arrayview
|
|
37
|
+
|
|
38
|
+
A viewer for multi-dimensional arrays.
|
|
39
|
+
|
|
40
|
+
- CLI and Python
|
|
41
|
+
- Jupyter / VS Code
|
|
42
|
+
- Browser / native
|
|
43
|
+
- SSH / tunnels
|
|
44
|
+
|
|
45
|
+
## CLI
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uvx arrayview scan.nii.gz
|
|
49
|
+
uvx arrayview --window browser scan.npy
|
|
50
|
+
uvx arrayview # demo
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Python
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from arrayview import view
|
|
57
|
+
view(arr)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## MATLAB
|
|
61
|
+
|
|
62
|
+
Add the `matlab/` directory to your MATLAB path, then:
|
|
63
|
+
|
|
64
|
+
```matlab
|
|
65
|
+
addpath('/path/to/arrayview/matlab')
|
|
66
|
+
|
|
67
|
+
A = rand(100, 200, 10);
|
|
68
|
+
arrayview(A)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Requires arrayview installed in [MATLAB's Python environment](https://www.mathworks.com/help/matlab/matlab_external/install-supported-python-implementation.html):
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install arrayview
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Arrays are passed zero-copy via the buffer protocol (in-process Python). `arrayview()` enables this automatically — just call it before any other `py.*` call in your MATLAB session.
|
|
78
|
+
|
|
79
|
+
## PyTorch / Deep Learning
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from arrayview import view_batch, TrainingMonitor
|
|
83
|
+
|
|
84
|
+
# Browse a DataLoader batch
|
|
85
|
+
view_batch(train_loader)
|
|
86
|
+
view_batch(train_loader, overlay='label')
|
|
87
|
+
|
|
88
|
+
# Live training monitor — updates every N epochs
|
|
89
|
+
monitor = TrainingMonitor(every=5, samples=3)
|
|
90
|
+
for epoch in range(100):
|
|
91
|
+
for batch in val_loader:
|
|
92
|
+
pred = model(batch['image'])
|
|
93
|
+
monitor.step(input=batch['image'], target=batch['label'],
|
|
94
|
+
prediction=pred, epoch=epoch)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`view_batch()` accepts DataLoaders, Datasets, dicts, tuples, or raw tensors. `TrainingMonitor` opens a compare window and calls `handle.update()` automatically. PyTorch is not required at import time.
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
## Formats
|
|
101
|
+
|
|
102
|
+
`.npy` `.npz` `.nii` `.nii.gz` `.zarr` `.pt` `.h5` `.tif` `.mat`
|
|
103
|
+
|
|
104
|
+
## Once open
|
|
105
|
+
|
|
106
|
+
`c` colormaps · `d` dynamic range · `v` 3-plane · `z` mosaic · `Shift+O` overlay toggle · `?` help · colorbar dblclick histogram
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
## nnInteractive Segmentation
|
|
110
|
+
|
|
111
|
+
`S` starts AI-assisted 3D segmentation (requires CUDA). Click/draw to segment, `Enter` to accept.
|
|
112
|
+
|
|
113
|
+
```toml
|
|
114
|
+
[nninteractive]
|
|
115
|
+
url = "http://gpu-server:1527" # skip auto-launch, use running server
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Or: `ARRAYVIEW_NNINTERACTIVE_URL=http://gpu-server:1527`
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
## Config
|
|
122
|
+
|
|
123
|
+
`~/.arrayview/config.toml`:
|
|
124
|
+
|
|
125
|
+
```toml
|
|
126
|
+
[viewer]
|
|
127
|
+
colormaps = ["gray", "viridis", "plasma"] # colormaps cycled by 'c'
|
|
128
|
+
|
|
129
|
+
[window]
|
|
130
|
+
default = "browser" # browser | native | vscode | inline
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
[Full documentation →](https://oscarvanderheide.github.io/arrayview/)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# arrayview
|
|
2
|
+
|
|
3
|
+
A viewer for multi-dimensional arrays.
|
|
4
|
+
|
|
5
|
+
- CLI and Python
|
|
6
|
+
- Jupyter / VS Code
|
|
7
|
+
- Browser / native
|
|
8
|
+
- SSH / tunnels
|
|
9
|
+
|
|
10
|
+
## CLI
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
uvx arrayview scan.nii.gz
|
|
14
|
+
uvx arrayview --window browser scan.npy
|
|
15
|
+
uvx arrayview # demo
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Python
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from arrayview import view
|
|
22
|
+
view(arr)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## MATLAB
|
|
26
|
+
|
|
27
|
+
Add the `matlab/` directory to your MATLAB path, then:
|
|
28
|
+
|
|
29
|
+
```matlab
|
|
30
|
+
addpath('/path/to/arrayview/matlab')
|
|
31
|
+
|
|
32
|
+
A = rand(100, 200, 10);
|
|
33
|
+
arrayview(A)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Requires arrayview installed in [MATLAB's Python environment](https://www.mathworks.com/help/matlab/matlab_external/install-supported-python-implementation.html):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install arrayview
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Arrays are passed zero-copy via the buffer protocol (in-process Python). `arrayview()` enables this automatically — just call it before any other `py.*` call in your MATLAB session.
|
|
43
|
+
|
|
44
|
+
## PyTorch / Deep Learning
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from arrayview import view_batch, TrainingMonitor
|
|
48
|
+
|
|
49
|
+
# Browse a DataLoader batch
|
|
50
|
+
view_batch(train_loader)
|
|
51
|
+
view_batch(train_loader, overlay='label')
|
|
52
|
+
|
|
53
|
+
# Live training monitor — updates every N epochs
|
|
54
|
+
monitor = TrainingMonitor(every=5, samples=3)
|
|
55
|
+
for epoch in range(100):
|
|
56
|
+
for batch in val_loader:
|
|
57
|
+
pred = model(batch['image'])
|
|
58
|
+
monitor.step(input=batch['image'], target=batch['label'],
|
|
59
|
+
prediction=pred, epoch=epoch)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`view_batch()` accepts DataLoaders, Datasets, dicts, tuples, or raw tensors. `TrainingMonitor` opens a compare window and calls `handle.update()` automatically. PyTorch is not required at import time.
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
## Formats
|
|
66
|
+
|
|
67
|
+
`.npy` `.npz` `.nii` `.nii.gz` `.zarr` `.pt` `.h5` `.tif` `.mat`
|
|
68
|
+
|
|
69
|
+
## Once open
|
|
70
|
+
|
|
71
|
+
`c` colormaps · `d` dynamic range · `v` 3-plane · `z` mosaic · `Shift+O` overlay toggle · `?` help · colorbar dblclick histogram
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
## nnInteractive Segmentation
|
|
75
|
+
|
|
76
|
+
`S` starts AI-assisted 3D segmentation (requires CUDA). Click/draw to segment, `Enter` to accept.
|
|
77
|
+
|
|
78
|
+
```toml
|
|
79
|
+
[nninteractive]
|
|
80
|
+
url = "http://gpu-server:1527" # skip auto-launch, use running server
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Or: `ARRAYVIEW_NNINTERACTIVE_URL=http://gpu-server:1527`
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
## Config
|
|
87
|
+
|
|
88
|
+
`~/.arrayview/config.toml`:
|
|
89
|
+
|
|
90
|
+
```toml
|
|
91
|
+
[viewer]
|
|
92
|
+
colormaps = ["gray", "viridis", "plasma"] # colormaps cycled by 'c'
|
|
93
|
+
|
|
94
|
+
[window]
|
|
95
|
+
default = "browser" # browser | native | vscode | inline
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
[Full documentation →](https://oscarvanderheide.github.io/arrayview/)
|
|
@@ -16,19 +16,40 @@ Auto-detects VS Code terminals and opens in Simple Browser. Works automatically.
|
|
|
16
16
|
|
|
17
17
|
## VS Code tunnel
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
The VS Code extension uses a **direct webview** transport: the viewer runs
|
|
20
|
+
inside a VS Code webview panel and communicates with a Python subprocess via
|
|
21
|
+
the extension host. No port forwarding or public ports are needed — everything
|
|
22
|
+
stays inside the tunnel.
|
|
20
23
|
|
|
21
24
|
```bash
|
|
22
|
-
#
|
|
23
|
-
|
|
25
|
+
arrayview volume.nii.gz # opens in a webview tab automatically
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### How it works
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
Viewer (webview) ←postMessage→ Extension Host ←stdin/stdout→ Python
|
|
24
32
|
```
|
|
25
33
|
|
|
26
|
-
|
|
34
|
+
Instead of a WebSocket connection to a running server, the extension spawns a
|
|
35
|
+
dedicated Python process per array. Slice requests travel through VS Code's
|
|
36
|
+
`postMessage` IPC and the extension relays them to Python's stdin as JSON.
|
|
37
|
+
Binary RGBA responses flow back through stdout with a length prefix.
|
|
38
|
+
|
|
39
|
+
This avoids the main limitation of the WebSocket approach: VS Code tunnels
|
|
40
|
+
don't expose arbitrary ports, so the old method required manually setting
|
|
41
|
+
port 8000 to "Public" in the Ports tab.
|
|
42
|
+
|
|
43
|
+
### Fallback: WebSocket mode
|
|
44
|
+
|
|
45
|
+
If you prefer the traditional WebSocket server (e.g. for multi-hop setups or
|
|
46
|
+
when sharing the viewer URL), you can still use it:
|
|
27
47
|
|
|
28
48
|
```bash
|
|
29
|
-
arrayview
|
|
49
|
+
arrayview --serve
|
|
30
50
|
```
|
|
31
51
|
|
|
52
|
+
Set port 8000 to Public in the VS Code Ports tab, then load arrays normally.
|
|
32
53
|
The server persists across invocations. Kill it with `arrayview --kill`.
|
|
33
54
|
|
|
34
55
|
## Multi-hop
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
function arrayview(A, varargin)
|
|
2
|
+
% ARRAYVIEW View a numeric array in the ArrayView browser viewer.
|
|
3
|
+
%
|
|
4
|
+
% arrayview(A) view array, name taken from variable name
|
|
5
|
+
% arrayview(A, 'name', 'MyName') use custom display name
|
|
6
|
+
% arrayview(A, 'port', 8200) use custom port (default 8123)
|
|
7
|
+
%
|
|
8
|
+
% Requires arrayview installed in MATLAB's Python environment:
|
|
9
|
+
% % In your system Python or conda env:
|
|
10
|
+
% pip install arrayview
|
|
11
|
+
% % Then configure MATLAB to use it:
|
|
12
|
+
% pyenv('Version', '/path/to/python')
|
|
13
|
+
%
|
|
14
|
+
% For zero-copy (recommended for large arrays), arrayview() auto-enables
|
|
15
|
+
% in-process Python. Call arrayview() before any other py.* call in your
|
|
16
|
+
% session, or set it manually:
|
|
17
|
+
% pyenv('ExecutionMode', 'InProcess')
|
|
18
|
+
|
|
19
|
+
% --- Version guard ---
|
|
20
|
+
if verLessThan('matlab', '9.11') % 9.11 = R2021b
|
|
21
|
+
error('arrayview:unsupportedVersion', ...
|
|
22
|
+
'arrayview() requires MATLAB R2021b (9.11) or later.');
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
% --- Auto-configure in-process Python for zero-copy ---
|
|
26
|
+
% Must happen before Python is first loaded in this session.
|
|
27
|
+
pe = pyenv();
|
|
28
|
+
if strcmp(pe.Status, 'NotLoaded')
|
|
29
|
+
pyenv('ExecutionMode', 'InProcess');
|
|
30
|
+
elseif strcmp(pe.ExecutionMode, 'OutOfProcess')
|
|
31
|
+
warning('arrayview:outOfProcess', '%s', ...
|
|
32
|
+
['ArrayView: Python is running out-of-process — the array will be ' ...
|
|
33
|
+
'copied (doubles memory).' newline 'To avoid this, restart MATLAB and call ' ...
|
|
34
|
+
'arrayview() before any other py.* call.']);
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
% --- Parse arguments ---
|
|
38
|
+
defname = inputname(1);
|
|
39
|
+
if isempty(defname)
|
|
40
|
+
defname = 'Array';
|
|
41
|
+
end
|
|
42
|
+
p = inputParser;
|
|
43
|
+
addParameter(p, 'name', defname);
|
|
44
|
+
addParameter(p, 'port', 8123);
|
|
45
|
+
parse(p, varargin{:});
|
|
46
|
+
|
|
47
|
+
% --- Input validation ---
|
|
48
|
+
validateattributes(A, {'numeric', 'logical'}, {}, 'arrayview', 'A');
|
|
49
|
+
|
|
50
|
+
% --- Wrap array (zero-copy in in-process mode via buffer protocol) ---
|
|
51
|
+
try
|
|
52
|
+
np = py.importlib.import_module('numpy');
|
|
53
|
+
catch ME
|
|
54
|
+
error('arrayview:numpyNotFound', ...
|
|
55
|
+
['NumPy not found in MATLAB''s Python environment.' newline ...
|
|
56
|
+
'Install it with:' newline ...
|
|
57
|
+
' pip install numpy' newline ...
|
|
58
|
+
'Then restart MATLAB. (Error: %s)'], ME.message);
|
|
59
|
+
end
|
|
60
|
+
arr = np.asarray(A);
|
|
61
|
+
|
|
62
|
+
% --- Call arrayview.view() ---
|
|
63
|
+
try
|
|
64
|
+
av = py.importlib.import_module('arrayview');
|
|
65
|
+
catch ME
|
|
66
|
+
error('arrayview:notInstalled', ...
|
|
67
|
+
['ArrayView Python package not found.' newline ...
|
|
68
|
+
'Install it in MATLAB''s Python environment:' newline ...
|
|
69
|
+
' pip install arrayview' newline ...
|
|
70
|
+
'Then restart MATLAB. (Error: %s)'], ME.message);
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
av.view(arr, name=p.Results.name, port=int32(p.Results.port));
|
|
74
|
+
end
|