arrayview 0.46.0__tar.gz → 0.46.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 (115) hide show
  1. {arrayview-0.46.0 → arrayview-0.46.1}/PKG-INFO +1 -14
  2. {arrayview-0.46.0 → arrayview-0.46.1}/README.md +0 -13
  3. {arrayview-0.46.0 → arrayview-0.46.1}/pyproject.toml +1 -1
  4. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_analysis.py +4 -0
  5. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_io.py +37 -5
  6. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_launcher.py +15 -4
  7. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_routes_loading.py +38 -14
  8. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_viewer.html +551 -185
  9. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_mode_roundtrip.py +9 -22
  10. {arrayview-0.46.0 → arrayview-0.46.1}/.gitignore +0 -0
  11. {arrayview-0.46.0 → arrayview-0.46.1}/LICENSE +0 -0
  12. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/ARCHITECTURE.md +0 -0
  13. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/__init__.py +0 -0
  14. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/__main__.py +0 -0
  15. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_app.py +0 -0
  16. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_bootstrap_app.py +0 -0
  17. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_config.py +0 -0
  18. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_diagnostics.py +0 -0
  19. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_dicom.py +0 -0
  20. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_diff.py +0 -0
  21. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_extra_ports.py +0 -0
  22. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_icon.png +0 -0
  23. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_imaging.py +0 -0
  24. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_instance_registry.py +0 -0
  25. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_launch_plan.py +0 -0
  26. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_launch_trace.py +0 -0
  27. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_lifecycle.py +0 -0
  28. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_overlays.py +0 -0
  29. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_platform.py +0 -0
  30. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_render.py +0 -0
  31. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_routes_analysis.py +0 -0
  32. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_routes_drop.py +0 -0
  33. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_routes_export.py +0 -0
  34. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_routes_persistence.py +0 -0
  35. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_routes_preferences.py +0 -0
  36. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_routes_preload.py +0 -0
  37. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_routes_query.py +0 -0
  38. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_routes_rendering.py +0 -0
  39. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_routes_segmentation.py +0 -0
  40. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_routes_state.py +0 -0
  41. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_routes_vectorfield.py +0 -0
  42. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_routes_websocket.py +0 -0
  43. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_segmentation.py +0 -0
  44. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_server.py +0 -0
  45. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_session.py +0 -0
  46. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_session_spec.py +0 -0
  47. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_shell.html +0 -0
  48. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_source_safety.py +0 -0
  49. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_synthetic_mri.py +0 -0
  50. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_torch.py +0 -0
  51. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_tutorial.py +0 -0
  52. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_vectorfield.py +0 -0
  53. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_vscode.py +0 -0
  54. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_vscode_browser.py +0 -0
  55. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_vscode_extension.py +0 -0
  56. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_vscode_signal.py +0 -0
  57. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/_watch.py +0 -0
  58. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/arrayview-opener.vsix +0 -0
  59. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/gsap.min.js +0 -0
  60. {arrayview-0.46.0 → arrayview-0.46.1}/src/arrayview/todo.md +0 -0
  61. {arrayview-0.46.0 → arrayview-0.46.1}/tests/capture_patient_loading.py +0 -0
  62. {arrayview-0.46.0 → arrayview-0.46.1}/tests/capture_v_animation.py +0 -0
  63. {arrayview-0.46.0 → arrayview-0.46.1}/tests/conftest.py +0 -0
  64. {arrayview-0.46.0 → arrayview-0.46.1}/tests/lifecycle_matrix.py +0 -0
  65. {arrayview-0.46.0 → arrayview-0.46.1}/tests/make_vectorfield_test_arrays.py +0 -0
  66. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_1d_arrays.py +0 -0
  67. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_api.py +0 -0
  68. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_backend_shared.py +0 -0
  69. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_bootstrap_app.py +0 -0
  70. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_browser.py +0 -0
  71. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_cli.py +0 -0
  72. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_colorbar_hover_highlight.py +0 -0
  73. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_config.py +0 -0
  74. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_dicom.py +0 -0
  75. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_display_opener_results.py +0 -0
  76. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_doctor_commands.py +0 -0
  77. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_drop_import.py +0 -0
  78. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_instance_registry.py +0 -0
  79. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_invocation_contract_matrix.py +0 -0
  80. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_io.py +0 -0
  81. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_large_arrays.py +0 -0
  82. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_launch_blackbox.py +0 -0
  83. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_launch_diagnostics.py +0 -0
  84. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_launch_execution_ownership.py +0 -0
  85. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_launch_plan.py +0 -0
  86. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_launch_trace.py +0 -0
  87. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_lifecycle_contract.py +0 -0
  88. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_loading_server.py +0 -0
  89. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_mode_consistency.py +0 -0
  90. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_mode_matrix.py +0 -0
  91. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_nifti_meta.py +0 -0
  92. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_nifti_series.py +0 -0
  93. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_preferences_ui.py +0 -0
  94. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_public_invocation_regressions.py +0 -0
  95. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_render_failure_reporting.py +0 -0
  96. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_render_progress.py +0 -0
  97. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_rgb_pixel_art.py +0 -0
  98. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_roi_hud.py +0 -0
  99. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_safe_server_stop.py +0 -0
  100. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_server_identity_status.py +0 -0
  101. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_session_spec.py +0 -0
  102. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_source_safety.py +0 -0
  103. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_spawn_ownership.py +0 -0
  104. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_torch.py +0 -0
  105. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_tunnel_warm_path.py +0 -0
  106. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_tutorial.py +0 -0
  107. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_tutorial_browser.py +0 -0
  108. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_value_formatting.py +0 -0
  109. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_view_handle_lifecycle.py +0 -0
  110. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_view_launch_execution.py +0 -0
  111. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_vscode_ack_protocol.py +0 -0
  112. {arrayview-0.46.0 → arrayview-0.46.1}/tests/test_window_level_preview.py +0 -0
  113. {arrayview-0.46.0 → arrayview-0.46.1}/tests/ui_audit.py +0 -0
  114. {arrayview-0.46.0 → arrayview-0.46.1}/tests/v_anim_frames/.gitkeep +0 -0
  115. {arrayview-0.46.0 → arrayview-0.46.1}/tests/visual_smoke.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: arrayview
