arrayview 0.50.0__tar.gz → 0.50.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 (90) hide show
  1. {arrayview-0.50.0 → arrayview-0.50.1}/PKG-INFO +1 -1
  2. {arrayview-0.50.0 → arrayview-0.50.1}/pyproject.toml +1 -1
  3. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_routes_loading.py +20 -0
  4. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_routes_websocket.py +8 -0
  5. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_viewer.html +21 -0
  6. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_vscode_extension.py +1 -1
  7. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_vscode_signal.py +6 -2
  8. arrayview-0.50.1/src/arrayview/arrayview-opener.vsix +0 -0
  9. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_api.py +24 -0
  10. arrayview-0.50.0/src/arrayview/arrayview-opener.vsix +0 -0
  11. {arrayview-0.50.0 → arrayview-0.50.1}/.gitignore +0 -0
  12. {arrayview-0.50.0 → arrayview-0.50.1}/LICENSE +0 -0
  13. {arrayview-0.50.0 → arrayview-0.50.1}/README.md +0 -0
  14. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/ARCHITECTURE.md +0 -0
  15. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/__init__.py +0 -0
  16. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/__main__.py +0 -0
  17. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_analysis.py +0 -0
  18. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_app.py +0 -0
  19. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_bootstrap_app.py +0 -0
  20. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_config.py +0 -0
  21. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_diagnostics.py +0 -0
  22. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_dicom.py +0 -0
  23. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_diff.py +0 -0
  24. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_extra_ports.py +0 -0
  25. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_icon.png +0 -0
  26. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_imaging.py +0 -0
  27. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_instance_registry.py +0 -0
  28. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_io.py +0 -0
  29. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_launch_plan.py +0 -0
  30. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_launch_trace.py +0 -0
  31. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_launcher.py +0 -0
  32. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_lifecycle.py +0 -0
  33. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_overlays.py +0 -0
  34. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_platform.py +0 -0
  35. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_render.py +0 -0
  36. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_routes_analysis.py +0 -0
  37. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_routes_drop.py +0 -0
  38. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_routes_export.py +0 -0
  39. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_routes_persistence.py +0 -0
  40. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_routes_preferences.py +0 -0
  41. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_routes_preload.py +0 -0
  42. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_routes_query.py +0 -0
  43. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_routes_rendering.py +0 -0
  44. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_routes_segmentation.py +0 -0
  45. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_routes_state.py +0 -0
  46. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_routes_vectorfield.py +0 -0
  47. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_segmentation.py +0 -0
  48. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_server.py +0 -0
  49. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_session.py +0 -0
  50. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_session_spec.py +0 -0
  51. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_shell.html +0 -0
  52. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_source_safety.py +0 -0
  53. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_synthetic_mri.py +0 -0
  54. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_torch.py +0 -0
  55. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_tutorial.py +0 -0
  56. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_vectorfield.py +0 -0
  57. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_view_memory.py +0 -0
  58. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_vscode.py +0 -0
  59. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_vscode_browser.py +0 -0
  60. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/_watch.py +0 -0
  61. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/gsap.min.js +0 -0
  62. {arrayview-0.50.0 → arrayview-0.50.1}/src/arrayview/todo.md +0 -0
  63. {arrayview-0.50.0 → arrayview-0.50.1}/tests/capture_patient_loading.py +0 -0
  64. {arrayview-0.50.0 → arrayview-0.50.1}/tests/capture_v_animation.py +0 -0
  65. {arrayview-0.50.0 → arrayview-0.50.1}/tests/conftest.py +0 -0
  66. {arrayview-0.50.0 → arrayview-0.50.1}/tests/make_vectorfield_test_arrays.py +0 -0
  67. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_1d_arrays.py +0 -0
  68. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_backend_shared.py +0 -0
  69. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_browser.py +0 -0
  70. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_cli.py +0 -0
  71. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_config.py +0 -0
  72. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_dicom.py +0 -0
  73. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_drop_import.py +0 -0
  74. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_instance_registry.py +0 -0
  75. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_io.py +0 -0
  76. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_large_arrays.py +0 -0
  77. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_launch_blackbox.py +0 -0
  78. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_mode_roundtrip.py +0 -0
  79. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_nifti_meta.py +0 -0
  80. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_nifti_series.py +0 -0
  81. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_rgb_pixel_art.py +0 -0
  82. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_safe_server_stop.py +0 -0
  83. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_source_safety.py +0 -0
  84. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_torch.py +0 -0
  85. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_tutorial.py +0 -0
  86. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_view_memory.py +0 -0
  87. {arrayview-0.50.0 → arrayview-0.50.1}/tests/test_vsix_bundle.py +0 -0
  88. {arrayview-0.50.0 → arrayview-0.50.1}/tests/ui_audit.py +0 -0
  89. {arrayview-0.50.0 → arrayview-0.50.1}/tests/v_anim_frames/.gitkeep +0 -0
  90. {arrayview-0.50.0 → arrayview-0.50.1}/tests/visual_smoke.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: arrayview
