arrayview 0.25.0__tar.gz → 0.26.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/context/frontend.md +1 -1
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/context/project-state.md +14 -2
- {arrayview-0.25.0 → arrayview-0.26.0}/PKG-INFO +1 -1
- {arrayview-0.25.0 → arrayview-0.26.0}/docs/comparing.md +4 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/docs/display.md +6 -2
- {arrayview-0.25.0 → arrayview-0.26.0}/docs/loading.md +5 -2
- arrayview-0.26.0/docs/measurement.md +40 -0
- arrayview-0.26.0/docs/viewing.md +47 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/pyproject.toml +1 -1
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/__init__.py +1 -1
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_io.py +2 -2
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_launcher.py +183 -26
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_server.py +3 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_stdio_server.py +2 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_viewer.html +304 -154
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_api.py +130 -10
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_browser.py +63 -24
- {arrayview-0.25.0 → arrayview-0.26.0}/uv.lock +1 -1
- arrayview-0.25.0/docs/measurement.md +0 -34
- arrayview-0.25.0/docs/viewing.md +0 -36
- {arrayview-0.25.0 → arrayview-0.26.0}/.agents/skills/frontend-designer/SKILL.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.agents/skills/invocation-consistency/SKILL.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.agents/skills/modes-consistency/SKILL.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.agents/skills/playwright-cli/SKILL.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.agents/skills/playwright-cli/references/element-attributes.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.agents/skills/playwright-cli/references/playwright-tests.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.agents/skills/playwright-cli/references/request-mocking.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.agents/skills/playwright-cli/references/running-code.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.agents/skills/playwright-cli/references/session-management.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.agents/skills/playwright-cli/references/storage-state.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.agents/skills/playwright-cli/references/test-generation.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.agents/skills/playwright-cli/references/tracing.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.agents/skills/playwright-cli/references/video-recording.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.agents/skills/todo-workflow/SKILL.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.agents/skills/ui-consistency-audit/SKILL.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.agents/skills/viewer-ui-checklist/SKILL.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.agents/skills/visual-bug-fixing/SKILL.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.github/copilot-instructions.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.github/workflows/docs.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.github/workflows/python-publish.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.gitignore +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.ignore +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/AGENTS.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/ROUTER.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/SETUP.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/SYNC.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/context/architecture.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/context/conventions.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/context/decisions.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/context/lifecycle.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/context/render-pipeline.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/context/setup.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/context/stack.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/patterns/INDEX.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/patterns/README.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/patterns/add-file-format.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/patterns/add-server-endpoint.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/patterns/animation-verify.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/patterns/debug-render.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/patterns/debug-vscode-extension-python.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/patterns/extend-compare-mode.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/patterns/extract-server-route-module.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/patterns/frontend-change.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/setup.sh +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.mex/sync.sh +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.opencode/opencode.json +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T18-46-49-737Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T18-48-21-979Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T18-51-35-665Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T19-07-01-393Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T19-14-37-969Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T19-21-30-940Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T19-23-08-126Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T19-29-33-155Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T19-31-25-336Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T19-31-53-789Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T19-39-12-257Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T19-39-16-449Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T20-15-25-513Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T20-25-13-179Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T20-39-01-435Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T21-01-27-659Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T21-01-41-283Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T21-03-00-625Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T21-04-12-887Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T21-33-39-044Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T21-38-01-530Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T21-45-20-383Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T21-55-11-545Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T21-56-03-307Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T21-56-35-733Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T21-57-12-181Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T21-57-37-748Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T21-58-13-679Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-06T22-37-23-895Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T00-39-18-637Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T01-41-46-243Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T04-31-48-472Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T12-14-15-632Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T12-14-47-582Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T12-16-23-471Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T12-17-10-247Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T12-18-24-707Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T12-20-06-164Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T12-20-28-342Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T12-21-54-962Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T12-22-34-666Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T12-23-11-336Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T12-23-36-260Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T12-24-09-267Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T12-24-35-434Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T12-25-57-010Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T12-34-48-823Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T12-46-46-468Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T12-48-17-930Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T12-49-26-400Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T12-50-31-563Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/page-2026-05-07T12-56-45-568Z.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.playwright-cli/theme-dark.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.python-version +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/.vscode/settings.json +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/AGENTS.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/CONTRIBUTING.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/DESIGN.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/LICENSE +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/README.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/docs/configuration.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/docs/index.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/docs/lifecycle.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/docs/logo.png +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/docs/remote.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/docs/stylesheets/extra.css +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/matlab/arrayview.m +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/mkdocs.yml +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/plans/2026-04-14-immersive-animation.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/plans/2026-05-07-unified-colormap-picker.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/plans/arrayview_tool_menu_fix_prompt.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/plans/arrayview_tool_menu_implementation_plan.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/plans/egg-placement-mockup.html +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/plans/refactoring_plan.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/plans/webview/LOG.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/scripts/demo.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/scripts/release.sh +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/ARCHITECTURE.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/__main__.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_analysis.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_app.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_codex_open.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_config.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_diff.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_icon.png +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_imaging.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_lifecycle.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_overlays.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_platform.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_render.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_routes_analysis.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_routes_export.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_routes_loading.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_routes_persistence.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_routes_preload.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_routes_query.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_routes_rendering.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_routes_segmentation.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_routes_state.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_routes_vectorfield.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_routes_websocket.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_segmentation.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_session.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_shell.html +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_synthetic_mri.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_torch.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_vectorfield.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_vscode.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_vscode_browser.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_vscode_extension.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_vscode_shm.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/_vscode_signal.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/arrayview-opener.vsix +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/src/arrayview/gsap.min.js +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/capture_v_animation.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/conftest.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/lifecycle_matrix.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/make_vectorfield_test_arrays.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_backend_shared.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_cli.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_colorbar_hover_highlight.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_command_reachability.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_config.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_cross_mode_parametrized.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_interactions.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_large_arrays.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_lifecycle_contract.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_loading_server.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_mode_consistency.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_mode_entry_batching.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_mode_matrix.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_mode_roundtrip.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_nifti_meta.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_rgb_pixel_art.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_stdio_server.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_torch.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_view_component_integration.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/test_view_component_unit.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/ui_audit.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/v_anim_frames/.gitkeep +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/tests/visual_smoke.py +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/vscode-extension/AGENTS.md +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/vscode-extension/LICENSE +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/vscode-extension/extension.js +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/vscode-extension/lifecycle_helpers.js +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/vscode-extension/package.json +0 -0
- {arrayview-0.25.0 → arrayview-0.26.0}/vscode-extension/test_lifecycle_helpers.js +0 -0
|
@@ -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
|
|
|
@@ -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.
|
|
@@ -67,7 +67,7 @@ def list_mat_keys(filepath):
|
|
|
67
67
|
for k, v in mat.items():
|
|
68
68
|
if k.startswith("_"):
|
|
69
69
|
continue
|
|
70
|
-
if isinstance(v, np.ndarray) and v.ndim >= 1:
|
|
70
|
+
if isinstance(v, np.ndarray) and v.ndim >= 1 and v.dtype.kind in ("b", "i", "u", "f", "c"):
|
|
71
71
|
keys.append({"key": k, "shape": list(v.shape), "dtype": str(v.dtype)})
|
|
72
72
|
return keys
|
|
73
73
|
except NotImplementedError:
|
|
@@ -77,7 +77,7 @@ def list_mat_keys(filepath):
|
|
|
77
77
|
keys = []
|
|
78
78
|
for k in f.keys():
|
|
79
79
|
ds = f[k]
|
|
80
|
-
if isinstance(ds, h5py.Dataset) and len(ds.shape) >= 1:
|
|
80
|
+
if isinstance(ds, h5py.Dataset) and len(ds.shape) >= 1 and ds.dtype.kind in ("b", "i", "u", "f", "c"):
|
|
81
81
|
keys.append({"key": k, "shape": list(ds.shape), "dtype": str(ds.dtype)})
|
|
82
82
|
f.close()
|
|
83
83
|
return keys
|
|
@@ -448,6 +448,17 @@ def _open_webview_cli(
|
|
|
448
448
|
Returns True once the child has imported pywebview and created the window.
|
|
449
449
|
Returns False if it crashed; in that case the caller should fall back to browser.
|
|
450
450
|
"""
|
|
451
|
+
opened, _proc = _open_webview_cli_tracked(url, win_w, win_h, shell_port=shell_port)
|
|
452
|
+
return opened
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _open_webview_cli_tracked(
|
|
456
|
+
url: str,
|
|
457
|
+
win_w: int,
|
|
458
|
+
win_h: int,
|
|
459
|
+
shell_port: int | None = None,
|
|
460
|
+
) -> tuple[bool, subprocess.Popen | None]:
|
|
461
|
+
"""Launch pywebview and return the child process when startup succeeds."""
|
|
451
462
|
import tempfile
|
|
452
463
|
|
|
453
464
|
_vprint("[ArrayView] Launching native window (PyWebView)...", flush=True)
|
|
@@ -483,21 +494,64 @@ def _open_webview_cli(
|
|
|
483
494
|
)
|
|
484
495
|
if stderr_out:
|
|
485
496
|
_vprint(f"[ArrayView] webview stderr: {stderr_out}", flush=True)
|
|
486
|
-
return False
|
|
497
|
+
return False, None
|
|
487
498
|
if os.path.exists(ready_file):
|
|
488
499
|
try:
|
|
489
500
|
os.unlink(ready_file)
|
|
490
501
|
except OSError:
|
|
491
502
|
pass
|
|
492
503
|
_vprint("[ArrayView] Native window started successfully", flush=True)
|
|
493
|
-
return True
|
|
504
|
+
return True, proc
|
|
494
505
|
time.sleep(0.02)
|
|
495
506
|
try:
|
|
496
507
|
os.unlink(ready_file)
|
|
497
508
|
except OSError:
|
|
498
509
|
pass
|
|
499
510
|
_vprint("[ArrayView] Native window started successfully", flush=True)
|
|
500
|
-
return True
|
|
511
|
+
return True, proc
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def _server_viewer_connections_seen(port: int, timeout: float = 0.5) -> int:
|
|
515
|
+
"""Return the daemon's monotonic viewer WebSocket connection count."""
|
|
516
|
+
url = f"http://{_LOOPBACK_HOST}:{port}/ping"
|
|
517
|
+
try:
|
|
518
|
+
with urllib.request.urlopen(url, timeout=timeout) as resp:
|
|
519
|
+
if resp.status != 200:
|
|
520
|
+
return 0
|
|
521
|
+
payload = json.loads(resp.read().decode("utf-8"))
|
|
522
|
+
if payload.get("ok") is True and payload.get("service") == "arrayview":
|
|
523
|
+
return int(
|
|
524
|
+
payload.get("viewer_connections_seen")
|
|
525
|
+
or payload.get("viewer_sockets")
|
|
526
|
+
or 0
|
|
527
|
+
)
|
|
528
|
+
except Exception:
|
|
529
|
+
pass
|
|
530
|
+
return 0
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def _wait_for_viewer_connection(
|
|
534
|
+
port: int,
|
|
535
|
+
*,
|
|
536
|
+
before: int,
|
|
537
|
+
timeout: float = 8.0,
|
|
538
|
+
) -> bool:
|
|
539
|
+
"""Wait until the backend has accepted a new viewer WebSocket connection."""
|
|
540
|
+
deadline = time.monotonic() + timeout
|
|
541
|
+
while time.monotonic() < deadline:
|
|
542
|
+
if _server_viewer_connections_seen(port, timeout=0.3) > before:
|
|
543
|
+
return True
|
|
544
|
+
time.sleep(0.1)
|
|
545
|
+
return False
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def _terminate_native_process(proc: subprocess.Popen | None) -> None:
|
|
549
|
+
if proc is None or proc.poll() is not None:
|
|
550
|
+
return
|
|
551
|
+
try:
|
|
552
|
+
proc.terminate()
|
|
553
|
+
except Exception:
|
|
554
|
+
return
|
|
501
555
|
|
|
502
556
|
|
|
503
557
|
# ── Server Port Utilities ─────────────────────────────────────────
|
|
@@ -809,7 +863,15 @@ def _open_cli_existing_server_view(
|
|
|
809
863
|
return
|
|
810
864
|
if notify_webview and not notified:
|
|
811
865
|
url_shell = _shell_url(port, sid, name, compare_sids=compare_sids)
|
|
812
|
-
|
|
866
|
+
seen_before = _server_viewer_connections_seen(port)
|
|
867
|
+
opened, proc = _open_webview_cli_tracked(url_shell, 1200, 800)
|
|
868
|
+
if not opened or not _wait_for_viewer_connection(port, before=seen_before):
|
|
869
|
+
if opened:
|
|
870
|
+
_vprint(
|
|
871
|
+
"[ArrayView] Native window did not connect to the backend; falling back to browser",
|
|
872
|
+
flush=True,
|
|
873
|
+
)
|
|
874
|
+
_terminate_native_process(proc)
|
|
813
875
|
_vprint("[ArrayView] Falling back to browser", flush=True)
|
|
814
876
|
_print_viewer_location(url)
|
|
815
877
|
_open_browser(
|
|
@@ -993,7 +1055,9 @@ def _handle_cli_spawned_daemon(
|
|
|
993
1055
|
)
|
|
994
1056
|
|
|
995
1057
|
early_webview_opened = False
|
|
1058
|
+
early_webview_proc = None
|
|
996
1059
|
early_webview_notified = False
|
|
1060
|
+
early_webview_connected = False
|
|
997
1061
|
if (
|
|
998
1062
|
use_webview
|
|
999
1063
|
and not is_remote
|
|
@@ -1001,7 +1065,7 @@ def _handle_cli_spawned_daemon(
|
|
|
1001
1065
|
and not compare_files
|
|
1002
1066
|
):
|
|
1003
1067
|
url_shell_early = _shell_url(port, sid, name)
|
|
1004
|
-
early_webview_opened =
|
|
1068
|
+
early_webview_opened, early_webview_proc = _open_webview_cli_tracked(
|
|
1005
1069
|
url_shell_early, 1400, 900, shell_port=port
|
|
1006
1070
|
)
|
|
1007
1071
|
|
|
@@ -1019,6 +1083,7 @@ def _handle_cli_spawned_daemon(
|
|
|
1019
1083
|
sys.exit(1)
|
|
1020
1084
|
|
|
1021
1085
|
if early_webview_opened:
|
|
1086
|
+
seen_before = _server_viewer_connections_seen(port)
|
|
1022
1087
|
try:
|
|
1023
1088
|
notify_result = _notify_existing_session(
|
|
1024
1089
|
port,
|
|
@@ -1030,6 +1095,21 @@ def _handle_cli_spawned_daemon(
|
|
|
1030
1095
|
early_webview_notified = bool(notify_result.get("notified"))
|
|
1031
1096
|
except Exception:
|
|
1032
1097
|
early_webview_notified = False
|
|
1098
|
+
if early_webview_notified:
|
|
1099
|
+
early_webview_connected = _wait_for_viewer_connection(
|
|
1100
|
+
port,
|
|
1101
|
+
before=seen_before,
|
|
1102
|
+
)
|
|
1103
|
+
if not early_webview_connected:
|
|
1104
|
+
_vprint(
|
|
1105
|
+
"[ArrayView] Native window did not connect to the backend; falling back to browser",
|
|
1106
|
+
flush=True,
|
|
1107
|
+
)
|
|
1108
|
+
_terminate_native_process(early_webview_proc)
|
|
1109
|
+
|
|
1110
|
+
should_retry_webview = use_webview and not (
|
|
1111
|
+
early_webview_opened and not early_webview_connected
|
|
1112
|
+
)
|
|
1033
1113
|
|
|
1034
1114
|
_open_cli_spawned_view(
|
|
1035
1115
|
port=port,
|
|
@@ -1037,14 +1117,14 @@ def _handle_cli_spawned_daemon(
|
|
|
1037
1117
|
compare_sids=compare_sids,
|
|
1038
1118
|
overlay_sid=overlay_sid,
|
|
1039
1119
|
dims_override=dims_override,
|
|
1040
|
-
use_webview=
|
|
1120
|
+
use_webview=should_retry_webview,
|
|
1041
1121
|
name=name,
|
|
1042
1122
|
base_file=base_file,
|
|
1043
1123
|
watch=watch,
|
|
1044
1124
|
window_mode=window_mode,
|
|
1045
1125
|
floating=floating,
|
|
1046
1126
|
is_remote=is_remote,
|
|
1047
|
-
webview_already_opened=
|
|
1127
|
+
webview_already_opened=early_webview_connected,
|
|
1048
1128
|
)
|
|
1049
1129
|
|
|
1050
1130
|
|
|
@@ -1075,7 +1155,15 @@ def _open_cli_spawned_view(
|
|
|
1075
1155
|
if webview_already_opened:
|
|
1076
1156
|
return
|
|
1077
1157
|
url_shell = _shell_url(port, sid, name, compare_sids=compare_sids)
|
|
1078
|
-
|
|
1158
|
+
seen_before = _server_viewer_connections_seen(port)
|
|
1159
|
+
opened, proc = _open_webview_cli_tracked(url_shell, 1400, 900)
|
|
1160
|
+
if not opened or not _wait_for_viewer_connection(port, before=seen_before):
|
|
1161
|
+
if opened:
|
|
1162
|
+
_vprint(
|
|
1163
|
+
"[ArrayView] Native window did not connect to the backend; falling back to browser",
|
|
1164
|
+
flush=True,
|
|
1165
|
+
)
|
|
1166
|
+
_terminate_native_process(proc)
|
|
1079
1167
|
_vprint("[ArrayView] Falling back to browser", flush=True)
|
|
1080
1168
|
_print_viewer_location(url)
|
|
1081
1169
|
_open_browser(
|
|
@@ -2621,7 +2709,16 @@ def _serve_daemon(
|
|
|
2621
2709
|
from arrayview._io import load_data, load_data_with_meta, list_array_keys
|
|
2622
2710
|
|
|
2623
2711
|
try:
|
|
2624
|
-
|
|
2712
|
+
# Multi-array .npz/.mat: load the first array so the session is
|
|
2713
|
+
# created, then store keys so the viewer can show a picker.
|
|
2714
|
+
_array_keys = None
|
|
2715
|
+
if (filepath.endswith(".npz") or filepath.endswith(".mat")) and not cleanup:
|
|
2716
|
+
try:
|
|
2717
|
+
_array_keys = list_array_keys(filepath)
|
|
2718
|
+
except Exception:
|
|
2719
|
+
pass
|
|
2720
|
+
_load_key = _array_keys[0]["key"] if _array_keys and len(_array_keys) > 1 else None
|
|
2721
|
+
data, spatial_meta = load_data_with_meta(filepath, key=_load_key)
|
|
2625
2722
|
if cleanup:
|
|
2626
2723
|
try:
|
|
2627
2724
|
os.unlink(filepath)
|
|
@@ -2632,15 +2729,9 @@ def _serve_daemon(
|
|
|
2632
2729
|
)
|
|
2633
2730
|
session.sid = sid
|
|
2634
2731
|
session.spatial_meta = spatial_meta
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
keys = list_array_keys(filepath)
|
|
2639
|
-
if len(keys) > 1:
|
|
2640
|
-
session.array_keys = keys
|
|
2641
|
-
session.array_filepath = filepath
|
|
2642
|
-
except Exception:
|
|
2643
|
-
pass
|
|
2732
|
+
if _array_keys and len(_array_keys) > 1:
|
|
2733
|
+
session.array_keys = _array_keys
|
|
2734
|
+
session.array_filepath = filepath
|
|
2644
2735
|
if spatial_meta is not None:
|
|
2645
2736
|
session.original_volume = data
|
|
2646
2737
|
if rgb:
|
|
@@ -2905,6 +2996,8 @@ def arrayview():
|
|
|
2905
2996
|
return
|
|
2906
2997
|
|
|
2907
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}")
|
|
2908
3001
|
parser.add_argument(
|
|
2909
3002
|
"files",
|
|
2910
3003
|
nargs="*",
|
|
@@ -3165,10 +3258,62 @@ def arrayview():
|
|
|
3165
3258
|
# --diagnose: print detection results and exit
|
|
3166
3259
|
if getattr(args, "diagnose", False):
|
|
3167
3260
|
import json as _json
|
|
3261
|
+
import importlib.util as _importlib_util
|
|
3168
3262
|
from ._platform import (
|
|
3169
3263
|
_find_vscode_ipc_hook,
|
|
3170
3264
|
_in_jupyter,
|
|
3171
3265
|
)
|
|
3266
|
+
from arrayview._config import CONFIG_PATH, get_window_default, load_config
|
|
3267
|
+
from arrayview._platform import detect_environment
|
|
3268
|
+
|
|
3269
|
+
_det_env = detect_environment()
|
|
3270
|
+
_config_window = get_window_default(_det_env)
|
|
3271
|
+
_in_vs = _in_vscode_terminal()
|
|
3272
|
+
_is_remote = _is_vscode_remote()
|
|
3273
|
+
_can_native = _can_native_window()
|
|
3274
|
+
_window_plan = _resolve_cli_window_mode(
|
|
3275
|
+
explicit_window=args.window,
|
|
3276
|
+
browser_flag=args.browser,
|
|
3277
|
+
config_window=_config_window,
|
|
3278
|
+
in_vscode_terminal=_in_vs,
|
|
3279
|
+
is_vscode_remote=_is_remote,
|
|
3280
|
+
can_native_window=_can_native,
|
|
3281
|
+
)
|
|
3282
|
+
|
|
3283
|
+
def _localhost_candidates() -> list[dict[str, object]]:
|
|
3284
|
+
out = []
|
|
3285
|
+
try:
|
|
3286
|
+
infos = socket.getaddrinfo(
|
|
3287
|
+
_LOOPBACK_HOST,
|
|
3288
|
+
args.port,
|
|
3289
|
+
type=socket.SOCK_STREAM,
|
|
3290
|
+
)
|
|
3291
|
+
except Exception as exc:
|
|
3292
|
+
return [{"error": str(exc)}]
|
|
3293
|
+
for family, socktype, proto, _canon, sockaddr in infos:
|
|
3294
|
+
out.append(
|
|
3295
|
+
{
|
|
3296
|
+
"family": getattr(socket.AddressFamily(family), "name", str(family)),
|
|
3297
|
+
"socktype": socktype,
|
|
3298
|
+
"proto": proto,
|
|
3299
|
+
"sockaddr": str(sockaddr),
|
|
3300
|
+
}
|
|
3301
|
+
)
|
|
3302
|
+
return out
|
|
3303
|
+
|
|
3304
|
+
def _bind_probe(port: int) -> dict[str, object]:
|
|
3305
|
+
try:
|
|
3306
|
+
sock = _make_loopback_socket(port)
|
|
3307
|
+
except Exception as exc:
|
|
3308
|
+
return {"ok": False, "error": str(exc)}
|
|
3309
|
+
try:
|
|
3310
|
+
return {
|
|
3311
|
+
"ok": True,
|
|
3312
|
+
"family": getattr(socket.AddressFamily(sock.family), "name", str(sock.family)),
|
|
3313
|
+
"sockname": str(sock.getsockname()),
|
|
3314
|
+
}
|
|
3315
|
+
finally:
|
|
3316
|
+
sock.close()
|
|
3172
3317
|
|
|
3173
3318
|
diag: dict = {
|
|
3174
3319
|
"env": {
|
|
@@ -3182,28 +3327,40 @@ def arrayview():
|
|
|
3182
3327
|
"WAYLAND_DISPLAY": os.environ.get("WAYLAND_DISPLAY"),
|
|
3183
3328
|
},
|
|
3184
3329
|
"detection": {
|
|
3185
|
-
"in_vscode_terminal":
|
|
3186
|
-
"is_vscode_remote":
|
|
3330
|
+
"in_vscode_terminal": _in_vs,
|
|
3331
|
+
"is_vscode_remote": _is_remote,
|
|
3187
3332
|
"in_vscode_tunnel": _in_vscode_tunnel(),
|
|
3188
|
-
"can_native_window":
|
|
3333
|
+
"can_native_window": _can_native,
|
|
3189
3334
|
"in_jupyter": _in_jupyter(),
|
|
3190
3335
|
"vscode_ipc_hook_recovered": _find_vscode_ipc_hook(),
|
|
3191
3336
|
},
|
|
3337
|
+
"launch_plan": {
|
|
3338
|
+
**_window_plan,
|
|
3339
|
+
"port": args.port,
|
|
3340
|
+
"port_in_use": _port_in_use(args.port),
|
|
3341
|
+
"arrayview_server_alive": _server_alive(args.port),
|
|
3342
|
+
},
|
|
3343
|
+
"loopback": {
|
|
3344
|
+
"host": _LOOPBACK_HOST,
|
|
3345
|
+
"getaddrinfo": _localhost_candidates(),
|
|
3346
|
+
"bind_probe": _bind_probe(0),
|
|
3347
|
+
},
|
|
3348
|
+
"native_dependencies": {
|
|
3349
|
+
"qtpy": _importlib_util.find_spec("qtpy") is not None,
|
|
3350
|
+
"gi": _importlib_util.find_spec("gi") is not None,
|
|
3351
|
+
"webview": _importlib_util.find_spec("webview") is not None,
|
|
3352
|
+
},
|
|
3192
3353
|
"pid": os.getpid(),
|
|
3193
3354
|
"ppid": os.getppid(),
|
|
3194
3355
|
"platform": sys.platform,
|
|
3195
3356
|
"python": sys.executable,
|
|
3196
3357
|
}
|
|
3197
|
-
from arrayview._config import CONFIG_PATH, get_window_default, load_config
|
|
3198
|
-
from arrayview._platform import detect_environment
|
|
3199
|
-
|
|
3200
|
-
_det_env = detect_environment()
|
|
3201
3358
|
diag["config"] = {
|
|
3202
3359
|
"detected_environment": _det_env,
|
|
3203
3360
|
"config_file": CONFIG_PATH,
|
|
3204
3361
|
"config_contents": load_config() or None,
|
|
3205
3362
|
"ARRAYVIEW_WINDOW": os.environ.get("ARRAYVIEW_WINDOW") or None,
|
|
3206
|
-
"resolved_window_pref":
|
|
3363
|
+
"resolved_window_pref": _config_window,
|
|
3207
3364
|
}
|
|
3208
3365
|
print(_json.dumps(diag, indent=2))
|
|
3209
3366
|
return
|
|
@@ -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 (
|
|
@@ -152,6 +153,7 @@ def ping():
|
|
|
152
153
|
"pid": os.getpid(),
|
|
153
154
|
"hostname": socket.gethostname(),
|
|
154
155
|
"viewer_sockets": _session_mod.VIEWER_SOCKETS,
|
|
156
|
+
"viewer_connections_seen": _session_mod.VIEWER_CONNECTIONS_SEEN,
|
|
155
157
|
}
|
|
156
158
|
|
|
157
159
|
|
|
@@ -194,6 +196,7 @@ def get_ui(sid: str = None):
|
|
|
194
196
|
.replace("__DEFAULT_THEME_IDX__", str(_default_theme_idx))
|
|
195
197
|
.replace("__DEFAULT_ROUNDED_PANES__", _default_rounded_panes)
|
|
196
198
|
.replace("__BODY_CLASS__", "av-loading" if sid else "")
|
|
199
|
+
.replace("__ARRAYVIEW_VERSION__", _av_version)
|
|
197
200
|
)
|
|
198
201
|
headers = {"Cache-Control": "no-store"}
|
|
199
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,
|
|
@@ -686,6 +687,7 @@ def _handle_get_viewer_html(msg: dict) -> None:
|
|
|
686
687
|
.replace("__DEFAULT_THEME_IDX__", str(_default_theme_idx))
|
|
687
688
|
.replace("__DEFAULT_ROUNDED_PANES__", _default_rounded_panes)
|
|
688
689
|
.replace("__BODY_CLASS__", "av-loading" if sid else "")
|
|
690
|
+
.replace("__ARRAYVIEW_VERSION__", _av_version)
|
|
689
691
|
)
|
|
690
692
|
|
|
691
693
|
_write_json({"html": html})
|