arrayview 0.47.0__py3-none-any.whl → 0.47.3__py3-none-any.whl

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/_io.py CHANGED
@@ -1863,3 +1863,44 @@ def _peek_file_shape(fpath: str, ext: str):
1863
1863
  except Exception:
1864
1864
  pass
1865
1865
  return None
1866
+
1867
+
1868
+ def nifti_grid_meta(filepath):
1869
+ """Return a NIfTI file's native-order spatial metadata (header only), or None."""
1870
+ if not (filepath.endswith(".nii") or filepath.endswith(".nii.gz")):
1871
+ return None
1872
+ return _nifti_header_with_meta(filepath, native=True)[1]
1873
+
1874
+
1875
+ def align_overlay_to_base(ov_path, ov_data, base_meta):
1876
+ """Resample a NIfTI overlay onto the base volume's voxel grid.
1877
+
1878
+ Only used when the overlay's spatial shape differs from the base, which
1879
+ otherwise leaves the overlay undisplayable. Both files' affines place the
1880
+ voxels in world space; label data uses nearest-neighbour so mask values
1881
+ stay exact. Returns ``ov_data`` unchanged when alignment does not apply.
1882
+ """
1883
+ if not base_meta or not (ov_path.endswith(".nii") or ov_path.endswith(".nii.gz")):
1884
+ return ov_data
1885
+ base_shape = base_meta.get("spatial_shape")
1886
+ base_affine = base_meta.get("data_affine")
1887
+ if base_shape is None or base_affine is None:
1888
+ return ov_data
1889
+ ov_shape = tuple(int(v) for v in ov_data.shape)
1890
+ if len(ov_shape) != 3 or ov_shape == tuple(base_shape):
1891
+ return ov_data
1892
+ from scipy.ndimage import affine_transform
1893
+
1894
+ ov_affine = np.asarray(_nib().load(ov_path).affine, dtype=np.float64)
1895
+ base_to_ov = np.linalg.inv(ov_affine) @ np.asarray(base_affine, dtype=np.float64)
1896
+ src = np.asarray(ov_data)
1897
+ is_label = np.issubdtype(src.dtype, np.integer) or src.dtype == bool
1898
+ return affine_transform(
1899
+ src.astype(np.float32) if not is_label else src,
1900
+ base_to_ov[:3, :3],
1901
+ offset=base_to_ov[:3, 3],
1902
+ output_shape=tuple(base_shape),
1903
+ order=0 if is_label else 1,
1904
+ mode="constant",
1905
+ cval=0,
1906
+ )
arrayview/_launcher.py CHANGED
@@ -1314,6 +1314,7 @@ def _load_session_from_filepath(
1314
1314
  watch: bool = False,
1315
1315
  initial_view: dict | None = None,
1316
1316
  code_source: dict | None = None,
1317
+ align_to: str | None = None,
1317
1318
  ) -> dict:
1318
1319
  requested_sid = uuid.uuid4().hex
1319
1320
  payload = {
@@ -1329,6 +1330,8 @@ def _load_session_from_filepath(
1329
1330
  }
1330
1331
  if source_staging_dir:
1331
1332
  payload["source_staging_dir"] = source_staging_dir
1333
+ if align_to:
1334
+ payload["align_to"] = align_to
1332
1335
  if watch:
1333
1336
  payload["watch"] = True
1334
1337
  if initial_view:
@@ -1686,6 +1689,7 @@ def _register_cli_session_with_existing_server_impl(
1686
1689
  ov_name,
1687
1690
  expected_server_id=expected_server_id,
1688
1691
  source_staging_dir=(source_staging_dirs or {}).get(ov_path),
1692
+ align_to=os.path.abspath(base_file),
1689
1693
  )
1690
1694
  if "error" in ov_result:
1691
1695
  raise RuntimeError(
@@ -4803,6 +4807,7 @@ def _serve_daemon(
4803
4807
  array_choice_placeholder,
4804
4808
  load_data,
4805
4809
  load_data_with_meta,
4810
+ align_overlay_to_base,
4806
4811
  load_dir_collection,
4807
4812
  list_array_keys,
4808
4813
  )
@@ -4928,6 +4933,7 @@ def _serve_daemon(
4928
4933
  ):
4929
4934
  try:
4930
4935
  ov_data = load_data(ov_path)
4936
+ ov_data = align_overlay_to_base(ov_path, ov_data, spatial_meta)
4931
4937
  ov_session = _session_mod.Session(
4932
4938
  ov_data, filepath=ov_path, name=ov_name
4933
4939
  )
@@ -277,6 +277,7 @@ def register_loading_routes(app, *, notify_shells, setup_rgb) -> None:
277
277
  and os.path.abspath(existing.filepath) == abs_path
278
278
  and current_signature is not None
279
279
  and getattr(existing, "file_signature", None) == current_signature
280
+ and getattr(existing, "aligned_to", None) == body.get("align_to")
280
281
  ):
281
282
  if acquire_session_leases([existing.sid]):
282
283
  return {"sid": existing.sid, "name": existing.name, "notified": False}
@@ -458,6 +459,17 @@ def register_loading_routes(app, *, notify_shells, setup_rgb) -> None:
458
459
  key=_key,
459
460
  select=body.get("select"),
460
461
  )
