arrayview 0.30.0__tar.gz → 0.31.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.30.0 → arrayview-0.31.1}/.mex/context/project-state.md +18 -1
- {arrayview-0.30.0 → arrayview-0.31.1}/PKG-INFO +1 -1
- {arrayview-0.30.0 → arrayview-0.31.1}/docs/comparing.md +1 -1
- {arrayview-0.30.0 → arrayview-0.31.1}/docs/loading.md +24 -2
- {arrayview-0.30.0 → arrayview-0.31.1}/pyproject.toml +1 -1
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_analysis.py +9 -2
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_io.py +209 -40
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_launcher.py +250 -20
- arrayview-0.31.1/src/arrayview/_lifecycle.py +64 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_overlays.py +16 -1
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_render.py +58 -20
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_routes_loading.py +9 -2
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_routes_query.py +13 -1
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_routes_rendering.py +5 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_routes_websocket.py +16 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_server.py +1 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_session.py +96 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_viewer.html +473 -60
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_vscode_extension.py +1 -1
- arrayview-0.31.1/src/arrayview/arrayview-opener.vsix +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_api.py +199 -2
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_backend_shared.py +57 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_browser.py +152 -4
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_cli.py +162 -1
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_lifecycle_contract.py +8 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_nifti_series.py +232 -1
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_view_component_integration.py +11 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/visual_smoke.py +10 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/uv.lock +1 -1
- {arrayview-0.30.0 → arrayview-0.31.1}/vscode-extension/extension.js +7 -2
- {arrayview-0.30.0 → arrayview-0.31.1}/vscode-extension/lifecycle_helpers.js +12 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/vscode-extension/package.json +1 -1
- {arrayview-0.30.0 → arrayview-0.31.1}/vscode-extension/test_lifecycle_helpers.js +7 -0
- arrayview-0.30.0/src/arrayview/_lifecycle.py +0 -39
- arrayview-0.30.0/src/arrayview/arrayview-opener.vsix +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.agents/skills/frontend-designer/SKILL.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.agents/skills/invocation-consistency/SKILL.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.agents/skills/modes-consistency/SKILL.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.agents/skills/playwright-cli/SKILL.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.agents/skills/playwright-cli/references/element-attributes.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.agents/skills/playwright-cli/references/playwright-tests.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.agents/skills/playwright-cli/references/request-mocking.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.agents/skills/playwright-cli/references/running-code.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.agents/skills/playwright-cli/references/session-management.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.agents/skills/playwright-cli/references/storage-state.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.agents/skills/playwright-cli/references/test-generation.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.agents/skills/playwright-cli/references/tracing.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.agents/skills/playwright-cli/references/video-recording.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.agents/skills/todo-workflow/SKILL.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.agents/skills/ui-consistency-audit/SKILL.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.agents/skills/viewer-ui-checklist/SKILL.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.agents/skills/visual-bug-fixing/SKILL.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.github/copilot-instructions.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.github/workflows/docs.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.github/workflows/launch-contracts.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.github/workflows/python-publish.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.gitignore +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.ignore +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/AGENTS.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/ROUTER.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/SETUP.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/SYNC.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/context/architecture.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/context/conventions.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/context/decisions.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/context/frontend.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/context/lifecycle.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/context/render-pipeline.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/context/setup.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/context/stack.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/patterns/INDEX.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/patterns/README.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/patterns/add-file-format.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/patterns/add-server-endpoint.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/patterns/animation-verify.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/patterns/debug-render.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/patterns/debug-vscode-extension-python.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/patterns/extend-compare-mode.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/patterns/extract-server-route-module.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/patterns/frontend-change.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/setup.sh +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.mex/sync.sh +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.opencode/opencode.json +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T18-46-49-737Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T18-48-21-979Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T18-51-35-665Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T19-07-01-393Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T19-14-37-969Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T19-21-30-940Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T19-23-08-126Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T19-29-33-155Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T19-31-25-336Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T19-31-53-789Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T19-39-12-257Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T19-39-16-449Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T20-15-25-513Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T20-25-13-179Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T20-39-01-435Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T21-01-27-659Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T21-01-41-283Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T21-03-00-625Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T21-04-12-887Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T21-33-39-044Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T21-38-01-530Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T21-45-20-383Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T21-55-11-545Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T21-56-03-307Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T21-56-35-733Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T21-57-12-181Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T21-57-37-748Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T21-58-13-679Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-06T22-37-23-895Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T00-39-18-637Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T01-41-46-243Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T04-31-48-472Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T12-14-15-632Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T12-14-47-582Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T12-16-23-471Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T12-17-10-247Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T12-18-24-707Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T12-20-06-164Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T12-20-28-342Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T12-21-54-962Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T12-22-34-666Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T12-23-11-336Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T12-23-36-260Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T12-24-09-267Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T12-24-35-434Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T12-25-57-010Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T12-34-48-823Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T12-46-46-468Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T12-48-17-930Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T12-49-26-400Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T12-50-31-563Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/page-2026-05-07T12-56-45-568Z.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.playwright-cli/theme-dark.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.python-version +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/.vscode/settings.json +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/AGENTS.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/CONTRIBUTING.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/DESIGN.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/LICENSE +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/README.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/docs/configuration.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/docs/display.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/docs/foreign-hosts.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/docs/index.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/docs/launch-routing-refactor.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/docs/lifecycle.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/docs/logo.png +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/docs/measurement.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/docs/remote.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/docs/stylesheets/extra.css +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/docs/viewing.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/docs/vscode-window-routing-plan.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/experiments/ws_tunnel_diag/ws_tunnel_diag.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/matlab/arrayview.m +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/mkdocs.yml +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/plans/2026-04-14-immersive-animation.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/plans/2026-05-07-unified-colormap-picker.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/plans/arrayview_tool_menu_fix_prompt.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/plans/arrayview_tool_menu_implementation_plan.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/plans/egg-placement-mockup.html +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/plans/refactoring_plan.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/plans/roi-analysis-requirements.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/plans/webview/LOG.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/scripts/demo.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/scripts/release.sh +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/ARCHITECTURE.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/__init__.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/__main__.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_app.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_codex_open.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_config.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_diff.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_icon.png +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_imaging.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_instance_registry.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_launch_plan.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_platform.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_routes_analysis.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_routes_export.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_routes_persistence.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_routes_preload.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_routes_segmentation.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_routes_state.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_routes_vectorfield.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_segmentation.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_session_spec.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_shell.html +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_synthetic_mri.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_torch.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_vectorfield.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_vscode.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_vscode_browser.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/_vscode_signal.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/src/arrayview/gsap.min.js +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/capture_v_animation.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/conftest.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/lifecycle_matrix.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/make_vectorfield_test_arrays.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_colorbar_hover_highlight.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_command_reachability.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_config.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_cross_mode_parametrized.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_display_opener_results.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_doctor_commands.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_instance_registry.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_interactions.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_invocation_contract_matrix.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_io.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_large_arrays.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_launch_diagnostics.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_launch_plan.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_loading_server.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_mode_consistency.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_mode_matrix.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_mode_roundtrip.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_nifti_meta.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_rgb_pixel_art.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_safe_server_stop.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_server_identity_status.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_session_spec.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_torch.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_view_component_unit.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_view_handle_lifecycle.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_view_launch_execution.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/test_vscode_ack_protocol.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/ui_audit.py +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/tests/v_anim_frames/.gitkeep +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/vscode-extension/AGENTS.md +0 -0
- {arrayview-0.30.0 → arrayview-0.31.1}/vscode-extension/LICENSE +0 -0
|
@@ -32,8 +32,25 @@ last_updated: 2026-06-30
|
|
|
32
32
|
- qMRI map toggle (`_islandToggleQmriMap`) fade animation now covers dimbar and array-name in addition to panes and colorbars
|
|
33
33
|
- Segmentation menu shares the ROI layout (yellow accent, magnifier action icon, common `#export-overlay` modal). Pre-activation shows a pulsing "nnInteractive · connecting" row that morphs into the normal shape toolbar once `/seg/activate` resolves
|
|
34
34
|
- Overlays are a plugin tile (`OV`): per-overlay row with colour swatch, editable label, eye toggle, × delete; shared opacity slider; `+ add overlay` opens a filesystem picker rooted at the launched file's directory
|
|
35
|
+
- The floating overlay HUD is reconciled as soon as startup loading ends, so it
|
|
36
|
+
is visible on first load and `/ o` toggles it correctly from the first press.
|
|
37
|
+
Its drag grip allows viewport-clamped repositioning that survives mode and
|
|
38
|
+
layout reconciliation for the current viewer session. Hovering a visible row
|
|
39
|
+
focuses that mask and dims the other visible overlays across normal, compare,
|
|
40
|
+
multiview, and qMRI render paths. The HUD and overlay drawer can switch masks
|
|
41
|
+
between filled regions and outline-only contours.
|
|
35
42
|
- Vector field is a plugin tile (`VF`): row with visibility toggle + density/length sliders bi-directionally wired to the `[ ] { }` keyboard commands
|
|
36
|
-
- CLI `--overlay FILE` can be repeated to load multiple overlays at launch
|
|
43
|
+
- CLI `--overlay FILE` can be repeated to load multiple overlays at launch;
|
|
44
|
+
unnamed overlays use the filename stem and `NAME=FILE` supplies an explicit
|
|
45
|
+
HUD label.
|
|
46
|
+
- Stack collections support `--overlay-dir PATTERN` to discover per-case mask
|
|
47
|
+
filenames as sparse overlay roles; missing masks render as empty and common
|
|
48
|
+
patient/modality directory layouts pair automatically. `--case-regex` remains
|
|
49
|
+
an override for unusual layouts. CLI header scans show an in-place file counter
|
|
50
|
+
and elapsed time on interactive terminals.
|
|
51
|
+
- Reused collection sessions are protected by per-tab leases, including their
|
|
52
|
+
overlay sessions, and VS Code opener readiness now verifies the requested
|
|
53
|
+
session metadata before acknowledging `backend_ready`.
|
|
37
54
|
- Filesystem picker endpoint (`GET /fs/list`) clamped to `$HOME`. Accepts `base_sid` + `mode` (`overlay` | `vectorfield`) to filter entries by a cheap header-shape peek (`.npy`, `.nii`/`.nii.gz`, `.h5`, `.zarr`); overlay mode requires identical shape, vectorfield mode requires base shape plus one axis of size 3
|
|
38
55
|
- Island collapse affordance: inline `~` at the island's top-right animates the panel into the bottom-left `~` hint circle; external `~` hint only visible while the island is actually collapsed. New `/` hint circle at bottom-right opens the tool menu
|
|
39
56
|
- Compare center tool menu: `/` now re-opens the last-used compare center mode from the tool menu, while compare pane header buttons select diff / overlay / wipe directly. Eligible two-array compare layouts can switch into a big-left arrangement with a wider center pane, and the diff colorbar now matches that center-pane width.
|
|
@@ -37,7 +37,7 @@ Or use the file picker (`Cmd/Ctrl+O`) or drag-and-drop.
|
|
|
37
37
|
uvx arrayview volume.nii.gz --overlay mask.nii.gz
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
Binary mask (0/1), same spatial shape. `[`/`]` adjusts blend opacity. Multiple overlays get automatic palette colors.
|
|
40
|
+
Binary mask (0/1), same spatial shape. `[`/`]` adjusts blend opacity. Multiple overlays get automatic palette colors. Hover an overlay HUD row to focus that mask while dimming the others. Use the overlay HUD or `/ o` to switch between filled masks and outline-only contours.
|
|
41
41
|
|
|
42
42
|
For cohort review, use `--stack` so image and overlay patterns are paired into
|
|
43
43
|
aligned stacks:
|
|
@@ -85,8 +85,18 @@ Native windows also show a static preview while the live viewer warms up.
|
|
|
85
85
|
```bash
|
|
86
86
|
uvx arrayview base.npy moving.npy # compare mode
|
|
87
87
|
uvx arrayview volume.nii.gz --overlay mask.nii.gz
|
|
88
|
+
uvx arrayview volume.nii.gz --overlay "ground truth=mask.nii.gz"
|
|
88
89
|
```
|
|
89
90
|
|
|
91
|
+
An unnamed overlay uses the filename stem as its display name (`mask` above).
|
|
92
|
+
Use `NAME=FILE` when a more descriptive label is useful.
|
|
93
|
+
The overlay list opens at the top-right of the image and can be repositioned
|
|
94
|
+
with its small drag grip. Hover a visible row to focus that mask; the other
|
|
95
|
+
visible masks are dimmed until the pointer leaves the list. Use the HUD
|
|
96
|
+
`filled`/`outline` button, or the same control in `/ o`, to switch masks between
|
|
97
|
+
filled regions and contour-only outlines. The eye in the HUD header hides or
|
|
98
|
+
shows all overlays at once.
|
|
99
|
+
|
|
90
100
|
## Directory Pattern Collections
|
|
91
101
|
|
|
92
102
|
Use `--stack` when reviewing many arrays. Passing a directory scans supported
|
|
@@ -121,6 +131,18 @@ uvx arrayview --stack \
|
|
|
121
131
|
--overlay "pred=data/**/pred/**/*.nii.gz"
|
|
122
132
|
```
|
|
123
133
|
|
|
134
|
+
When every case has a directory containing separately named masks,
|
|
135
|
+
`--overlay-dir` discovers one overlay role per filename. Masks that are absent
|
|
136
|
+
for a case are shown as empty. ArrayView infers the shared case directory from
|
|
137
|
+
layouts such as `sub-0001/T1_W/` and `sub-0001/masks/`:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
uvx arrayview --stack "data/*/T1_W/*.nii.gz" \
|
|
141
|
+
--overlay-dir "data/*/masks"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Repeat `--overlay-dir` to combine masks from multiple per-case directories.
|
|
145
|
+
|
|
124
146
|
ArrayView pairs files by sorted match order. The first match from each pattern
|
|
125
147
|
belongs to the first case, the second match belongs to the second case, and so
|
|
126
148
|
on. Use `--dry-run` to inspect matches without opening a viewer.
|
|
@@ -129,8 +151,8 @@ on. Use `--dry-run` to inspect matches without opening a viewer.
|
|
|
129
151
|
uvx arrayview --stack "data/**/*_0000.nii.gz" --overlay "gt=data/**/labels/*.nii.gz" --dry-run
|
|
130
152
|
```
|
|
131
153
|
|
|
132
|
-
|
|
133
|
-
|
|
154
|
+
For unusual or ambiguous layouts, pass a regex with a `case` group to override
|
|
155
|
+
automatic directory inference:
|
|
134
156
|
|
|
135
157
|
```bash
|
|
136
158
|
uvx arrayview --stack "data/**/*.nii.gz" --case-regex "(?P<case>patient-[0-9]+)"
|
|
@@ -34,7 +34,15 @@ def _build_metadata(session) -> dict:
|
|
|
34
34
|
"is_rgb": session.rgb_axis is not None,
|
|
35
35
|
"has_source_file": bool(getattr(session, "filepath", None)),
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
collection_spatial_ndim = getattr(session, "collection_spatial_ndim", None)
|
|
38
|
+
default_shape = target_shape
|
|
39
|
+
if collection_spatial_ndim is not None:
|
|
40
|
+
spatial_ndim = int(collection_spatial_ndim)
|
|
41
|
+
if 2 <= spatial_ndim <= len(target_shape):
|
|
42
|
+
# Collection axes are appended after the image dimensions. They
|
|
43
|
+
# select a case; they must not influence the startup plane.
|
|
44
|
+
default_shape = target_shape[:spatial_ndim]
|
|
45
|
+
default_dims = _session_mod._startup_dims_for_data(session.data, default_shape)
|
|
38
46
|
if default_dims is not None:
|
|
39
47
|
meta["default_dims"] = [int(default_dims[0]), int(default_dims[1])]
|
|
40
48
|
if getattr(session, "spatial_meta", None) is not None:
|
|
@@ -49,7 +57,6 @@ def _build_metadata(session) -> dict:
|
|
|
49
57
|
)
|
|
50
58
|
if getattr(session, "array_keys", None):
|
|
51
59
|
meta["array_keys"] = session.array_keys
|
|
52
|
-
collection_spatial_ndim = getattr(session, "collection_spatial_ndim", None)
|
|
53
60
|
if collection_spatial_ndim is not None:
|
|
54
61
|
meta["collection_spatial_ndim"] = int(collection_spatial_ndim)
|
|
55
62
|
if getattr(session.data, "_av_ragged", False):
|
|
@@ -7,6 +7,7 @@ import glob
|
|
|
7
7
|
import os
|
|
8
8
|
import re
|
|
9
9
|
import threading
|
|
10
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
10
11
|
from collections import OrderedDict
|
|
11
12
|
|
|
12
13
|
import numpy as np
|
|
@@ -249,6 +250,8 @@ class _NiftiSeries:
|
|
|
249
250
|
self._vol_cache.move_to_end(cache_key)
|
|
250
251
|
return self._vol_cache[cache_key]
|
|
251
252
|
filepath = self._file_matrix[p_idx][m_idx]
|
|
253
|
+
if filepath is None:
|
|
254
|
+
return np.zeros(self._vol_shape, dtype=self._dtype)
|
|
252
255
|
nib = _nib()
|
|
253
256
|
img = nib.load(filepath)
|
|
254
257
|
canon = nib.as_closest_canonical(img)
|
|
@@ -347,6 +350,8 @@ class _FileSeries:
|
|
|
347
350
|
self._vol_cache.move_to_end(cache_key)
|
|
348
351
|
return self._vol_cache[cache_key]
|
|
349
352
|
filepath = self._file_matrix[p_idx][m_idx]
|
|
353
|
+
if filepath is None:
|
|
354
|
+
return np.zeros(self._vol_shape, dtype=self._dtype)
|
|
350
355
|
vol = load_data(filepath)
|
|
351
356
|
with self._cache_lock:
|
|
352
357
|
self._vol_cache[cache_key] = vol
|
|
@@ -493,6 +498,8 @@ class _RaggedFileSeries:
|
|
|
493
498
|
self._vol_cache.move_to_end(cache_key)
|
|
494
499
|
return self._vol_cache[cache_key]
|
|
495
500
|
filepath = self._file_matrix[p_idx][m_idx]
|
|
501
|
+
if filepath is None:
|
|
502
|
+
return np.zeros(self._spatial_shapes[p_idx][m_idx], dtype=self._dtype)
|
|
496
503
|
if self._all_nifti:
|
|
497
504
|
nib = _nib()
|
|
498
505
|
img = nib.load(filepath)
|
|
@@ -612,7 +619,43 @@ def _collection_pattern_map(pattern, case_regex):
|
|
|
612
619
|
return by_case
|
|
613
620
|
|
|
614
621
|
|
|
615
|
-
def
|
|
622
|
+
def _collection_ancestor_case_key(filepath, depth):
|
|
623
|
+
parent = os.path.abspath(filepath)
|
|
624
|
+
for _ in range(depth):
|
|
625
|
+
parent = os.path.dirname(parent)
|
|
626
|
+
return os.path.basename(parent)
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
def _collection_paths_map(paths, pattern, *, ancestor_depth):
|
|
630
|
+
by_case = {}
|
|
631
|
+
for path in paths:
|
|
632
|
+
case = _collection_ancestor_case_key(path, ancestor_depth)
|
|
633
|
+
if not case or case in by_case:
|
|
634
|
+
return None
|
|
635
|
+
by_case[case] = path
|
|
636
|
+
return by_case
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
def _infer_collection_layout_maps(path_lists, patterns):
|
|
640
|
+
"""Infer a shared per-case directory depth for collection patterns."""
|
|
641
|
+
if not path_lists or len(path_lists[0]) < 2:
|
|
642
|
+
return None, None
|
|
643
|
+
for depth in (1, 2, 3):
|
|
644
|
+
maps = [
|
|
645
|
+
_collection_paths_map(paths, pattern, ancestor_depth=depth)
|
|
646
|
+
for paths, pattern in zip(path_lists, patterns)
|
|
647
|
+
]
|
|
648
|
+
if any(by_case is None for by_case in maps):
|
|
649
|
+
continue
|
|
650
|
+
case_ids = set(maps[0])
|
|
651
|
+
if case_ids and all(set(by_case) == case_ids for by_case in maps[1:]):
|
|
652
|
+
return maps, depth
|
|
653
|
+
return None, None
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
def _series_from_file_matrix(
|
|
657
|
+
file_matrix, *, load="lazy", stack="auto", scan_progress=None, scan_label=None
|
|
658
|
+
):
|
|
616
659
|
if load not in {"lazy", "eager"}:
|
|
617
660
|
raise ValueError("load must be 'lazy' or 'eager'.")
|
|
618
661
|
if stack not in {"auto", "dense", "ragged"}:
|
|
@@ -620,17 +663,19 @@ def _series_from_file_matrix(file_matrix, *, load="lazy", stack="auto"):
|
|
|
620
663
|
if not file_matrix or not file_matrix[0]:
|
|
621
664
|
raise ValueError("Cannot build an empty file series.")
|
|
622
665
|
|
|
623
|
-
all_paths = [path for row in file_matrix for path in row]
|
|
666
|
+
all_paths = [path for row in file_matrix for path in row if path is not None]
|
|
667
|
+
if not all_paths:
|
|
668
|
+
raise ValueError("Cannot build a file series without any files.")
|
|
624
669
|
all_nifti = all(_is_nifti_path(path) for path in all_paths)
|
|
625
670
|
ref_shape = None
|
|
626
671
|
ref_dtype = None
|
|
627
672
|
spatial_meta = None
|
|
628
673
|
|
|
629
674
|
if all_nifti:
|
|
630
|
-
nib = _nib()
|
|
631
675
|
spatial_shapes = []
|
|
632
|
-
for fpath in
|
|
633
|
-
|
|
676
|
+
for fpath, img, item_meta in _iter_nifti_headers(
|
|
677
|
+
all_paths, scan_progress=scan_progress, scan_label=scan_label
|
|
678
|
+
):
|
|
634
679
|
shape = tuple(item_meta["canonical_shape"])
|
|
635
680
|
dtype = img.get_data_dtype()
|
|
636
681
|
spatial_shapes.append(shape)
|
|
@@ -651,8 +696,14 @@ def _series_from_file_matrix(file_matrix, *, load="lazy", stack="auto"):
|
|
|
651
696
|
shape_matrix = []
|
|
652
697
|
pos = 0
|
|
653
698
|
for row in file_matrix:
|
|
654
|
-
|
|
655
|
-
|
|
699
|
+
row_shapes = []
|
|
700
|
+
for path in row:
|
|
701
|
+
if path is None:
|
|
702
|
+
row_shapes.append(ref_shape)
|
|
703
|
+
else:
|
|
704
|
+
row_shapes.append(spatial_shapes[pos])
|
|
705
|
+
pos += 1
|
|
706
|
+
shape_matrix.append(row_shapes)
|
|
656
707
|
shapes_differ = any(shape != ref_shape for shape in spatial_shapes)
|
|
657
708
|
if stack == "dense" and shapes_differ:
|
|
658
709
|
raise ValueError("Dense stacking requires every file to have the same shape.")
|
|
@@ -668,8 +719,9 @@ def _series_from_file_matrix(file_matrix, *, load="lazy", stack="auto"):
|
|
|
668
719
|
series = _NiftiSeries(file_matrix, ref_shape, ref_dtype, spatial_meta)
|
|
669
720
|
if load == "eager":
|
|
670
721
|
for p, row in enumerate(file_matrix):
|
|
671
|
-
for m,
|
|
672
|
-
|
|
722
|
+
for m, path in enumerate(row):
|
|
723
|
+
if path is not None:
|
|
724
|
+
series._get_volume(p, m)
|
|
673
725
|
return series, spatial_meta
|
|
674
726
|
|
|
675
727
|
spatial_shapes = []
|
|
@@ -691,11 +743,19 @@ def _series_from_file_matrix(file_matrix, *, load="lazy", stack="auto"):
|
|
|
691
743
|
f"Dtype mismatch: {os.path.basename(fpath)!r} has dtype "
|
|
692
744
|
f"{dtype}, expected {ref_dtype}."
|
|
693
745
|
)
|
|
746
|
+
if scan_progress is not None:
|
|
747
|
+
scan_progress(scan_label, fpath)
|
|
694
748
|
shape_matrix = []
|
|
695
749
|
pos = 0
|
|
696
750
|
for row in file_matrix:
|
|
697
|
-
|
|
698
|
-
|
|
751
|
+
row_shapes = []
|
|
752
|
+
for path in row:
|
|
753
|
+
if path is None:
|
|
754
|
+
row_shapes.append(ref_shape)
|
|
755
|
+
else:
|
|
756
|
+
row_shapes.append(spatial_shapes[pos])
|
|
757
|
+
pos += 1
|
|
758
|
+
shape_matrix.append(row_shapes)
|
|
699
759
|
shapes_differ = any(shape != ref_shape for shape in spatial_shapes)
|
|
700
760
|
if stack == "dense" and shapes_differ:
|
|
701
761
|
raise ValueError("Dense stacking requires every file to have the same shape.")
|
|
@@ -705,23 +765,63 @@ def _series_from_file_matrix(file_matrix, *, load="lazy", stack="auto"):
|
|
|
705
765
|
series = _FileSeries(file_matrix, ref_shape, ref_dtype, spatial_meta=None)
|
|
706
766
|
if load == "eager":
|
|
707
767
|
for p, row in enumerate(file_matrix):
|
|
708
|
-
for m,
|
|
709
|
-
|
|
768
|
+
for m, path in enumerate(row):
|
|
769
|
+
if path is not None:
|
|
770
|
+
series._get_volume(p, m)
|
|
710
771
|
return series, None
|
|
711
772
|
|
|
712
773
|
|
|
713
|
-
def
|
|
774
|
+
def _collection_scan_workers():
|
|
775
|
+
raw = os.environ.get("ARRAYVIEW_COLLECTION_SCAN_WORKERS")
|
|
776
|
+
if raw is not None:
|
|
777
|
+
try:
|
|
778
|
+
return max(1, int(raw))
|
|
779
|
+
except ValueError:
|
|
780
|
+
return 1
|
|
781
|
+
cpu = os.cpu_count() or 4
|
|
782
|
+
return max(1, min(8, cpu))
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def _iter_nifti_headers(paths, *, scan_progress=None, scan_label=None):
|
|
786
|
+
workers = _collection_scan_workers()
|
|
787
|
+
if workers <= 1 or len(paths) < 8:
|
|
788
|
+
for path in paths:
|
|
789
|
+
img, item_meta = _nifti_header_with_meta(path)
|
|
790
|
+
if scan_progress is not None:
|
|
791
|
+
scan_progress(scan_label, path)
|
|
792
|
+
yield path, img, item_meta
|
|
793
|
+
return
|
|
794
|
+
|
|
795
|
+
with ThreadPoolExecutor(max_workers=workers) as executor:
|
|
796
|
+
for path, result in zip(paths, executor.map(_nifti_header_with_meta, paths)):
|
|
797
|
+
img, item_meta = result
|
|
798
|
+
if scan_progress is not None:
|
|
799
|
+
scan_progress(scan_label, path)
|
|
800
|
+
yield path, img, item_meta
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
def load_dir_collection(
|
|
804
|
+
base_patterns,
|
|
805
|
+
overlays=None,
|
|
806
|
+
case_regex=None,
|
|
807
|
+
*,
|
|
808
|
+
load="lazy",
|
|
809
|
+
stack="auto",
|
|
810
|
+
scan_progress=None,
|
|
811
|
+
):
|
|
714
812
|
"""Load recursive collection patterns as aligned lazy image/overlay stacks.
|
|
715
813
|
|
|
716
814
|
*base_patterns* are image channel/modality patterns. *overlays* is an
|
|
717
|
-
ordered list of ``(name, pattern)`` pairs.
|
|
718
|
-
|
|
719
|
-
|
|
815
|
+
ordered list of ``(name, pattern)`` pairs. A third true value marks a
|
|
816
|
+
sparse overlay whose missing cases should render as empty masks. By
|
|
817
|
+
default, each pattern's sorted matches are paired by position. With
|
|
818
|
+
*case_regex*, files are paired by the regex's named ``case`` group instead.
|
|
720
819
|
"""
|
|
721
820
|
if not base_patterns:
|
|
722
821
|
raise ValueError("--stack requires at least one positional image pattern.")
|
|
723
822
|
overlays = overlays or []
|
|
724
823
|
|
|
824
|
+
inferred_case_depth = None
|
|
725
825
|
if case_regex:
|
|
726
826
|
base_maps = [
|
|
727
827
|
_collection_pattern_map(pattern, case_regex=case_regex)
|
|
@@ -738,56 +838,125 @@ def load_dir_collection(base_patterns, overlays=None, case_regex=None, *, load="
|
|
|
738
838
|
base_matrix = [[by_case[case] for by_case in base_maps] for case in case_ids]
|
|
739
839
|
else:
|
|
740
840
|
base_lists = [_collection_pattern_paths(pattern) for pattern in base_patterns]
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
841
|
+
inferred_maps, inferred_case_depth = _infer_collection_layout_maps(
|
|
842
|
+
base_lists, base_patterns
|
|
843
|
+
)
|
|
844
|
+
if inferred_maps is not None:
|
|
845
|
+
case_ids = sorted(inferred_maps[0])
|
|
846
|
+
base_matrix = [
|
|
847
|
+
[by_case[case] for by_case in inferred_maps] for case in case_ids
|
|
848
|
+
]
|
|
849
|
+
else:
|
|
850
|
+
n_cases = len(base_lists[0])
|
|
851
|
+
for idx, paths in enumerate(base_lists[1:], start=2):
|
|
852
|
+
if len(paths) != n_cases:
|
|
853
|
+
raise ValueError(
|
|
854
|
+
f"Image pattern {idx} matched {len(paths)} file(s), "
|
|
855
|
+
f"expected {n_cases} to match image pattern 1."
|
|
856
|
+
)
|
|
753
857
|
case_ids = [
|
|
754
|
-
|
|
858
|
+
_default_collection_case_key(path)
|
|
755
859
|
for path in base_lists[0]
|
|
756
860
|
]
|
|
757
|
-
|
|
758
|
-
|
|
861
|
+
if len(set(case_ids)) != len(case_ids):
|
|
862
|
+
case_ids = [
|
|
863
|
+
os.path.basename(os.path.dirname(path))
|
|
864
|
+
or _default_collection_case_key(path)
|
|
865
|
+
for path in base_lists[0]
|
|
866
|
+
]
|
|
867
|
+
base_matrix = [list(row) for row in zip(*base_lists)]
|
|
868
|
+
data, spatial_meta = _series_from_file_matrix(
|
|
869
|
+
base_matrix,
|
|
870
|
+
load=load,
|
|
871
|
+
stack=stack,
|
|
872
|
+
scan_progress=scan_progress,
|
|
873
|
+
scan_label="images",
|
|
874
|
+
)
|
|
759
875
|
spatial_shape = data._vol_shape
|
|
760
876
|
|
|
761
877
|
overlay_items = []
|
|
762
878
|
overlay_reports = []
|
|
763
|
-
for
|
|
764
|
-
|
|
879
|
+
for overlay_spec in overlays:
|
|
880
|
+
name, pattern = overlay_spec[:2]
|
|
881
|
+
allow_missing = len(overlay_spec) > 2 and bool(overlay_spec[2])
|
|
882
|
+
report_pattern = overlay_spec[3] if len(overlay_spec) > 3 else pattern
|
|
883
|
+
if isinstance(pattern, dict):
|
|
884
|
+
by_case = pattern
|
|
885
|
+
missing_cases = [case for case in case_ids if case not in by_case]
|
|
886
|
+
if missing_cases and not allow_missing:
|
|
887
|
+
raise ValueError(
|
|
888
|
+
f"Overlay {name!r} is missing case(s): "
|
|
889
|
+
f"{', '.join(repr(c) for c in missing_cases)}."
|
|
890
|
+
)
|
|
891
|
+
matrix = [[by_case.get(case)] for case in case_ids]
|
|
892
|
+
extras = sorted(set(by_case.keys()) - set(case_ids))
|
|
893
|
+
elif case_regex:
|
|
765
894
|
by_case = _collection_pattern_map(pattern, case_regex=case_regex)
|
|
766
895
|
missing_cases = [case for case in case_ids if case not in by_case]
|
|
767
|
-
if missing_cases:
|
|
896
|
+
if missing_cases and not allow_missing:
|
|
768
897
|
raise ValueError(
|
|
769
898
|
f"Overlay {name!r} is missing case(s): "
|
|
770
899
|
f"{', '.join(repr(c) for c in missing_cases)}."
|
|
771
900
|
)
|
|
772
|
-
matrix = [[by_case
|
|
901
|
+
matrix = [[by_case.get(case)] for case in case_ids]
|
|
773
902
|
extras = sorted(set(by_case.keys()) - set(case_ids))
|
|
903
|
+
elif inferred_case_depth is not None:
|
|
904
|
+
paths = _collection_pattern_paths(pattern)
|
|
905
|
+
by_case = _collection_paths_map(
|
|
906
|
+
paths, pattern, ancestor_depth=inferred_case_depth
|
|
907
|
+
)
|
|
908
|
+
if by_case is None:
|
|
909
|
+
raise ValueError(
|
|
910
|
+
f"Overlay {name!r} has multiple files for an inferred case. "
|
|
911
|
+
"Use --case-regex to describe this layout explicitly."
|
|
912
|
+
)
|
|
913
|
+
missing_cases = [case for case in case_ids if case not in by_case]
|
|
914
|
+
if missing_cases and not allow_missing:
|
|
915
|
+
raise ValueError(
|
|
916
|
+
f"Overlay {name!r} is missing case(s): "
|
|
917
|
+
f"{', '.join(repr(c) for c in missing_cases)}."
|
|
918
|
+
)
|
|
919
|
+
matrix = [[by_case.get(case)] for case in case_ids]
|
|
920
|
+
extras = sorted(set(by_case) - set(case_ids))
|
|
774
921
|
else:
|
|
775
922
|
paths = _collection_pattern_paths(pattern)
|
|
776
|
-
if len(paths) != len(case_ids):
|
|
923
|
+
if len(paths) != len(case_ids) and not allow_missing:
|
|
777
924
|
raise ValueError(
|
|
778
925
|
f"Overlay {name!r} matched {len(paths)} file(s), "
|
|
779
926
|
f"expected {len(case_ids)} to match image pattern 1."
|
|
780
927
|
)
|
|
928
|
+
if allow_missing:
|
|
929
|
+
raise ValueError(
|
|
930
|
+
"Could not infer case directories for sparse overlays. "
|
|
931
|
+
"Use a per-case directory layout or pass --case-regex."
|
|
932
|
+
)
|
|
781
933
|
matrix = [[path] for path in paths]
|
|
782
934
|
extras = []
|
|
783
|
-
ov_data, _ov_meta = _series_from_file_matrix(
|
|
935
|
+
ov_data, _ov_meta = _series_from_file_matrix(
|
|
936
|
+
matrix,
|
|
937
|
+
load=load,
|
|
938
|
+
stack=stack,
|
|
939
|
+
scan_progress=scan_progress,
|
|
940
|
+
scan_label=name,
|
|
941
|
+
)
|
|
784
942
|
if not getattr(data, "_av_ragged", False) and ov_data._vol_shape != spatial_shape:
|
|
785
943
|
raise ValueError(
|
|
786
944
|
f"Overlay {name!r} has spatial shape {ov_data._vol_shape}, "
|
|
787
945
|
f"expected {spatial_shape}."
|
|
788
946
|
)
|
|
789
|
-
overlay_items.append({"name": name, "data": ov_data, "pattern":
|
|
790
|
-
overlay_reports.append(
|
|
947
|
+
overlay_items.append({"name": name, "data": ov_data, "pattern": report_pattern})
|
|
948
|
+
overlay_reports.append(
|
|
949
|
+
{
|
|
950
|
+
"name": name,
|
|
951
|
+
"pattern": report_pattern,
|
|
952
|
+
"ignored_cases": extras,
|
|
953
|
+
"missing_cases": (
|
|
954
|
+
missing_cases
|
|
955
|
+
if case_regex or inferred_case_depth is not None
|
|
956
|
+
else []
|
|
957
|
+
),
|
|
958
|
+
}
|
|
959
|
+
)
|
|
791
960
|
|
|
792
961
|
summary = {
|
|
793
962
|
"cases": case_ids,
|