arrayview 0.31.1__tar.gz → 0.31.3__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.31.1 → arrayview-0.31.3}/.github/workflows/launch-contracts.yml +4 -4
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/context/lifecycle.md +7 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/context/project-state.md +6 -5
- {arrayview-0.31.1 → arrayview-0.31.3}/PKG-INFO +1 -1
- {arrayview-0.31.1 → arrayview-0.31.3}/docs/index.md +2 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/docs/loading.md +4 -45
- arrayview-0.31.3/docs/stack-overlays.md +129 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/mkdocs.yml +1 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/pyproject.toml +1 -1
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_instance_registry.py +6 -0
- arrayview-0.31.3/src/arrayview/_overlays.py +150 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_routes_loading.py +2 -1
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_routes_rendering.py +12 -1
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_routes_websocket.py +15 -1
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_session.py +1 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_vscode_browser.py +17 -7
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_vscode_extension.py +174 -23
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_vscode_signal.py +28 -0
- arrayview-0.31.3/src/arrayview/arrayview-opener.vsix +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_api.py +71 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_cli.py +2 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_display_opener_results.py +20 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_lifecycle_contract.py +125 -4
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_vscode_ack_protocol.py +50 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/uv.lock +1 -1
- {arrayview-0.31.1 → arrayview-0.31.3}/vscode-extension/extension.js +98 -26
- {arrayview-0.31.1 → arrayview-0.31.3}/vscode-extension/lifecycle_helpers.js +27 -1
- {arrayview-0.31.1 → arrayview-0.31.3}/vscode-extension/package.json +1 -1
- {arrayview-0.31.1 → arrayview-0.31.3}/vscode-extension/test_lifecycle_helpers.js +26 -7
- arrayview-0.31.1/src/arrayview/_overlays.py +0 -76
- arrayview-0.31.1/src/arrayview/arrayview-opener.vsix +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.agents/skills/frontend-designer/SKILL.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.agents/skills/invocation-consistency/SKILL.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.agents/skills/modes-consistency/SKILL.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.agents/skills/playwright-cli/SKILL.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.agents/skills/playwright-cli/references/element-attributes.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.agents/skills/playwright-cli/references/playwright-tests.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.agents/skills/playwright-cli/references/request-mocking.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.agents/skills/playwright-cli/references/running-code.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.agents/skills/playwright-cli/references/session-management.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.agents/skills/playwright-cli/references/storage-state.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.agents/skills/playwright-cli/references/test-generation.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.agents/skills/playwright-cli/references/tracing.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.agents/skills/playwright-cli/references/video-recording.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.agents/skills/todo-workflow/SKILL.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.agents/skills/ui-consistency-audit/SKILL.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.agents/skills/viewer-ui-checklist/SKILL.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.agents/skills/visual-bug-fixing/SKILL.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.github/copilot-instructions.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.github/workflows/docs.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.github/workflows/python-publish.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.gitignore +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.ignore +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/AGENTS.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/ROUTER.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/SETUP.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/SYNC.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/context/architecture.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/context/conventions.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/context/decisions.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/context/frontend.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/context/render-pipeline.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/context/setup.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/context/stack.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/patterns/INDEX.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/patterns/README.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/patterns/add-file-format.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/patterns/add-server-endpoint.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/patterns/animation-verify.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/patterns/debug-render.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/patterns/debug-vscode-extension-python.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/patterns/extend-compare-mode.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/patterns/extract-server-route-module.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/patterns/frontend-change.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/setup.sh +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.mex/sync.sh +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.opencode/opencode.json +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T18-46-49-737Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T18-48-21-979Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T18-51-35-665Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T19-07-01-393Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T19-14-37-969Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T19-21-30-940Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T19-23-08-126Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T19-29-33-155Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T19-31-25-336Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T19-31-53-789Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T19-39-12-257Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T19-39-16-449Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T20-15-25-513Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T20-25-13-179Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T20-39-01-435Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T21-01-27-659Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T21-01-41-283Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T21-03-00-625Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T21-04-12-887Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T21-33-39-044Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T21-38-01-530Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T21-45-20-383Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T21-55-11-545Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T21-56-03-307Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T21-56-35-733Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T21-57-12-181Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T21-57-37-748Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T21-58-13-679Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-06T22-37-23-895Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T00-39-18-637Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T01-41-46-243Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T04-31-48-472Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T12-14-15-632Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T12-14-47-582Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T12-16-23-471Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T12-17-10-247Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T12-18-24-707Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T12-20-06-164Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T12-20-28-342Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T12-21-54-962Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T12-22-34-666Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T12-23-11-336Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T12-23-36-260Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T12-24-09-267Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T12-24-35-434Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T12-25-57-010Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T12-34-48-823Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T12-46-46-468Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T12-48-17-930Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T12-49-26-400Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T12-50-31-563Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/page-2026-05-07T12-56-45-568Z.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.playwright-cli/theme-dark.yml +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.python-version +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/.vscode/settings.json +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/AGENTS.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/CONTRIBUTING.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/DESIGN.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/LICENSE +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/README.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/docs/comparing.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/docs/configuration.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/docs/display.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/docs/foreign-hosts.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/docs/launch-routing-refactor.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/docs/lifecycle.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/docs/logo.png +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/docs/measurement.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/docs/remote.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/docs/stylesheets/extra.css +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/docs/viewing.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/docs/vscode-window-routing-plan.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/experiments/ws_tunnel_diag/ws_tunnel_diag.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/matlab/arrayview.m +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/plans/2026-04-14-immersive-animation.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/plans/2026-05-07-unified-colormap-picker.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/plans/arrayview_tool_menu_fix_prompt.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/plans/arrayview_tool_menu_implementation_plan.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/plans/egg-placement-mockup.html +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/plans/refactoring_plan.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/plans/roi-analysis-requirements.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/plans/webview/LOG.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/scripts/demo.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/scripts/release.sh +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/ARCHITECTURE.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/__init__.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/__main__.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_analysis.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_app.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_codex_open.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_config.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_diff.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_icon.png +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_imaging.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_io.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_launch_plan.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_launcher.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_lifecycle.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_platform.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_render.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_routes_analysis.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_routes_export.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_routes_persistence.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_routes_preload.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_routes_query.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_routes_segmentation.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_routes_state.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_routes_vectorfield.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_segmentation.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_server.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_session_spec.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_shell.html +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_synthetic_mri.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_torch.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_vectorfield.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_viewer.html +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/_vscode.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/src/arrayview/gsap.min.js +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/capture_v_animation.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/conftest.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/lifecycle_matrix.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/make_vectorfield_test_arrays.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_backend_shared.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_browser.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_colorbar_hover_highlight.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_command_reachability.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_config.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_cross_mode_parametrized.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_doctor_commands.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_instance_registry.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_interactions.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_invocation_contract_matrix.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_io.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_large_arrays.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_launch_diagnostics.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_launch_plan.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_loading_server.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_mode_consistency.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_mode_matrix.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_mode_roundtrip.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_nifti_meta.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_nifti_series.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_rgb_pixel_art.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_safe_server_stop.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_server_identity_status.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_session_spec.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_torch.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_view_component_integration.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_view_component_unit.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_view_handle_lifecycle.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/test_view_launch_execution.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/ui_audit.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/v_anim_frames/.gitkeep +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/tests/visual_smoke.py +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/vscode-extension/AGENTS.md +0 -0
- {arrayview-0.31.1 → arrayview-0.31.3}/vscode-extension/LICENSE +0 -0
|
@@ -30,12 +30,12 @@ jobs:
|
|
|
30
30
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
31
31
|
runs-on: ${{ matrix.os }}
|
|
32
32
|
steps:
|
|
33
|
-
- uses: actions/checkout@
|
|
34
|
-
- uses: actions/setup-python@
|
|
33
|
+
- uses: actions/checkout@v7
|
|
34
|
+
- uses: actions/setup-python@v6
|
|
35
35
|
with:
|
|
36
36
|
python-version: "3.12"
|
|
37
|
-
- uses: astral-sh/setup-uv@
|
|
38
|
-
- uses: actions/setup-node@
|
|
37
|
+
- uses: astral-sh/setup-uv@v8
|
|
38
|
+
- uses: actions/setup-node@v6
|
|
39
39
|
with:
|
|
40
40
|
node-version: "22"
|
|
41
41
|
- run: uv sync --frozen --group dev --group test
|
|
@@ -70,6 +70,13 @@ This contract describes who owns the backend, when it starts, and what closes it
|
|
|
70
70
|
- `release_session()` is the session-release primitive.
|
|
71
71
|
- Viewer WebSocket connect/disconnect owns active viewer counts.
|
|
72
72
|
- URL panel disposal must release every SID encoded in the URL: `sid`, `compare_sid`, `compare_sids`, and `overlay_sid`.
|
|
73
|
+
- Tunnel panel disposal posts release requests to the local backend immediately;
|
|
74
|
+
the forwarded public URL is display-only and is not the cleanup authority.
|
|
75
|
+
- Reused file and collection sessions acquire one lease per tab, so closing one
|
|
76
|
+
tab cannot invalidate another tab that shares the same SID.
|
|
77
|
+
- A VS Code readiness ACK includes the live opener version and is terminal only
|
|
78
|
+
after the requested SID exists and the viewer reports its first rendered frame.
|
|
79
|
+
- Older ArrayView packages must not delete or downgrade a newer installed opener.
|
|
73
80
|
- Tunnel registration cleanup must not remove live same-tunnel sibling windows.
|
|
74
81
|
- Explicit cleanup wins over implicit disappearance.
|
|
75
82
|
- Any VS Code extension source change must rebuild `src/arrayview/arrayview-opener.vsix` and keep the packaged version in sync.
|
|
@@ -7,7 +7,7 @@ triggers:
|
|
|
7
7
|
- "recent work"
|
|
8
8
|
- "active feature"
|
|
9
9
|
- "shipped recently"
|
|
10
|
-
last_updated: 2026-
|
|
10
|
+
last_updated: 2026-07-13
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# Project State
|
|
@@ -21,7 +21,7 @@ last_updated: 2026-06-30
|
|
|
21
21
|
- Backend transport: FastAPI HTTP/WebSocket is the single viewer transport; shared helpers keep route modules small for metadata/analysis, compare/diff, overlay compositing, and vector field layout/arrow sampling.
|
|
22
22
|
- NIfTI spatial metadata, RAS resampling
|
|
23
23
|
- Directory collections are header-scanned and lazy by default: compatible files form a dense virtual stack, mixed shapes use a ragged collection, and `--load lazy|eager` plus `--stack-policy auto|dense|ragged` make both choices explicit. Compressed NIfTI volumes use a byte-bounded LRU cache; `view_dir()` exposes the same controls.
|
|
24
|
-
- VS Code extension v0.14.
|
|
24
|
+
- VS Code extension v0.14.41 — stable per-window routing, queued concurrent requests, remote-only tunnel claims, live opener-version ACKs, first-frame readiness, and immediate local-backend session release. An installed-but-stale extension host now fails with a clear one-time reload instruction instead of opening an unverified blank tab; older cached ArrayView packages never delete a newer opener.
|
|
25
25
|
- Colorbar refactor: `ColorBar` JS class partially migrated (in progress)
|
|
26
26
|
- Colormap picker: `c` opens an expanded colorbar-island grid without changing the colormap; subsequent `c` taps cycle, hover/hjkl/arrows live-preview, Enter/click commits, Esc cancels, and auto-dismiss pauses while hovered
|
|
27
27
|
- Cold-start loading spinner in VS Code and native shell
|
|
@@ -48,9 +48,10 @@ last_updated: 2026-06-30
|
|
|
48
48
|
patient/modality directory layouts pair automatically. `--case-regex` remains
|
|
49
49
|
an override for unusual layouts. CLI header scans show an in-place file counter
|
|
50
50
|
and elapsed time on interactive terminals.
|
|
51
|
-
- Reused collection sessions are protected by per-tab leases, including
|
|
52
|
-
overlay sessions
|
|
53
|
-
|
|
51
|
+
- Reused file and collection sessions are protected by per-tab leases, including
|
|
52
|
+
collection overlay sessions. The VS Code opener verifies the local SID before
|
|
53
|
+
creating a panel and waits for the viewer's first rendered frame before
|
|
54
|
+
acknowledging `backend_ready`.
|
|
54
55
|
- 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
|
|
55
56
|
- 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 tool menu
|
|
56
57
|
- Compare center tool menu: `/` now re-opens the last-used compare center mode from the tool menu, while compare pane header buttons select diff / overlay / wipe directly. Eligible two-array compare layouts can switch into a big-left arrangement with a wider center pane, and the diff colorbar now matches that center-pane width.
|
|
@@ -30,6 +30,8 @@ MATLAB and Julia setup: [MATLAB and Julia](foreign-hosts.md).
|
|
|
30
30
|
|
|
31
31
|
Remote and tunnel setup: [Remote](remote.md).
|
|
32
32
|
|
|
33
|
+
Stacked patient collections and masks: [Stack and Overlay Collections](stack-overlays.md).
|
|
34
|
+
|
|
33
35
|
## Formats
|
|
34
36
|
|
|
35
37
|
`.npy` `.npz` `.nii` `.nii.gz` `.zarr` `.pt` `.h5` `.tif` `.mat`
|
|
@@ -85,7 +85,7 @@ Native windows also show a static preview while the live viewer warms up.
|
|
|
85
85
|
```bash
|
|
86
86
|
uvx arrayview base.npy moving.npy # compare mode
|
|
87
87
|
uvx arrayview volume.nii.gz --overlay mask.nii.gz
|
|
88
|
-
uvx arrayview volume.nii.gz --overlay "ground truth=
|
|
88
|
+
uvx arrayview volume.nii.gz --overlay "ground truth=mask_gt.nii.gz" --overlay "prediction=mask_pred.nii.gz"
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
An unnamed overlay uses the filename stem as its display name (`mask` above).
|
|
@@ -99,8 +99,7 @@ shows all overlays at once.
|
|
|
99
99
|
|
|
100
100
|
## Directory Pattern Collections
|
|
101
101
|
|
|
102
|
-
Use `--stack` when reviewing many arrays.
|
|
103
|
-
files recursively; passing patterns keeps the aligned channel/overlay workflow.
|
|
102
|
+
Use `--stack` when reviewing many arrays.
|
|
104
103
|
|
|
105
104
|
```bash
|
|
106
105
|
uvx arrayview --stack scans/
|
|
@@ -115,48 +114,8 @@ dense` requires matching shapes, while `--stack-policy ragged` forces collection
|
|
|
115
114
|
semantics. `--load eager` is intended for small datasets that should be loaded
|
|
116
115
|
up front.
|
|
117
116
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
```bash
|
|
121
|
-
uvx arrayview --stack "data/**/*_0000.nii.gz" "data/**/*_0001.nii.gz"
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
Overlays become named recursive patterns:
|
|
125
|
-
|
|
126
|
-
```bash
|
|
127
|
-
uvx arrayview --stack \
|
|
128
|
-
"data/**/*_0000.nii.gz" \
|
|
129
|
-
"data/**/*_0001.nii.gz" \
|
|
130
|
-
--overlay "gt=data/**/labels/**/*.nii.gz" \
|
|
131
|
-
--overlay "pred=data/**/pred/**/*.nii.gz"
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
When every case has a directory containing separately named masks,
|
|
135
|
-
`--overlay-dir` discovers one overlay role per filename. Masks that are absent
|
|
136
|
-
for a case are shown as empty. ArrayView infers the shared case directory from
|
|
137
|
-
layouts such as `sub-0001/T1_W/` and `sub-0001/masks/`:
|
|
138
|
-
|
|
139
|
-
```bash
|
|
140
|
-
uvx arrayview --stack "data/*/T1_W/*.nii.gz" \
|
|
141
|
-
--overlay-dir "data/*/masks"
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
Repeat `--overlay-dir` to combine masks from multiple per-case directories.
|
|
145
|
-
|
|
146
|
-
ArrayView pairs files by sorted match order. The first match from each pattern
|
|
147
|
-
belongs to the first case, the second match belongs to the second case, and so
|
|
148
|
-
on. Use `--dry-run` to inspect matches without opening a viewer.
|
|
149
|
-
|
|
150
|
-
```bash
|
|
151
|
-
uvx arrayview --stack "data/**/*_0000.nii.gz" --overlay "gt=data/**/labels/*.nii.gz" --dry-run
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
For unusual or ambiguous layouts, pass a regex with a `case` group to override
|
|
155
|
-
automatic directory inference:
|
|
156
|
-
|
|
157
|
-
```bash
|
|
158
|
-
uvx arrayview --stack "data/**/*.nii.gz" --case-regex "(?P<case>patient-[0-9]+)"
|
|
159
|
-
```
|
|
117
|
+
Pattern collections, named overlays, multiple overlays, and `--overlay-dir`:
|
|
118
|
+
[Stack and Overlay Collections](stack-overlays.md).
|
|
160
119
|
|
|
161
120
|
## NIfTI Series (4D/5D from a directory)
|
|
162
121
|
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Stack and Overlay Collections
|
|
2
|
+
|
|
3
|
+
Review many arrays with aligned image and mask patterns.
|
|
4
|
+
|
|
5
|
+
## Patient Stack
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uvx arrayview --stack "data/*/T1w.nii.gz"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`*` matches one path segment. `**` matches recursively:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uvx arrayview --stack "data/**/*.nii.gz"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
ArrayView sorts matches and adds a collection index dimension. Use `--dry-run`
|
|
18
|
+
to inspect pairings without opening the viewer:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
uvx arrayview --stack "data/*/T1w.nii.gz" --dry-run
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## MR Contrasts
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
uvx arrayview --stack \
|
|
28
|
+
"data/*/T1w.nii.gz" \
|
|
29
|
+
"data/*/T2w.nii.gz" \
|
|
30
|
+
"data/*/FLAIR.nii.gz"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Each positional pattern becomes a contrast dimension. The first `T1w`, first
|
|
34
|
+
`T2w`, and first `FLAIR` belong to patient 1; the second match from each pattern
|
|
35
|
+
belongs to patient 2.
|
|
36
|
+
|
|
37
|
+
For ambiguous layouts, define the patient key:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
uvx arrayview --stack "data/**/*.nii.gz" \
|
|
41
|
+
--case-regex "(?P<case>sub-[0-9]+)"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Ragged Shapes
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
uvx arrayview --stack "data/*/T1w.nii.gz" --stack-policy ragged
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`--stack-policy auto` uses a dense stack when shapes match and ragged mode when
|
|
51
|
+
they do not. `--stack-policy dense` requires matching shapes.
|
|
52
|
+
|
|
53
|
+
## One Overlay
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
uvx arrayview --stack "data/*/T1w.nii.gz" \
|
|
57
|
+
--overlay "lesion=data/*/masks/lesion.nii.gz"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Use `name=pattern` to control the overlay label. Without `name=`, the filename
|
|
61
|
+
stem is used.
|
|
62
|
+
|
|
63
|
+
## Multiple Overlays
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
uvx arrayview --stack "data/*/T1w.nii.gz" \
|
|
67
|
+
--overlay "gt=data/*/masks/gt.nii.gz" \
|
|
68
|
+
--overlay "pred=data/*/masks/pred.nii.gz"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Overlays are paired to cases by sorted match order unless `--case-regex` is
|
|
72
|
+
provided.
|
|
73
|
+
|
|
74
|
+
## Overlay Directories
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
uvx arrayview --stack "data/*/T1w.nii.gz" \
|
|
78
|
+
--overlay-dir "data/*/masks"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`--overlay-dir` creates one overlay role per mask filename inside each matched
|
|
82
|
+
directory. Missing masks are shown as empty overlays.
|
|
83
|
+
|
|
84
|
+
Repeat it to combine mask folders:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
uvx arrayview --stack "data/*/T1w.nii.gz" \
|
|
88
|
+
--overlay-dir "data/*/manual_masks" \
|
|
89
|
+
--overlay-dir "data/*/model_masks"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Complete Example
|
|
93
|
+
|
|
94
|
+
```text
|
|
95
|
+
data/
|
|
96
|
+
sub-001/
|
|
97
|
+
anat/T1w.nii.gz
|
|
98
|
+
anat/T2w.nii.gz
|
|
99
|
+
masks/lesion.nii.gz
|
|
100
|
+
masks/edema.nii.gz
|
|
101
|
+
sub-002/
|
|
102
|
+
anat/T1w.nii.gz
|
|
103
|
+
anat/T2w.nii.gz
|
|
104
|
+
masks/lesion.nii.gz
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
uvx arrayview --stack \
|
|
109
|
+
"data/*/anat/T1w.nii.gz" \
|
|
110
|
+
"data/*/anat/T2w.nii.gz" \
|
|
111
|
+
--overlay-dir "data/*/masks" \
|
|
112
|
+
--stack-policy auto \
|
|
113
|
+
--dry-run
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Remove `--dry-run` to open the viewer.
|
|
117
|
+
|
|
118
|
+
## Compressed NIfTI Performance
|
|
119
|
+
|
|
120
|
+
`.nii.gz` files are lazy through nibabel, but gzip compression limits random
|
|
121
|
+
slice access. Large compressed collections can feel slower when scrolling across
|
|
122
|
+
many patients or contrasts.
|
|
123
|
+
|
|
124
|
+
For repeated review, prefer uncompressed `.nii`, chunked `.zarr`, or a local
|
|
125
|
+
fast disk cache. Use `--load eager` only for small collections that fit in RAM:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
uvx arrayview --stack "data/*/T1w.nii.gz" --load eager
|
|
129
|
+
```
|
|
@@ -115,11 +115,17 @@ def _windows_process_start(pid: int) -> str | None:
|
|
|
115
115
|
handle = kernel32.OpenProcess(0x1000, False, pid)
|
|
116
116
|
if not handle:
|
|
117
117
|
return None
|
|
118
|
+
still_active = 259
|
|
119
|
+
exit_code = ctypes.c_ulong()
|
|
118
120
|
creation = ctypes.c_ulonglong()
|
|
119
121
|
exit_time = ctypes.c_ulonglong()
|
|
120
122
|
kernel = ctypes.c_ulonglong()
|
|
121
123
|
user = ctypes.c_ulonglong()
|
|
122
124
|
try:
|
|
125
|
+
if not kernel32.GetExitCodeProcess(handle, ctypes.byref(exit_code)):
|
|
126
|
+
return None
|
|
127
|
+
if exit_code.value != still_active:
|
|
128
|
+
return None
|
|
123
129
|
ok = kernel32.GetProcessTimes(handle, ctypes.byref(creation),
|
|
124
130
|
ctypes.byref(exit_time), ctypes.byref(kernel),
|
|
125
131
|
ctypes.byref(user))
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Shared overlay compositing helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from arrayview._render import (
|
|
8
|
+
_build_mosaic_grid,
|
|
9
|
+
_composite_overlay_mask,
|
|
10
|
+
_extract_overlay_mask,
|
|
11
|
+
_overlay_is_label_map,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _parse_hex_color(hex_str: str) -> np.ndarray | None:
|
|
16
|
+
"""Parse a 6-char hex RGB color."""
|
|
17
|
+
h = hex_str.strip().lstrip("#")
|
|
18
|
+
if len(h) != 6:
|
|
19
|
+
return None
|
|
20
|
+
try:
|
|
21
|
+
return np.array(
|
|
22
|
+
[int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)],
|
|
23
|
+
dtype=np.uint8,
|
|
24
|
+
)
|
|
25
|
+
except ValueError:
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _composite_overlays(
|
|
30
|
+
rgba: np.ndarray,
|
|
31
|
+
overlay_sid_str: str | None,
|
|
32
|
+
overlay_colors_str: str | None,
|
|
33
|
+
overlay_alpha: float,
|
|
34
|
+
overlay_alphas_str: str | None,
|
|
35
|
+
overlay_outline: bool,
|
|
36
|
+
dim_x: int,
|
|
37
|
+
dim_y: int,
|
|
38
|
+
idx_tuple: tuple[int, ...],
|
|
39
|
+
shape_hw: tuple[int, int],
|
|
40
|
+
base_shape: tuple[int, ...] | None = None,
|
|
41
|
+
) -> np.ndarray:
|
|
42
|
+
"""Composite one or more overlay masks onto RGBA pixels."""
|
|
43
|
+
if not overlay_sid_str:
|
|
44
|
+
return rgba
|
|
45
|
+
sids = [s.strip() for s in overlay_sid_str.split(",") if s.strip()]
|
|
46
|
+
colors_raw = (
|
|
47
|
+
[c.strip() for c in overlay_colors_str.split(",")] if overlay_colors_str else []
|
|
48
|
+
)
|
|
49
|
+
alphas_raw = (
|
|
50
|
+
[a.strip() for a in overlay_alphas_str.split(",")] if overlay_alphas_str else []
|
|
51
|
+
)
|
|
52
|
+
for i, sid in enumerate(sids):
|
|
53
|
+
color = _parse_hex_color(colors_raw[i]) if i < len(colors_raw) else None
|
|
54
|
+
alpha = overlay_alpha
|
|
55
|
+
if i < len(alphas_raw):
|
|
56
|
+
try:
|
|
57
|
+
alpha = float(alphas_raw[i])
|
|
58
|
+
except ValueError:
|
|
59
|
+
alpha = overlay_alpha
|
|
60
|
+
alpha = max(0.0, min(1.0, alpha))
|
|
61
|
+
ov_raw = _extract_overlay_mask(
|
|
62
|
+
sid,
|
|
63
|
+
dim_x,
|
|
64
|
+
dim_y,
|
|
65
|
+
idx_tuple,
|
|
66
|
+
expected_shape=shape_hw,
|
|
67
|
+
base_shape=base_shape,
|
|
68
|
+
)
|
|
69
|
+
rgba = _composite_overlay_mask(
|
|
70
|
+
rgba,
|
|
71
|
+
ov_raw,
|
|
72
|
+
alpha=alpha,
|
|
73
|
+
is_label=_overlay_is_label_map(sid, ov_raw),
|
|
74
|
+
override_color=color,
|
|
75
|
+
outline_only=overlay_outline,
|
|
76
|
+
)
|
|
77
|
+
return rgba
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _composite_mosaic_overlays(
|
|
81
|
+
rgba: np.ndarray,
|
|
82
|
+
overlay_sid_str: str | None,
|
|
83
|
+
overlay_colors_str: str | None,
|
|
84
|
+
overlay_alpha: float,
|
|
85
|
+
overlay_alphas_str: str | None,
|
|
86
|
+
overlay_outline: bool,
|
|
87
|
+
dim_x: int,
|
|
88
|
+
dim_y: int,
|
|
89
|
+
dim_z: int,
|
|
90
|
+
idx_tuple: tuple[int, ...],
|
|
91
|
+
base_shape: tuple[int, ...],
|
|
92
|
+
mosaic_cols: int | None = None,
|
|
93
|
+
) -> np.ndarray:
|
|
94
|
+
"""Composite each overlay as a mosaic using the base mosaic's frame indices.
|
|
95
|
+
|
|
96
|
+
Missing overlay axes are resolved per frame by ``_extract_overlay_mask``.
|
|
97
|
+
That lets a mask omit, for example, an echo axis without materialising a
|
|
98
|
+
repeated full-volume copy.
|
|
99
|
+
"""
|
|
100
|
+
if not overlay_sid_str:
|
|
101
|
+
return rgba
|
|
102
|
+
sids = [sid.strip() for sid in overlay_sid_str.split(",") if sid.strip()]
|
|
103
|
+
colors_raw = (
|
|
104
|
+
[color.strip() for color in overlay_colors_str.split(",")]
|
|
105
|
+
if overlay_colors_str
|
|
106
|
+
else []
|
|
107
|
+
)
|
|
108
|
+
alphas_raw = (
|
|
109
|
+
[alpha.strip() for alpha in overlay_alphas_str.split(",")]
|
|
110
|
+
if overlay_alphas_str
|
|
111
|
+
else []
|
|
112
|
+
)
|
|
113
|
+
tile_shape = (int(base_shape[dim_y]), int(base_shape[dim_x]))
|
|
114
|
+
n_frames = int(base_shape[dim_z])
|
|
115
|
+
|
|
116
|
+
for i, sid in enumerate(sids):
|
|
117
|
+
color = _parse_hex_color(colors_raw[i]) if i < len(colors_raw) else None
|
|
118
|
+
try:
|
|
119
|
+
alpha = float(alphas_raw[i]) if i < len(alphas_raw) else overlay_alpha
|
|
120
|
+
except ValueError:
|
|
121
|
+
alpha = overlay_alpha
|
|
122
|
+
alpha = max(0.0, min(1.0, alpha))
|
|
123
|
+
|
|
124
|
+
frames = []
|
|
125
|
+
for frame in range(n_frames):
|
|
126
|
+
frame_idx = list(idx_tuple)
|
|
127
|
+
frame_idx[dim_z] = frame
|
|
128
|
+
ov_raw = _extract_overlay_mask(
|
|
129
|
+
sid,
|
|
130
|
+
dim_x,
|
|
131
|
+
dim_y,
|
|
132
|
+
tuple(frame_idx),
|
|
133
|
+
expected_shape=tile_shape,
|
|
134
|
+
base_shape=base_shape,
|
|
135
|
+
)
|
|
136
|
+
frames.append(
|
|
137
|
+
ov_raw if ov_raw is not None else np.zeros(tile_shape, dtype=np.float32)
|
|
138
|
+
)
|
|
139
|
+
grid, _, _ = _build_mosaic_grid(frames, n_frames, cols=mosaic_cols)
|
|
140
|
+
# Gaps are transparent, not NaN label values or heatmap samples.
|
|
141
|
+
grid = np.nan_to_num(grid, nan=0.0)
|
|
142
|
+
rgba = _composite_overlay_mask(
|
|
143
|
+
rgba,
|
|
144
|
+
grid,
|
|
145
|
+
alpha=alpha,
|
|
146
|
+
is_label=_overlay_is_label_map(sid, grid),
|
|
147
|
+
override_color=color,
|
|
148
|
+
outline_only=overlay_outline,
|
|
149
|
+
)
|
|
150
|
+
return rgba
|
|
@@ -141,7 +141,8 @@ def register_loading_routes(app, *, notify_shells, setup_rgb) -> None:
|
|
|
141
141
|
else:
|
|
142
142
|
for existing in SESSIONS.values():
|
|
143
143
|
if existing.filepath and os.path.abspath(existing.filepath) == abs_path:
|
|
144
|
-
|
|
144
|
+
if acquire_session_leases([existing.sid]):
|
|
145
|
+
return {"sid": existing.sid, "name": existing.name, "notified": False}
|
|
145
146
|
if not dir_patterns and not os.environ.get("ARRAYVIEW_SKIP_RAM_GUARD"):
|
|
146
147
|
from ._io import FULL_LOAD_EXTS
|
|
147
148
|
|
|
@@ -6,7 +6,7 @@ from fastapi import Depends, Response
|
|
|
6
6
|
from fastapi.responses import JSONResponse
|
|
7
7
|
|
|
8
8
|
from arrayview._diff import _compute_diff, _diff_histogram, _render_diff_rgba
|
|
9
|
-
from arrayview._overlays import _composite_overlays
|
|
9
|
+
from arrayview._overlays import _composite_mosaic_overlays, _composite_overlays
|
|
10
10
|
from arrayview._render import (
|
|
11
11
|
LUTS,
|
|
12
12
|
_build_mosaic_grid,
|
|
@@ -178,6 +178,11 @@ def register_rendering_routes(app, *, get_session_or_404) -> None:
|
|
|
178
178
|
vmin_override=vmin_override,
|
|
179
179
|
vmax_override=vmax_override,
|
|
180
180
|
)
|
|
181
|
+
rgba = _composite_mosaic_overlays(
|
|
182
|
+
rgba, overlay_sid, overlay_colors, overlay_alpha, overlay_alphas,
|
|
183
|
+
overlay_outline, dim_x, dim_y, dim_z, idx_tuple, session.shape,
|
|
184
|
+
mosaic_cols,
|
|
185
|
+
)
|
|
181
186
|
if vmin_override is not None and vmax_override is not None:
|
|
182
187
|
vmin, vmax = float(vmin_override), float(vmax_override)
|
|
183
188
|
else:
|
|
@@ -264,6 +269,12 @@ def register_rendering_routes(app, *, get_session_or_404) -> None:
|
|
|
264
269
|
)
|
|
265
270
|
if qmri_role:
|
|
266
271
|
vmin, vmax = _qmri_adjust_vmin_vmax(vmin, vmax, qmri_role)
|
|
272
|
+
if dim_z >= 0 and qmri_role:
|
|
273
|
+
rgba = _composite_mosaic_overlays(
|
|
274
|
+
rgba, overlay_sid, overlay_colors, overlay_alpha, overlay_alphas,
|
|
275
|
+
overlay_outline, dim_x, dim_y, dim_z, idx_tuple, session.shape,
|
|
276
|
+
mosaic_cols,
|
|
277
|
+
)
|
|
267
278
|
render_ms = (time.perf_counter() - render_t0) * 1000.0
|
|
268
279
|
encode_t0 = time.perf_counter()
|
|
269
280
|
img = _pil_image().fromarray(rgba[:, :, :3], mode="RGB")
|
|
@@ -8,7 +8,7 @@ from starlette.websockets import WebSocketDisconnect
|
|
|
8
8
|
import arrayview._session as _session_mod
|
|
9
9
|
from arrayview._analysis import _build_metadata
|
|
10
10
|
from arrayview._lifecycle import release_session
|
|
11
|
-
from arrayview._overlays import _composite_overlays
|
|
11
|
+
from arrayview._overlays import _composite_mosaic_overlays, _composite_overlays
|
|
12
12
|
from arrayview._render import (
|
|
13
13
|
LUTS,
|
|
14
14
|
_prepare_display,
|
|
@@ -232,6 +232,12 @@ def register_websocket_routes(app) -> None:
|
|
|
232
232
|
vmax_override=vmax_override,
|
|
233
233
|
),
|
|
234
234
|
)
|
|
235
|
+
rgba = _composite_mosaic_overlays(
|
|
236
|
+
rgba, msg.get("overlay_sid"), msg.get("overlay_colors"),
|
|
237
|
+
float(msg.get("overlay_alpha", 0.45)), msg.get("overlay_alphas"),
|
|
238
|
+
bool(msg.get("overlay_outline", False)), dim_x, dim_y, dim_z,
|
|
239
|
+
idx_tuple, session.shape, mosaic_cols,
|
|
240
|
+
)
|
|
235
241
|
h, w = rgba.shape[:2]
|
|
236
242
|
raw = extract_slice(session, dim_x, dim_y, list(idx_tuple))
|
|
237
243
|
_, vmin, vmax = _prepare_display(
|
|
@@ -344,6 +350,14 @@ def register_websocket_routes(app) -> None:
|
|
|
344
350
|
session.shape,
|
|
345
351
|
)
|
|
346
352
|
|
|
353
|
+
if dim_z >= 0 and qmri_role:
|
|
354
|
+
rgba = _composite_mosaic_overlays(
|
|
355
|
+
rgba, msg.get("overlay_sid"), msg.get("overlay_colors"),
|
|
356
|
+
float(msg.get("overlay_alpha", 0.45)), msg.get("overlay_alphas"),
|
|
357
|
+
bool(msg.get("overlay_outline", False)), dim_x, dim_y, dim_z,
|
|
358
|
+
idx_tuple, session.shape, mosaic_cols,
|
|
359
|
+
)
|
|
360
|
+
|
|
347
361
|
render_ms = (time.perf_counter() - render_t0) * 1000.0
|
|
348
362
|
post_t0 = time.perf_counter()
|
|
349
363
|
header = np.array([seq, w, h], dtype=np.uint32).tobytes()
|
|
@@ -283,6 +283,7 @@ class Session:
|
|
|
283
283
|
self.fft_original_data = None
|
|
284
284
|
self.fft_axes = None
|
|
285
285
|
self.data_version = 0 # incremented by /reload when file is reloaded
|
|
286
|
+
self.viewer_leases = 1 # one release is required for each tab using this SID
|
|
286
287
|
|
|
287
288
|
self.rgb_axis = None # set by _setup_rgb(); axis index in actual shape
|
|
288
289
|
self.spatial_shape = data.shape # shape without rgb_axis (set by _setup_rgb)
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
import json
|
|
7
|
+
import platform
|
|
7
8
|
import subprocess
|
|
8
9
|
import sys
|
|
9
10
|
import threading
|
|
@@ -15,7 +16,7 @@ from enum import Enum
|
|
|
15
16
|
|
|
16
17
|
from arrayview._session import _vprint
|
|
17
18
|
from arrayview._platform import _in_vscode_terminal, _is_vscode_remote
|
|
18
|
-
from arrayview._vscode_extension import _configure_vscode_port_preview, _ensure_vscode_extension
|
|
19
|
+
from arrayview._vscode_extension import _configure_vscode_port_preview, _ensure_vscode_extension
|
|
19
20
|
from arrayview._vscode_signal import (
|
|
20
21
|
AckState,
|
|
21
22
|
_open_via_signal_file,
|
|
@@ -132,9 +133,13 @@ def _open_browser(
|
|
|
132
133
|
if is_remote:
|
|
133
134
|
# Remote/tunnel: install extension + write signal file.
|
|
134
135
|
ext_ok = _ensure_vscode_extension()
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
from arrayview import _vscode_extension as _extension_state
|
|
137
|
+
if not ext_ok and _extension_state._VSCODE_EXT_RELOAD_REQUIRED:
|
|
138
|
+
return OpenResult(
|
|
139
|
+
OpenState.FAILED,
|
|
140
|
+
"vscode-extension",
|
|
141
|
+
"ArrayView updated its VS Code opener; reload this VS Code window once, then retry",
|
|
142
|
+
)
|
|
138
143
|
# URL-based mode: port is forwarded by VS Code and the viewer
|
|
139
144
|
# connects via WebSocket through the devtunnel.
|
|
140
145
|
_configure_vscode_port_preview(parsed_port)
|
|
@@ -171,8 +176,13 @@ def _open_browser(
|
|
|
171
176
|
# Local VS Code terminal (or --window vscode forced): install extension + signal file.
|
|
172
177
|
_configure_vscode_port_preview(parsed_port)
|
|
173
178
|
ext_ok = _ensure_vscode_extension()
|
|
174
|
-
|
|
175
|
-
|
|
179
|
+
from arrayview import _vscode_extension as _extension_state
|
|
180
|
+
if not ext_ok and _extension_state._VSCODE_EXT_RELOAD_REQUIRED:
|
|
181
|
+
return OpenResult(
|
|
182
|
+
OpenState.FAILED,
|
|
183
|
+
"vscode-extension",
|
|
184
|
+
"ArrayView updated its VS Code opener; reload this VS Code window once, then retry",
|
|
185
|
+
)
|
|
176
186
|
# Always write the signal file: the extension may already be
|
|
177
187
|
# installed even if _ensure failed (e.g. `code` CLI not found).
|
|
178
188
|
request = _open_via_signal_file(
|
|
@@ -219,7 +229,7 @@ def _open_browser(
|
|
|
219
229
|
port_hint = int(url.split(":")[2].split("/")[0].split("?")[0])
|
|
220
230
|
except Exception:
|
|
221
231
|
port_hint = parsed_port
|
|
222
|
-
hostname =
|
|
232
|
+
hostname = platform.node() or "<this-host>"
|
|
223
233
|
print(
|
|
224
234
|
f"[ArrayView] Plain SSH session detected.\n"
|
|
225
235
|
f"\n"
|