arrayview 0.14.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.
- {arrayview-0.14.0 → arrayview-0.16.0}/.claude/skills/ui-consistency-audit/SKILL.md +2 -2
- {arrayview-0.14.0 → arrayview-0.16.0}/.claude/skills/viewer-ui-checklist/SKILL.md +3 -3
- arrayview-0.16.0/.github/copilot-instructions.md +39 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/.gitignore +2 -1
- arrayview-0.16.0/.ignore +1 -0
- arrayview-0.16.0/.mex/AGENTS.md +9 -0
- arrayview-0.16.0/.mex/ROUTER.md +86 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/.mex/SETUP.md +1 -1
- arrayview-0.16.0/.mex/context/architecture.md +90 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/.mex/context/conventions.md +12 -5
- arrayview-0.16.0/.mex/context/decisions.md +78 -0
- arrayview-0.16.0/.mex/context/frontend.md +146 -0
- arrayview-0.16.0/.mex/context/project-state.md +45 -0
- arrayview-0.16.0/.mex/context/render-pipeline.md +110 -0
- arrayview-0.16.0/.mex/context/setup.md +66 -0
- arrayview-0.16.0/.mex/context/stack.md +59 -0
- arrayview-0.16.0/.mex/patterns/INDEX.md +29 -0
- arrayview-0.16.0/.mex/patterns/add-file-format.md +96 -0
- arrayview-0.16.0/.mex/patterns/add-server-endpoint.md +86 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/.mex/patterns/debug-render.md +7 -3
- {arrayview-0.14.0 → arrayview-0.16.0}/.mex/patterns/frontend-change.md +28 -18
- arrayview-0.16.0/.opencode/opencode.json +4 -0
- arrayview-0.16.0/.vscode/settings.json +208 -0
- arrayview-0.16.0/AGENTS.md +50 -0
- arrayview-0.16.0/IMMERSIVE_ANIMATION.md +43 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/PKG-INFO +2 -1
- {arrayview-0.14.0 → arrayview-0.16.0}/docs/loading.md +6 -1
- {arrayview-0.14.0 → arrayview-0.16.0}/docs/remote.md +3 -3
- arrayview-0.16.0/plans/2026-04-14-immersive-animation.md +388 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/pyproject.toml +2 -1
- {arrayview-0.14.0 → arrayview-0.16.0}/src/arrayview/ARCHITECTURE.md +3 -3
- {arrayview-0.14.0 → arrayview-0.16.0}/src/arrayview/_app.py +2 -1
- {arrayview-0.14.0 → arrayview-0.16.0}/src/arrayview/_io.py +8 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/src/arrayview/_launcher.py +452 -302
- {arrayview-0.14.0 → arrayview-0.16.0}/src/arrayview/_platform.py +23 -1
- {arrayview-0.14.0 → arrayview-0.16.0}/src/arrayview/_render.py +55 -40
- {arrayview-0.14.0 → arrayview-0.16.0}/src/arrayview/_server.py +215 -20
- {arrayview-0.14.0 → arrayview-0.16.0}/src/arrayview/_session.py +104 -15
- {arrayview-0.14.0 → arrayview-0.16.0}/src/arrayview/_shell.html +80 -27
- {arrayview-0.14.0 → arrayview-0.16.0}/src/arrayview/_stdio_server.py +51 -13
- {arrayview-0.14.0 → arrayview-0.16.0}/src/arrayview/_viewer.html +2447 -878
- {arrayview-0.14.0 → arrayview-0.16.0}/src/arrayview/_vscode.py +25 -16
- arrayview-0.16.0/src/arrayview/arrayview-opener.vsix +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/tests/test_api.py +176 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/tests/test_browser.py +29 -7
- {arrayview-0.14.0 → arrayview-0.16.0}/tests/test_interactions.py +4 -8
- arrayview-0.16.0/tests/test_loading_server.py +107 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/tests/test_mode_roundtrip.py +0 -28
- {arrayview-0.14.0 → arrayview-0.16.0}/tests/visual_smoke.py +34 -30
- {arrayview-0.14.0 → arrayview-0.16.0}/uv.lock +1103 -1
- arrayview-0.16.0/vscode-extension/AGENTS.md +26 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/vscode-extension/extension.js +25 -127
- {arrayview-0.14.0 → arrayview-0.16.0}/vscode-extension/package.json +11 -1
- arrayview-0.14.0/.mex/AGENTS.md +0 -33
- arrayview-0.14.0/.mex/ROUTER.md +0 -76
- arrayview-0.14.0/.mex/context/architecture.md +0 -89
- arrayview-0.14.0/.mex/context/decisions.md +0 -71
- arrayview-0.14.0/.mex/context/setup.md +0 -68
- arrayview-0.14.0/.mex/context/stack.md +0 -60
- arrayview-0.14.0/.mex/patterns/INDEX.md +0 -11
- arrayview-0.14.0/.mex/patterns/add-file-format.md +0 -70
- arrayview-0.14.0/.mex/patterns/vscode-display.md +0 -113
- arrayview-0.14.0/AGENTS.md +0 -65
- arrayview-0.14.0/src/arrayview/arrayview-opener.vsix +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/.claude/skills/invocation-consistency/SKILL.md +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/.claude/skills/modes-consistency/SKILL.md +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/.claude/skills/visual-bug-fixing/SKILL.md +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/.github/workflows/docs.yml +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/.github/workflows/python-publish.yml +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/.mex/SYNC.md +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/.mex/patterns/README.md +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/.python-version +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/CONTRIBUTING.md +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/LICENSE +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/README.md +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/docs/comparing.md +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/docs/configuration.md +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/docs/display.md +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/docs/index.md +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/docs/logo.png +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/docs/measurement.md +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/docs/stylesheets/extra.css +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/docs/viewing.md +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/matlab/arrayview.m +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/mkdocs.yml +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/plans/webview/LOG.md +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/scripts/demo.py +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/scripts/release.sh +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/src/arrayview/__init__.py +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/src/arrayview/__main__.py +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/src/arrayview/_config.py +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/src/arrayview/_icon.png +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/src/arrayview/_segmentation.py +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/src/arrayview/_torch.py +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/src/arrayview/gsap.min.js +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/tests/conftest.py +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/tests/make_vectorfield_test_arrays.py +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/tests/test_cli.py +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/tests/test_command_reachability.py +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/tests/test_config.py +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/tests/test_cross_mode_parametrized.py +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/tests/test_large_arrays.py +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/tests/test_mode_consistency.py +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/tests/test_mode_matrix.py +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/tests/test_nifti_meta.py +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/tests/test_rgb_pixel_art.py +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/tests/test_torch.py +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/tests/test_view_component_integration.py +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/tests/test_view_component_unit.py +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/tests/ui_audit.py +0 -0
- {arrayview-0.14.0 → arrayview-0.16.0}/vscode-extension/LICENSE +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ui-consistency-audit
|
|
3
|
-
description: Use when
|
|
3
|
+
description: Use when the user explicitly requests a full visual audit, when validating UI work for a release, or when diagnosing a cross-mode visual regression in arrayview. Proactively identifies all affected mode combinations, prescribes per-mode behavior, checks for UI clashes, and runs a Playwright-based visual audit after implementation. Absorbs and replaces the modes-consistency skill.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# ArrayView UI Consistency Audit
|
|
7
7
|
|
|
8
8
|
## Rule
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
This is the **full** visual audit path. Do not invoke it by default for every UI change. During normal feature development, do targeted verification for the specific area touched. Use this skill when the user explicitly asks for a broad visual check, when a regression spans modes/layouts, or when validating UI work for a release. This skill has two phases: a **proactive phase** (before coding) that plans the cross-mode behavior, and a **reactive phase** (after coding) that runs an automated visual audit.
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: viewer-ui-checklist
|
|
3
|
-
description: Use when
|
|
3
|
+
description: Use when the user explicitly asks to sync UI docs/help/test coverage, or when preparing a UI change for release. Ensures visual_smoke.py, help overlay, and docs/ stay in sync.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# ArrayView UI Checklist
|
|
7
7
|
|
|
8
8
|
## Rule
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
This is the release/explicit-sync checklist for UI work. Do not invoke it by default for every UI edit during development. Use it when the user explicitly asks for the full sync, or when preparing a UI change for release.
|
|
11
11
|
|
|
12
12
|
`README.md` is intentionally minimal and stable — do **not** add per-feature shortcuts or behavior to it. User-facing docs live in `docs/` and are organized by topic (`display.md`, `viewing.md`, `comparing.md`, `loading.md`, `measurement.md`, `remote.md`, `configuration.md`).
|
|
13
13
|
|
|
@@ -19,7 +19,7 @@ Every UI change to arrayview MUST be reflected in `tests/visual_smoke.py`, the h
|
|
|
19
19
|
- Layout changes (canvas sizing, colorbar position, overlays)
|
|
20
20
|
- New overlay, dialog, or panel
|
|
21
21
|
|
|
22
|
-
## Steps (
|
|
22
|
+
## Steps (when this checklist is invoked)
|
|
23
23
|
|
|
24
24
|
1. **Update coverage table** at the top of `visual_smoke.py`
|
|
25
25
|
- If shortcut is now testable: change `✗` to `✓ NN` with scenario number
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agents
|
|
3
|
+
description: Always-loaded project anchor. Read this first. Contains project identity, non-negotiables, commands, and pointer to ROUTER.md for full context.
|
|
4
|
+
last_updated: [YYYY-MM-DD]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# [Project Name]
|
|
8
|
+
|
|
9
|
+
## What This Is
|
|
10
|
+
<!-- One sentence. What does this project do?
|
|
11
|
+
Length: 1 sentence maximum.
|
|
12
|
+
Not a tagline — a factual description of what the software does.
|
|
13
|
+
Example: "A REST API for managing inventory across multiple warehouse locations." -->
|
|
14
|
+
|
|
15
|
+
## Non-Negotiables
|
|
16
|
+
<!-- Hard rules the agent must never violate. Not preferences — rules.
|
|
17
|
+
These are the things that, if broken, cause real damage to the codebase.
|
|
18
|
+
Length: 3-7 items maximum. More than 7 means the list has not been prioritised.
|
|
19
|
+
Example:
|
|
20
|
+
- Never write database queries outside of the repository layer
|
|
21
|
+
- Never commit secrets or API keys
|
|
22
|
+
- Always handle errors explicitly — no silent failures -->
|
|
23
|
+
|
|
24
|
+
## Commands
|
|
25
|
+
<!-- The exact commands needed to work on this project.
|
|
26
|
+
Include: run dev server, run tests, run linter, build.
|
|
27
|
+
Use the actual commands from this codebase — not placeholders.
|
|
28
|
+
Example:
|
|
29
|
+
- Dev: `npm run dev`
|
|
30
|
+
- Test: `npm test`
|
|
31
|
+
- Lint: `npm run lint`
|
|
32
|
+
- Build: `npm run build` -->
|
|
33
|
+
|
|
34
|
+
## After Every Task
|
|
35
|
+
After completing any task: update `.mex/ROUTER.md` project state and any `.mex/` files that are now out of date. If no pattern existed for the task you just completed, create one in `.mex/patterns/`.
|
|
36
|
+
|
|
37
|
+
## Navigation
|
|
38
|
+
At the start of every session, read `.mex/ROUTER.md` before doing anything else.
|
|
39
|
+
For full project context, patterns, and task guidance — everything is there.
|
arrayview-0.16.0/.ignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.worktrees/
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: router
|
|
3
|
+
description: Navigation hub for task routing, project state, and behavioral guidance. Start here, then load the minimum extra context needed for the task.
|
|
4
|
+
edges:
|
|
5
|
+
- target: context/project-state.md
|
|
6
|
+
condition: when the task depends on what is currently shipped, in progress, or recently changed
|
|
7
|
+
- target: context/architecture.md
|
|
8
|
+
condition: when working on system design, integrations, or understanding how components connect
|
|
9
|
+
- target: context/stack.md
|
|
10
|
+
condition: when working with specific technologies, libraries, or making tech decisions
|
|
11
|
+
- target: context/conventions.md
|
|
12
|
+
condition: when writing new code, reviewing code, or unsure about project patterns
|
|
13
|
+
- target: context/decisions.md
|
|
14
|
+
condition: when making architectural choices or understanding why something is built a certain way
|
|
15
|
+
- target: context/setup.md
|
|
16
|
+
condition: when setting up the dev environment or running the project for the first time
|
|
17
|
+
- target: context/frontend.md
|
|
18
|
+
condition: when working on _viewer.html — modes, reconcilers, command registry, View Component System
|
|
19
|
+
- target: context/render-pipeline.md
|
|
20
|
+
condition: when working on rendering, colormaps, LUTs, caching, or the render thread
|
|
21
|
+
- target: patterns/INDEX.md
|
|
22
|
+
condition: when starting a task — check the pattern index for a matching pattern file
|
|
23
|
+
last_updated: 2026-04-17
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
# arrayview — Router
|
|
27
|
+
|
|
28
|
+
## What This Is
|
|
29
|
+
Python package for interactively viewing multi-dimensional arrays (numpy, NIfTI, zarr, etc.) with a FastAPI backend, single-file HTML/JS frontend, and multi-environment display routing (Jupyter, VS Code, SSH, native window).
|
|
30
|
+
|
|
31
|
+
## Non-Negotiables
|
|
32
|
+
- Never split `_viewer.html` — entire frontend is one self-contained file, no build step
|
|
33
|
+
- All heavy imports (numpy, matplotlib, nibabel, FastAPI, uvicorn) must be lazy — CLI fast path stays near-zero cost
|
|
34
|
+
- New rendering features must be consistent across all six invocation environments
|
|
35
|
+
- Global state lives in `_session.py` only — `SESSIONS`, `SERVER_LOOP`, `VIEWER_SOCKETS` never redefined elsewhere
|
|
36
|
+
- Render thread must remain a raw `threading.Thread` + `SimpleQueue`, not `concurrent.futures`
|
|
37
|
+
|
|
38
|
+
## Commands
|
|
39
|
+
- Test: `uv run pytest tests/`
|
|
40
|
+
- Visual smoke: `uv run pytest tests/visual_smoke.py`
|
|
41
|
+
- CLI: `uvx arrayview <file>`
|
|
42
|
+
- Build: `uv build`
|
|
43
|
+
|
|
44
|
+
## Context Budget
|
|
45
|
+
|
|
46
|
+
1. Start with this file plus **at most one pattern and one context file**.
|
|
47
|
+
2. Treat frontmatter `edges` as **optional suggestions**, not a preload list.
|
|
48
|
+
3. Follow **one extra edge only when blocked**. Do not recursively fan out through second-hop edges.
|
|
49
|
+
4. Load `context/project-state.md` only when the task depends on current shipped or in-progress work.
|
|
50
|
+
5. For UI, animation, or behavior bugs: ask plain-English clarification questions **before** reading git history, large diffs, or broad source sweeps.
|
|
51
|
+
|
|
52
|
+
## Current Project State
|
|
53
|
+
|
|
54
|
+
Load `context/project-state.md` only when you need active-workstream or recent-shipping detail.
|
|
55
|
+
|
|
56
|
+
## Routing Table
|
|
57
|
+
|
|
58
|
+
| Task type | Load |
|
|
59
|
+
|-----------|------|
|
|
60
|
+
| Checking current shipped / in-progress status | `context/project-state.md` |
|
|
61
|
+
| Understanding system architecture | `context/architecture.md` |
|
|
62
|
+
| Working with a specific technology | `context/stack.md` |
|
|
63
|
+
| Writing or reviewing code | `context/conventions.md` |
|
|
64
|
+
| Making a design decision | `context/decisions.md` |
|
|
65
|
+
| Setting up or running the project | `context/setup.md` |
|
|
66
|
+
| Editing `_viewer.html` (frontend) | `context/frontend.md` + `patterns/frontend-change.md` |
|
|
67
|
+
| Render pipeline, colormaps, caching | `context/render-pipeline.md` |
|
|
68
|
+
| Adding a new file format | `patterns/add-file-format.md` |
|
|
69
|
+
| Adding a server route / WebSocket endpoint | `patterns/add-server-endpoint.md` |
|
|
70
|
+
| Visual bugs / render artifacts | `patterns/debug-render.md` |
|
|
71
|
+
| Any specific task | Check `patterns/INDEX.md` for a matching pattern |
|
|
72
|
+
|
|
73
|
+
## Behavioural Contract
|
|
74
|
+
|
|
75
|
+
1. **CONTEXT** — Load from the routing table. Start small: this file + one pattern + one context file is the default cap. Do not preload unrelated docs.
|
|
76
|
+
2. **CLARIFY** — If the task is about UI behavior, animation, or UX expectations, ask plain-English questions before deep investigation.
|
|
77
|
+
3. **BUILD** — Do the work. If deviating from an established pattern, say so before writing code.
|
|
78
|
+
4. **VERIFY** — If a pattern file was loaded, use its Verify section. Otherwise use the shared Verify Checklist in `context/conventions.md`.
|
|
79
|
+
5. **DEBUG** — If verification fails, check `patterns/INDEX.md` for one debug pattern, fix, then re-run VERIFY.
|
|
80
|
+
6. **GROW** — After completing the task: create/update patterns, update stale context files, and update `context/project-state.md` if significant.
|
|
81
|
+
|
|
82
|
+
## After Completing a Task
|
|
83
|
+
|
|
84
|
+
- [ ] Update `context/project-state.md` if shipped or in-progress status changed
|
|
85
|
+
- [ ] Update any stale `.mex/context/` or `.mex/patterns/` files touched by the task
|
|
86
|
+
- [ ] If this revealed a repeatable workflow, add or update a pattern in `.mex/patterns/`
|
|
@@ -224,5 +224,5 @@ A well-populated scaffold should give the agent enough to:
|
|
|
224
224
|
Once the scaffold is populated, use these to keep it aligned with your codebase:
|
|
225
225
|
|
|
226
226
|
- **`mex check`** — detect drift (zero tokens, zero AI)
|
|
227
|
-
- **`.mex/
|
|
227
|
+
- **`.mex/SYNC.md`** — interactive drift check + targeted or full resync
|
|
228
228
|
- **`mex watch`** — auto drift score after every commit
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: architecture
|
|
3
|
+
description: How the major pieces of arrayview connect and flow. Load when working on system design, integrations, or understanding how components interact.
|
|
4
|
+
triggers:
|
|
5
|
+
- "architecture"
|
|
6
|
+
- "system design"
|
|
7
|
+
- "how does X connect to Y"
|
|
8
|
+
- "integration"
|
|
9
|
+
- "flow"
|
|
10
|
+
- "data flow"
|
|
11
|
+
- "display routing"
|
|
12
|
+
edges:
|
|
13
|
+
- target: context/stack.md
|
|
14
|
+
condition: when specific technology details are needed
|
|
15
|
+
- target: context/decisions.md
|
|
16
|
+
condition: when understanding why the architecture is structured this way
|
|
17
|
+
- target: context/frontend.md
|
|
18
|
+
condition: when the task involves _viewer.html, modes, reconcilers, or the View Component System
|
|
19
|
+
- target: context/render-pipeline.md
|
|
20
|
+
condition: when the task involves slice extraction, colormaps, caching, or the render thread
|
|
21
|
+
last_updated: 2026-04-15
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# Architecture
|
|
25
|
+
|
|
26
|
+
## System Overview
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
CLI / Python API (view() or uvx arrayview <file>)
|
|
30
|
+
└─ _launcher.py → FastAPI server (_server.py, uvicorn) [network mode]
|
|
31
|
+
→ _stdio_server.py [VS Code direct webview]
|
|
32
|
+
|
|
33
|
+
Server (either mode)
|
|
34
|
+
├─ _session.py Session objects, global state, render thread, caches
|
|
35
|
+
├─ _render.py extract_slice → apply_complex_mode → render_rgba → PNG pipeline
|
|
36
|
+
└─ _io.py load_data() — npy/npz/nii/zarr/h5/mat/tif/pt routing
|
|
37
|
+
|
|
38
|
+
Browser (_viewer.html — single self-contained HTML+JS+CSS file)
|
|
39
|
+
├─ WebSocket /ws/{sid} binary PNG slices from server
|
|
40
|
+
├─ GET /meta/{sid} session metadata on first load
|
|
41
|
+
└─ Canvas + UI colorbars, eggs, dynamic islands, mode transitions
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
A `view()` call creates a `Session`, starts the FastAPI server if not running,
|
|
45
|
+
registers the session via HTTP POST `/load`, then opens a display for the detected
|
|
46
|
+
environment (Jupyter inline, VS Code webview panel or direct webview, native
|
|
47
|
+
pywebview, or system browser).
|
|
48
|
+
|
|
49
|
+
## Key Components
|
|
50
|
+
|
|
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
|
+
- **`_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
|
+
- **`_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
|
+
- **`_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
|
+
- **`_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
|
+
- **`_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, webview panel and direct webview opening.
|
|
58
|
+
- **`_stdio_server.py`** — Alternative to FastAPI for VS Code tunnel (direct webview): JSON on stdin, length-prefixed binary on stdout.
|
|
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
|
+
|
|
61
|
+
## Display Routing
|
|
62
|
+
|
|
63
|
+
| Environment | Default display | Server mode |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| Jupyter | Inline iframe | network |
|
|
66
|
+
| VS Code local | Webview panel | network |
|
|
67
|
+
| VS Code tunnel | Direct webview (stdio) | stdio |
|
|
68
|
+
| Julia | System browser | network |
|
|
69
|
+
| CLI / Python script | Native pywebview | network |
|
|
70
|
+
| SSH terminal | Prints URL — user forwards port with `ssh -L` | network |
|
|
71
|
+
|
|
72
|
+
Detection logic: `_platform.py`. Display opening: `_launcher.py` + `_vscode.py`.
|
|
73
|
+
|
|
74
|
+
## External Dependencies
|
|
75
|
+
|
|
76
|
+
- **FastAPI + uvicorn** — async HTTP/WebSocket server, lazy-imported in `_launcher.py`. Never call uvicorn directly — use the `_uvicorn()` accessor.
|
|
77
|
+
- **nibabel** — NIfTI file loading. Lazy-imported in `_io.py` via `_nib()`. Only loaded for `.nii` / `.nii.gz`.
|
|
78
|
+
- **numpy** — Core array type throughout. The only non-lazy import in the render path.
|
|
79
|
+
- **matplotlib** — Colormap LUT generation only. Lazy, initialized once by `_init_luts()` in `_render.py`.
|
|
80
|
+
- **qmricolors** — Registers the `lipari` and `navia` colormaps. Git dependency (`oscarvanderheide/qmricolors`).
|
|
81
|
+
- **zarr** — Lazy chunk access for `.zarr` / `.zarr.zip`. Chunk presets via `zarr_chunk_preset()` in `_session.py`.
|
|
82
|
+
- **pywebview** — Native OS window. Lazy, only started when `_can_native_window()` is true.
|
|
83
|
+
|
|
84
|
+
## What Does NOT Exist Here
|
|
85
|
+
|
|
86
|
+
- No persistent storage — sessions are in-memory only; nothing is written to disk by the server.
|
|
87
|
+
- No authentication or multi-user access control — server binds to localhost.
|
|
88
|
+
- No build step for the frontend — `_viewer.html` is a single static file served from package resources.
|
|
89
|
+
- No background job queue — heavy ops run in the render thread or prefetch pool, both owned by `_session.py`.
|
|
90
|
+
- No nnInteractive server — `_segmentation.py` is a pure HTTP client to a separately running nnInteractive process.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: conventions
|
|
3
|
-
description: How code is written in arrayview — naming, structure, lazy imports,
|
|
3
|
+
description: How code is written in arrayview — naming, structure, lazy imports, frontend organization, and the shared Verify Checklist.
|
|
4
4
|
triggers:
|
|
5
5
|
- "convention"
|
|
6
6
|
- "pattern"
|
|
@@ -14,9 +14,11 @@ edges:
|
|
|
14
14
|
condition: when a convention depends on understanding the system structure
|
|
15
15
|
- target: context/stack.md
|
|
16
16
|
condition: when the convention is library-specific
|
|
17
|
-
- target:
|
|
18
|
-
condition: when
|
|
19
|
-
|
|
17
|
+
- target: context/frontend.md
|
|
18
|
+
condition: when applying frontend conventions (section separators, reconcilers, command registry)
|
|
19
|
+
- target: context/render-pipeline.md
|
|
20
|
+
condition: when applying render pipeline conventions (pipeline order, cache patterns, LUT usage)
|
|
21
|
+
last_updated: 2026-04-15
|
|
20
22
|
---
|
|
21
23
|
|
|
22
24
|
# Conventions
|
|
@@ -77,6 +79,11 @@ Always check in this priority order. Never short-circuit.
|
|
|
77
79
|
|
|
78
80
|
**Float rendering convention** — slices are always converted to `np.float32` before applying colormap. RGB uint8 arrays skip colormap and go directly to RGBA. Complex arrays are transformed by `apply_complex_mode()` before any colormap step.
|
|
79
81
|
|
|
82
|
+
## Context Discipline
|
|
83
|
+
|
|
84
|
+
- Load this as the shared reference alongside a task pattern; do not use it to fan out into more task-specific files.
|
|
85
|
+
- If you need a different task workflow, go back to `ROUTER.md` or `patterns/INDEX.md` and pick one pattern deliberately.
|
|
86
|
+
|
|
80
87
|
## Verify Checklist
|
|
81
88
|
|
|
82
89
|
Before presenting any code change:
|
|
@@ -86,4 +93,4 @@ Before presenting any code change:
|
|
|
86
93
|
- [ ] Frontend changes are in `_viewer.html` only — no new JS/CSS files
|
|
87
94
|
- [ ] New rendering functions follow the `extract_slice → apply_complex_mode → apply_colormap_rgba` pipeline order
|
|
88
95
|
- [ ] Environment detection changes go in `_platform.py`, not inline in `_launcher.py` or `_vscode.py`
|
|
89
|
-
- [ ] Cross-mode consistency: any new visual feature
|
|
96
|
+
- [ ] Cross-mode consistency: any new visual feature is checked in all six invocation environments when the task or verification scope requires it
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: decisions
|
|
3
|
+
description: Key architectural and technical decisions with reasoning. Load when making design choices or understanding why something is built a certain way.
|
|
4
|
+
triggers:
|
|
5
|
+
- "why do we"
|
|
6
|
+
- "why is it"
|
|
7
|
+
- "decision"
|
|
8
|
+
- "alternative"
|
|
9
|
+
- "we chose"
|
|
10
|
+
edges:
|
|
11
|
+
- target: context/architecture.md
|
|
12
|
+
condition: when a decision relates to system structure
|
|
13
|
+
- target: context/stack.md
|
|
14
|
+
condition: when a decision relates to technology choice
|
|
15
|
+
- target: context/frontend.md
|
|
16
|
+
condition: when a decision relates to the frontend architecture or viewer modes
|
|
17
|
+
last_updated: 2026-04-16
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Decisions
|
|
21
|
+
|
|
22
|
+
## Decision Log
|
|
23
|
+
|
|
24
|
+
### Single self-contained _viewer.html — no build step
|
|
25
|
+
**Date:** pre-2024 (initial design)
|
|
26
|
+
**Status:** Active
|
|
27
|
+
**Decision:** The entire frontend (CSS + JS) lives in one `_viewer.html` file with no bundler or build step.
|
|
28
|
+
**Reasoning:** The package is installed as a Python wheel. Shipping a single file that works as a package resource eliminates build infrastructure (npm, webpack, etc.) and makes the project trivially installable. The file is served directly from package resources via `importlib.resources`.
|
|
29
|
+
**Alternatives considered:** Separate JS/CSS files with a bundler (rejected — adds npm as a required toolchain and a build step to every contribution); Jinja2 templating (rejected — adds complexity without benefit when one file is sufficient).
|
|
30
|
+
**Consequences:** Frontend is ~15 600 lines in one file. All edits happen in `_viewer.html` only — never create companion JS/CSS files.
|
|
31
|
+
|
|
32
|
+
### Lazy imports everywhere in _launcher.py
|
|
33
|
+
**Date:** 2024 (extracted from _app.py)
|
|
34
|
+
**Status:** Active
|
|
35
|
+
**Decision:** `_launcher.py` does not import numpy, _session, _render, _io, or uvicorn at module level. All are deferred to first use via accessor functions (`_server_mod()`, `_uvicorn()`, etc.) or `_LazyMod`.
|
|
36
|
+
**Reasoning:** The CLI fast path — when the server is already running — only needs to send a `/load` HTTP request. Eager imports add ~300–350 ms per invocation, which is unacceptable for a tool used interactively dozens of times per session.
|
|
37
|
+
**Alternatives considered:** Importing everything eagerly (rejected — too slow); using `importlib.import_module` everywhere (equivalent, but the `_mod_cache = None` + accessor pattern is more readable and explicit).
|
|
38
|
+
**Consequences:** Any new heavy import added to `_launcher.py` must follow the accessor pattern. Violating this breaks the fast path.
|
|
39
|
+
|
|
40
|
+
### Global state lives exclusively in _session.py
|
|
41
|
+
**Date:** 2024 (modular refactor from _app.py)
|
|
42
|
+
**Status:** Active
|
|
43
|
+
**Decision:** `SESSIONS`, `SERVER_LOOP`, `VIEWER_SOCKETS`, `VIEWER_SIDS`, `SHELL_SOCKETS`, `_RENDER_QUEUE`, `_RENDER_THREAD` are all defined in `_session.py` and imported by name elsewhere. No other module defines or shadows these.
|
|
44
|
+
**Reasoning:** Before the refactor, global state was scattered across `_app.py`. Centralizing it in one module makes concurrent access patterns explicit and prevents accidental redefinition.
|
|
45
|
+
**Alternatives considered:** Thread-local storage (rejected — server loop and sessions need to be shared across threads); a dedicated `State` singleton class (rejected — no benefit over module-level globals for this use case).
|
|
46
|
+
**Consequences:** Any new global mutable state must be added to `_session.py`. Never redefine these names in `_server.py`, `_launcher.py`, or anywhere else.
|
|
47
|
+
|
|
48
|
+
### Render thread is threading.Thread + SimpleQueue, not concurrent.futures
|
|
49
|
+
**Date:** 2024
|
|
50
|
+
**Status:** Active
|
|
51
|
+
**Decision:** The CPU-bound render work runs in a raw `threading.Thread` driven by `_queue.SimpleQueue`, not a `ThreadPoolExecutor`.
|
|
52
|
+
**Reasoning:** During Python interpreter shutdown, `concurrent.futures` sets a `_global_shutdown` flag that prevents submitting new work. Because the server can still receive requests during shutdown, this caused render failures. Raw `threading.Thread` with `daemon=True` is unaffected by the executor lifecycle.
|
|
53
|
+
**Alternatives considered:** `ThreadPoolExecutor` (rejected — shutdown race); `asyncio.run_in_executor` with default executor (same issue).
|
|
54
|
+
**Consequences:** The prefetch pool *does* use `concurrent.futures.ThreadPoolExecutor` (it submits non-critical background work and gracefully handles `RuntimeError` on shutdown). Only the critical render path must stay on `SimpleQueue`.
|
|
55
|
+
|
|
56
|
+
### stdio transport for VS Code tunnel (direct webview)
|
|
57
|
+
**Date:** 2024
|
|
58
|
+
**Status:** Active
|
|
59
|
+
**Decision:** When a VS Code tunnel is detected, `_stdio_server.py` replaces FastAPI+WebSocket. Messages are JSON on stdin, length-prefixed binary on stdout. The VS Code extension bridges `postMessage` ↔ subprocess stdio.
|
|
60
|
+
**Reasoning:** VS Code tunnel environments block arbitrary TCP ports. The Direct WebView API bypasses the network entirely by running the viewer as a subprocess of the extension. This gives reliable display without requiring port forwarding.
|
|
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
|
+
**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
|
+
|
|
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
|
+
|
|
72
|
+
### UI visibility changes go through reconcilers, not ad hoc style/classList calls
|
|
73
|
+
**Date:** 2025 (View Component System refactor)
|
|
74
|
+
**Status:** Active
|
|
75
|
+
**Decision:** All visibility and layout state changes in `_viewer.html` must go through the four reconciler functions (unified UI reconciler, layout container visibility, compare sub-mode state, CB/island visibility).
|
|
76
|
+
**Reasoning:** Before reconcilers, mode-switch functions each managed their own ad hoc `style.display` toggles. This caused regressions where fixing one mode's layout broke another. Reconcilers enforce a single source of truth for UI state.
|
|
77
|
+
**Alternatives considered:** Each mode function manages its own visibility (previous approach, rejected due to regression rate); CSS class toggling with no reconciler (rejected — same problem in a different form).
|
|
78
|
+
**Consequences:** When adding a new UI element, wire it into the appropriate reconciler. Never set `style.display` or toggle classes in mode-entry/exit functions — that work belongs in the reconcilers.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: frontend
|
|
3
|
+
description: _viewer.html internals — mode matrix, reconcilers, command registry, View Component System, CSS architecture. Load for any task touching _viewer.html.
|
|
4
|
+
triggers:
|
|
5
|
+
- "_viewer.html"
|
|
6
|
+
- "frontend"
|
|
7
|
+
- "mode"
|
|
8
|
+
- "reconciler"
|
|
9
|
+
- "keybind"
|
|
10
|
+
- "colorbar"
|
|
11
|
+
- "canvas"
|
|
12
|
+
- "dynamic island"
|
|
13
|
+
- "egg"
|
|
14
|
+
- "command registry"
|
|
15
|
+
- "View Component"
|
|
16
|
+
- "modeManager"
|
|
17
|
+
edges:
|
|
18
|
+
- target: context/architecture.md
|
|
19
|
+
condition: when understanding how the frontend connects to the server
|
|
20
|
+
- target: context/decisions.md
|
|
21
|
+
condition: when understanding why the frontend is a single file or why reconcilers exist
|
|
22
|
+
- target: context/conventions.md
|
|
23
|
+
condition: when writing new frontend code and need section separator conventions
|
|
24
|
+
- target: patterns/frontend-change.md
|
|
25
|
+
condition: when making a concrete change to _viewer.html
|
|
26
|
+
last_updated: 2026-04-17
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
# Frontend (_viewer.html)
|
|
30
|
+
|
|
31
|
+
~15 600 lines. Single file, no build step. All CSS and JS inline.
|
|
32
|
+
|
|
33
|
+
## Section Organization
|
|
34
|
+
|
|
35
|
+
Section separators: `/* ── Section Name ── */` in CSS, `// ── Section Name ──` in JS.
|
|
36
|
+
|
|
37
|
+
**CSS (lines ~7–1500)**
|
|
38
|
+
| Section | What it covers |
|
|
39
|
+
|---------|----------------|
|
|
40
|
+
| Theme Variables and Base Layout | CSS custom properties, dark theme palette, root layout grid |
|
|
41
|
+
| ColorBar and Dynamic Islands | Colorbar positioning, egg badges, info bar, dimension sliders |
|
|
42
|
+
| Generic ColorBar class styles | Shared `.av-colorbar` styles used by the `ColorBar` JS class |
|
|
43
|
+
| Compare, Overlay, and Prompt Styles | Side-by-side panes, overlay blend, prompt dialogs |
|
|
44
|
+
| Help and Info Overlays | Help shortcut overlay, array-info panel |
|
|
45
|
+
| Immersive, Fullscreen, and Compact Mode | Zen mode hide rules, fullscreen layout, compact overrides |
|
|
46
|
+
|
|
47
|
+
**JavaScript (lines ~1500–14750)**
|
|
48
|
+
| Section | What it covers |
|
|
49
|
+
|---------|----------------|
|
|
50
|
+
| Constants and Transport Setup | WS URL construction, stdio/postMessage transport abstraction |
|
|
51
|
+
| Viewer State Variables | All mutable state: slice indices, zoom, mode flags |
|
|
52
|
+
| Mode Registry | Mode name → enter/exit function mapping |
|
|
53
|
+
| PanManager | Canvas panning state machine |
|
|
54
|
+
| Canvas Scaling and Layout | `scaleCanvas()`, `mvScaleAllCanvases()`, `compareScaleCanvases()`, `qvScaleAllCanvases()` |
|
|
55
|
+
| Compare Mode | Multi-pane compare infrastructure, drag-to-reorder |
|
|
56
|
+
| Colorbar Rendering and Histogram | Colorbar draw routines, histogram morph |
|
|
57
|
+
| ColorBar class | Reusable `ColorBar` class (~900 lines) |
|
|
58
|
+
| WebSocket and Data Transport | Binary slice receive, request queueing, reconnect |
|
|
59
|
+
| Initialization and Metadata Fetch | `/meta` fetch, loading screen |
|
|
60
|
+
| Info Bar and Pixel Display | Bottom info bar, hover pixel readout |
|
|
61
|
+
| State Persistence and Restore | URL hash state, `sessionStorage` save/restore |
|
|
62
|
+
| Rendering Pipeline | `updateView()`, play/animate, screenshot |
|
|
63
|
+
| ROI and Selection Modes | Rectangle/circle/freehand ROI, statistics computation, qMRI cross-pane mirroring |
|
|
64
|
+
| nnInteractive Segmentation | Click-to-segment UI, mask overlay, undo stack |
|
|
65
|
+
| Keyboard Shortcuts | Command registry + command palette |
|
|
66
|
+
| Mode Transitions | Compare/multiview/qMRI enter/exit, crosshair animation |
|
|
67
|
+
| Scroll, Zoom, and Pan | Mouse wheel, pinch zoom |
|
|
68
|
+
| Immersive Mode, Cross-Fade, and Visual Effects | Zen mode, fullscreen, animated transitions |
|
|
69
|
+
| UI Validation and Reconciliation | Reconcilers (~line 13666) |
|
|
70
|
+
| Colormap Strip and Wipe/Flicker Compare Tools | A/B wipe, flicker, checkerboard |
|
|
71
|
+
| Ruler, Line Profile, and Mini-Map | Distance measurement, intensity profile |
|
|
72
|
+
| Compact Mode and Touch Input | Touch gesture handling |
|
|
73
|
+
| File Picker and Session Management | File browser, drag-and-drop |
|
|
74
|
+
|
|
75
|
+
## Mode Matrix
|
|
76
|
+
|
|
77
|
+
| Mode | Scale function | `modeManager.modeName` |
|
|
78
|
+
|------|---------------|------------------------|
|
|
79
|
+
| Normal | `scaleCanvas()` | `'normal'` |
|
|
80
|
+
| Immersive (Zen) | `scaleCanvas()` | `'normal'` |
|
|
81
|
+
| Compact | `scaleCanvas()` | `'normal'` |
|
|
82
|
+
| Multiview (3-pane oblique) | `mvScaleAllCanvases()` | `'multiview'` |
|
|
83
|
+
| Compare | `compareScaleCanvases()` | `'compare'` |
|
|
84
|
+
| Diff / Registration / Wipe/Flicker/Checker | `compareScaleCanvases()` | `'compare'` |
|
|
85
|
+
| Compare + MV | `compareMvScaleAllCanvases()` | `'compare-mv'` |
|
|
86
|
+
| Compare + qMRI | `compareQmriScaleAllCanvases()` | `'compare-qmri'` |
|
|
87
|
+
| qMRI | `qvScaleAllCanvases()` | `'qmri'` |
|
|
88
|
+
| qMRI Mosaic | `qvScaleAllCanvases()` | `'qmri-mosaic'` |
|
|
89
|
+
| MIP (3-D volume) | N/A (WebGL) | `'mip'` |
|
|
90
|
+
|
|
91
|
+
## Key Concepts
|
|
92
|
+
|
|
93
|
+
### Reconcilers (~line 13666)
|
|
94
|
+
Four functions enforcing consistent UI state across mode changes:
|
|
95
|
+
1. **Unified UI reconciler** — master state enforcer
|
|
96
|
+
2. **Layout container visibility reconciler** — show/hide mode-specific containers
|
|
97
|
+
3. **Compare sub-mode state reconciler** — diff/overlay/wipe/flicker/checkerboard
|
|
98
|
+
4. **CB / island visibility reconciler** — colorbar and dynamic island show/hide
|
|
99
|
+
|
|
100
|
+
**Rule:** All visibility changes go through reconcilers. Never set `style.display` or toggle classes in mode-entry/exit functions — that belongs in the reconcilers.
|
|
101
|
+
|
|
102
|
+
### Command Registry
|
|
103
|
+
Three tables: `commands` (id → `{title, when, run}`), `keybinds` (key+modifiers → command id), `makeContext(state)` (mode/state flag bag). `dispatchCommand(e)` is the keydown prefix handler.
|
|
104
|
+
|
|
105
|
+
**Rule:** When adding or changing a keybind, update both `commands`/`keybinds` tables AND `GUIDE_TABS` (the static data structure that renders the help overlay). Do not edit overlay HTML directly.
|
|
106
|
+
|
|
107
|
+
### View Component System (Phases 1–15 complete)
|
|
108
|
+
Introduces `View`, `Slicer`, `Layer`, `LayoutStrategy`, `modeManager` alongside the legacy render pipeline.
|
|
109
|
+
|
|
110
|
+
| Primitive | What it owns |
|
|
111
|
+
|-----------|-------------|
|
|
112
|
+
| `makeDisplayState(overrides)` | Plain-object factory: `{vmin, vmax, cmapIdx, logScale, complexMode, renderMode, projectionMode, …}` |
|
|
113
|
+
| `class View` | Canvas + ColorBar + DisplayState + Slicer + Layers[]. Has `init()`, `render()`, `requestRender()` |
|
|
114
|
+
| `class Slicer` | `FreeSliceSlicer`, `OrthogonalSlicer(axis)` |
|
|
115
|
+
| `class Layer` | `CrosshairLayer`, `VectorFieldLayer`, `OverlayLayer` — duck-typed, composable |
|
|
116
|
+
| `class LayoutStrategy` | `NormalLayout`, `MultiViewLayout`, `CompareLayout`, `QmriLayout`, etc. |
|
|
117
|
+
| `const modeManager` | Singleton: `{currentViews[], currentLayout, modeName, enterMode(), …}` |
|
|
118
|
+
|
|
119
|
+
**Sync-block pattern:** Every `enterXxx()` function ends with a block that creates thin `View` wrappers over existing legacy canvases/colorbars and populates `modeManager.currentViews` + `modeName`. The legacy render pipeline is unchanged.
|
|
120
|
+
|
|
121
|
+
**Dual-write pattern:** Where commands update legacy globals (`logScale`, `colormap_idx`, etc.), they also write to `view.displayState.xField` so both systems stay in sync.
|
|
122
|
+
|
|
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
|
+
|
|
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
|
+
|
|
128
|
+
### Eggs
|
|
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.
|
|
130
|
+
|
|
131
|
+
### Dynamic Islands
|
|
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.
|
|
136
|
+
|
|
137
|
+
## CSS Architecture
|
|
138
|
+
|
|
139
|
+
- **Dark theme only.** `#0c0c0c` background, `#d8d8d8` text, `#f5c842` yellow accents.
|
|
140
|
+
- **No CSS framework.** All custom properties in `:root`.
|
|
141
|
+
- **Mode-specific overrides** use `.immersive-mode`, `.compact-mode`, `.multiview-mode` classes on `body`.
|
|
142
|
+
- **`av-loading` class** on `body` during initial load — hides UI until data arrives.
|
|
143
|
+
- **Canvas size management:** `scaleCanvas()` (and variants) are the only legitimate way to set canvas dimensions. Never set canvas `width`/`height` directly.
|
|
144
|
+
|
|
145
|
+
## WebSocket Binary Protocol
|
|
146
|
+
Slice data arrives as raw RGBA bytes. Header format and byte offsets are tightly coupled between `_server.py` (Python) and the WS handler in `_viewer.html` (JS). Changes to one must match the other exactly.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: project-state
|
|
3
|
+
description: Current working, in-progress, and shelved status. Load only when the task depends on active workstreams or recent shipped behavior.
|
|
4
|
+
triggers:
|
|
5
|
+
- "current state"
|
|
6
|
+
- "what's in progress"
|
|
7
|
+
- "recent work"
|
|
8
|
+
- "active feature"
|
|
9
|
+
- "shipped recently"
|
|
10
|
+
last_updated: 2026-04-17
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Project State
|
|
14
|
+
|
|
15
|
+
## Working
|
|
16
|
+
|
|
17
|
+
- CLI (`uvx arrayview file.npy`) and Python API (`view(arr)`) — both stable
|
|
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
|
+
- File formats: `.npy`, `.npz`, `.nii`/`.nii.gz`, `.zarr`, `.h5`/`.hdf5`, `.mat`, `.tif`/`.tiff`, `.pt`/`.pth`
|
|
20
|
+
- Rendering pipeline: colormaps, complex modes, mosaic, RGB/RGBA, projections, overlays
|
|
21
|
+
- NIfTI spatial metadata, RAS resampling
|
|
22
|
+
- VS Code extension v0.14.5 — stable window ID via `EnvironmentVariableCollection`; `arrayview.openInFloatingWindow` setting moves new tabs to a floating window; `view(arr, floating=True)` and `arrayview file.npy --floating` open in a floating window per-call regardless of global setting; `!vscode.env.remoteName` guard removed (remote VS Code supports floating windows); floating mode now uses a single persistent shell hub panel (`_shell.html`) so all arrays share one floating window as tabs instead of opening separate windows; fixed: second CLI call now injects tab via `new_tab` postMessage relay (extension -> hub wrapper -> shell iframe) instead of relying on WebSocket notify which wasn't sent in VS Code mode
|
|
23
|
+
- Colorbar refactor: `ColorBar` JS class partially migrated (in progress)
|
|
24
|
+
- Colorbar island flip: `c` and `d` keys trigger 3D `rotateX` card flip (front=colorbar, back=cmap thumbnails/histogram)
|
|
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
|
|
36
|
+
|
|
37
|
+
## In Progress
|
|
38
|
+
|
|
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
|
|
41
|
+
|
|
42
|
+
## Not Yet Built
|
|
43
|
+
|
|
44
|
+
- Independent split view for mismatched-shape arrays (designed, shelved)
|
|
45
|
+
- Admin/config UI (file-based `~/.arrayview/config.toml` only)
|