arrayview 0.47.3__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.3 → arrayview-0.47.4}/PKG-INFO +1 -1
- {arrayview-0.47.3 → arrayview-0.47.4}/pyproject.toml +1 -1
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_io.py +68 -4
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_launcher.py +12 -11
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_routes_loading.py +11 -11
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_source_safety.py +2 -0
- {arrayview-0.47.3 → 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.3 → arrayview-0.47.4}/tests/test_api.py +24 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/test_cli.py +28 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/test_io.py +38 -0
- arrayview-0.47.3/src/arrayview/arrayview-opener.vsix +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/.gitignore +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/LICENSE +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/README.md +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/ARCHITECTURE.md +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/__init__.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/__main__.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_analysis.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_app.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_bootstrap_app.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_config.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_diagnostics.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_dicom.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_diff.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_extra_ports.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_icon.png +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_imaging.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_instance_registry.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_launch_plan.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_launch_trace.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_lifecycle.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_overlays.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_platform.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_render.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_routes_analysis.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_routes_drop.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_routes_export.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_routes_persistence.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_routes_preferences.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_routes_preload.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_routes_query.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_routes_rendering.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_routes_segmentation.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_routes_state.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_routes_vectorfield.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_routes_websocket.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_segmentation.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_server.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_session.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_session_spec.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_shell.html +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_synthetic_mri.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_torch.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_tutorial.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_vectorfield.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_viewer.html +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_vscode.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_vscode_browser.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_vscode_signal.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/_watch.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/gsap.min.js +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/src/arrayview/todo.md +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/capture_patient_loading.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/capture_v_animation.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/conftest.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/make_vectorfield_test_arrays.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/test_1d_arrays.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/test_backend_shared.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/test_browser.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/test_config.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/test_dicom.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/test_drop_import.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/test_instance_registry.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/test_large_arrays.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/test_launch_blackbox.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/test_mode_roundtrip.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/test_nifti_meta.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/test_nifti_series.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/test_rgb_pixel_art.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/test_safe_server_stop.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/test_source_safety.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/test_torch.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/test_tutorial.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/test_vsix_bundle.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/ui_audit.py +0 -0
- {arrayview-0.47.3 → arrayview-0.47.4}/tests/v_anim_frames/.gitkeep +0 -0
- {arrayview-0.47.3 → 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")
|
|
@@ -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
|
|
@@ -423,6 +423,30 @@ class TestLoad:
|
|
|
423
423
|
assert "Direct server access" in response.json()["error"]
|
|
424
424
|
assert time.monotonic() - started < 2
|
|
425
425
|
|
|
426
|
+
@pytest.mark.parametrize("staging", [None, "1", "0"])
|
|
427
|
+
@pytest.mark.parametrize("nested", [False, True])
|
|
428
|
+
def test_network_collection_respects_staging_setting(self, client, tmp_path, monkeypatch, staging, nested):
|
|
429
|
+
images = tmp_path / "images"
|
|
430
|
+
images.mkdir()
|
|
431
|
+
np.save(images / "case_a.npy", np.ones((4, 5, 6), dtype=np.float32))
|
|
432
|
+
mountpoint = images if nested else tmp_path
|
|
433
|
+
mountinfo = tmp_path / "mountinfo"
|
|
434
|
+
mountinfo.write_text(f"42 31 0:55 / {mountpoint} rw - cifs //server/share rw\n")
|
|
435
|
+
monkeypatch.setenv("_ARRAYVIEW_TEST_MOUNTINFO", str(mountinfo))
|
|
436
|
+
if staging is None:
|
|
437
|
+
monkeypatch.delenv("ARRAYVIEW_SKIP_SOURCE_STAGING", raising=False)
|
|
438
|
+
else:
|
|
439
|
+
monkeypatch.setenv("ARRAYVIEW_SKIP_SOURCE_STAGING", staging)
|
|
440
|
+
pattern = str(tmp_path / "*" / "case_a.npy") if nested else str(images / "*.npy")
|
|
441
|
+
body = client.post("/load", json={"dir_patterns": [pattern]}).json()
|
|
442
|
+
if staging == "0":
|
|
443
|
+
assert "network mount" in body["error"]
|
|
444
|
+
else:
|
|
445
|
+
assert "error" not in body
|
|
446
|
+
meta = client.get(f"/metadata/{body['sid']}").json()
|
|
447
|
+
assert meta["shape"] == [4, 5, 6, 1]
|
|
448
|
+
assert meta["case_names"] == ["case_a"]
|
|
449
|
+
|
|
426
450
|
def test_load_rejects_arbitrary_staging_cleanup_authority(
|
|
427
451
|
self, client, arr_2d, tmp_path, monkeypatch
|
|
428
452
|
):
|
|
@@ -477,6 +477,34 @@ def test_cli_dir_dry_run_prints_collection_summary(monkeypatch, tmp_path):
|
|
|
477
477
|
assert "caseA, caseB" in text
|
|
478
478
|
|
|
479
479
|
|
|
480
|
+
@pytest.mark.parametrize("staging", [None, "1", "0"])
|
|
481
|
+
@pytest.mark.parametrize("nested", [False, True])
|
|
482
|
+
def test_cli_network_collection_respects_staging_setting(monkeypatch, tmp_path, staging, nested):
|
|
483
|
+
images = tmp_path / "images"
|
|
484
|
+
images.mkdir()
|
|
485
|
+
np.save(images / "case_a.npy", np.ones((4, 5, 6), dtype=np.float32))
|
|
486
|
+
mountpoint = images if nested else tmp_path
|
|
487
|
+
mountinfo = tmp_path / "mountinfo"
|
|
488
|
+
mountinfo.write_text(f"42 31 0:55 / {mountpoint} rw - cifs //server/share rw\n")
|
|
489
|
+
monkeypatch.setenv("_ARRAYVIEW_TEST_MOUNTINFO", str(mountinfo))
|
|
490
|
+
if staging is None:
|
|
491
|
+
monkeypatch.delenv("ARRAYVIEW_SKIP_SOURCE_STAGING", raising=False)
|
|
492
|
+
else:
|
|
493
|
+
monkeypatch.setenv("ARRAYVIEW_SKIP_SOURCE_STAGING", staging)
|
|
494
|
+
pattern = str(tmp_path / "*" / "case_a.npy") if nested else str(images / "*.npy")
|
|
495
|
+
monkeypatch.setattr(sys, "argv", ["arrayview", "--match", pattern, "--dry-run"])
|
|
496
|
+
stdout = io.StringIO()
|
|
497
|
+
monkeypatch.setattr(sys, "stdout", stdout)
|
|
498
|
+
if staging == "0":
|
|
499
|
+
with pytest.raises(SystemExit) as exc:
|
|
500
|
+
appmod.arrayview()
|
|
501
|
+
assert exc.value.code == 1
|
|
502
|
+
assert "network directory collections" in stdout.getvalue()
|
|
503
|
+
else:
|
|
504
|
+
appmod.arrayview()
|
|
505
|
+
assert "cases: 1" in stdout.getvalue()
|
|
506
|
+
|
|
507
|
+
|
|
480
508
|
def test_cli_stack_can_continue_with_only_cases_that_have_overlays(
|
|
481
509
|
monkeypatch, tmp_path
|
|
482
510
|
):
|
|
@@ -51,3 +51,41 @@ def test_mat_loader_returns_key_list_for_multiple_numeric_arrays(tmp_path):
|
|
|
51
51
|
np.ones((4, 5), dtype=np.uint16),
|
|
52
52
|
)
|
|
53
53
|
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _write_gipl(path, xyz, image_type, opener=open):
|
|
57
|
+
header = bytearray(256)
|
|
58
|
+
dims = list(xyz.shape) + [1] * (4 - xyz.ndim)
|
|
59
|
+
header[0:8] = np.array(dims, ">u2").tobytes()
|
|
60
|
+
header[8:10] = np.array([image_type], ">u2").tobytes()
|
|
61
|
+
header[252:256] = np.array([4026526128], ">u4").tobytes()
|
|
62
|
+
with opener(path, "wb") as f:
|
|
63
|
+
f.write(bytes(header) + xyz.T.astype(xyz.dtype.newbyteorder(">")).tobytes())
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_gipl_loader_reads_x_y_z_order_plain_and_gzipped(tmp_path):
|
|
67
|
+
import gzip
|
|
68
|
+
|
|
69
|
+
from arrayview._io import _SUPPORTED_EXTS, _get_ext, load_data
|
|
70
|
+
|
|
71
|
+
expected = np.arange(4 * 5 * 3, dtype=np.int16).reshape(4, 5, 3) - 30
|
|
72
|
+
_write_gipl(tmp_path / "vol.gipl", expected, 15)
|
|
73
|
+
_write_gipl(tmp_path / "vol.gipl.gz", expected, 15, opener=gzip.open)
|
|
74
|
+
|
|
75
|
+
for name in ("vol.gipl", "vol.gipl.gz"):
|
|
76
|
+
path = str(tmp_path / name)
|
|
77
|
+
assert _get_ext(path) in _SUPPORTED_EXTS
|
|
78
|
+
loaded = load_data(path)
|
|
79
|
+
assert loaded.dtype == np.int16
|
|
80
|
+
np.testing.assert_array_equal(loaded, expected)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_gipl_loader_rejects_non_gipl_file(tmp_path):
|
|
84
|
+
import pytest
|
|
85
|
+
|
|
86
|
+
from arrayview._io import load_data
|
|
87
|
+
|
|
88
|
+
path = tmp_path / "bogus.gipl"
|
|
89
|
+
path.write_bytes(bytes(300))
|
|
90
|
+
with pytest.raises(ValueError, match="not a GIPL file"):
|
|
91
|
+
load_data(str(path))
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|