3
- Version: 0.50.0
3
+ Version: 0.50.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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "arrayview"
7
- version = "0.50.0"
7
+ version = "0.50.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"
@@ -630,6 +630,26 @@ def register_loading_routes(app, *, notify_shells, setup_rgb) -> None:
630
630
  revealed = await asyncio.to_thread(_request_vscode_tab_reveal, *target)
631
631
  return {"sid": sid, "revealed": revealed}
632
632
 
633
+ @app.post("/popout/{sid}")
634
+ async def toggle_session_tab_window(sid: str, request: Request):
635
+ """Move this VS Code browser tab into its own window, or back."""
636
+ from arrayview._routes_websocket import viewer_tab_window_id
637
+ from arrayview._vscode_signal import _request_vscode_tab_reveal
638
+
639
+ try:
640
+ body = await request.json()
641
+ except Exception:
642
+ body = {}
643
+ path = body.get("path") if isinstance(body, dict) else None
644
+ window_id = viewer_tab_window_id(sid, path) if isinstance(path, str) else None
645
+ # A bare "/" matches every tab; only the query singles this one out.
646
+ if window_id is None or not urllib.parse.urlsplit(path).query:
647
+ return {"sid": sid, "moved": False}
648
+ moved = await asyncio.to_thread(
649
+ _request_vscode_tab_reveal, window_id, path, 5.0, "toggle-window"
650
+ )
651
+ return {"sid": sid, "moved": moved}
652
+
633
653
  @app.post("/release/{sid}")
634
654
  def release_viewer_session(sid: str, request: Request):
635
655
  """Release a session when its owning viewer tab/window closes."""
@@ -326,6 +326,14 @@ def viewer_tab_reveal_target(sid: str) -> tuple[str, str] | None:
326
326
  return None
327
327
 
328
328
 
329
+ def viewer_tab_window_id(sid: str, path: str) -> str | None:
330
+ """VS Code window that owns the live tab at *path* showing *sid*."""
331
+ for tab_sid, tab_path, window_id in _VIEWER_TAB_ADDRESSES.values():
332
+ if tab_sid == sid and tab_path == path:
333
+ return window_id
334
+ return None
335
+
336
+
329
337
  def _handle_link_message(ws, msg: dict) -> None:
330
338
  action = msg.get("action")
331
339
  if action == "join":
@@ -28621,6 +28621,25 @@
28621
28621
  }
28622
28622
  },
28623
28623
  },
28624
+ 'window.togglePopout': {
28625
+ title: 'Pop this tab out into its own window / put it back',
28626
+ when: [],
28627
+ run: async () => {
28628
+ if (!_vscodeTabWindowId) {
28629
+ showStatus('pop out: only in a VS Code tab');
28630
+ return;
28631
+ }
28632
+ try {
28633
+ const r = await proxyFetch(`/popout/${sid}`, {
28634
+ method: 'POST',
28635
+ headers: { 'Content-Type': 'application/json' },
28636
+ body: JSON.stringify({ path: location.pathname + location.search }),
28637
+ });
28638
+ if (r.ok && (await r.json()).moved) return;
28639
+ } catch (_) {}
28640
+ showStatus('pop out: VS Code did not respond');
28641
+ },
28642
+ },
28624
28643
  'immersive.toggle': {
28625
28644
  title: 'Toggle immersive mode',
28626
28645
  when: [],
@@ -30481,6 +30500,7 @@
30481
30500
  { key: '-', command: 'zoom.out' },
30482
30501
  { key: '0', command: 'zoom.reset' },
30483
30502
  { key: 'F', command: 'immersive.toggle' },
30503
+ { key: 'W', command: 'window.togglePopout' },
30484
30504
  { key: 'n', command: 'compare.cycleTarget' },
30485
30505
  { key: '<', command: 'playback.adjustFps' },
30486
30506
  { key: '>', command: 'playback.adjustFps' },
@@ -30654,6 +30674,7 @@
30654
30674
  { key: 'Drag', action: 'Pan the image (when zoomed in)' },
30655
30675
  { key: 'Shift+F', action: 'Toggle immersive mode', hint: 'hides chrome and fills the viewport with the array' },
30656
30676
  { key: 'Z', action: 'Zen mode — hide UI chrome', hint: 'in compare + center active, hides the two source panes instead' },
30677
+ { key: 'Shift+W', action: 'Pop tab out into its own window / put it back', hint: 'VS Code tabs only' },
30657
30678
  ]
