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.
- {arrayview-0.47.1 → arrayview-0.47.4}/PKG-INFO +1 -1
- {arrayview-0.47.1 → arrayview-0.47.4}/pyproject.toml +1 -1
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_io.py +68 -4
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_launcher.py +12 -11
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_loading.py +11 -11
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_preferences.py +1 -1
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_source_safety.py +2 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_viewer.html +6 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_vscode_extension.py +1 -1
- arrayview-0.47.4/src/arrayview/arrayview-opener.vsix +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/conftest.py +15 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_api.py +632 -4094
- arrayview-0.47.4/tests/test_browser.py +1010 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_cli.py +28 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_io.py +38 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_launch_blackbox.py +69 -283
- arrayview-0.47.4/tests/test_vsix_bundle.py +19 -0
- arrayview-0.47.1/src/arrayview/arrayview-opener.vsix +0 -0
- arrayview-0.47.1/tests/lifecycle_matrix.py +0 -416
- arrayview-0.47.1/tests/test_bootstrap_app.py +0 -114
- arrayview-0.47.1/tests/test_browser.py +0 -7533
- arrayview-0.47.1/tests/test_colorbar_hover_highlight.py +0 -156
- arrayview-0.47.1/tests/test_display_opener_results.py +0 -536
- arrayview-0.47.1/tests/test_doctor_commands.py +0 -104
- arrayview-0.47.1/tests/test_invocation_contract_matrix.py +0 -232
- arrayview-0.47.1/tests/test_launch_diagnostics.py +0 -137
- arrayview-0.47.1/tests/test_launch_execution_ownership.py +0 -283
- arrayview-0.47.1/tests/test_launch_plan.py +0 -816
- arrayview-0.47.1/tests/test_launch_trace.py +0 -231
- arrayview-0.47.1/tests/test_lifecycle_contract.py +0 -2511
- arrayview-0.47.1/tests/test_loading_server.py +0 -107
- arrayview-0.47.1/tests/test_mode_consistency.py +0 -1248
- arrayview-0.47.1/tests/test_mode_matrix.py +0 -658
- arrayview-0.47.1/tests/test_preferences_ui.py +0 -185
- arrayview-0.47.1/tests/test_public_invocation_regressions.py +0 -350
- arrayview-0.47.1/tests/test_render_failure_reporting.py +0 -74
- arrayview-0.47.1/tests/test_render_progress.py +0 -275
- arrayview-0.47.1/tests/test_roi_hud.py +0 -200
- arrayview-0.47.1/tests/test_server_identity_status.py +0 -82
- arrayview-0.47.1/tests/test_session_spec.py +0 -93
- arrayview-0.47.1/tests/test_spawn_ownership.py +0 -175
- arrayview-0.47.1/tests/test_tunnel_warm_path.py +0 -160
- arrayview-0.47.1/tests/test_tutorial_browser.py +0 -481
- arrayview-0.47.1/tests/test_value_formatting.py +0 -126
- arrayview-0.47.1/tests/test_view_handle_lifecycle.py +0 -110
- arrayview-0.47.1/tests/test_view_launch_execution.py +0 -311
- arrayview-0.47.1/tests/test_vscode_ack_protocol.py +0 -485
- arrayview-0.47.1/tests/test_window_level_preview.py +0 -101
- {arrayview-0.47.1 → arrayview-0.47.4}/.gitignore +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/LICENSE +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/README.md +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/ARCHITECTURE.md +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/__init__.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/__main__.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_analysis.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_app.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_bootstrap_app.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_config.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_diagnostics.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_dicom.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_diff.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_extra_ports.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_icon.png +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_imaging.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_instance_registry.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_launch_plan.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_launch_trace.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_lifecycle.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_overlays.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_platform.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_render.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_analysis.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_drop.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_export.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_persistence.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_preload.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_query.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_rendering.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_segmentation.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_state.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_vectorfield.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_routes_websocket.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_segmentation.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_server.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_session.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_session_spec.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_shell.html +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_synthetic_mri.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_torch.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_tutorial.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_vectorfield.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_vscode.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_vscode_browser.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_vscode_signal.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/_watch.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/gsap.min.js +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/src/arrayview/todo.md +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/capture_patient_loading.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/capture_v_animation.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/make_vectorfield_test_arrays.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_1d_arrays.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_backend_shared.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_config.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_dicom.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_drop_import.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_instance_registry.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_large_arrays.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_mode_roundtrip.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_nifti_meta.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_nifti_series.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_rgb_pixel_art.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_safe_server_stop.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_source_safety.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_torch.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/test_tutorial.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/ui_audit.py +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/v_anim_frames/.gitkeep +0 -0
- {arrayview-0.47.1 → arrayview-0.47.4}/tests/visual_smoke.py +0 -0
|
@@ -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
|
-
|
|
6215
|
-
pattern
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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 ("
|
|
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
|
]
|
|
@@ -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.
|
|
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.
|
|
Binary file
|
|
@@ -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
|
# ---------------------------------------------------------------------------
|