462
+ if body.get("align_to"):
463
+ from ._io import align_overlay_to_base, nifti_grid_meta
464
+
465
+ base_meta = await asyncio.to_thread(
466
+ nifti_grid_meta, str(body["align_to"])
467
+ )
468
+ aligned = await asyncio.to_thread(
469
+ align_overlay_to_base, filepath, data, base_meta
470
+ )
471
+ if aligned is not data:
472
+ data, spatial_meta = aligned, base_meta
461
473
  except Exception as e:
462
474
  if staging_dir:
463
475
  cleanup_staging_directory(staging_dir)
@@ -479,6 +491,7 @@ def register_loading_routes(app, *, notify_shells, setup_rgb) -> None:
479
491
  str(value) for value in body.get("related_sids", [])
480
492
  ]
481
493
  apply_launch_view(session, body)
494
+ session.aligned_to = body.get("align_to")
482
495
  if staging_dir:
483
496
  session._source_staging_dirs = [str(staging_dir)]
484
497
  if not dir_patterns:
@@ -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
  ]
arrayview/_session.py CHANGED
@@ -765,6 +765,12 @@ def upgrade_memmap_in_background(session) -> bool:
765
765
  from ._io import _nifti_display_array
766
766
 
767
767
  loaded = _nifti_display_array(data)
768
+ # An uncompressed proxy whose dtype needs no conversion comes
769
+ # back as a view of the file map, so nothing was read yet and
770
+ # every new orientation would still fault from disk. Copy in
771
+ # file order to make it one sequential read.
772
+ if not loaded.flags.owndata:
773
+ loaded = np.array(loaded, copy=True, order="K")
768
774
  except Exception:
769
775
  return # keep serving from the lazy source
770
776
  # A /reload or a released session must not be clobbered by a read that
arrayview/_viewer.html CHANGED
@@ -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.63" # 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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: arrayview
3
- Version: 0.47.0
3
+ Version: 0.47.3
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
@@ -12,10 +12,10 @@ arrayview/_extra_ports.py,sha256=MZcx04NI9Eg4coydb-yPIHp7uJbE6UscyYGnw8fxid0,111
12
12
  arrayview/_icon.png,sha256=a8RTAbvweLhh3hzD5eGg76Peq-KSDcS6YrCphRb_HEs,2748
13
13
  arrayview/_imaging.py,sha256=MQX1uy4m1lofOeoLDhCp6cHO5p_V5fXm4qlSu9Ykn-c,534
14
14
  arrayview/_instance_registry.py,sha256=h7vyvCl0LTt9R8sDB1Vxe4a-KzfkLVuwrSw_ANpPTD8,8833
15
- arrayview/_io.py,sha256=Baw0H8Z0RJ-Y2OB4If9FzY0mY9GB6oTqXkDJLwIq468,73530
15
+ arrayview/_io.py,sha256=0QAKjU9WkBdXAfIAGv7HCeWpAwbn9TMCYlip36I2U4I,75238
16
16
  arrayview/_launch_plan.py,sha256=KGgIPGsflXArECB2txnIAewjuPvF4fZTbO63alC8x28,23570
17
17
  arrayview/_launch_trace.py,sha256=4yjJdkFyEvSrPN14QVFJ3u6on2q0TH2ySSdgUp9SjKI,5668