3
- Version: 0.46.0
3
+ Version: 0.46.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
@@ -56,19 +56,6 @@ uvx arrayview your_array.npy
56
56
  uvx arrayview path/to/dicom-series/
57
57
  ```
58
58
 
59
- Press `v` for the three-plane ortho view. `Shift+3` replaces the current slice,
60
- or all three ortho panes, with interactive 3D cutaway renders.
61
- Drop another file onto an open viewer to compare, open separately, or overlay it.
62
- Press `Shift+R` for ROI analysis: a compact HUD lists each region's mean and standard
63
- deviation. The HUD sits beside the image when space allows; drag its header
64
- to move it. Hover a row or region to highlight its counterpart, and use each
65
- row's trash icon to delete that region. Open the detailed analysis with the
66
- button beside the HUD's column headings.
67
- Hold a flood-fill seed and drag up/down to adjust sensitivity; a translucent tapered gauge
68
- at the starting point shows the current value and the 1%–100% range. The
69
- pointer hides while adjusting; only the gauge marker moves.
70
-
71
-
72
59
  Check the [docs](https://oscarvanderheide.github.io/arrayview/) to learn more.
73
60
 
74
61
  **Warning**: Arrayview is still under active development. Things may break or change without warning.
@@ -19,19 +19,6 @@ uvx arrayview your_array.npy
19
19
  uvx arrayview path/to/dicom-series/
20
20
  ```
21
21
 
