arrayview 0.31.1__tar.gz → 0.31.3__tar.gz

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