arrayview 0.47.1__tar.gz → 0.47.4__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 (118) hide show
  1. {arrayview-0.47.1 → arrayview-0.47.4}/PKG-INFO +1 -1
  2. {arrayview-0.47.1 → arrayview-0.47.4}/pyproject.toml +1 -1
  3. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_io.py +68 -4
  4. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_launcher.py +12 -11
  5. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_loading.py +11 -11
  6. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_preferences.py +1 -1
  7. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_source_safety.py +2 -0
  8. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_viewer.html +6 -0
  9. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_vscode_extension.py +1 -1
  10. arrayview-0.47.4/src/arrayview/arrayview-opener.vsix +0 -0
  11. {arrayview-0.47.1 → arrayview-0.47.4}/tests/conftest.py +15 -0
  12. {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_api.py +632 -4094
  13. arrayview-0.47.4/tests/test_browser.py +1010 -0
  14. {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_cli.py +28 -0
  15. {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_io.py +38 -0
  16. {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_launch_blackbox.py +69 -283
  17. arrayview-0.47.4/tests/test_vsix_bundle.py +19 -0
  18. arrayview-0.47.1/src/arrayview/arrayview-opener.vsix +0 -0
  19. arrayview-0.47.1/tests/lifecycle_matrix.py +0 -416
  20. arrayview-0.47.1/tests/test_bootstrap_app.py +0 -114
  21. arrayview-0.47.1/tests/test_browser.py +0 -7533
  22. arrayview-0.47.1/tests/test_colorbar_hover_highlight.py +0 -156
  23. arrayview-0.47.1/tests/test_display_opener_results.py +0 -536
  24. arrayview-0.47.1/tests/test_doctor_commands.py +0 -104
  25. arrayview-0.47.1/tests/test_invocation_contract_matrix.py +0 -232
  26. arrayview-0.47.1/tests/test_launch_diagnostics.py +0 -137
  27. arrayview-0.47.1/tests/test_launch_execution_ownership.py +0 -283
  28. arrayview-0.47.1/tests/test_launch_plan.py +0 -816
  29. arrayview-0.47.1/tests/test_launch_trace.py +0 -231
  30. arrayview-0.47.1/tests/test_lifecycle_contract.py +0 -2511
  31. arrayview-0.47.1/tests/test_loading_server.py +0 -107
  32. arrayview-0.47.1/tests/test_mode_consistency.py +0 -1248
  33. arrayview-0.47.1/tests/test_mode_matrix.py +0 -658
  34. arrayview-0.47.1/tests/test_preferences_ui.py +0 -185
  35. arrayview-0.47.1/tests/test_public_invocation_regressions.py +0 -350
  36. arrayview-0.47.1/tests/test_render_failure_reporting.py +0 -74
  37. arrayview-0.47.1/tests/test_render_progress.py +0 -275
  38. arrayview-0.47.1/tests/test_roi_hud.py +0 -200
  39. arrayview-0.47.1/tests/test_server_identity_status.py +0 -82
  40. arrayview-0.47.1/tests/test_session_spec.py +0 -93
  41. arrayview-0.47.1/tests/test_spawn_ownership.py +0 -175
  42. arrayview-0.47.1/tests/test_tunnel_warm_path.py +0 -160
  43. arrayview-0.47.1/tests/test_tutorial_browser.py +0 -481
  44. arrayview-0.47.1/tests/test_value_formatting.py +0 -126
  45. arrayview-0.47.1/tests/test_view_handle_lifecycle.py +0 -110
  46. arrayview-0.47.1/tests/test_view_launch_execution.py +0 -311
  47. arrayview-0.47.1/tests/test_vscode_ack_protocol.py +0 -485
  48. arrayview-0.47.1/tests/test_window_level_preview.py +0 -101
  49. {arrayview-0.47.1 → arrayview-0.47.4}/.gitignore +0 -0
  50. {arrayview-0.47.1 → arrayview-0.47.4}/LICENSE +0 -0
  51. {arrayview-0.47.1 → arrayview-0.47.4}/README.md +0 -0
  52. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/ARCHITECTURE.md +0 -0
  53. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/__init__.py +0 -0
  54. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/__main__.py +0 -0
  55. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_analysis.py +0 -0
  56. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_app.py +0 -0
  57. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_bootstrap_app.py +0 -0
  58. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_config.py +0 -0
  59. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_diagnostics.py +0 -0
  60. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_dicom.py +0 -0
  61. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_diff.py +0 -0
  62. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_extra_ports.py +0 -0
  63. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_icon.png +0 -0
  64. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_imaging.py +0 -0
  65. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_instance_registry.py +0 -0
  66. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_launch_plan.py +0 -0
  67. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_launch_trace.py +0 -0
  68. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_lifecycle.py +0 -0
  69. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_overlays.py +0 -0
  70. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_platform.py +0 -0
  71. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_render.py +0 -0
  72. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_analysis.py +0 -0
  73. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_drop.py +0 -0
  74. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_export.py +0 -0
  75. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_persistence.py +0 -0
  76. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_preload.py +0 -0
  77. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_query.py +0 -0
  78. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_rendering.py +0 -0
  79. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_segmentation.py +0 -0
  80. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_state.py +0 -0
  81. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_vectorfield.py +0 -0
  82. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_websocket.py +0 -0
  83. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_segmentation.py +0 -0
  84. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_server.py +0 -0
  85. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_session.py +0 -0
  86. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_session_spec.py +0 -0
  87. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_shell.html +0 -0
  88. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_synthetic_mri.py +0 -0
  89. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_torch.py +0 -0
  90. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_tutorial.py +0 -0
  91. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_vectorfield.py +0 -0
  92. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_vscode.py +0 -0
  93. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_vscode_browser.py +0 -0
  94. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_vscode_signal.py +0 -0
  95. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_watch.py +0 -0
  96. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/gsap.min.js +0 -0
  97. {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/todo.md +0 -0
  98. {arrayview-0.47.1 → arrayview-0.47.4}/tests/capture_patient_loading.py +0 -0
  99. {arrayview-0.47.1 → arrayview-0.47.4}/tests/capture_v_animation.py +0 -0
  100. {arrayview-0.47.1 → arrayview-0.47.4}/tests/make_vectorfield_test_arrays.py +0 -0
  101. {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_1d_arrays.py +0 -0
  102. {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_backend_shared.py +0 -0
  103. {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_config.py +0 -0
  104. {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_dicom.py +0 -0
  105. {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_drop_import.py +0 -0
  106. {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_instance_registry.py +0 -0
  107. {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_large_arrays.py +0 -0
  108. {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_mode_roundtrip.py +0 -0
  109. {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_nifti_meta.py +0 -0
  110. {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_nifti_series.py +0 -0
  111. {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_rgb_pixel_art.py +0 -0
  112. {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_safe_server_stop.py +0 -0
  113. {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_source_safety.py +0 -0
  114. {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_torch.py +0 -0
  115. {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_tutorial.py +0 -0
  116. {arrayview-0.47.1 → arrayview-0.47.4}/tests/ui_audit.py +0 -0
  117. {arrayview-0.47.1 → arrayview-0.47.4}/tests/v_anim_frames/.gitkeep +0 -0
  118. {arrayview-0.47.1 → arrayview-0.47.4}/tests/visual_smoke.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: arrayview
3
- Version: 0.47.1
3
+ Version: 0.47.4
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.47.1"
7
+ version = "0.47.4"
8
8
  description = "Fast multi-dimensional array viewer"
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  requires-python = ">=3.12"
@@ -667,7 +667,7 @@ class _FileSeries:
667
667
  """Lazy 4D/5D view over a directory of same-shape array files.
668
668
 
669
669
  Like ``_NiftiSeries`` but works with any supported file format
670
- (.npy, .npz, .zarr, .pt/.pth, .h5/.hdf5, .tif/.tiff, .mat).
670
+ (.npy, .npz, .zarr, .pt/.pth, .h5/.hdf5, .tif/.tiff, .mat, .gipl).
671
671
  Uses ``load_data`` for each volume — no nibabel reorientation.
672
672
  """
673
673
 
@@ -940,6 +940,8 @@ def _get_ext(filepath):
940
940
  return ".nii.gz"
941
941
  if lower.endswith(".zarr.zip"):
942
942
  return ".zarr.zip"
943
+ if lower.endswith(".gipl.gz"):
944
+ return ".gipl.gz"
943
945
  return os.path.splitext(lower)[1]
944
946
 
945
947
 
@@ -950,7 +952,7 @@ def _is_supported_collection_path(filepath):
950
952
  def _strip_array_ext(filepath):
951
953
  base = os.path.basename(filepath)
952
954
  lower = base.lower()
953
- for suffix in (".nii.gz", ".zarr.zip"):
955
+ for suffix in (".nii.gz", ".zarr.zip", ".gipl.gz"):
954
956
  if lower.endswith(suffix):
955
957
  return base[: -len(suffix)]
956
958
  return os.path.splitext(base)[0]
@@ -1738,6 +1740,8 @@ def load_data(filepath, key=None, *, progress=None):
1738
1740
  import tifffile
1739
1741
 
1740
1742
  return tifffile.imread(filepath)
1743
+ elif filepath.lower().endswith((".gipl", ".gipl.gz")):
1744
+ return _load_gipl(filepath)
1741
1745
  elif filepath.endswith(".mat"):
1742
1746
  try:
1743
1747
  import scipy.io
@@ -1783,10 +1787,68 @@ def load_data(filepath, key=None, *, progress=None):
1783
1787
  return data
1784
1788
  raise ValueError(
1785
1789
  "Unsupported format. Supported: .npy, .npz, .nii/.nii.gz, .zarr, "
1786
- ".pt/.pth, .h5/.hdf5, .tif/.tiff, .mat"
1790
+ ".pt/.pth, .h5/.hdf5, .tif/.tiff, .mat, .gipl/.gipl.gz"
1787
1791
  )
1788
1792
 
1789
1793
 
1794
+ # GIPL (Guys Image Processing Lab) image types -> numpy dtypes. Data is
1795
+ # big-endian; complex types store interleaved real/imaginary components.
1796
+ _GIPL_DTYPES = {
1797
+ 1: ">u1", # binary, one value per byte
1798
+ 7: ">i1",
1799
+ 8: ">u1",
1800
+ 15: ">i2",
1801
+ 16: ">u2",
1802
+ 31: ">u4",
1803
+ 32: ">i4",
1804
+ 64: ">f4",
1805
+ 65: ">f8",
1806
+ 144: ">i2", # complex short
1807
+ 160: ">i4", # complex int
1808
+ 192: ">f4", # complex float
1809
+ 193: ">f8", # complex double
1810
+ }
1811
+ _GIPL_COMPLEX = frozenset([144, 160, 192, 193])
1812
+ _GIPL_MAGIC = (4026526128, 719555000)
1813
+ _GIPL_HEADER_SIZE = 256
1814
+
1815
+
1816
+ def _load_gipl(filepath):
1817
+ """Read a .gipl/.gipl.gz file into an array indexed (x, y, z, t).
1818
+
1819
+ Trailing singleton dimensions are dropped, matching how a NIfTI volume
1820
+ of the same data is shown.
1821
+ """
1822
+ import gzip
1823
+
1824
+ opener = gzip.open if filepath.lower().endswith(".gz") else open
1825
+ with opener(filepath, "rb") as f:
1826
+ raw = f.read()
1827
+ if len(raw) < _GIPL_HEADER_SIZE:
1828
+ raise ValueError(f"{filepath!r} is too short to be a GIPL file.")
1829
+ header = raw[:_GIPL_HEADER_SIZE]
1830
+ magic = int(np.frombuffer(header, ">u4", count=1, offset=252)[0])
1831
+ if magic not in _GIPL_MAGIC:
1832
+ raise ValueError(f"{filepath!r} is not a GIPL file (bad magic number).")
1833
+ dims = [int(d) for d in np.frombuffer(header, ">u2", count=4, offset=0)]
1834
+ image_type = int(np.frombuffer(header, ">u2", count=1, offset=8)[0])
1835
+ if image_type not in _GIPL_DTYPES:
1836
+ raise ValueError(f"{filepath!r} has unsupported GIPL image type {image_type}.")
1837
+ dims = [max(d, 1) for d in dims]
1838
+ is_complex = image_type in _GIPL_COMPLEX
1839
+ count = int(np.prod(dims)) * (2 if is_complex else 1)
1840
+ data = np.frombuffer(raw, _GIPL_DTYPES[image_type], count=count, offset=_GIPL_HEADER_SIZE)
1841
+ data = data.astype(data.dtype.newbyteorder("="))
1842
+ if is_complex:
1843
+ data = data.astype(np.float64 if data.itemsize == 8 else np.float32)
1844
+ data = data[0::2] + 1j * data[1::2]
1845
+ # x varies fastest on disk.
1846
+ arr = data.reshape(dims[::-1]).transpose(3, 2, 1, 0)
1847
+ while arr.ndim > 2 and arr.shape[-1] == 1:
1848
+ arr = arr[..., 0]
1849
+ return np.ascontiguousarray(arr)
1850
+
1851
+
1790
1852
  def _tensor_to_numpy(obj, source="object"):
1791
1853
  """Convert a tensor-like object (PyTorch, etc.) to a numpy array."""
1792
1854
  if isinstance(obj, np.ndarray):
@@ -1823,13 +1885,15 @@ _SUPPORTED_EXTS = frozenset(
1823
1885
  ".tif",
1824
1886
  ".tiff",
1825
1887
  ".mat",
1888
+ ".gipl",
1889
+ ".gipl.gz",
1826
1890
  ".dcm",
1827
1891
  ]
1828
1892
  )
1829
1893
 
1830
1894
  # Formats that load the entire array into RAM (no mmap/lazy access).
1831
1895
  # RAM guard only applies to these.
1832
- FULL_LOAD_EXTS = frozenset([".pt", ".pth", ".tif", ".tiff", ".mat"])
1896
+ FULL_LOAD_EXTS = frozenset([".pt", ".pth", ".tif", ".tiff", ".mat", ".gipl", ".gipl.gz"])
1833
1897
 
1834
1898
 
1835
1899
  def _peek_file_shape(fpath: str, ext: str):
@@ -6211,17 +6211,18 @@ def arrayview():
6211
6211
  pattern for _name, pattern in _normalize_dir_overlay_specs(args.overlay or [])
6212
6212
  )
6213
6213
  collection_inputs.extend(args.overlay_dir or [])
6214
- for pattern in collection_inputs:
6215
- pattern = lexical_abspath(pattern)
6216
- probe_prefix = scan_root_before_magic(pattern)
6217
- mount = direct_network_mount(probe_prefix)
6218
- nested_mount = network_mount_below(probe_prefix)
6219
- if mount is not None or nested_mount is not None:
6220
- _print_failure(
6221
- "network directory collections are not accessed directly. "
6222
- "Copy the collection to local storage before using --match."
6223
- )
6224
- sys.exit(1)
6214
+ if os.environ.get("ARRAYVIEW_SKIP_SOURCE_STAGING", "1") == "0":
6215
+ for pattern in collection_inputs:
6216
+ pattern = lexical_abspath(pattern)
6217
+ probe_prefix = scan_root_before_magic(pattern)
6218
+ mount = direct_network_mount(probe_prefix)
6219
+ nested_mount = network_mount_below(probe_prefix)
6220
+ if mount is not None or nested_mount is not None:
6221
+ _print_failure(
6222
+ "network directory collections are not accessed directly. "
6223
+ "Copy the collection to local storage before using --match."
6224
+ )
6225
+ sys.exit(1)
6225
6226
  if len(dir_patterns) == 1 and os.path.isdir(dir_patterns[0]):
6226
6227
  dir_patterns = [os.path.join(dir_patterns[0], "**", "*")]
6227
6228
  dir_overlay_specs = [
@@ -167,17 +167,17 @@ def register_loading_routes(app, *, notify_shells, setup_rgb) -> None:
167
167
  f"{nested_sibling_mount.mountpoint!r}; copy the series locally first."
168
168
  )
169
169
  }
170
- for pattern in dir_patterns or []:
171
- pattern_path = lexical_abspath(str(pattern))
172
- scan_root = scan_root_before_magic(pattern_path)
173
- unsafe_mount = direct_network_mount(scan_root) or network_mount_below(scan_root)
174
- if unsafe_mount is not None:
175
- return {
176
- "error": (
177
- f"Directory scan would enter network mount "
178
- f"{unsafe_mount.mountpoint!r}; copy the collection locally first."
179
- )
180
- }
170
+ for pattern in dir_patterns or []:
171
+ pattern_path = lexical_abspath(str(pattern))
172
+ scan_root = scan_root_before_magic(pattern_path)
173
+ unsafe_mount = direct_network_mount(scan_root) or network_mount_below(scan_root)
174
+ if unsafe_mount is not None:
175
+ return {
176
+ "error": (
177
+ f"Directory scan would enter network mount "
178
+ f"{unsafe_mount.mountpoint!r}; copy the collection locally first."
179
+ )
180
+ }
181
181
  if requested_sid in CANCELLED_PENDING_SESSIONS:
182
182
  CANCELLED_PENDING_SESSIONS.discard(requested_sid)
183
183
  raise HTTPException(status_code=409, detail="load request was cancelled")
@@ -37,7 +37,7 @@ def _response_payload() -> dict:
37
37
  if not native_available:
38
38
  defaults["window"]["default"] = "browser"
39
39
  defaults["window"]["terminal"] = "browser"
40
- for key in ("default", "terminal", "vscode", "jupyter", "julia"):
40
+ for key in ("terminal", "vscode", "jupyter", "julia"):
41
41
  schema["window"][key] = [
42
42
  value for value in schema["window"][key] if value != "native"
43
43
  ]
@@ -53,6 +53,8 @@ _KNOWN_LOCAL_STAGE_FILESYSTEMS = frozenset(
53
53
  }
54
54
  )
55
55
  _SELF_CONTAINED_FILE_EXTENSIONS = (
56
+ ".gipl",
57
+ ".gipl.gz",
56
58
  ".h5",
57
59
  ".hdf5",
58
60
  ".mat",
@@ -7086,6 +7086,7 @@
7086
7086
  let _mvPrevZoom = 1.0;
7087
7087
  let _cmvPrevSquareStretch = false;
7088
7088
  let _qmriPrevZoom = 1.0; // saved on enterQmri, restored on exitQmri (mirrors _mvPrevZoom)
7089
+ let _qmriPrevActiveDim = null; // scroll dim before qMRI moved it off the map dim; null while rebuilding keeps the original
7089
7090
  let _comparePrevZoom = 1.0; // saved on enterCompareMode, restored on exitCompareMode
7090
7091
  let _comparePrevNormalView = null; // Phase 4: saved primary View, restored on exitCompareMode
7091
7092
  let _eggsVisible = true;
@@ -35472,6 +35473,7 @@
35472
35473
  if (multiViewActive) exitMultiView();
35473
35474
  if (stackMosaicActive) exitStackMosaic();
35474
35475
  _qmriPrevZoom = userZoom; // save prior zoom so exitQmri can restore (mirrors enterMultiView)
35476
+ if (_qmriPrevActiveDim === null) _qmriPrevActiveDim = activeDim;
35475
35477
  userZoom = 1.0;
35476
35478
  qvPan.reset();
35477
35479
  // Opening straight into qMRI never draws the main canvas, so its
@@ -35994,6 +35996,10 @@
35994
35996
  wrap.innerHTML = '';
35995
35997
  wrap.className = '';
35996
35998
  userZoom = _qmriPrevZoom; // restore prior zoom (mirrors exitMultiView line 10521)
35999
+ if (!_qmriSyntheticRebuilding && _qmriPrevActiveDim !== null) {
36000
+ if (_qmriPrevActiveDim < shape.length) activeDim = _qmriPrevActiveDim;
36001
+ _qmriPrevActiveDim = null;
36002
+ }
35997
36003
  if (!_qmriSyntheticRebuilding) updateView();
35998
36004
  _reconcileUI();
35999
36005
  renderInfo();
@@ -58,7 +58,7 @@ _VSCODE_EXT_FRESH_INSTALL = False # True if we just installed it this session
58
58
  _VSCODE_EXT_RELOAD_REQUIRED = False # installed files are newer than the live host
59
59
  _VSCODE_EXT_INSTALL_FAILED = False # automatic install could not complete safely
60
60
  _VSCODE_EXT_NO_LIVE_WINDOW = False # no live host claims this terminal's window id
61
- _VSCODE_EXT_VERSION = "0.15.62" # current bundled extension version
61
+ _VSCODE_EXT_VERSION = "0.15.64" # current bundled extension version
62
62
  _VSCODE_CONFIGURED_PORTS: set[int] = set()
63
63
  # Version-skew notices already printed, so a long-lived process (Jupyter, a
64
64
  # script opening several arrays) states the mismatch once instead of per call.
@@ -34,6 +34,21 @@ def isolated_runtime_directory(tmp_path_factory, monkeypatch):
34
34
  )
35
35
 
36
36
 
37
+ @pytest.fixture(autouse=True)
38
+ def no_leaked_fake_sessions():
39
+ """Drop anything a test put in the shared session table that isn't a session.
40
+
41
+ The test server is shared by the whole run, so one stray placeholder makes
42
+ every later load in every later file fail.
43
+ """
44
+ from arrayview._session import SESSIONS, Session
45
+
46
+ yield
47
+ for sid, value in list(SESSIONS.items()):
48
+ if not isinstance(value, Session):
49
+ SESSIONS.pop(sid, None)
50
+
51
+
37
52
  # ---------------------------------------------------------------------------
38
53
  # Server
39
54
  # ---------------------------------------------------------------------------