22
- Press `v` for the three-plane ortho view. `Shift+3` replaces the current slice,
23
- or all three ortho panes, with interactive 3D cutaway renders.
24
- Drop another file onto an open viewer to compare, open separately, or overlay it.
25
- Press `Shift+R` for ROI analysis: a compact HUD lists each region's mean and standard
26
- deviation. The HUD sits beside the image when space allows; drag its header
27
- to move it. Hover a row or region to highlight its counterpart, and use each
28
- row's trash icon to delete that region. Open the detailed analysis with the
29
- button beside the HUD's column headings.
30
- Hold a flood-fill seed and drag up/down to adjust sensitivity; a translucent tapered gauge
31
- at the starting point shows the current value and the 1%–100% range. The
32
- pointer hides while adjusting; only the gauge marker moves.
33
-
34
-
35
22
  Check the [docs](https://oscarvanderheide.github.io/arrayview/) to learn more.
36
23
 
37
24
  **Warning**: Arrayview is still under active development. Things may break or change without warning.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "arrayview"
7
- version = "0.46.0"
7
+ version = "0.46.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"
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import itertools
6
+ import os
6
7
 
7
8
  import numpy as np
8
9
 
@@ -63,6 +64,9 @@ def _build_metadata(session) -> dict:
63
64
  )
64
65
  if getattr(session, "array_keys", None):
65
66
  meta["array_keys"] = session.array_keys
67
+ meta["array_key"] = getattr(session, "array_key", None)
68
+ meta["array_prompt"] = bool(getattr(session, "array_prompt", False))
69
+ meta["array_filename"] = os.path.basename(getattr(session, "array_filepath", "") or "")
66
70
  if collection_spatial_ndim is not None:
67
71
  meta["collection_spatial_ndim"] = int(collection_spatial_ndim)
68
72
  if getattr(session.data, "_av_ragged", False):
@@ -67,6 +67,38 @@ def _is_viewable_mat_array(value):
67
67
  )
68
68
 
69
69
 
70
+ def _is_viewable_h5_dataset(ds):
71
+ """True for HDF5 datasets in a v7.3 .mat file ArrayView can display.
72
+
73
+ MATLAB v7.3 stores complex arrays as a compound dtype with 'real' and
74
+ 'imag' fields, which has dtype kind 'V' rather than 'c'.
75
+ """
76
+ import h5py
77
+
78
+ if not isinstance(ds, h5py.Dataset) or len(ds.shape) < 1:
79
+ return False
80
+ names = ds.dtype.names
81
+ if names:
82
+ return "real" in names and "imag" in names
83
+ return ds.dtype.kind in ("b", "i", "u", "f", "c")
84
+
85
+
86
+ def _h5_display_dtype(ds):
87
+ """Dtype string to show for a v7.3 .mat dataset (compound complex → complex)."""
88
+ if ds.dtype.names:
89
+ return "complex128" if ds.dtype["real"].itemsize == 8 else "complex64"
90
+ return str(ds.dtype)
91
+
92
+
93
+ def array_choice_placeholder():
94
+ """Stand-in data for a multi-array file whose array is not chosen yet.
95
+
96
+ The session exists so the viewer can open and ask which array to load;
97
+ nothing from the file is read until the user picks one.
98
+ """
99
+ return np.zeros((2, 2), dtype=np.float32)
100
+
101
+
70
102
  def list_npz_keys(filepath):