18
- arrayview/_launcher.py,sha256=wRF6dHnsJ-jZ-QPxgkesHdTXbMTWDYUM5NEkqxmFoy4,242851
18
+ arrayview/_launcher.py,sha256=W_CjLFa3_D7IwTYmBX0-7h6XrX4EKvwsJqgbgIu8y7g,243108
19
19
  arrayview/_lifecycle.py,sha256=bqG0oKr-e-5S1ssCTlGmF-yBE47ilrpO1S_Owfjs9Z8,5365
20
20
  arrayview/_overlays.py,sha256=CksypG9rWfCz_3RRGIeyaKeXse17AFxvkEjmYyXnIIE,5399
21
21
  arrayview/_platform.py,sha256=qSWjRcx9rLIdLvKrRArSueFnbyn5GPKfTEMOc5mCIUs,29970
@@ -23,9 +23,9 @@ arrayview/_render.py,sha256=sSNPWreJumIyFKvZtAS5v2gxbNHRVyAdc7JGj34caPw,34121
23
23
  arrayview/_routes_analysis.py,sha256=Xz9Ysr04u07pzSRB4Fzmcl6XwVxyaG6xCWxGwYZi_80,33085
24
24
  arrayview/_routes_drop.py,sha256=FC4e7STdCo3hYIA0C4Yy9Ka2UKFx7nLDU5dkujmc1Vo,12477
25
25
  arrayview/_routes_export.py,sha256=Ha8mMO2E9TlRL5x25-ADV8_8xUiC-jRz9N1oWwgKOsg,3898
26
- arrayview/_routes_loading.py,sha256=ZOHOFdqMDwmdjtzhq07LQUGG7BHmP1od4IiZDWWRZtQ,41662
26
+ arrayview/_routes_loading.py,sha256=rC0cw4QRaTycvzvWemencUQCkCGyaT7GzUd9gH9Rc5Y,42360
27
27
  arrayview/_routes_persistence.py,sha256=T03csHb7ppGT-8yMDf227XkqhnhjgSLYj0RW2wmg59g,17176
28
- arrayview/_routes_preferences.py,sha256=SKnhTf2X2vRyYi06Wb2si4dXMB1y0XiSBr28RZ5oKUw,4691
28
+ arrayview/_routes_preferences.py,sha256=xoMHbPKliYspgjaclhx2r8HbP9mFo9Td-nTF76eWVMg,4680
29
29
  arrayview/_routes_preload.py,sha256=KOsankNguxdIWmFW9iZcY9p2SvUN1mOKY7KkW1qMGcc,1729
30
30
  arrayview/_routes_query.py,sha256=QkIfx_H2h8OE57hWI-A7auneLdaNKKdsLMjuUrkLRzU,24955
31
31
  arrayview/_routes_rendering.py,sha256=EQq2QzdtUS_pv35k7prLdlRdO5nByIlQ_Eu0nhVsn1Y,22970
@@ -35,7 +35,7 @@ arrayview/_routes_vectorfield.py,sha256=OWZtAONwRKQHdYnbDAMbOVf0q6pH7kqV8fa1icDh
35
35
  arrayview/_routes_websocket.py,sha256=1EWgjCNZ1hbDCGY13c3TyBqL1_u4uCWztGzk00lWfBs,41272
36
36
  arrayview/_segmentation.py,sha256=aENWHmefrKDScXhcZL1X3KgYXE784dpMkmFFo83qnrw,7377
37
37
  arrayview/_server.py,sha256=A8O8J6N4WTDcujFHjTWjlJYWIzkJe8y9XmKpj9XozE0,22918
38
- arrayview/_session.py,sha256=7obqUU3kPf_1-VA7wgJMKYkr_r_r2vXT-R6CDtbyhFk,35209
38
+ arrayview/_session.py,sha256=-Jems6lGOVU3uLs4Uq1ggIfY-qTrAXNzpUk6nMbuTBA,35616
39
39
  arrayview/_session_spec.py,sha256=mKDfMRsPYQdEmN9hLXmDU_9N7etiDyBXr8mz-fB8QBo,6825
40
40
  arrayview/_shell.html,sha256=vNa2Kiwx_eoB5H329jolYafLhwJDCH9DsJafBqvVMWU,13194
