arrayview 0.43.0__tar.gz → 0.43.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.
Files changed (112) hide show
  1. {arrayview-0.43.0 → arrayview-0.43.1}/PKG-INFO +2 -2
  2. {arrayview-0.43.0 → arrayview-0.43.1}/pyproject.toml +1 -1
  3. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_diagnostics.py +2 -1
  4. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_routes_loading.py +23 -22
  5. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_routes_rendering.py +16 -1
  6. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_routes_vectorfield.py +20 -19
  7. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_session.py +10 -1
  8. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_source_safety.py +8 -0
  9. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_viewer.html +383 -55
  10. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_vscode_extension.py +1 -1
  11. arrayview-0.43.1/src/arrayview/arrayview-opener.vsix +0 -0
  12. {arrayview-0.43.0 → arrayview-0.43.1}/tests/lifecycle_matrix.py +1 -0
  13. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_api.py +3 -0
  14. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_launch_diagnostics.py +4 -0
  15. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_mode_matrix.py +75 -0
  16. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_source_safety.py +5 -0
  17. arrayview-0.43.0/src/arrayview/arrayview-opener.vsix +0 -0
  18. {arrayview-0.43.0 → arrayview-0.43.1}/.gitignore +0 -0
  19. {arrayview-0.43.0 → arrayview-0.43.1}/LICENSE +0 -0
  20. {arrayview-0.43.0 → arrayview-0.43.1}/README.md +0 -0
  21. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/ARCHITECTURE.md +0 -0
  22. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/__init__.py +0 -0
  23. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/__main__.py +0 -0
  24. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_analysis.py +0 -0
  25. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_app.py +0 -0
  26. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_config.py +0 -0
  27. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_dicom.py +0 -0
  28. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_diff.py +0 -0
  29. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_extra_ports.py +0 -0
  30. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_icon.png +0 -0
  31. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_imaging.py +0 -0
  32. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_instance_registry.py +0 -0
  33. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_io.py +0 -0
  34. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_launch_plan.py +0 -0
  35. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_launch_trace.py +0 -0
  36. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_launcher.py +0 -0
  37. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_lifecycle.py +0 -0
  38. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_overlays.py +0 -0
  39. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_platform.py +0 -0
  40. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_render.py +0 -0
  41. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_routes_analysis.py +0 -0
  42. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_routes_drop.py +0 -0
  43. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_routes_export.py +0 -0
  44. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_routes_persistence.py +0 -0
  45. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_routes_preferences.py +0 -0
  46. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_routes_preload.py +0 -0
  47. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_routes_query.py +0 -0
  48. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_routes_segmentation.py +0 -0
  49. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_routes_state.py +0 -0
  50. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_routes_websocket.py +0 -0
  51. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_segmentation.py +0 -0
  52. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_server.py +0 -0
  53. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_session_spec.py +0 -0
  54. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_shell.html +0 -0
  55. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_synthetic_mri.py +0 -0
  56. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_torch.py +0 -0
  57. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_tutorial.py +0 -0
  58. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_vectorfield.py +0 -0
  59. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_vscode.py +0 -0
  60. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_vscode_browser.py +0 -0
  61. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_vscode_signal.py +0 -0
  62. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/_watch.py +0 -0
  63. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/gsap.min.js +0 -0
  64. {arrayview-0.43.0 → arrayview-0.43.1}/src/arrayview/todo.md +0 -0
  65. {arrayview-0.43.0 → arrayview-0.43.1}/tests/capture_patient_loading.py +0 -0
  66. {arrayview-0.43.0 → arrayview-0.43.1}/tests/capture_v_animation.py +0 -0
  67. {arrayview-0.43.0 → arrayview-0.43.1}/tests/conftest.py +0 -0
  68. {arrayview-0.43.0 → arrayview-0.43.1}/tests/make_vectorfield_test_arrays.py +0 -0
  69. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_1d_arrays.py +0 -0
  70. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_backend_shared.py +0 -0
  71. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_browser.py +0 -0
  72. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_cli.py +0 -0
  73. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_colorbar_hover_highlight.py +0 -0
  74. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_config.py +0 -0
  75. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_dicom.py +0 -0
  76. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_display_opener_results.py +0 -0
  77. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_doctor_commands.py +0 -0
  78. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_drop_import.py +0 -0
  79. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_instance_registry.py +0 -0
  80. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_invocation_contract_matrix.py +0 -0
  81. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_io.py +0 -0
  82. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_large_arrays.py +0 -0
  83. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_launch_blackbox.py +0 -0
  84. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_launch_execution_ownership.py +0 -0
  85. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_launch_plan.py +0 -0
  86. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_launch_trace.py +0 -0
  87. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_lifecycle_contract.py +0 -0
  88. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_loading_server.py +0 -0
  89. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_mode_consistency.py +0 -0
  90. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_mode_roundtrip.py +0 -0
  91. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_nifti_meta.py +0 -0
  92. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_nifti_series.py +0 -0
  93. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_preferences_ui.py +0 -0
  94. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_public_invocation_regressions.py +0 -0
  95. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_render_failure_reporting.py +0 -0
  96. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_render_progress.py +0 -0
  97. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_rgb_pixel_art.py +0 -0
  98. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_safe_server_stop.py +0 -0
  99. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_server_identity_status.py +0 -0
  100. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_session_spec.py +0 -0
  101. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_spawn_ownership.py +0 -0
  102. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_torch.py +0 -0
  103. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_tutorial.py +0 -0
  104. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_tutorial_browser.py +0 -0
  105. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_value_formatting.py +0 -0
  106. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_view_handle_lifecycle.py +0 -0
  107. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_view_launch_execution.py +0 -0
  108. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_vscode_ack_protocol.py +0 -0
  109. {arrayview-0.43.0 → arrayview-0.43.1}/tests/test_window_level_preview.py +0 -0
  110. {arrayview-0.43.0 → arrayview-0.43.1}/tests/ui_audit.py +0 -0
  111. {arrayview-0.43.0 → arrayview-0.43.1}/tests/v_anim_frames/.gitkeep +0 -0
  112. {arrayview-0.43.0 → arrayview-0.43.1}/tests/visual_smoke.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: arrayview