30658
30679
  },
30659
30680
  {
@@ -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.71" # current bundled extension version
61
+ _VSCODE_EXT_VERSION = "0.15.74" # 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.
@@ -694,10 +694,13 @@ _VSCODE_REVEAL_ACK_PREFIX = "reveal-tab-ack-"
694
694
 
695
695
 
696
696
  def _request_vscode_tab_reveal(
697
- window_id: str, url_filter: str, timeout: float = 2.5
697
+ window_id: str, url_filter: str, timeout: float = 2.5, action: str = "reveal"
698
698
  ) -> bool:
699
699
  """Ask the opener in *window_id* to bring the browser tab matching
700
- *url_filter* forward. True only when that window confirms it did."""
700
+ *url_filter* forward. True only when that window confirms it did.
701
+
702
+ ``action="toggle-window"`` also moves that tab into its own window, or
703
+ back into the main window when it was popped out before."""
701
704
  signal_dir = Path(os.path.expanduser("~/.arrayview"))
702
705
  request_id = uuid.uuid4().hex
703
706
  request_path = signal_dir / f"{_VSCODE_REVEAL_REQUEST_PREFIX}{request_id}.json"
@@ -706,6 +709,7 @@ def _request_vscode_tab_reveal(
706
709
  "requestId": request_id,
707
710
  "windowId": window_id,
708
711
  "urlFilter": url_filter,
712
+ "action": action,
709
713
  "sentAtMs": int(time.time() * 1000),
710
714
  "maxAgeMs": int(timeout * 1000),
711
715
  }
@@ -4536,3 +4536,27 @@ class TestRevealTab:
4536
4536
  assert c.post(f"/reveal/{sid}").json()["revealed"] is True
4537
4537
  assert asked == [("w1", "/?t=ab12c")]
4538
4538
  assert c.post(f"/reveal/{sid}").json()["revealed"] is False
4539
+
4540
+ def test_popout_asks_the_window_owning_this_tab(self, tmp_path, monkeypatch):
4541
+ """Shift+W asks the VS Code window holding this exact tab to move it."""
4542
+ from arrayview._app import app
4543
+ import arrayview._vscode_signal as signal_mod
4544
+
4545
+ asked = []
4546
+ monkeypatch.setattr(
4547
+ signal_mod, "_request_vscode_tab_reveal",
4548
+ lambda *args: asked.append(args) or True,
4549
+ )
4550
+ np.save(tmp_path / "pop.npy", np.zeros((4, 4), dtype=np.float32))
4551
+ with TestClient(app) as c:
4552
+ sid = c.post("/load", json={"filepath": str(tmp_path / "pop.npy")}).json()["sid"]
4553
+ pop = lambda path: c.post(f"/popout/{sid}", json={"path": path}).json()["moved"]
4554
+ assert pop("/?t=ab12c") is False
4555
+ with c.websocket_connect(f"/ws/{sid}") as ws:
4556
+ ws.receive_json()
4557
+ ws.send_json({"type": "tab-address", "path": "/?t=ab12c", "window_id": "w1"})
4558
+ ws.send_json({"type": "link", "action": "leave"}) # round-trip barrier
4559
+ import time; time.sleep(0.2)
4560
+ assert pop("/?t=other") is False
4561
+ assert pop("/?t=ab12c") is True
4562
+ assert asked == [("w1", "/?t=ab12c", 5.0, "toggle-window")]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes