arrayview 0.25.1__tar.gz → 0.26.1__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.25.1 → arrayview-0.26.1}/.agents/skills/invocation-consistency/SKILL.md +21 -3
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/context/architecture.md +12 -1
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/context/frontend.md +1 -1
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/context/project-state.md +14 -2
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/patterns/add-file-format.md +20 -4
- {arrayview-0.25.1 → arrayview-0.26.1}/PKG-INFO +1 -1
- {arrayview-0.25.1 → arrayview-0.26.1}/docs/comparing.md +4 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/docs/display.md +6 -2
- {arrayview-0.25.1 → arrayview-0.26.1}/docs/loading.md +5 -2
- arrayview-0.26.1/docs/measurement.md +40 -0
- arrayview-0.26.1/docs/viewing.md +47 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/pyproject.toml +1 -1
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/__init__.py +2 -1
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_io.py +17 -2
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_launcher.py +17 -9
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_platform.py +3 -1
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_routes_loading.py +7 -1
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_server.py +2 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_stdio_server.py +8 -1
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_viewer.html +267 -120
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_api.py +82 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_browser.py +63 -24
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_command_reachability.py +18 -0
- arrayview-0.26.1/tests/test_io.py +82 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/uv.lock +1 -1
- arrayview-0.25.1/docs/measurement.md +0 -34
- arrayview-0.25.1/docs/viewing.md +0 -36
- {arrayview-0.25.1 → arrayview-0.26.1}/.agents/skills/frontend-designer/SKILL.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.agents/skills/modes-consistency/SKILL.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.agents/skills/playwright-cli/SKILL.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.agents/skills/playwright-cli/references/element-attributes.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.agents/skills/playwright-cli/references/playwright-tests.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.agents/skills/playwright-cli/references/request-mocking.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.agents/skills/playwright-cli/references/running-code.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.agents/skills/playwright-cli/references/session-management.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.agents/skills/playwright-cli/references/storage-state.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.agents/skills/playwright-cli/references/test-generation.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.agents/skills/playwright-cli/references/tracing.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.agents/skills/playwright-cli/references/video-recording.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.agents/skills/todo-workflow/SKILL.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.agents/skills/ui-consistency-audit/SKILL.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.agents/skills/viewer-ui-checklist/SKILL.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.agents/skills/visual-bug-fixing/SKILL.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.github/copilot-instructions.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.github/workflows/docs.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.github/workflows/python-publish.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.gitignore +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.ignore +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/AGENTS.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/ROUTER.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/SETUP.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/SYNC.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/context/conventions.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/context/decisions.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/context/lifecycle.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/context/render-pipeline.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/context/setup.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/context/stack.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/patterns/INDEX.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/patterns/README.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/patterns/add-server-endpoint.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/patterns/animation-verify.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/patterns/debug-render.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/patterns/debug-vscode-extension-python.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/patterns/extend-compare-mode.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/patterns/extract-server-route-module.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/patterns/frontend-change.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/setup.sh +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.mex/sync.sh +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.opencode/opencode.json +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T18-46-49-737Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T18-48-21-979Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T18-51-35-665Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T19-07-01-393Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T19-14-37-969Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T19-21-30-940Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T19-23-08-126Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T19-29-33-155Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T19-31-25-336Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T19-31-53-789Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T19-39-12-257Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T19-39-16-449Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T20-15-25-513Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T20-25-13-179Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T20-39-01-435Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T21-01-27-659Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T21-01-41-283Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T21-03-00-625Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T21-04-12-887Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T21-33-39-044Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T21-38-01-530Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T21-45-20-383Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T21-55-11-545Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T21-56-03-307Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T21-56-35-733Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T21-57-12-181Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T21-57-37-748Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T21-58-13-679Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-06T22-37-23-895Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T00-39-18-637Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T01-41-46-243Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T04-31-48-472Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T12-14-15-632Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T12-14-47-582Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T12-16-23-471Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T12-17-10-247Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T12-18-24-707Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T12-20-06-164Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T12-20-28-342Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T12-21-54-962Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T12-22-34-666Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T12-23-11-336Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T12-23-36-260Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T12-24-09-267Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T12-24-35-434Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T12-25-57-010Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T12-34-48-823Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T12-46-46-468Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T12-48-17-930Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T12-49-26-400Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T12-50-31-563Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/page-2026-05-07T12-56-45-568Z.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.playwright-cli/theme-dark.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.python-version +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/.vscode/settings.json +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/AGENTS.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/CONTRIBUTING.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/DESIGN.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/LICENSE +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/README.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/docs/configuration.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/docs/index.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/docs/lifecycle.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/docs/logo.png +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/docs/remote.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/docs/stylesheets/extra.css +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/matlab/arrayview.m +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/mkdocs.yml +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/plans/2026-04-14-immersive-animation.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/plans/2026-05-07-unified-colormap-picker.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/plans/arrayview_tool_menu_fix_prompt.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/plans/arrayview_tool_menu_implementation_plan.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/plans/egg-placement-mockup.html +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/plans/refactoring_plan.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/plans/webview/LOG.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/scripts/demo.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/scripts/release.sh +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/ARCHITECTURE.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/__main__.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_analysis.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_app.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_codex_open.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_config.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_diff.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_icon.png +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_imaging.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_lifecycle.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_overlays.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_render.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_routes_analysis.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_routes_export.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_routes_persistence.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_routes_preload.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_routes_query.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_routes_rendering.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_routes_segmentation.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_routes_state.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_routes_vectorfield.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_routes_websocket.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_segmentation.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_session.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_shell.html +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_synthetic_mri.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_torch.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_vectorfield.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_vscode.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_vscode_browser.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_vscode_extension.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_vscode_shm.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/_vscode_signal.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/arrayview-opener.vsix +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/src/arrayview/gsap.min.js +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/capture_v_animation.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/conftest.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/lifecycle_matrix.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/make_vectorfield_test_arrays.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_backend_shared.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_cli.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_colorbar_hover_highlight.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_config.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_cross_mode_parametrized.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_interactions.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_large_arrays.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_lifecycle_contract.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_loading_server.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_mode_consistency.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_mode_entry_batching.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_mode_matrix.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_mode_roundtrip.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_nifti_meta.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_rgb_pixel_art.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_stdio_server.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_torch.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_view_component_integration.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/test_view_component_unit.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/ui_audit.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/v_anim_frames/.gitkeep +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/tests/visual_smoke.py +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/vscode-extension/AGENTS.md +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/vscode-extension/LICENSE +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/vscode-extension/extension.js +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/vscode-extension/lifecycle_helpers.js +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/vscode-extension/package.json +0 -0
- {arrayview-0.25.1 → arrayview-0.26.1}/vscode-extension/test_lifecycle_helpers.js +0 -0
|
@@ -20,6 +20,15 @@ Every behavior that depends on *how* arrayview is started (server lifecycle, bro
|
|
|
20
20
|
| **VS Code terminal** | `arrayview()` or `view()` | VS Code Simple Browser | in-process or subprocess |
|
|
21
21
|
| **Plain SSH (no VS Code)** | `arrayview()` or `view()` | prints port-forward hint | in-process or subprocess |
|
|
22
22
|
|
|
23
|
+
## Transport Split
|
|
24
|
+
|
|
25
|
+
ArrayView has two backend transports. Invocation-sensitive fixes must account for both:
|
|
26
|
+
|
|
27
|
+
- **Network mode**: FastAPI routes and WebSocket server. File registration goes through `/load` in `_routes_loading.py`; spawned CLI sessions load in `_launcher._serve_daemon()`.
|
|
28
|
+
- **VS Code direct mode**: stdio subprocess. VS Code tunnel launches write a signal file; the extension runs `python -m arrayview --mode stdio <file>`, and file registration goes through `_stdio_server._handle_register()`.
|
|
29
|
+
|
|
30
|
+
Do not assume a `/load` or daemon fix covers VS Code tunnel. Shared behavior such as file-format key selection belongs in `_io.py` helpers and must be used by both transports.
|
|
31
|
+
|
|
23
32
|
## Key Detection Functions (all in `_platform.py`)
|
|
24
33
|
|
|
25
34
|
```python
|
|
@@ -86,17 +95,24 @@ for pid_str in client_pids:
|
|
|
86
95
|
- Jupyter: repeated calls don't fail on port-already-in-use
|
|
87
96
|
- Julia: `_view_julia()` passes new params via CLI flag or file
|
|
88
97
|
|
|
89
|
-
2. **
|
|
98
|
+
2. **File/session registration consistent?**
|
|
99
|
+
- FastAPI `/load` path handles the feature
|
|
100
|
+
- CLI spawned daemon path handles the feature
|
|
101
|
+
- VS Code direct stdio `_handle_register()` path handles the feature
|
|
102
|
+
- Direct `--mode stdio <file>` CLI path handles the feature
|
|
103
|
+
- Multi-array/default-key behavior is centralized in `_io.py`
|
|
104
|
+
|
|
105
|
+
3. **Browser/display opening?**
|
|
90
106
|
- Local native window (macOS): `pywebview` opens correctly
|
|
91
107
|
- VS Code terminal: Simple Browser opens via extension signal file
|
|
92
108
|
- VS Code tunnel: reaches client-side Simple Browser, not remote host
|
|
93
109
|
- Verify `_ensure_vscode_extension()` installs/updates if VSIX changed
|
|
94
110
|
|
|
95
|
-
|
|
111
|
+
4. **Environment detection?**
|
|
96
112
|
- Check detection order: Julia → Jupyter → VS Code remote → VS Code terminal → local
|
|
97
113
|
- `_find_vscode_ipc_hook()` walks up to 12 parent processes; new subprocess wrappers may need the same
|
|
98
114
|
|
|
99
|
-
|
|
115
|
+
5. **VS Code extension changed?**
|
|
100
116
|
- Rebuild VSIX: `cd vscode-extension && vsce package -o ../src/arrayview/arrayview-opener.vsix`
|
|
101
117
|
- Bump `_VSCODE_EXT_VERSION` in `_vscode.py` and `vscode-extension/package.json` together
|
|
102
118
|
|
|
@@ -111,6 +127,8 @@ uv run python -c "from arrayview import view; import numpy as np; view(np.zeros(
|
|
|
111
127
|
## Red Flags — STOP
|
|
112
128
|
|
|
113
129
|
- "I changed server startup but only tested CLI" → test all paths
|
|
130
|
+
- "I fixed `/load` but not `_stdio_server.py`" → VS Code tunnel can still be broken
|
|
131
|
+
- "I fixed `_serve_daemon()` but `--diagnose` says `is_vscode_remote: true`" → tunnel uses stdio direct mode, not the daemon
|
|
114
132
|
- "Works locally but not in tunnel" → check `_is_vscode_remote()` path and port exposure
|
|
115
133
|
- "I used `127.0.0.1` in the URL" → use `localhost`
|
|
116
134
|
- "I fixed tmux with `#{client_pid}`" → only works with one client; use `list-clients`
|
|
@@ -20,7 +20,7 @@ edges:
|
|
|
20
20
|
condition: when the task involves _viewer.html, modes, reconcilers, or the View Component System
|
|
21
21
|
- target: context/render-pipeline.md
|
|
22
22
|
condition: when the task involves slice extraction, colormaps, caching, or the render thread
|
|
23
|
-
last_updated: 2026-06-
|
|
23
|
+
last_updated: 2026-06-11
|
|
24
24
|
---
|
|
25
25
|
|
|
26
26
|
# Architecture
|
|
@@ -90,6 +90,17 @@ Tool launch, tool activation, and drawer visibility are separate states.
|
|
|
90
90
|
|
|
91
91
|
Detection logic: `_platform.py`. Display opening: `_launcher.py` + `_vscode.py`.
|
|
92
92
|
|
|
93
|
+
## File Registration Paths
|
|
94
|
+
|
|
95
|
+
File parsing belongs in `_io.py`, but session registration happens through different transports:
|
|
96
|
+
|
|
97
|
+
- **Network `/load`**: existing FastAPI server path in `_routes_loading.py`.
|
|
98
|
+
- **Spawned CLI daemon**: `_launcher._serve_daemon()` loads before publishing the pending session.
|
|
99
|
+
- **VS Code tunnel direct webview**: `_stdio_server._handle_register()` handles `python -m arrayview --mode stdio <file>` spawned by the extension.
|
|
100
|
+
- **Direct stdio CLI mode**: `_launcher.arrayview()` preloads positional files before starting `run_stdio_server()`.
|
|
101
|
+
|
|
102
|
+
For multi-array formats or formats with non-displayable members, keep key filtering/default selection in `_io.py` helpers and call those helpers from every registration path. A fix in `/load` alone does not cover VS Code tunnel.
|
|
103
|
+
|
|
93
104
|
## External Dependencies
|
|
94
105
|
|
|
95
106
|
- **FastAPI + uvicorn** — async HTTP/WebSocket server, lazy-imported in `_launcher.py`. Never call uvicorn directly — use the `_uvicorn()` accessor.
|
|
@@ -18,7 +18,7 @@ edges:
|
|
|
18
18
|
condition: when writing new frontend code and need section separator conventions
|
|
19
19
|
- target: patterns/frontend-change.md
|
|
20
20
|
condition: when making a concrete change to _viewer.html
|
|
21
|
-
last_updated: 2026-05
|
|
21
|
+
last_updated: 2026-06-05
|
|
22
22
|
---
|
|
23
23
|
|
|
24
24
|
# Frontend (_viewer.html)
|
|
@@ -7,7 +7,7 @@ triggers:
|
|
|
7
7
|
- "recent work"
|
|
8
8
|
- "active feature"
|
|
9
9
|
- "shipped recently"
|
|
10
|
-
last_updated: 2026-05
|
|
10
|
+
last_updated: 2026-06-05
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# Project State
|
|
@@ -40,6 +40,18 @@ last_updated: 2026-05-13
|
|
|
40
40
|
|
|
41
41
|
## Recently Completed
|
|
42
42
|
|
|
43
|
+
- ROI keybind changed from `r` to `Shift+R` (`R`); rotate/flip is now `r`, transpose is `t`
|
|
44
|
+
- Crossfade animation added to rotate/transpose transitions
|
|
45
|
+
- `--version` flag and version string in help overlay
|
|
46
|
+
- In-viewer array picker for multi-array `.mat` and `.npz` files
|
|
47
|
+
- ROI stats tooltips for qMRI canvases (per-parameter-map sub-rows in the island)
|
|
48
|
+
- Invocation lifecycle contract defined and hardened: local VS Code CLI launches use transient daemon shutdown, remote/tunnel launches remain persistent only where transport requires it, URL webview backend checks run in the extension host against `/ping`, URL panel disposal releases all sessions encoded in the viewer URL, quick viewer connect/disconnect races detected by monotonic connection counter, ambiguous multi-window tunnel routing fails closed, stale viewer SID retry state cleared on WebSocket disconnect, bundled opener extension rebuilt as v0.14.12
|
|
49
|
+
- Hover info wrong values fixed
|
|
50
|
+
- Native launcher startup restored
|
|
51
|
+
- qMRI pane sizing stabilized
|
|
52
|
+
- VS Code extension Windows support (select/pipes, venv path, ppid)
|
|
53
|
+
- Tool launcher motion refined
|
|
54
|
+
|
|
43
55
|
- VS Code tab lifecycle hardening: local VS Code CLI launches now use transient daemon shutdown instead of `persist=True`, remote/tunnel launches remain persistent only where transport requires it, URL webview backend checks run in the extension host against `/ping`, URL panel disposal releases all sessions encoded in the viewer URL via tested lifecycle helpers, quick viewer connect/disconnect races are detected by a monotonic connection counter, ambiguous multi-window tunnel routing now fails closed instead of opening in a guessed window, stale viewer SID retry state is cleared on WebSocket disconnect, and the bundled opener extension was rebuilt as v0.14.12.
|
|
44
56
|
- Shift+C colormap picker redesign: the old centered shortlist is now a narrow translucent right-edge drawer with a close button, a yellow `Colormaps` title plus a `Favorites` subtitle, and a plain 12-swatch two-column quick set that stays visible above the search field. Search matches render in a separate results area below the input, Enter first exits the search field before a second Enter commits, arrow-key movement follows the visible two-column grid, and repeated `c` presses cycle through the currently visible swatches while the picker is open.
|
|
45
57
|
- Detached compare-on-X: single-array non-spatial dimensions now support the same compare-center family as two-array compare. The frontend reuses compare mode with per-pane detached indices, the dimbar shows a purple `X`, the compare titles show index-over-total labels, `[` / `]` control pane A, `{` / `}` control pane B, and repeated `X` exits detached compare after cycling the center modes. Focused coverage now includes a browser regression for detached entry/scrubbing/exit plus API coverage for split `/diff` indices on the same session.
|
|
@@ -55,7 +67,7 @@ last_updated: 2026-05-13
|
|
|
55
67
|
## In Progress
|
|
56
68
|
|
|
57
69
|
- 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.
|
|
58
|
-
- ROI + qMRI integration refinements: floodfill not yet supported on qMRI panes;
|
|
70
|
+
- ROI + qMRI integration refinements: floodfill not yet supported on qMRI panes; per-pane stats are re-fetched on each ROI draw but not updated on slice scroll
|
|
59
71
|
|
|
60
72
|
## Not Yet Built
|
|
61
73
|
|
|
@@ -15,20 +15,26 @@ edges:
|
|
|
15
15
|
condition: for the lazy import pattern and Verify Checklist
|
|
16
16
|
- target: context/render-pipeline.md
|
|
17
17
|
condition: when the new format returns an unusual dtype that may affect the render pipeline
|
|
18
|
-
last_updated: 2026-
|
|
18
|
+
last_updated: 2026-06-11
|
|
19
19
|
---
|
|
20
20
|
|
|
21
21
|
# Add File Format
|
|
22
22
|
|
|
23
23
|
## Context
|
|
24
24
|
|
|
25
|
-
All file
|
|
25
|
+
All file parsing goes through `_io.load_data(filepath)` or `_io.load_data_with_meta(filepath)` in `src/arrayview/_io.py`. The function dispatches on file extension. New formats must:
|
|
26
26
|
1. Register the extension in `_SUPPORTED_EXTS`
|
|
27
27
|
2. Add a lazy import (if the library is not already imported at module level)
|
|
28
28
|
3. Add a dispatch branch in `load_data()`
|
|
29
29
|
4. Return a numpy array (or array-like with `.shape` and `.dtype`)
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
Transport/session creation is separate from parsing:
|
|
32
|
+
|
|
33
|
+
- FastAPI `/load`: `src/arrayview/_routes_loading.py`
|
|
34
|
+
- CLI spawned daemon and direct stdio CLI mode: `src/arrayview/_launcher.py`
|
|
35
|
+
- VS Code tunnel direct webview: `src/arrayview/_stdio_server.py`
|
|
36
|
+
|
|
37
|
+
The server and CLI must not import format libraries directly. They may call generic `_io` helpers such as `list_array_keys()` or `default_array_key()` when they need to choose a key before creating a session.
|
|
32
38
|
|
|
33
39
|
## Steps
|
|
34
40
|
|
|
@@ -74,18 +80,28 @@ The server (`_server.py`) and CLI (`_launcher.py`) never import format libraries
|
|
|
74
80
|
return list(_mylib().peek_shape(fpath))
|
|
75
81
|
```
|
|
76
82
|
|
|
83
|
+
7. **Keep key/default selection consistent across transports** for multi-array formats (`.npz`, `.mat`, HDF5-like containers):
|
|
84
|
+
- Put filtering and default-key logic in `_io.py`, not in a route or launcher branch.
|
|
85
|
+
- Use the same helper from FastAPI `/load`, CLI daemon/direct paths, and `_stdio_server.py`.
|
|
86
|
+
- If `/load` returns picker keys for multiple arrays, define what non-picker transports do (usually first displayable key).
|
|
87
|
+
|
|
77
88
|
## Gotchas
|
|
78
89
|
|
|
79
90
|
- **Do not import the format library at the top of `_io.py`** without checking import cost first. Nibabel is the reference: it is lazy because it costs ~200 ms on first import.
|
|
80
91
|
- **`.nii.gz` is two extensions** — the dispatch checks the full suffix, not just the last dot. New formats with compound extensions (`.foo.bar`) need special suffix handling in `load_data()`.
|
|
81
92
|
- **Zarr is not loaded via `load_data()`** in all paths — zarr arrays can also be passed directly to `view()` as array objects. Ensure zarr-specific logic lives in `_session.py` / `_render.py`, not only in the file-load path.
|
|
82
|
-
- **No format-specific code in `_server.py` or `
|
|
93
|
+
- **No format-specific parsing code in `_server.py`, `_launcher.py`, or `_stdio_server.py`** — if you find yourself adding a format import or dtype/shape filter in those modules, move it to `_io.py`.
|
|
94
|
+
- **VS Code tunnel is stdio, not HTTP** — `arrayview file.ext` in a VS Code tunnel writes a signal file; the extension then runs `python -m arrayview --mode stdio file.ext`. A `/load` fix alone does not cover this path.
|
|
83
95
|
|
|
84
96
|
## Verify
|
|
85
97
|
|
|
86
98
|
- [ ] Extension added to `_SUPPORTED_EXTS`
|
|
87
99
|
- [ ] New library import is lazy (accessor function pattern) unless it imports in <10 ms
|
|
88
100
|
- [ ] `load_data("myfile.myext")` returns a numpy array with correct shape and dtype
|
|
101
|
+
- [ ] If the format can contain multiple arrays or non-displayable members, add tests for `list_array_keys()` / `default_array_key()` and direct `load_data(..., key=...)`
|
|
102
|
+
- [ ] FastAPI `/load` works for the file, including picker/default-key behavior
|
|
103
|
+
- [ ] `_stdio_server._handle_register()` works for the file, because VS Code tunnel direct webview uses stdio instead of `/load`
|
|
104
|
+
- [ ] CLI daemon/direct paths use generic `_io` helpers for key selection instead of duplicating format rules
|
|
89
105
|
- [ ] `view(load_data("myfile.myext"))` opens the viewer without errors
|
|
90
106
|
- [ ] If full-load format, added to `FULL_LOAD_EXTS`
|
|
91
107
|
- [ ] `uv run pytest` on `tests/test_mode_consistency.py` still passes (no dtype regressions)
|
|
@@ -9,6 +9,10 @@ uvx arrayview base.npy a.npy b.npy c.npy # up to 6
|
|
|
9
9
|
|
|
10
10
|
Or use the file picker (`Cmd/Ctrl+O`) or drag-and-drop.
|
|
11
11
|
|
|
12
|
+
## Switching between arrays
|
|
13
|
+
|
|
14
|
+
`n` cycles the compare target array (when multiple arrays are loaded).
|
|
15
|
+
|
|
12
16
|
## Center pane
|
|
13
17
|
|
|
14
18
|
`X` cycles through comparison modes:
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
## Dynamic range
|
|
9
9
|
|
|
10
10
|
`d` cycles quantile presets: 0–100%, 1–99%, 5–95%, 10–90%. Expands the colorbar into a histogram.
|
|
11
|
-
`D`
|
|
11
|
+
`D` opens a histogram dim picker (3-state toggle for which dimensions participate).
|
|
12
12
|
|
|
13
13
|
## Window / level
|
|
14
14
|
|
|
@@ -28,4 +28,8 @@ Drag the colorbar to shift the window. Scroll on the colorbar to narrow or widen
|
|
|
28
28
|
|
|
29
29
|
## Layout
|
|
30
30
|
|
|
31
|
-
`b` toggles a border around the canvas. `a` stretches panes to a square aspect ratio.
|
|
31
|
+
`b` toggles a border around the canvas. `B` toggles rounded panes. `a` stretches panes to a square aspect ratio.
|
|
32
|
+
|
|
33
|
+
## Caveat
|
|
34
|
+
|
|
35
|
+
ArrayView works across six invocation environments (CLI, Python script, Jupyter, Julia, VS Code, SSH). Not every feature has been verified in every mode. If something behaves differently than documented, check the [remote](remote.md) page for environment-specific notes or open an issue.
|
|
@@ -11,10 +11,12 @@
|
|
|
11
11
|
| `.pt` / `.pth` | torch | Converted to numpy |
|
|
12
12
|
| `.h5` / `.hdf5` | h5py | Must contain one dataset |
|
|
13
13
|
| `.tif` / `.tiff` | tifffile | Full load |
|
|
14
|
-
| `.mat` | scipy |
|
|
14
|
+
| `.mat` | scipy / h5py | Multi-array: in-viewer picker for array selection |
|
|
15
15
|
|
|
16
16
|
Optional libraries are imported only when needed.
|
|
17
17
|
|
|
18
|
+
Multi-array formats (`.npz`, `.mat`) show an in-viewer picker when they contain more than one array.
|
|
19
|
+
|
|
18
20
|
## CLI
|
|
19
21
|
|
|
20
22
|
```bash
|
|
@@ -22,6 +24,7 @@ uvx arrayview volume.nii.gz
|
|
|
22
24
|
uvx arrayview volume.npy --window browser
|
|
23
25
|
uvx arrayview image.npy --rgb
|
|
24
26
|
uvx arrayview --watch data.npy # reload on file change
|
|
27
|
+
uvx arrayview --version # print version
|
|
25
28
|
```
|
|
26
29
|
|
|
27
30
|
## Python
|
|
@@ -49,7 +52,7 @@ Key parameters:
|
|
|
49
52
|
|-----------|------|-------------|
|
|
50
53
|
| `data` | array-like | The array to display |
|
|
51
54
|
| `name` | str | Label shown in the viewer tab |
|
|
52
|
-
| `port` | int | Server port (default 8123) |
|
|
55
|
+
| `port` | int | Server port (default 8123 for Python API, 8000 for CLI) |
|
|
53
56
|
| `window` | str \| None | How to open the viewer (see below) |
|
|
54
57
|
| `rgb` | bool | Treat last/first axis as RGB/RGBA channels |
|
|
55
58
|
| `overlay` | array or list | Arrays composited as overlays |
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Measurement
|
|
2
|
+
|
|
3
|
+
## ROI
|
|
4
|
+
|
|
5
|
+
`Shift+R` opens ROI options. Draw on the canvas to measure a region — live stats (mean, std, min, max) appear in the floating panel.
|
|
6
|
+
|
|
7
|
+
Shapes: rectangle, ellipse, freehand, flood fill. Switch via the shape icons in the panel.
|
|
8
|
+
|
|
9
|
+
For flood fill, `[` / `]` adjusts tolerance.
|
|
10
|
+
|
|
11
|
+
Click an existing ROI to select it. `Delete` / `Backspace` removes it.
|
|
12
|
+
|
|
13
|
+
`N` exports the ROI mask as `.npy`.
|
|
14
|
+
|
|
15
|
+
## Ruler
|
|
16
|
+
|
|
17
|
+
`u` — enter ruler mode. Click two points to measure pixel distance. Press `u` again to exit.
|
|
18
|
+
|
|
19
|
+
## Pixel info
|
|
20
|
+
|
|
21
|
+
Hover over the image to see coordinates and value reflected on the colorbar.
|
|
22
|
+
|
|
23
|
+
Click the colorbar to copy that value to the clipboard.
|
|
24
|
+
|
|
25
|
+
`i` — toggle a persistent hover tooltip that follows the cursor.
|
|
26
|
+
|
|
27
|
+
`I` — show a data info overlay: shape, dtype, size, file path.
|
|
28
|
+
|
|
29
|
+
## Export
|
|
30
|
+
|
|
31
|
+
| Key | Action |
|
|
32
|
+
|-----|--------|
|
|
33
|
+
| `s` | Open save options (screenshot PNG, GIF, .npy export) |
|
|
34
|
+
| `e` | Copy a reusable URL to clipboard |
|
|
35
|
+
|
|
36
|
+
Screenshots download as PNG. GIF saves an animation along the current slice dimension. `.npy` export saves the current slice.
|
|
37
|
+
|
|
38
|
+
## Caveat
|
|
39
|
+
|
|
40
|
+
ArrayView works across six invocation environments (CLI, Python script, Jupyter, Julia, VS Code, SSH). Not every feature has been verified in every mode. If something behaves differently than documented, check the [remote](remote.md) page for environment-specific notes or open an issue.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Viewing
|
|
2
|
+
|
|
3
|
+
## Normal
|
|
4
|
+
|
|
5
|
+
The default view. Shows a single 2D slice. Scroll to move through slices. `h`/`l` changes the scroll dimension; `j`/`k` steps one slice at a time.
|
|
6
|
+
|
|
7
|
+
`r` rotates 90 degrees or flips the axis. `t` transposes x and y.
|
|
8
|
+
|
|
9
|
+
## Multi-view
|
|
10
|
+
|
|
11
|
+
`v` — splits the canvas into three orthogonal planes (axial, coronal, sagittal). Drag the crosshair to navigate. Shift+drag for oblique rotation. `V` opens custom dimension selection; `o` resets the orientation.
|
|
12
|
+
|
|
13
|
+
## Mosaic
|
|
14
|
+
|
|
15
|
+
`z` — lays out a grid of slices along the next dimension. Scrolling then steps through the dimension after that.
|
|
16
|
+
|
|
17
|
+
## Projections
|
|
18
|
+
|
|
19
|
+
`p` — cycles through statistical projections along the scroll axis: MAX, MIN, MEAN, STD, SOS.
|
|
20
|
+
|
|
21
|
+
## FFT
|
|
22
|
+
|
|
23
|
+
`f` — displays a centered FFT of the array. Prompts for which axes to transform.
|
|
24
|
+
|
|
25
|
+
## RGB
|
|
26
|
+
|
|
27
|
+
Rendered as direct color via `--rgb` on the CLI or `rgb=True` in Python. The first or last dimension must be size 3 (RGB) or 4 (RGBA). No direct keybind — set at launch.
|
|
28
|
+
|
|
29
|
+
## Immersive
|
|
30
|
+
|
|
31
|
+
`Shift+F` — fullscreen with all chrome hidden. Move the mouse to briefly reveal controls.
|
|
32
|
+
|
|
33
|
+
## Zen mode
|
|
34
|
+
|
|
35
|
+
`Z` — hides UI chrome (dimbar, info, island) for a clean view. Press `Z` again to restore.
|
|
36
|
+
|
|
37
|
+
## Complex data
|
|
38
|
+
|
|
39
|
+
`m` — cycles through magnitude, phase, real, and imaginary parts for complex-valued arrays.
|
|
40
|
+
|
|
41
|
+
## Navigation
|
|
42
|
+
|
|
43
|
+
Scroll through slices. `/` opens the tool launcher for ortho, compare, qMRI, ROI, segmentation, FFT, projections, and overlays. The direct keys still work. `0` resets the zoom. `?` shows the full key guide.
|
|
44
|
+
|
|
45
|
+
## Caveat
|
|
46
|
+
|
|
47
|
+
ArrayView works across six invocation environments (CLI, Python script, Jupyter, Julia, VS Code, SSH). Not every feature has been verified in every mode. If something behaves differently than documented, check the [remote](remote.md) page for environment-specific notes or open an issue.
|
|
@@ -38,6 +38,15 @@ def _fix_mat_complex(arr):
|
|
|
38
38
|
return arr
|
|
39
39
|
|
|
40
40
|
|
|
41
|
+
def _is_viewable_mat_array(value):
|
|
42
|
+
"""True for MATLAB arrays ArrayView can display directly."""
|
|
43
|
+
return (
|
|
44
|
+
isinstance(value, np.ndarray)
|
|
45
|
+
and value.ndim >= 1
|
|
46
|
+
and value.dtype.kind in ("b", "i", "u", "f", "c")
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
41
50
|
def list_npz_keys(filepath):
|
|
42
51
|
"""Return [{key, shape, dtype}] for each ndarray in an .npz file.
|
|
43
52
|
|
|
@@ -67,7 +76,7 @@ def list_mat_keys(filepath):
|
|
|
67
76
|
for k, v in mat.items():
|
|
68
77
|
if k.startswith("_"):
|
|
69
78
|
continue
|
|
70
|
-
if
|
|
79
|
+
if _is_viewable_mat_array(v):
|
|
71
80
|
keys.append({"key": k, "shape": list(v.shape), "dtype": str(v.dtype)})
|
|
72
81
|
return keys
|
|
73
82
|
except NotImplementedError:
|
|
@@ -95,6 +104,12 @@ def list_array_keys(filepath):
|
|
|
95
104
|
return []
|
|
96
105
|
|
|
97
106
|
|
|
107
|
+
def default_array_key(filepath):
|
|
108
|
+
"""Return the first selectable array key for multi-array formats, if any."""
|
|
109
|
+
keys = list_array_keys(filepath)
|
|
110
|
+
return keys[0]["key"] if keys else None
|
|
111
|
+
|
|
112
|
+
|
|
98
113
|
def _select_npz_array(npz, filepath):
|
|
99
114
|
"""Load the first array from a multi-array .npz file.
|
|
100
115
|
|
|
@@ -233,7 +248,7 @@ def load_data(filepath, key=None):
|
|
|
233
248
|
arrays = {
|
|
234
249
|
k: v
|
|
235
250
|
for k, v in mat.items()
|
|
236
|
-
if not k.startswith("_") and
|
|
251
|
+
if not k.startswith("_") and _is_viewable_mat_array(v)
|
|
237
252
|
}
|
|
238
253
|
if key is not None:
|
|
239
254
|
return _fix_mat_complex(arrays[key])
|
|
@@ -2717,7 +2717,7 @@ def _serve_daemon(
|
|
|
2717
2717
|
_array_keys = list_array_keys(filepath)
|
|
2718
2718
|
except Exception:
|
|
2719
2719
|
pass
|
|
2720
|
-
_load_key = _array_keys[0]["key"] if _array_keys
|
|
2720
|
+
_load_key = _array_keys[0]["key"] if _array_keys else None
|
|
2721
2721
|
data, spatial_meta = load_data_with_meta(filepath, key=_load_key)
|
|
2722
2722
|
if cleanup:
|
|
2723
2723
|
try:
|
|
@@ -2996,6 +2996,8 @@ def arrayview():
|
|
|
2996
2996
|
return
|
|
2997
2997
|
|
|
2998
2998
|
parser = argparse.ArgumentParser(description="Lightning Fast ND Array Viewer")
|
|
2999
|
+
from arrayview import __version__ as _av_version
|
|
3000
|
+
parser.add_argument("--version", action="version", version=f"arrayview {_av_version}")
|
|
2999
3001
|
parser.add_argument(
|
|
3000
3002
|
"files",
|
|
3001
3003
|
nargs="*",
|
|
@@ -3185,12 +3187,12 @@ def arrayview():
|
|
|
3185
3187
|
)
|
|
3186
3188
|
print(f"SESSION:{info}", file=sys.stderr)
|
|
3187
3189
|
elif args.files:
|
|
3188
|
-
from arrayview._io import load_data
|
|
3190
|
+
from arrayview._io import default_array_key, load_data
|
|
3189
3191
|
from arrayview._session import SESSIONS, Session
|
|
3190
3192
|
|
|
3191
3193
|
# First file is the main array
|
|
3192
3194
|
base_path = args.files[0]
|
|
3193
|
-
data = load_data(base_path)
|
|
3195
|
+
data = load_data(base_path, key=default_array_key(base_path))
|
|
3194
3196
|
session = Session(
|
|
3195
3197
|
data=data,
|
|
3196
3198
|
filepath=base_path,
|
|
@@ -3203,7 +3205,10 @@ def arrayview():
|
|
|
3203
3205
|
if getattr(args, "vectorfield", None):
|
|
3204
3206
|
from arrayview._vectorfield import _configure_vectorfield
|
|
3205
3207
|
|
|
3206
|
-
vf_data = load_data(
|
|
3208
|
+
vf_data = load_data(
|
|
3209
|
+
args.vectorfield,
|
|
3210
|
+
key=default_array_key(args.vectorfield),
|
|
3211
|
+
)
|
|
3207
3212
|
_configure_vectorfield(
|
|
3208
3213
|
session, vf_data,
|
|
3209
3214
|
getattr(args, "vectorfield_components_dim", None),
|
|
@@ -3214,7 +3219,7 @@ def arrayview():
|
|
|
3214
3219
|
# Load overlay(s) as separate sessions
|
|
3215
3220
|
overlay_sids = []
|
|
3216
3221
|
for ov_path in (getattr(args, "overlay", None) or []):
|
|
3217
|
-
ov_data = load_data(ov_path)
|
|
3222
|
+
ov_data = load_data(ov_path, key=default_array_key(ov_path))
|
|
3218
3223
|
ov_session = Session(
|
|
3219
3224
|
data=ov_data,
|
|
3220
3225
|
filepath=ov_path,
|
|
@@ -3230,7 +3235,7 @@ def arrayview():
|
|
|
3230
3235
|
if getattr(args, "compare", None):
|
|
3231
3236
|
compare_paths.append(args.compare)
|
|
3232
3237
|
for cp in compare_paths:
|
|
3233
|
-
cmp_data = load_data(cp)
|
|
3238
|
+
cmp_data = load_data(cp, key=default_array_key(cp))
|
|
3234
3239
|
cmp_session = Session(
|
|
3235
3240
|
data=cmp_data,
|
|
3236
3241
|
filepath=cp,
|
|
@@ -3570,18 +3575,21 @@ def arrayview():
|
|
|
3570
3575
|
|
|
3571
3576
|
if args.vectorfield:
|
|
3572
3577
|
try:
|
|
3573
|
-
from arrayview._io import load_data
|
|
3578
|
+
from arrayview._io import default_array_key, load_data
|
|
3574
3579
|
from arrayview._render import _detect_rgb_axis
|
|
3575
3580
|
from arrayview._vectorfield import _resolve_vfield_layout
|
|
3576
3581
|
|
|
3577
|
-
base_data = load_data(base_file)
|
|
3582
|
+
base_data = load_data(base_file, key=default_array_key(base_file))
|
|
3578
3583
|
image_shape = tuple(int(s) for s in base_data.shape)
|
|
3579
3584
|
if args.rgb:
|
|
3580
3585
|
rgb_axis = _detect_rgb_axis(image_shape)
|
|
3581
3586
|
image_shape = tuple(
|
|
3582
3587
|
s for i, s in enumerate(image_shape) if i != rgb_axis
|
|
3583
3588
|
)
|
|
3584
|
-
vf_data = load_data(
|
|
3589
|
+
vf_data = load_data(
|
|
3590
|
+
args.vectorfield,
|
|
3591
|
+
key=default_array_key(args.vectorfield),
|
|
3592
|
+
)
|
|
3585
3593
|
layout = _resolve_vfield_layout(
|
|
3586
3594
|
tuple(int(s) for s in vf_data.shape),
|
|
3587
3595
|
image_shape,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import importlib.util
|
|
5
6
|
import os
|
|
6
7
|
import subprocess
|
|
7
8
|
import sys
|
|
@@ -414,12 +415,13 @@ def _can_native_window() -> bool:
|
|
|
414
415
|
# Plain SSH (no VS Code): the display is on the client machine, not here.
|
|
415
416
|
if os.environ.get("SSH_CLIENT") or os.environ.get("SSH_CONNECTION"):
|
|
416
417
|
return False
|
|
418
|
+
if importlib.util.find_spec("webview") is None:
|
|
419
|
+
return False
|
|
417
420
|
if sys.platform in ("darwin", "win32"):
|
|
418
421
|
return True
|
|
419
422
|
# Linux/BSD: need a display server AND pywebview's GUI bindings
|
|
420
423
|
if not (os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY")):
|
|
421
424
|
return False
|
|
422
|
-
import importlib.util
|
|
423
425
|
|
|
424
426
|
return (
|
|
425
427
|
importlib.util.find_spec("qtpy") is not None
|
|
@@ -123,8 +123,14 @@ def register_loading_routes(app, *, notify_shells, setup_rgb) -> None:
|
|
|
123
123
|
_array_keys = await asyncio.to_thread(list_array_keys, filepath)
|
|
124
124
|
if len(_array_keys) > 1 and not _key:
|
|
125
125
|
return {"array_keys": _array_keys, "filepath": filepath}
|
|
126
|
+
if len(_array_keys) == 1 and not _key:
|
|
127
|
+
_key = _array_keys[0]["key"]
|
|
126
128
|
|
|
127
|
-
data, spatial_meta = await asyncio.to_thread(
|
|
129
|
+
data, spatial_meta = await asyncio.to_thread(
|
|
130
|
+
load_data_with_meta,
|
|
131
|
+
filepath,
|
|
132
|
+
key=_key,
|
|
133
|
+
)
|
|
128
134
|
except Exception as e:
|
|
129
135
|
return {"error": str(e)}
|
|
130
136
|
session = await asyncio.to_thread(Session, data, filepath=filepath, name=name)
|
|
@@ -20,6 +20,7 @@ from arrayview._session import (
|
|
|
20
20
|
SESSIONS,
|
|
21
21
|
COLORMAPS,
|
|
22
22
|
)
|
|
23
|
+
from arrayview import __version__ as _av_version
|
|
23
24
|
import arrayview._session as _session_mod # for mutable VIEWER_SOCKETS
|
|
24
25
|
|
|
25
26
|
from arrayview._render import (
|
|
@@ -195,6 +196,7 @@ def get_ui(sid: str = None):
|
|
|
195
196
|
.replace("__DEFAULT_THEME_IDX__", str(_default_theme_idx))
|
|
196
197
|
.replace("__DEFAULT_ROUNDED_PANES__", _default_rounded_panes)
|
|
197
198
|
.replace("__BODY_CLASS__", "av-loading" if sid else "")
|
|
199
|
+
.replace("__ARRAYVIEW_VERSION__", _av_version)
|
|
198
200
|
)
|
|
199
201
|
headers = {"Cache-Control": "no-store"}
|
|
200
202
|
return HTMLResponse(content=html, headers=headers)
|
|
@@ -23,6 +23,7 @@ import uuid
|
|
|
23
23
|
|
|
24
24
|
import numpy as np
|
|
25
25
|
|
|
26
|
+
from arrayview import __version__ as _av_version
|
|
26
27
|
from arrayview._analysis import (
|
|
27
28
|
_build_metadata,
|
|
28
29
|
_lebesgue_slice,
|
|
@@ -114,7 +115,12 @@ def _handle_register(msg: dict) -> None:
|
|
|
114
115
|
name = msg.get("name") or __import__("os").path.basename(file_path)
|
|
115
116
|
options = msg.get("options", {})
|
|
116
117
|
|
|
117
|
-
|
|
118
|
+
from ._io import default_array_key
|
|
119
|
+
|
|
120
|
+
data, spatial_meta = load_data_with_meta(
|
|
121
|
+
file_path,
|
|
122
|
+
key=default_array_key(file_path),
|
|
123
|
+
)
|
|
118
124
|
session = Session(data=data, filepath=file_path, name=name)
|
|
119
125
|
session.spatial_meta = spatial_meta
|
|
120
126
|
if spatial_meta is not None:
|
|
@@ -686,6 +692,7 @@ def _handle_get_viewer_html(msg: dict) -> None:
|
|
|
686
692
|
.replace("__DEFAULT_THEME_IDX__", str(_default_theme_idx))
|
|
687
693
|
.replace("__DEFAULT_ROUNDED_PANES__", _default_rounded_panes)
|
|
688
694
|
.replace("__BODY_CLASS__", "av-loading" if sid else "")
|
|
695
|
+
.replace("__ARRAYVIEW_VERSION__", _av_version)
|
|
689
696
|
)
|
|
690
697
|
|
|
691
698
|
_write_json({"html": html})
|