71
103
  """Return [{key, shape, dtype}] for each ndarray in an .npz file.
72
104
 
@@ -106,8 +138,8 @@ def list_mat_keys(filepath):
106
138
  keys = []
107
139
  for k in f.keys():
108
140
  ds = f[k]
109
- if isinstance(ds, h5py.Dataset) and len(ds.shape) >= 1 and ds.dtype.kind in ("b", "i", "u", "f", "c"):
110
- keys.append({"key": k, "shape": list(ds.shape), "dtype": str(ds.dtype)})
141
+ if _is_viewable_h5_dataset(ds):
142
+ keys.append({"key": k, "shape": list(ds.shape), "dtype": _h5_display_dtype(ds)})
111
143
  f.close()
112
144
  return keys
113
145
 
@@ -1726,11 +1758,11 @@ def load_data(filepath, key=None, *, progress=None):
1726
1758
  import h5py
1727
1759
 
1728
1760
  f = h5py.File(filepath, "r")
1729
- arrays = {k: f[k] for k in f.keys() if isinstance(f[k], h5py.Dataset)}
1761
+ arrays = {k: f[k] for k in f.keys() if _is_viewable_h5_dataset(f[k])}
1730
1762
  if key is not None:
1731
- return np.array(arrays[key])
1763
+ return _fix_mat_complex(np.array(arrays[key]))
1732
1764
  if len(arrays) == 1:
1733
- return np.array(next(iter(arrays.values())))
1765
+ return _fix_mat_complex(np.array(next(iter(arrays.values()))))
1734
1766
  raise ValueError(
1735
1767
  f".mat (v7.3) file contains multiple datasets: {list(arrays.keys())}. "
1736
1768
  "Select one in the viewer or pass a key."
@@ -1321,6 +1321,9 @@ def _load_session_from_filepath(
1321
1321
  "requested_sid": requested_sid,
1322
1322
  "release_on_disconnect": release_on_disconnect,
1323
1323
  "related_sids": related_sids or [],
1324
+ # Multi-array .npz/.mat: open the first array instead of returning
1325
+ # the key list; the viewer then offers the choice.
1326
+ "array_default": True,
1324
1327
  }
1325
1328
  if source_staging_dir:
1326
1329
  payload["source_staging_dir"] = source_staging_dir
@@ -4691,6 +4694,7 @@ def _serve_daemon(
4691
4694
 
4692
4695
  def _load():
4693
4696
  from arrayview._io import (
4697
+ array_choice_placeholder,
4694
4698
  load_data,
4695
4699
  load_data_with_meta,
4696
4700
  load_dir_collection,
@@ -4705,8 +4709,8 @@ def _serve_daemon(
4705
4709
  if dir_patterns is None and not cleanup
4706
4710
  else None
4707
4711
  )
4708
- # Multi-array .npz/.mat: load the first array so the session is
4709
- # created, then store keys so the viewer can show a picker.
4712
+ # Multi-array .npz/.mat: create the session with a placeholder and
4713
+ # store the keys; the viewer asks which array to load.
4710
4714
  _array_keys = None
4711
4715
  if dir_patterns is not None:
4712
4716
  data, spatial_meta, dir_overlay_items, _summary = load_dir_collection(
@@ -4723,8 +4727,12 @@ def _serve_daemon(
4723
4727
  _array_keys = list_array_keys(filepath)
4724
4728
  except Exception:
4725
4729
  pass
4726
- _load_key = _array_keys[0]["key"] if _array_keys else None
4727
- data, spatial_meta = load_data_with_meta(filepath, key=_load_key)
4730
+ if _array_keys and len(_array_keys) > 1:
4731
+ _load_key = None
4732
+ data, spatial_meta = array_choice_placeholder(), None
4733
+ else:
4734
+ _load_key = _array_keys[0]["key"] if _array_keys else None
4735
+ data, spatial_meta = load_data_with_meta(filepath, key=_load_key)
4728
4736
  dir_overlay_items = None
4729
4737
  collection_spatial_ndim = None
4730
4738
  else:
@@ -4758,6 +4766,9 @@ def _serve_daemon(
4758
4766
  if _array_keys and len(_array_keys) > 1:
4759
4767
  session.array_keys = _array_keys
4760
4768
  session.array_filepath = filepath
4769
+ session.array_key = _load_key
4770
+ # Nothing is loaded yet; the viewer asks once which array to show.
4771
+ session.array_prompt = True
4761
4772
  if spatial_meta is not None:
4762
4773
  session.original_volume = data
4763
4774
  if rgb:
@@ -413,12 +413,18 @@ def register_loading_routes(app, *, notify_shells, setup_rgb) -> None:
413
413
  PENDING_SESSIONS.add(dir_pending_sid)
414
414
  try:
415
415
  try:
416
- from ._io import load_data_with_meta, load_dir_collection, list_array_keys
416
+ from ._io import (
417
+ array_choice_placeholder,
418
+ list_array_keys,
419
+ load_data_with_meta,
420
+ load_dir_collection,
421
+ )
417
422
 
418
423
  # Multi-array .npz/.mat: if no key provided, return the key list so
419
424
  # the client can show a picker instead of blocking on terminal input.
420
425
  _key = body.get("key")
421
426
  _array_keys = None
427
+ _array_prompt = False
422
428
  if dir_patterns:
423
429
  data, spatial_meta, dir_overlay_items, summary = await asyncio.to_thread(
424
430
  load_dir_collection,
@@ -432,16 +438,22 @@ def register_loading_routes(app, *, notify_shells, setup_rgb) -> None:
432
438
  elif filepath.endswith(".npz") or filepath.endswith(".mat"):
433
439
  _array_keys = await asyncio.to_thread(list_array_keys, filepath)
434
440
  if len(_array_keys) > 1 and not _key:
435
- return {"array_keys": _array_keys, "filepath": filepath}
441
+ if not body.get("array_default"):
442
+ return {"array_keys": _array_keys, "filepath": filepath}
443
+ # Command-line launch: open the viewer without reading
444
+ # any array; it asks which one to load.
445
+ _array_prompt = True
446
+ data, spatial_meta = array_choice_placeholder(), None
436
447
  if len(_array_keys) == 1 and not _key:
437
448
  _key = _array_keys[0]["key"]
438
449
 
439
- data, spatial_meta = await asyncio.to_thread(
440
- load_data_with_meta,
441
- filepath,
442
- key=_key,
443
- select=body.get("select"),
444
- ) if not dir_patterns else (data, spatial_meta)
450
+ if not dir_patterns and not _array_prompt:
451
+ data, spatial_meta = await asyncio.to_thread(
452
+ load_data_with_meta,
453
+ filepath,
454
+ key=_key,
455
+ select=body.get("select"),
456
+ )
445
457
  except Exception as e:
446
458
  if staging_dir:
447
459
  cleanup_staging_directory(staging_dir)
@@ -482,6 +494,8 @@ def register_loading_routes(app, *, notify_shells, setup_rgb) -> None:
482
494
  if _array_keys and len(_array_keys) > 1:
483
495
  session.array_keys = _array_keys
484
496
  session.array_filepath = filepath
497
+ session.array_key = _key
498
+ session.array_prompt = _array_prompt
485
499
  if body.get("rgb"):
486
500
  try:
487
501
  await asyncio.to_thread(setup_rgb, session)
@@ -627,15 +641,25 @@ def register_loading_routes(app, *, notify_shells, setup_rgb) -> None:
627
641
  return {"error": str(e)}
628
642
  session.data = data
629
643
  session.shape = data.shape
644
+ session.spatial_shape = data.shape
645
+ session.rgb_axis = None
646
+ session.fft_original_data = None
647
+ session.fft_axes = None
648
+ session.reset_caches()
630
649
  session.data_version += 1
631
- session.raw_cache.clear()
632
- session.rgba_cache.clear()
633
- session.mosaic_cache.clear()
634
- session._raw_bytes = 0
635
- session._rgba_bytes = 0
636
- session._mosaic_bytes = 0
637
650
  session._estimated_mem = session._estimate_memory()
638
651
  # Keep array_keys so the user can switch again, but don't auto-prompt
652
+ session.array_key = key
653
+ session.array_prompt = False
654
+ return {"ok": True}
655
+
656
+ @app.post("/session/{sid}/array-prompt-done")
657
+ async def array_prompt_done(sid: str):
658
+ """Record that the array picker was offered, so it never reopens by itself."""
659
+ session = SESSIONS.get(sid)
660
+ if session is None:
661
+ raise HTTPException(status_code=404, detail="Session not found")
662
+ session.array_prompt = False
639
663
  return {"ok": True}
640
664
 
641
665
  @app.get("/fs/list")