41
41
  arrayview/_source_safety.py,sha256=EetyfZFZrVYWV3bdFe4sZovrcdc8Fkr9vqLxc6oUBxI,27635
@@ -43,17 +43,17 @@ arrayview/_synthetic_mri.py,sha256=CNL-2WC835i6WCqkvmWBCO6WQ5w7HE5trNb8OyEfe64,7
43
43
  arrayview/_torch.py,sha256=13sqIMCUu_-b9SQ9KfDCExDz2UMStL87EDVjnbJxAEo,7536
44
44
  arrayview/_tutorial.py,sha256=2zZHJGJDa4n30GE1-9ADjKFAifGBH9vvHXP4uDt7BaA,19547
45
45
  arrayview/_vectorfield.py,sha256=eDqMCMEGjO8Jdt-6Auh01cz3WD9dbF7xYUWD9xHAf1E,8115
46
- arrayview/_viewer.html,sha256=pUCkoJRPvZMCvNEh-zoGDOTtjX0A1D7vdLqHI9Tzr3A,2371938
46
+ arrayview/_viewer.html,sha256=aiHPSbCCtj90EnHV0nJ7kh8YkxAaUmzdDYJGW5KC0Zg,2372368
47
47
  arrayview/_vscode.py,sha256=EKnGN2zrjlybc4FQOmYA8BvYdtkcJbLHDEdgtvO7ul4,837
48
48
  arrayview/_vscode_browser.py,sha256=VCH9JpAAjFuhO0wQe8tIGjAL5837AXg_bpmBFtWOQqc,20573
49
- arrayview/_vscode_extension.py,sha256=AUfGJpPBPzKIRyVF0wvrDqr5F0eVrZpFEHoxuNjO15M,53156
49
+ arrayview/_vscode_extension.py,sha256=aXkuw5lyqahOjK5OJfKYySFPX5yt1jSEUgqvoHiF3ZE,53156
50
50
  arrayview/_vscode_signal.py,sha256=N3PCHG6-3tCEc_tHLjg2dhSEQ2EnF_psE2IJRBpAuac,54938
51
51
  arrayview/_watch.py,sha256=PxxS_5hcj_S6xQf9Ep4p1HLRkrI5iDMh1aRa4olV7ew,1873
52
- arrayview/arrayview-opener.vsix,sha256=1kbHgrG_kyxbDFa6WQ_KojmMPiogHlcu62L86HraVbk,61155
52
+ arrayview/arrayview-opener.vsix,sha256=dga4sUvFEK8N-UjKcmhYW0SDedci83Q2gx4-XB8BoTY,61308
53
53
  arrayview/gsap.min.js,sha256=VGihm4idI0E1uqrWezWsiScB9F_jMosn7qs6pPSkeac,72304
54
54
  arrayview/todo.md,sha256=KhnRzpn6_jezR0nDDB61-zhox-rbOmEGW-A7Z0_7FnU,622
55
- arrayview-0.47.0.dist-info/METADATA,sha256=x5u-7ZK2lktztD4BFipekM4rP8bh1YL5KwI9Jd1F5Ss,2288
56
- arrayview-0.47.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
57
- arrayview-0.47.0.dist-info/entry_points.txt,sha256=-0s08axutXkTbINiKs0Q7iv-33aWuTaamUg2q2-NyQ4,60
58
- arrayview-0.47.0.dist-info/licenses/LICENSE,sha256=34OivjM9faB2QaPjZpMRTIiSx31V3U9cEyQ6F2k-bH0,1062
59
- arrayview-0.47.0.dist-info/RECORD,,
55
+ arrayview-0.47.3.dist-info/METADATA,sha256=lxECYFGKs3cRc80zXrP19y9TYQPbBHW-bi4qFf5hWas,2288
56
+ arrayview-0.47.3.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
57
+ arrayview-0.47.3.dist-info/entry_points.txt,sha256=-0s08axutXkTbINiKs0Q7iv-33aWuTaamUg2q2-NyQ4,60
58
+ arrayview-0.47.3.dist-info/licenses/LICENSE,sha256=34OivjM9faB2QaPjZpMRTIiSx31V3U9cEyQ6F2k-bH0,1062
59
+ arrayview-0.47.3.dist-info/RECORD,,