3
- Version: 0.43.0
3
+ Version: 0.43.1
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "arrayview"
7
- version = "0.43.0"
7
+ version = "0.43.1"
8
8
  description = "Fast multi-dimensional array viewer"
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  requires-python = ">=3.12"
@@ -96,7 +96,8 @@ _DIAGNOSES: list[tuple[str, str, str, str]] = [
96
96
  (r"did not start the viewer script", ACTION,
97
97
  "VS Code opened an integrated-browser tab, but the tab did not navigate "
98
98
  "to the ArrayView page.",
99
- _RETRY + " If it repeats, reload the VS Code window."),
99
+ _RELOAD + " Retrying without reloading sends the next launch to the "
100
+ "same stuck browser."),
100
101
  (r"did not render a frame|did not become ready",
101
102
  ACTION,
102
103
  "The viewer opened but showed no frame before the deadline — usually a "
@@ -137,32 +137,33 @@ def register_loading_routes(app, *, notify_shells, setup_rgb) -> None:
137
137
  staging_dir = validated_staging_directory(abs_path, claimed_staging)
138
138
  if claimed_staging and staging_dir is None:
139
139
  raise HTTPException(status_code=400, detail="invalid source staging directory")
140
- unsafe_mount = direct_network_mount(abs_path)
141
- if unsafe_mount is not None:
142
- return {
143
- "error": (
144
- f"Direct server access to network mount {unsafe_mount.mountpoint!r} "
145
- "is refused. Launch the file through the arrayview CLI so it can "
146
- "be staged safely."
147
- )
148
- }
149
- nested_source_mount = network_mount_below(abs_path)
150
- if nested_source_mount is not None:
151
- return {
152
- "error": (
153
- f"Source discovery would enter network mount "
154
- f"{nested_source_mount.mountpoint!r}; copy the collection locally first."
155
- )
156
- }
157
- if source_may_require_sibling_discovery(abs_path):
158
- nested_sibling_mount = network_mount_below(os.path.dirname(abs_path))
159
- if nested_sibling_mount is not None:
140
+ if os.environ.get("ARRAYVIEW_SKIP_SOURCE_STAGING", "1") == "0":
141
+ unsafe_mount = direct_network_mount(abs_path)
142
+ if unsafe_mount is not None:
160
143
  return {
161
144
  "error": (
162
- f"Related-file discovery would enter network mount "
163
- f"{nested_sibling_mount.mountpoint!r}; copy the series locally first."
145
+ f"Direct server access to network mount {unsafe_mount.mountpoint!r} "
146
+ "is refused. Launch the file through the arrayview CLI so it can "
147
+ "be staged safely."
164
148
  )
165
149
  }
150
+ nested_source_mount = network_mount_below(abs_path)
151
+ if nested_source_mount is not None:
152
+ return {
153
+ "error": (
154
+ f"Source discovery would enter network mount "
155
+ f"{nested_source_mount.mountpoint!r}; copy the collection locally first."
156
+ )
157
+ }
158
+ if source_may_require_sibling_discovery(abs_path):
159
+ nested_sibling_mount = network_mount_below(os.path.dirname(abs_path))
160
+ if nested_sibling_mount is not None:
161
+ return {
162
+ "error": (
163
+ f"Related-file discovery would enter network mount "
164
+ f"{nested_sibling_mount.mountpoint!r}; copy the series locally first."
165
+ )
166
+ }
166
167
  for pattern in dir_patterns or []:
167
168
  pattern_path = lexical_abspath(str(pattern))
168
169
  scan_root = scan_root_before_magic(pattern_path)
@@ -23,7 +23,7 @@ from arrayview._render import (
23
23
  render_rgb_rgba,
24
24
  render_rgba,
25
25
  )
26
- from arrayview._session import HEAVY_OP_LIMIT_BYTES, SESSIONS
26
+ from arrayview._session import HEAVY_OP_LIMIT_BYTES, SESSIONS, _schedule_prefetch
27
27
  from arrayview._synthetic_mri import (
28
28
  _qmri_adjust_vmin_vmax,
29
29
  qmri_display_slice,
@@ -276,6 +276,21 @@ def register_rendering_routes(app, *, get_session_or_404) -> None:
276
276
  mosaic_cols,
277
277
  )
278
278
  render_ms = (time.perf_counter() - render_t0) * 1000.0
279
+ # This endpoint is what compare mode polls per scroll step (the live
280
+ # WS path has its own prefetch call). Without this, a second array
281
+ # open puts every pane on the request-then-wait path with nothing
282
+ # warmed ahead, which is a much bigger hit than the extra data alone.
283
+ # No per-request direction field here (stateless HTTP), so infer it
284
+ # from the index this session last rendered, mirroring the WS guard.
285
+ if slice_dim >= 0 and dim_z < 0:
286
+ prev_idx = getattr(session, "_http_prefetch_prev_idx", None)
287
+ direction = 1
288
+ if prev_idx is not None and len(prev_idx) == len(idx_tuple):
289
+ delta = idx_tuple[slice_dim] - prev_idx[slice_dim]
290
+ if delta:
291
+ direction = 1 if delta > 0 else -1
292
+ session._http_prefetch_prev_idx = idx_tuple
293
+ _schedule_prefetch(session, dim_x, dim_y, list(idx_tuple), slice_dim, direction)
279
294
  encode_t0 = time.perf_counter()
280
295
  img = _pil_image().fromarray(rgba[:, :, :3], mode="RGB")
281
296
  buf = io.BytesIO()
@@ -79,26 +79,27 @@ def register_vectorfield_routes(app) -> None:
79
79
  staging_dir = validated_staging_directory(filepath, claimed_staging)
80
80
  if claimed_staging and staging_dir is None:
81
81
  raise HTTPException(status_code=400, detail="invalid source staging directory")
82
- unsafe_mount = direct_network_mount(filepath)
83
- if unsafe_mount is not None:
84
- return {
85
- "error": (
86
- f"Direct server access to network mount {unsafe_mount.mountpoint!r} "
87
- "is refused; launch through the arrayview CLI."
88
- )
89
- }
90
- nested_mount = network_mount_below(filepath)
91
- if source_may_require_sibling_discovery(filepath):
92
- nested_mount = nested_mount or network_mount_below(
93
- os.path.dirname(filepath)
94
- )
95
- if nested_mount is not None:
96
- return {
97
- "error": (
98
- f"Vector-field discovery would enter network mount "
99
- f"{nested_mount.mountpoint!r}; copy it locally first."
82
+ if os.environ.get("ARRAYVIEW_SKIP_SOURCE_STAGING", "1") == "0":
83
+ unsafe_mount = direct_network_mount(filepath)
84
+ if unsafe_mount is not None:
85
+ return {
86
+ "error": (
87
+ f"Direct server access to network mount {unsafe_mount.mountpoint!r} "
88
+ "is refused; launch through the arrayview CLI."
89
+ )
90
+ }
91
+ nested_mount = network_mount_below(filepath)
92
+ if source_may_require_sibling_discovery(filepath):
93
+ nested_mount = nested_mount or network_mount_below(
94
+ os.path.dirname(filepath)
100
95
  )
101
- }
96
+ if nested_mount is not None:
97
+ return {
98
+ "error": (
99
+ f"Vector-field discovery would enter network mount "
100
+ f"{nested_mount.mountpoint!r}; copy it locally first."
101
+ )
102
+ }
102
103
  components_dim = body.get("components_dim")
103
104
  session = SESSIONS.get(sid)
104
105
  if not session:
@@ -141,14 +141,22 @@ _OVERLAY_PREFETCH_POOL = None
141
141
  _PREFETCH_LOCK = threading.Lock()
142
142
 
143
143
 
144
+ _PREFETCH_WORKERS: int = max(2, min(4, os.cpu_count() or 2))
145
+
146
+
144
147
  def _get_prefetch_pool():
145
148
  global _PREFETCH_POOL
146
149
  with _PREFETCH_LOCK:
147
150
  if _PREFETCH_POOL is None or _PREFETCH_POOL._shutdown:
148
151
  import concurrent.futures
149
152
 
153
+ # One pool is shared by every open session (see _schedule_prefetch).
154
+ # A single worker meant a second open array serialized behind the
155
+ # first array's lookahead, so scrolling any pane lagged. Size like
156
+ # _RENDER_WORKERS so a handful of concurrently open arrays each get
157
+ # a lane.
150
158
  _PREFETCH_POOL = concurrent.futures.ThreadPoolExecutor(
151
- max_workers=1, thread_name_prefix="arrayview-prefetch"
159
+ max_workers=_PREFETCH_WORKERS, thread_name_prefix="arrayview-prefetch"
152
160
  )
153
161
  return _PREFETCH_POOL
154
162
 
@@ -674,6 +682,7 @@ __all__ = [
674
682
  "_render",
675
683
  # Prefetch
676
684
  "_PREFETCH_POOL",
685
+ "_PREFETCH_WORKERS",
677
686
  "_OVERLAY_PREFETCH_POOL",
678
687
  "_PREFETCH_LOCK",
679
688
  "_get_prefetch_pool",
@@ -713,6 +713,14 @@ def prepare_source(path: str, *, timeout: float | None = None) -> PreparedSource
713
713
  """Preflight and, for a network file, make a bounded local snapshot."""
714
714
  absolute = lexical_abspath(path)
715
715
  mount = mount_for_path(absolute)
716
+ if os.environ.get("ARRAYVIEW_SKIP_SOURCE_STAGING", "1") != "0":
717
+ # Network-source staging is opt-in, not the default: use the source
718
+ # path directly, exactly like a local file. This is the pre-staging
719
+ # behavior (fast open, but a dropped network mount while the file is
720
+ # memory-mapped can wedge the process in a way not even `kill -9`
721
+ # recovers from — see docs/loading.md). Set
722
+ # ARRAYVIEW_SKIP_SOURCE_STAGING=0 to restore the safety copy.
723
+ return PreparedSource(absolute, absolute, None, mount)
716
724
  if mount is None or mount.filesystem not in _NETWORK_FILESYSTEMS:
717
725
  unsafe_discovery = discovery_network_mount(absolute)
718
726
  if unsafe_discovery is not None: