arrayview 0.26.2__py3-none-any.whl → 0.26.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/_launch_plan.py +285 -0
- arrayview/_launcher.py +131 -54
- arrayview/_platform.py +14 -9
- arrayview/_server.py +1 -0
- arrayview/_viewer.html +49 -9
- arrayview/_vscode_signal.py +31 -57
- {arrayview-0.26.2.dist-info → arrayview-0.26.3.dist-info}/METADATA +1 -1
- {arrayview-0.26.2.dist-info → arrayview-0.26.3.dist-info}/RECORD +11 -10
- {arrayview-0.26.2.dist-info → arrayview-0.26.3.dist-info}/WHEEL +0 -0
- {arrayview-0.26.2.dist-info → arrayview-0.26.3.dist-info}/entry_points.txt +0 -0
- {arrayview-0.26.2.dist-info → arrayview-0.26.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""Data-only launch routing primitives and environment snapshots."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import asdict, dataclass
|
|
6
|
+
from enum import Enum
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import socket
|
|
10
|
+
import sys
|
|
11
|
+
import urllib.request
|
|
12
|
+
|
|
13
|
+
_LOOPBACK_HOST = "localhost"
|
|
14
|
+
_PING_TIMEOUT_SECONDS = 0.2
|
|
15
|
+
|
|
16
|
+
_SNAPSHOT_ENV_KEYS = (
|
|
17
|
+
"ARRAYVIEW_WINDOW",
|
|
18
|
+
"ARRAYVIEW_WINDOW_ID",
|
|
19
|
+
"DISPLAY",
|
|
20
|
+
"TERM_PROGRAM",
|
|
21
|
+
"VSCODE_AGENT_FOLDER",
|
|
22
|
+
"VSCODE_INJECTION",
|
|
23
|
+
"VSCODE_IPC_HOOK_CLI",
|
|
24
|
+
"WAYLAND_DISPLAY",
|
|
25
|
+
"SSH_CLIENT",
|
|
26
|
+
"SSH_CONNECTION",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class _StrEnum(str, Enum):
|
|
31
|
+
def __str__(self) -> str:
|
|
32
|
+
return self.value
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Invocation(_StrEnum):
|
|
36
|
+
CLI = "cli"
|
|
37
|
+
PYTHON = "python"
|
|
38
|
+
JUPYTER = "jupyter"
|
|
39
|
+
JULIA = "julia"
|
|
40
|
+
STDIO = "stdio"
|
|
41
|
+
CODEX = "codex"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Environment(_StrEnum):
|
|
45
|
+
TERMINAL = "terminal"
|
|
46
|
+
VSCODE_LOCAL = "vscode_local"
|
|
47
|
+
VSCODE_REMOTE = "vscode_remote"
|
|
48
|
+
SSH = "ssh"
|
|
49
|
+
JUPYTER = "jupyter"
|
|
50
|
+
JULIA = "julia"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Transport(_StrEnum):
|
|
54
|
+
HTTP = "http"
|
|
55
|
+
STDIO_FILE = "stdio_file"
|
|
56
|
+
STDIO_SHM = "stdio_shm"
|
|
57
|
+
NONE = "none"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ServerOwner(_StrEnum):
|
|
61
|
+
EXISTING = "existing"
|
|
62
|
+
SPAWNED_DAEMON = "spawned_daemon"
|
|
63
|
+
IN_PROCESS = "in_process"
|
|
64
|
+
PERSISTENT = "persistent"
|
|
65
|
+
EXTERNAL = "external"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class Display(_StrEnum):
|
|
69
|
+
NATIVE = "native"
|
|
70
|
+
BROWSER = "browser"
|
|
71
|
+
VSCODE = "vscode"
|
|
72
|
+
INLINE = "inline"
|
|
73
|
+
NONE = "none"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class Registration(_StrEnum):
|
|
77
|
+
HTTP_LOAD = "http_load"
|
|
78
|
+
DAEMON_STARTUP = "daemon_startup"
|
|
79
|
+
IN_PROCESS_SESSION = "in_process_session"
|
|
80
|
+
STDIO_REGISTER = "stdio_register"
|
|
81
|
+
RELAY = "relay"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass(frozen=True)
|
|
85
|
+
class ServerSnapshot:
|
|
86
|
+
port: int
|
|
87
|
+
port_busy: bool
|
|
88
|
+
arrayview_server_alive: bool
|
|
89
|
+
server_pid: int | None = None
|
|
90
|
+
server_hostname: str | None = None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass(frozen=True)
|
|
94
|
+
class LaunchEnvironmentSnapshot:
|
|
95
|
+
invocation: Invocation
|
|
96
|
+
requested_window: str | None
|
|
97
|
+
environment: Environment
|
|
98
|
+
platform: str
|
|
99
|
+
env_vars: dict[str, str]
|
|
100
|
+
config_default: str | None
|
|
101
|
+
native_backend: str | None
|
|
102
|
+
server: ServerSnapshot
|
|
103
|
+
in_jupyter: bool
|
|
104
|
+
in_julia: bool
|
|
105
|
+
in_vscode_terminal: bool
|
|
106
|
+
is_vscode_remote: bool
|
|
107
|
+
in_vscode_tunnel: bool
|
|
108
|
+
ssh_connection: bool
|
|
109
|
+
ssh_client: bool
|
|
110
|
+
hostname: str
|
|
111
|
+
|
|
112
|
+
def to_dict(self) -> dict:
|
|
113
|
+
return asdict(self)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass(frozen=True)
|
|
117
|
+
class LaunchRequest:
|
|
118
|
+
port: int
|
|
119
|
+
requested_window: str | None = None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclass(frozen=True)
|
|
123
|
+
class LaunchPlan:
|
|
124
|
+
invocation: Invocation
|
|
125
|
+
environment: Environment
|
|
126
|
+
transport: Transport
|
|
127
|
+
server_owner: ServerOwner
|
|
128
|
+
display: Display
|
|
129
|
+
registration: Registration
|
|
130
|
+
fallback_display: Display | None = None
|
|
131
|
+
fallback_allowed: bool = False
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def snapshot_launch_environment(
|
|
135
|
+
port: int,
|
|
136
|
+
invocation: Invocation | str,
|
|
137
|
+
requested_window: str | None = None,
|
|
138
|
+
) -> LaunchEnvironmentSnapshot:
|
|
139
|
+
"""Capture launch-relevant facts without importing the server stack."""
|
|
140
|
+
inv = _coerce_invocation(invocation)
|
|
141
|
+
env_vars = _snapshot_env_vars()
|
|
142
|
+
in_jupyter = _platform_bool("_in_jupyter")
|
|
143
|
+
in_julia = _platform_bool("_is_julia_env")
|
|
144
|
+
in_vscode_terminal = _platform_bool("_in_vscode_terminal")
|
|
145
|
+
is_vscode_remote = _platform_bool("_is_vscode_remote")
|
|
146
|
+
in_vscode_tunnel = _platform_bool("_in_vscode_tunnel")
|
|
147
|
+
ssh_connection = bool(os.environ.get("SSH_CONNECTION"))
|
|
148
|
+
ssh_client = bool(os.environ.get("SSH_CLIENT"))
|
|
149
|
+
environment = _classify_environment(
|
|
150
|
+
in_jupyter=in_jupyter,
|
|
151
|
+
in_julia=in_julia,
|
|
152
|
+
in_vscode_terminal=in_vscode_terminal,
|
|
153
|
+
is_vscode_remote=is_vscode_remote,
|
|
154
|
+
ssh_connection=ssh_connection,
|
|
155
|
+
ssh_client=ssh_client,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
return LaunchEnvironmentSnapshot(
|
|
159
|
+
invocation=inv,
|
|
160
|
+
requested_window=requested_window,
|
|
161
|
+
environment=environment,
|
|
162
|
+
platform=sys.platform,
|
|
163
|
+
env_vars=env_vars,
|
|
164
|
+
config_default=_config_window_default(environment.value),
|
|
165
|
+
native_backend=_native_window_gui(),
|
|
166
|
+
server=_server_snapshot(port),
|
|
167
|
+
in_jupyter=in_jupyter,
|
|
168
|
+
in_julia=in_julia,
|
|
169
|
+
in_vscode_terminal=in_vscode_terminal,
|
|
170
|
+
is_vscode_remote=is_vscode_remote,
|
|
171
|
+
in_vscode_tunnel=in_vscode_tunnel,
|
|
172
|
+
ssh_connection=ssh_connection,
|
|
173
|
+
ssh_client=ssh_client,
|
|
174
|
+
hostname=socket.gethostname(),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _coerce_invocation(value: Invocation | str) -> Invocation:
|
|
179
|
+
if isinstance(value, Invocation):
|
|
180
|
+
return value
|
|
181
|
+
return Invocation(value)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _snapshot_env_vars() -> dict[str, str]:
|
|
185
|
+
return {key: os.environ[key] for key in _SNAPSHOT_ENV_KEYS if key in os.environ}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _platform_bool(name: str) -> bool:
|
|
189
|
+
try:
|
|
190
|
+
from arrayview import _platform
|
|
191
|
+
|
|
192
|
+
return bool(getattr(_platform, name)())
|
|
193
|
+
except Exception:
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _native_window_gui() -> str | None:
|
|
198
|
+
try:
|
|
199
|
+
from arrayview._platform import _native_window_gui as native_window_gui
|
|
200
|
+
|
|
201
|
+
return native_window_gui()
|
|
202
|
+
except Exception:
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _config_window_default(environment: str) -> str | None:
|
|
207
|
+
try:
|
|
208
|
+
from arrayview._config import load_config
|
|
209
|
+
|
|
210
|
+
cfg = load_config()
|
|
211
|
+
except Exception:
|
|
212
|
+
return None
|
|
213
|
+
window_cfg = cfg.get("window", {})
|
|
214
|
+
if not isinstance(window_cfg, dict):
|
|
215
|
+
return None
|
|
216
|
+
env_key = "vscode" if environment in {"vscode_local", "vscode_remote"} else environment
|
|
217
|
+
value = window_cfg.get(env_key) or window_cfg.get("default")
|
|
218
|
+
if isinstance(value, str):
|
|
219
|
+
return value.strip().lower() or None
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _classify_environment(
|
|
224
|
+
*,
|
|
225
|
+
in_jupyter: bool,
|
|
226
|
+
in_julia: bool,
|
|
227
|
+
in_vscode_terminal: bool,
|
|
228
|
+
is_vscode_remote: bool,
|
|
229
|
+
ssh_connection: bool,
|
|
230
|
+
ssh_client: bool,
|
|
231
|
+
) -> Environment:
|
|
232
|
+
if in_jupyter:
|
|
233
|
+
return Environment.JUPYTER
|
|
234
|
+
if in_julia:
|
|
235
|
+
return Environment.JULIA
|
|
236
|
+
if is_vscode_remote:
|
|
237
|
+
return Environment.VSCODE_REMOTE
|
|
238
|
+
if in_vscode_terminal:
|
|
239
|
+
return Environment.VSCODE_LOCAL
|
|
240
|
+
if ssh_connection or ssh_client:
|
|
241
|
+
return Environment.SSH
|
|
242
|
+
return Environment.TERMINAL
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _server_snapshot(port: int) -> ServerSnapshot:
|
|
246
|
+
payload = _ping_arrayview_server(port)
|
|
247
|
+
return ServerSnapshot(
|
|
248
|
+
port=port,
|
|
249
|
+
port_busy=_port_busy(port),
|
|
250
|
+
arrayview_server_alive=payload is not None,
|
|
251
|
+
server_pid=_int_or_none(payload.get("pid")) if payload else None,
|
|
252
|
+
server_hostname=_str_or_none(payload.get("hostname")) if payload else None,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _port_busy(port: int) -> bool:
|
|
257
|
+
try:
|
|
258
|
+
with socket.create_connection(
|
|
259
|
+
(_LOOPBACK_HOST, port), timeout=_PING_TIMEOUT_SECONDS
|
|
260
|
+
):
|
|
261
|
+
return True
|
|
262
|
+
except OSError:
|
|
263
|
+
return False
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _ping_arrayview_server(port: int) -> dict | None:
|
|
267
|
+
url = f"http://{_LOOPBACK_HOST}:{port}/ping"
|
|
268
|
+
try:
|
|
269
|
+
with urllib.request.urlopen(url, timeout=_PING_TIMEOUT_SECONDS) as resp:
|
|
270
|
+
if resp.status != 200:
|
|
271
|
+
return None
|
|
272
|
+
payload = json.loads(resp.read().decode("utf-8"))
|
|
273
|
+
except Exception:
|
|
274
|
+
return None
|
|
275
|
+
if payload.get("ok") is True and payload.get("service") == "arrayview":
|
|
276
|
+
return payload
|
|
277
|
+
return None
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _int_or_none(value: object) -> int | None:
|
|
281
|
+
return value if isinstance(value, int) else None
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _str_or_none(value: object) -> str | None:
|
|
285
|
+
return value if isinstance(value, str) else None
|
arrayview/_launcher.py
CHANGED
|
@@ -33,6 +33,7 @@ from arrayview._platform import (
|
|
|
33
33
|
_is_vscode_remote,
|
|
34
34
|
_in_vscode_tunnel,
|
|
35
35
|
_can_native_window,
|
|
36
|
+
_native_window_gui,
|
|
36
37
|
_find_vscode_ipc_hook,
|
|
37
38
|
_is_julia_env,
|
|
38
39
|
_in_julia_jupyter,
|
|
@@ -249,6 +250,7 @@ def _open_webview(
|
|
|
249
250
|
|
|
250
251
|
icon_path = _get_icon_png_path() or ""
|
|
251
252
|
inline_html_b64 = None
|
|
253
|
+
gui_name = _native_window_gui() or ""
|
|
252
254
|
|
|
253
255
|
if shell_port is not None:
|
|
254
256
|
shell_html = _build_inline_shell_html(url, shell_port)
|
|
@@ -258,14 +260,14 @@ def _open_webview(
|
|
|
258
260
|
if inline_html_b64:
|
|
259
261
|
script_lines = [
|
|
260
262
|
"import sys, base64, webview",
|
|
261
|
-
"u, w, h, icon, html_b64, ready_file = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), sys.argv[4], sys.argv[5], sys.argv[6]",
|
|
263
|
+
"u, w, h, icon, html_b64, ready_file, gui = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), sys.argv[4], sys.argv[5], sys.argv[6], sys.argv[7]",
|
|
262
264
|
"html = base64.b64decode(html_b64.encode()).decode()",
|
|
263
265
|
"class Api:",
|
|
264
266
|
" def set_title(self, title):",
|
|
265
267
|
" try: webview.windows[0].set_title(str(title)[:240])",
|
|
266
268
|
" except Exception: pass",
|
|
267
269
|
"win = webview.create_window('ArrayView', html=html, width=w, height=h, background_color='#0c0c0c', js_api=Api())",
|
|
268
|
-
"kw = {'gui':
|
|
270
|
+
"kw = {'gui': gui} if gui else {}",
|
|
269
271
|
"def _start_func():",
|
|
270
272
|
" if ready_file:",
|
|
271
273
|
" try:",
|
|
@@ -298,6 +300,7 @@ def _open_webview(
|
|
|
298
300
|
icon_path,
|
|
299
301
|
inline_html_b64,
|
|
300
302
|
ready_file or "",
|
|
303
|
+
gui_name,
|
|
301
304
|
],
|
|
302
305
|
stdout=subprocess.DEVNULL,
|
|
303
306
|
stderr=subprocess.PIPE if capture_stderr else subprocess.DEVNULL,
|
|
@@ -306,13 +309,13 @@ def _open_webview(
|
|
|
306
309
|
# URL mode — direct load (used when shell_port not provided)
|
|
307
310
|
script_lines = [
|
|
308
311
|
"import sys, webview",
|
|
309
|
-
"u, w, h, icon, ready_file = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), sys.argv[4], sys.argv[5]",
|
|
312
|
+
"u, w, h, icon, ready_file, gui = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), sys.argv[4], sys.argv[5], sys.argv[6]",
|
|
310
313
|
"class Api:",
|
|
311
314
|
" def set_title(self, title):",
|
|
312
315
|
" try: webview.windows[0].set_title(str(title)[:240])",
|
|
313
316
|
" except Exception: pass",
|
|
314
317
|
"win = webview.create_window('ArrayView', u, width=w, height=h, background_color='#0c0c0c', js_api=Api())",
|
|
315
|
-
"kw = {'gui':
|
|
318
|
+
"kw = {'gui': gui} if gui else {}",
|
|
316
319
|
"def _start_func():",
|
|
317
320
|
" if ready_file:",
|
|
318
321
|
" try:",
|
|
@@ -344,6 +347,7 @@ def _open_webview(
|
|
|
344
347
|
str(win_h),
|
|
345
348
|
icon_path,
|
|
346
349
|
ready_file or "",
|
|
350
|
+
gui_name,
|
|
347
351
|
],
|
|
348
352
|
stdout=subprocess.DEVNULL,
|
|
349
353
|
stderr=subprocess.PIPE if capture_stderr else subprocess.DEVNULL,
|
|
@@ -368,6 +372,7 @@ def _open_webview_with_fallback(
|
|
|
368
372
|
sockets_before = (
|
|
369
373
|
_session_mod.VIEWER_SOCKETS
|
|
370
374
|
) # capture count so we detect a NEW connection
|
|
375
|
+
shell_sockets_before = len(_session_mod.SHELL_SOCKETS)
|
|
371
376
|
|
|
372
377
|
def _read_stderr():
|
|
373
378
|
try:
|
|
@@ -390,14 +395,18 @@ def _open_webview_with_fallback(
|
|
|
390
395
|
_open_browser(url, floating=floating)
|
|
391
396
|
return
|
|
392
397
|
|
|
393
|
-
# Phase 2: process is alive — wait up to 8 s for a NEW viewer
|
|
394
|
-
#
|
|
395
|
-
#
|
|
398
|
+
# Phase 2: process is alive — wait up to 8 s for a NEW viewer or shell
|
|
399
|
+
# WebSocket. The native shell connection proves pywebview is usable;
|
|
400
|
+
# the embedded viewer iframe can legitimately connect later.
|
|
396
401
|
import arrayview._session as _sm
|
|
397
402
|
|
|
398
403
|
for _ in range(80):
|
|
399
404
|
time.sleep(0.1)
|
|
400
|
-
|
|
405
|
+
shell_connected = (
|
|
406
|
+
shell_port is not None
|
|
407
|
+
and len(_sm.SHELL_SOCKETS) > shell_sockets_before
|
|
408
|
+
)
|
|
409
|
+
if _sm.VIEWER_SOCKETS > sockets_before or shell_connected:
|
|
401
410
|
_vprint("[ArrayView] Native window connected successfully", flush=True)
|
|
402
411
|
if sys.platform == "darwin":
|
|
403
412
|
subprocess.Popen(
|
|
@@ -530,6 +539,21 @@ def _server_viewer_connections_seen(port: int, timeout: float = 0.5) -> int:
|
|
|
530
539
|
return 0
|
|
531
540
|
|
|
532
541
|
|
|
542
|
+
def _server_shell_sockets_open(port: int, timeout: float = 0.5) -> int:
|
|
543
|
+
"""Return the daemon's current native shell WebSocket count."""
|
|
544
|
+
url = f"http://{_LOOPBACK_HOST}:{port}/ping"
|
|
545
|
+
try:
|
|
546
|
+
with urllib.request.urlopen(url, timeout=timeout) as resp:
|
|
547
|
+
if resp.status != 200:
|
|
548
|
+
return 0
|
|
549
|
+
payload = json.loads(resp.read().decode("utf-8"))
|
|
550
|
+
if payload.get("ok") is True and payload.get("service") == "arrayview":
|
|
551
|
+
return int(payload.get("shell_sockets") or 0)
|
|
552
|
+
except Exception:
|
|
553
|
+
pass
|
|
554
|
+
return 0
|
|
555
|
+
|
|
556
|
+
|
|
533
557
|
def _wait_for_viewer_connection(
|
|
534
558
|
port: int,
|
|
535
559
|
*,
|
|
@@ -545,6 +569,23 @@ def _wait_for_viewer_connection(
|
|
|
545
569
|
return False
|
|
546
570
|
|
|
547
571
|
|
|
572
|
+
def _wait_for_native_shell_or_viewer_connection(
|
|
573
|
+
port: int,
|
|
574
|
+
*,
|
|
575
|
+
viewer_before: int,
|
|
576
|
+
timeout: float = 8.0,
|
|
577
|
+
) -> bool:
|
|
578
|
+
"""Wait until pywebview proves it is alive via shell or viewer WebSocket."""
|
|
579
|
+
deadline = time.monotonic() + timeout
|
|
580
|
+
while time.monotonic() < deadline:
|
|
581
|
+
if _server_viewer_connections_seen(port, timeout=0.3) > viewer_before:
|
|
582
|
+
return True
|
|
583
|
+
if _server_shell_sockets_open(port, timeout=0.3) > 0:
|
|
584
|
+
return True
|
|
585
|
+
time.sleep(0.1)
|
|
586
|
+
return False
|
|
587
|
+
|
|
588
|
+
|
|
548
589
|
def _terminate_native_process(proc: subprocess.Popen | None) -> None:
|
|
549
590
|
if proc is None or proc.poll() is not None:
|
|
550
591
|
return
|
|
@@ -554,6 +595,64 @@ def _terminate_native_process(proc: subprocess.Popen | None) -> None:
|
|
|
554
595
|
return
|
|
555
596
|
|
|
556
597
|
|
|
598
|
+
def _open_cli_native_shell_after_server(
|
|
599
|
+
*,
|
|
600
|
+
port: int,
|
|
601
|
+
sid: str,
|
|
602
|
+
name: str,
|
|
603
|
+
compare_sids: "_CompareSids | None",
|
|
604
|
+
win_w: int,
|
|
605
|
+
win_h: int,
|
|
606
|
+
) -> bool:
|
|
607
|
+
"""Open a CLI native shell and return whether it is usable."""
|
|
608
|
+
url_shell = _shell_url(port, sid, name, compare_sids=compare_sids)
|
|
609
|
+
viewer_before = _server_viewer_connections_seen(port)
|
|
610
|
+
opened, proc = _open_webview_cli_tracked(url_shell, win_w, win_h)
|
|
611
|
+
if opened and _wait_for_native_shell_or_viewer_connection(
|
|
612
|
+
port, viewer_before=viewer_before
|
|
613
|
+
):
|
|
614
|
+
return True
|
|
615
|
+
if opened:
|
|
616
|
+
_vprint(
|
|
617
|
+
"[ArrayView] Native window did not connect to the backend; falling back to browser",
|
|
618
|
+
flush=True,
|
|
619
|
+
)
|
|
620
|
+
_terminate_native_process(proc)
|
|
621
|
+
return False
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
def _activate_early_cli_native_shell(
|
|
625
|
+
*,
|
|
626
|
+
port: int,
|
|
627
|
+
sid: str,
|
|
628
|
+
name: str,
|
|
629
|
+
proc: subprocess.Popen | None,
|
|
630
|
+
) -> bool:
|
|
631
|
+
"""Attach an already-started preload shell to a spawned daemon session."""
|
|
632
|
+
viewer_before = _server_viewer_connections_seen(port)
|
|
633
|
+
try:
|
|
634
|
+
notify_result = _notify_existing_session(
|
|
635
|
+
port,
|
|
636
|
+
sid,
|
|
637
|
+
name,
|
|
638
|
+
url=_viewer_path(sid),
|
|
639
|
+
wait=True,
|
|
640
|
+
)
|
|
641
|
+
notified = bool(notify_result.get("notified"))
|
|
642
|
+
except Exception:
|
|
643
|
+
notified = False
|
|
644
|
+
if notified and _wait_for_native_shell_or_viewer_connection(
|
|
645
|
+
port, viewer_before=viewer_before
|
|
646
|
+
):
|
|
647
|
+
return True
|
|
648
|
+
_vprint(
|
|
649
|
+
"[ArrayView] Native window did not connect to the backend; falling back to browser",
|
|
650
|
+
flush=True,
|
|
651
|
+
)
|
|
652
|
+
_terminate_native_process(proc)
|
|
653
|
+
return False
|
|
654
|
+
|
|
655
|
+
|
|
557
656
|
# ── Server Port Utilities ─────────────────────────────────────────
|
|
558
657
|
|
|
559
658
|
|
|
@@ -862,23 +961,21 @@ def _open_cli_existing_server_view(
|
|
|
862
961
|
_vprint(f"Injected into existing window (port {port})")
|
|
863
962
|
return
|
|
864
963
|
if notify_webview and not notified:
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
_terminate_native_process(proc)
|
|
964
|
+
native_ready = _open_cli_native_shell_after_server(
|
|
965
|
+
port=port,
|
|
966
|
+
sid=sid,
|
|
967
|
+
name=name,
|
|
968
|
+
compare_sids=compare_sids,
|
|
969
|
+
win_w=1200,
|
|
970
|
+
win_h=800,
|
|
971
|
+
)
|
|
972
|
+
if not native_ready:
|
|
875
973
|
_vprint("[ArrayView] Falling back to browser", flush=True)
|
|
876
974
|
_print_viewer_location(url)
|
|
877
975
|
_open_browser(
|
|
878
976
|
url,
|
|
879
977
|
blocking=True,
|
|
880
978
|
title=f"ArrayView: {name}",
|
|
881
|
-
filepath=base_file,
|
|
882
979
|
floating=floating,
|
|
883
980
|
)
|
|
884
981
|
return
|
|
@@ -889,7 +986,6 @@ def _open_cli_existing_server_view(
|
|
|
889
986
|
blocking=True,
|
|
890
987
|
force_vscode=(window_mode == "vscode"),
|
|
891
988
|
title=f"ArrayView: {name}",
|
|
892
|
-
filepath=base_file,
|
|
893
989
|
floating=floating,
|
|
894
990
|
)
|
|
895
991
|
|
|
@@ -1056,7 +1152,6 @@ def _handle_cli_spawned_daemon(
|
|
|
1056
1152
|
|
|
1057
1153
|
early_webview_opened = False
|
|
1058
1154
|
early_webview_proc = None
|
|
1059
|
-
early_webview_notified = False
|
|
1060
1155
|
early_webview_connected = False
|
|
1061
1156
|
if (
|
|
1062
1157
|
use_webview
|
|
@@ -1083,29 +1178,12 @@ def _handle_cli_spawned_daemon(
|
|
|
1083
1178
|
sys.exit(1)
|
|
1084
1179
|
|
|
1085
1180
|
if early_webview_opened:
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
url=_viewer_path(sid),
|
|
1093
|
-
wait=True,
|
|
1094
|
-
)
|
|
1095
|
-
early_webview_notified = bool(notify_result.get("notified"))
|
|
1096
|
-
except Exception:
|
|
1097
|
-
early_webview_notified = False
|
|
1098
|
-
if early_webview_notified:
|
|
1099
|
-
early_webview_connected = _wait_for_viewer_connection(
|
|
1100
|
-
port,
|
|
1101
|
-
before=seen_before,
|
|
1102
|
-
)
|
|
1103
|
-
if not early_webview_connected:
|
|
1104
|
-
_vprint(
|
|
1105
|
-
"[ArrayView] Native window did not connect to the backend; falling back to browser",
|
|
1106
|
-
flush=True,
|
|
1107
|
-
)
|
|
1108
|
-
_terminate_native_process(early_webview_proc)
|
|
1181
|
+
early_webview_connected = _activate_early_cli_native_shell(
|
|
1182
|
+
port=port,
|
|
1183
|
+
sid=sid,
|
|
1184
|
+
name=name,
|
|
1185
|
+
proc=early_webview_proc,
|
|
1186
|
+
)
|
|
1109
1187
|
|
|
1110
1188
|
should_retry_webview = use_webview and not (
|
|
1111
1189
|
early_webview_opened and not early_webview_connected
|
|
@@ -1154,16 +1232,15 @@ def _open_cli_spawned_view(
|
|
|
1154
1232
|
if _should_notify_webview(use_webview, overlay_sid):
|
|
1155
1233
|
if webview_already_opened:
|
|
1156
1234
|
return
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
_terminate_native_process(proc)
|
|
1235
|
+
native_ready = _open_cli_native_shell_after_server(
|
|
1236
|
+
port=port,
|
|
1237
|
+
sid=sid,
|
|
1238
|
+
name=name,
|
|
1239
|
+
compare_sids=compare_sids,
|
|
1240
|
+
win_w=1400,
|
|
1241
|
+
win_h=900,
|
|
1242
|
+
)
|
|
1243
|
+
if not native_ready:
|
|
1167
1244
|
_vprint("[ArrayView] Falling back to browser", flush=True)
|
|
1168
1245
|
_print_viewer_location(url)
|
|
1169
1246
|
_open_browser(
|
arrayview/_platform.py
CHANGED
|
@@ -415,18 +415,23 @@ def _can_native_window() -> bool:
|
|
|
415
415
|
# Plain SSH (no VS Code): the display is on the client machine, not here.
|
|
416
416
|
if os.environ.get("SSH_CLIENT") or os.environ.get("SSH_CONNECTION"):
|
|
417
417
|
return False
|
|
418
|
+
return _native_window_gui() is not None
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def _native_window_gui() -> str | None:
|
|
422
|
+
"""Return the pywebview GUI backend name to use, or None if unavailable."""
|
|
418
423
|
if importlib.util.find_spec("webview") is None:
|
|
419
|
-
return
|
|
424
|
+
return None
|
|
420
425
|
if sys.platform in ("darwin", "win32"):
|
|
421
|
-
return
|
|
422
|
-
# Linux/BSD: need a display server AND pywebview's GUI bindings
|
|
426
|
+
return ""
|
|
427
|
+
# Linux/BSD: need a display server AND pywebview's GUI bindings.
|
|
423
428
|
if not (os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY")):
|
|
424
|
-
return
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
429
|
+
return None
|
|
430
|
+
if importlib.util.find_spec("qtpy") is not None:
|
|
431
|
+
return "qt"
|
|
432
|
+
if importlib.util.find_spec("gi") is not None:
|
|
433
|
+
return "gtk"
|
|
434
|
+
return None
|
|
430
435
|
|
|
431
436
|
|
|
432
437
|
# ---------------------------------------------------------------------------
|
arrayview/_server.py
CHANGED
arrayview/_viewer.html
CHANGED
|
@@ -11985,6 +11985,8 @@
|
|
|
11985
11985
|
rectRoiMode = true;
|
|
11986
11986
|
_roiShape = 'rect';
|
|
11987
11987
|
_roiVisible = true;
|
|
11988
|
+
hideAllPixelInfo();
|
|
11989
|
+
_resetDimbarHover();
|
|
11988
11990
|
canvas.style.cursor = 'crosshair';
|
|
11989
11991
|
if (qmriActive) qmriViews.forEach(v => { v.canvas.style.cursor = 'crosshair'; });
|
|
11990
11992
|
showStatus('ROI mode: rect · drag to draw');
|
|
@@ -11995,6 +11997,7 @@
|
|
|
11995
11997
|
|
|
11996
11998
|
function _deactivateRoiInteraction({ clear = false } = {}) {
|
|
11997
11999
|
rectRoiMode = false;
|
|
12000
|
+
_roiSuppressPixelInfo = false;
|
|
11998
12001
|
_roiShape = 'rect';
|
|
11999
12002
|
_roiFreehandPts = [];
|
|
12000
12003
|
_floodFillTolerance = 0.1;
|
|
@@ -14518,6 +14521,11 @@
|
|
|
14518
14521
|
}
|
|
14519
14522
|
function showPixelInfo(el, payload, cx, cy) {
|
|
14520
14523
|
if (!el || !_pixelInfoVisible) return;
|
|
14524
|
+
if (rectRoiMode && _roiSuppressPixelInfo) {
|
|
14525
|
+
el.style.display = 'none';
|
|
14526
|
+
delete el.dataset.pixelInfoKey;
|
|
14527
|
+
return;
|
|
14528
|
+
}
|
|
14521
14529
|
const key = (payload && typeof payload === 'object')
|
|
14522
14530
|
? `${payload.valueText ?? ''}|${payload.rawX ?? ''}|${payload.rawY ?? ''}`
|
|
14523
14531
|
: String(payload ?? '');
|
|
@@ -14530,6 +14538,7 @@
|
|
|
14530
14538
|
}
|
|
14531
14539
|
|
|
14532
14540
|
let _pixelHoverThrottle = false;
|
|
14541
|
+
let _roiSuppressPixelInfo = false;
|
|
14533
14542
|
const _pixelFmt = (v, contextValues = null) => formatScalarValue(v, { contextValues, maxDecimals: 7, minDecimals: 3 });
|
|
14534
14543
|
function _activeNumericContext(view = null) {
|
|
14535
14544
|
const vals = view && view.displayState
|
|
@@ -14538,6 +14547,17 @@
|
|
|
14538
14547
|
return vals.filter(Number.isFinite);
|
|
14539
14548
|
}
|
|
14540
14549
|
function fetchPixelInfo(opts) {
|
|
14550
|
+
if (rectRoiMode) {
|
|
14551
|
+
const roiIdx = _roiHitIndexForCanvasEvent(opts.event, opts.canvas, opts.imgW, opts.imgH);
|
|
14552
|
+
_roiSuppressPixelInfo = _roiHasStats(roiIdx);
|
|
14553
|
+
if (_roiSuppressPixelInfo) {
|
|
14554
|
+
if (opts.displayEl) {
|
|
14555
|
+
opts.displayEl.style.display = 'none';
|
|
14556
|
+
delete opts.displayEl.dataset.pixelInfoKey;
|
|
14557
|
+
}
|
|
14558
|
+
return false;
|
|
14559
|
+
}
|
|
14560
|
+
}
|
|
14541
14561
|
const indices = Array.isArray(opts.indices) ? opts.indices : [];
|
|
14542
14562
|
if (!indices.length || indices.some(v => !Number.isFinite(v))) return false;
|
|
14543
14563
|
const rect = opts.canvas.getBoundingClientRect();
|
|
@@ -16259,6 +16279,20 @@
|
|
|
16259
16279
|
}
|
|
16260
16280
|
return false;
|
|
16261
16281
|
}
|
|
16282
|
+
function _roiHasStats(idx) {
|
|
16283
|
+
return idx >= 0 && !!(_rois[idx] && (_rois[idx].stats || _rois[idx].qmriStats));
|
|
16284
|
+
}
|
|
16285
|
+
function _roiHitIndexForCanvasEvent(e, cv, imgW, imgH) {
|
|
16286
|
+
if (!rectRoiMode || !cv || !imgW || !imgH) return -1;
|
|
16287
|
+
const rect = cv.getBoundingClientRect();
|
|
16288
|
+
if (!rect.width || !rect.height) return -1;
|
|
16289
|
+
const cx = (e.clientX - rect.left) * imgW / rect.width;
|
|
16290
|
+
const cy = (e.clientY - rect.top) * imgH / rect.height;
|
|
16291
|
+
for (let i = _rois.length - 1; i >= 0; i--) {
|
|
16292
|
+
if (_pointInRoi(_rois[i], cx, cy)) return i;
|
|
16293
|
+
}
|
|
16294
|
+
return -1;
|
|
16295
|
+
}
|
|
16262
16296
|
function _updateRoiPanel() {
|
|
16263
16297
|
renderIsland();
|
|
16264
16298
|
}
|
|
@@ -16533,25 +16567,27 @@
|
|
|
16533
16567
|
for (let i = _rois.length - 1; i >= 0; i--) {
|
|
16534
16568
|
if (_pointInRoi(_rois[i], cx, cy)) { hitIdx = i; break; }
|
|
16535
16569
|
}
|
|
16536
|
-
|
|
16537
|
-
if (
|
|
16570
|
+
_roiSuppressPixelInfo = _roiHasStats(hitIdx);
|
|
16571
|
+
if (!_roiSuppressPixelInfo) { ttip.style.display = 'none'; return; }
|
|
16538
16572
|
const s = _rois[hitIdx].stats;
|
|
16539
16573
|
const q = _rois[hitIdx].qmriStats;
|
|
16540
16574
|
const c = _roiColors[hitIdx % _roiColors.length];
|
|
16541
16575
|
if (Array.isArray(q) && q.length && q[0].stats) {
|
|
16542
|
-
const ttipQ =
|
|
16576
|
+
const ttipQ = _getRoiRowTooltip();
|
|
16543
16577
|
if (ttipQ) {
|
|
16544
16578
|
ttipQ.innerHTML = _buildRoiTooltipContent(hitIdx);
|
|
16545
|
-
ttipQ.style.display = 'block';
|
|
16546
16579
|
_positionRoiRowTooltip({clientX: e.clientX, clientY: e.clientY});
|
|
16580
|
+
ttipQ.classList.add('visible');
|
|
16547
16581
|
}
|
|
16548
|
-
|
|
16582
|
+
const piEl = document.getElementById('main-pixel-info');
|
|
16583
|
+
if (_pixelInfoVisible && piEl) piEl.style.display = 'none';
|
|
16549
16584
|
return;
|
|
16550
16585
|
}
|
|
16551
16586
|
const fmt = v => { const a = Math.abs(v); if (a === 0) return '0'; if (a >= 1e4 || (a < 1e-2 && a > 0)) return v.toExponential(3); return parseFloat(v.toPrecision(4)).toString(); };
|
|
16552
16587
|
ttip.innerHTML = `<span style="color:${c.stroke};font-weight:bold">${hitIdx+1}</span> ${fmt(s.mean)} \u00B1 ${fmt(s.std)}`;
|
|
16553
16588
|
ttip.style.display = 'block';
|
|
16554
|
-
|
|
16589
|
+
const piEl = document.getElementById('main-pixel-info');
|
|
16590
|
+
if (_pixelInfoVisible && piEl) piEl.style.display = 'none';
|
|
16555
16591
|
const par = ttip.offsetParent;
|
|
16556
16592
|
if (par) {
|
|
16557
16593
|
const pr = par.getBoundingClientRect();
|
|
@@ -16690,8 +16726,10 @@
|
|
|
16690
16726
|
roiDragging = false;
|
|
16691
16727
|
_drawAllRois(); // clear in-progress preview
|
|
16692
16728
|
}
|
|
16729
|
+
_roiSuppressPixelInfo = false;
|
|
16693
16730
|
const ttip = document.getElementById('roi-hover-tooltip');
|
|
16694
16731
|
if (ttip) ttip.style.display = 'none';
|
|
16732
|
+
_hideRoiRowTooltip();
|
|
16695
16733
|
const piEl = document.getElementById('main-pixel-info');
|
|
16696
16734
|
if (piEl) piEl.style.display = 'none';
|
|
16697
16735
|
});
|
|
@@ -23209,7 +23247,8 @@
|
|
|
23209
23247
|
for (let ii = _rois.length - 1; ii >= 0; ii--) {
|
|
23210
23248
|
if (_pointInRoi(_rois[ii], cx, cy)) { hitIdx = ii; break; }
|
|
23211
23249
|
}
|
|
23212
|
-
|
|
23250
|
+
_roiSuppressPixelInfo = _roiHasStats(hitIdx);
|
|
23251
|
+
if (!_roiSuppressPixelInfo) {
|
|
23213
23252
|
const t = document.getElementById('roi-hover-tooltip');
|
|
23214
23253
|
if (t) t.style.display = 'none';
|
|
23215
23254
|
if (_pixelInfoVisible && view.pixelInfoEl) view.pixelInfoEl.style.display = 'none';
|
|
@@ -23217,11 +23256,11 @@
|
|
|
23217
23256
|
}
|
|
23218
23257
|
const q = _rois[hitIdx].qmriStats;
|
|
23219
23258
|
if (Array.isArray(q) && q.length && q[0].stats) {
|
|
23220
|
-
const tq =
|
|
23259
|
+
const tq = _getRoiRowTooltip();
|
|
23221
23260
|
if (tq) {
|
|
23222
23261
|
tq.innerHTML = _buildRoiTooltipContent(hitIdx);
|
|
23223
|
-
tq.classList.add('visible');
|
|
23224
23262
|
_positionRoiRowTooltip({clientX: e.clientX, clientY: e.clientY});
|
|
23263
|
+
tq.classList.add('visible');
|
|
23225
23264
|
}
|
|
23226
23265
|
if (_pixelInfoVisible && view.pixelInfoEl) view.pixelInfoEl.style.display = 'none';
|
|
23227
23266
|
return;
|
|
@@ -23237,6 +23276,7 @@
|
|
|
23237
23276
|
}
|
|
23238
23277
|
});
|
|
23239
23278
|
cv.addEventListener('mouseleave', () => {
|
|
23279
|
+
_roiSuppressPixelInfo = false;
|
|
23240
23280
|
const tq = document.getElementById('roi-stats-tooltip');
|
|
23241
23281
|
if (tq) tq.classList.remove('visible');
|
|
23242
23282
|
});
|
arrayview/_vscode_signal.py
CHANGED
|
@@ -283,65 +283,23 @@ def _open_direct_via_shm(
|
|
|
283
283
|
title: str | None = None,
|
|
284
284
|
floating: bool = False,
|
|
285
285
|
) -> bool:
|
|
286
|
-
"""
|
|
286
|
+
"""Compatibility shim for the canonical SHM direct-mode helper."""
|
|
287
|
+
import warnings
|
|
287
288
|
|
|
288
|
-
|
|
289
|
-
can read it without any disk I/O.
|
|
290
|
-
"""
|
|
291
|
-
import atexit
|
|
292
|
-
import sys as _sys
|
|
293
|
-
from multiprocessing.shared_memory import SharedMemory
|
|
294
|
-
|
|
295
|
-
import numpy as np
|
|
296
|
-
|
|
297
|
-
arr = np.ascontiguousarray(data)
|
|
298
|
-
shm = SharedMemory(create=True, size=arr.nbytes)
|
|
299
|
-
np.ndarray(arr.shape, dtype=arr.dtype, buffer=shm.buf)[:] = arr
|
|
300
|
-
|
|
301
|
-
# The subprocess will unlink the SHM after reading it. Unregister from
|
|
302
|
-
# Python's resource tracker so it doesn't warn about "leaked" SHM at exit.
|
|
303
|
-
from multiprocessing import resource_tracker
|
|
304
|
-
try:
|
|
305
|
-
resource_tracker.unregister(f"/{shm.name}", "shared_memory")
|
|
306
|
-
except Exception:
|
|
307
|
-
pass
|
|
308
|
-
|
|
309
|
-
# Keep the shm alive until the subprocess reads it (or this process exits).
|
|
310
|
-
_ACTIVE_SHM.append(shm)
|
|
289
|
+
from arrayview._vscode_shm import _open_direct_via_shm as _canonical_open_direct_via_shm
|
|
311
290
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
payload: dict = {
|
|
325
|
-
"action": "open-preview",
|
|
326
|
-
"mode": "direct",
|
|
327
|
-
"shm": {
|
|
328
|
-
"name": shm.name,
|
|
329
|
-
"shape": ",".join(str(int(s)) for s in arr.shape),
|
|
330
|
-
"dtype": str(arr.dtype),
|
|
331
|
-
},
|
|
332
|
-
"arrayName": name,
|
|
333
|
-
"pythonPath": _sys.executable,
|
|
334
|
-
"maxAgeMs": _VSCODE_SIGNAL_MAX_AGE_MS,
|
|
335
|
-
}
|
|
336
|
-
if title:
|
|
337
|
-
payload["title"] = title
|
|
338
|
-
if floating:
|
|
339
|
-
payload["floating"] = True
|
|
340
|
-
return _write_vscode_signal(payload, skip_compat=True)
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
# Shared memory blocks kept alive until process exit or subprocess reads them.
|
|
344
|
-
_ACTIVE_SHM: list = []
|
|
291
|
+
warnings.warn(
|
|
292
|
+
"arrayview._vscode_signal._open_direct_via_shm is deprecated; "
|
|
293
|
+
"use arrayview._vscode_shm._open_direct_via_shm",
|
|
294
|
+
DeprecationWarning,
|
|
295
|
+
stacklevel=2,
|
|
296
|
+
)
|
|
297
|
+
return _canonical_open_direct_via_shm(
|
|
298
|
+
data,
|
|
299
|
+
name=name,
|
|
300
|
+
title=title,
|
|
301
|
+
floating=floating,
|
|
302
|
+
)
|
|
345
303
|
|
|
346
304
|
|
|
347
305
|
def _schedule_remote_open_retries(
|
|
@@ -772,3 +730,19 @@ def _write_vscode_signal(payload: dict, delay: float = 0.0, skip_compat: bool =
|
|
|
772
730
|
return True
|
|
773
731
|
except Exception:
|
|
774
732
|
return False
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
def __getattr__(name: str):
|
|
736
|
+
if name == "_ACTIVE_SHM":
|
|
737
|
+
import warnings
|
|
738
|
+
|
|
739
|
+
from arrayview._vscode_shm import _ACTIVE_SHM
|
|
740
|
+
|
|
741
|
+
warnings.warn(
|
|
742
|
+
"arrayview._vscode_signal._ACTIVE_SHM is deprecated; "
|
|
743
|
+
"use arrayview._vscode_shm._ACTIVE_SHM",
|
|
744
|
+
DeprecationWarning,
|
|
745
|
+
stacklevel=2,
|
|
746
|
+
)
|
|
747
|
+
return _ACTIVE_SHM
|
|
748
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -9,10 +9,11 @@ arrayview/_diff.py,sha256=mSIo1ArNxkrB0v2iUMy3dezJOlpos0xNu1V_umNUMhU,5677
|
|
|
9
9
|
arrayview/_icon.png,sha256=a8RTAbvweLhh3hzD5eGg76Peq-KSDcS6YrCphRb_HEs,2748
|
|
10
10
|
arrayview/_imaging.py,sha256=MQX1uy4m1lofOeoLDhCp6cHO5p_V5fXm4qlSu9Ykn-c,534
|
|
11
11
|
arrayview/_io.py,sha256=z9gFRI7OneCnm5jBTLwdkDARrHF-LyqHspksYQx1lTg,11585
|
|
12
|
-
arrayview/
|
|
12
|
+
arrayview/_launch_plan.py,sha256=kiWitWvKe2Wetj_pS1M8OtaSqZ11aWF6KDDD6IPnKIM,7393
|
|
13
|
+
arrayview/_launcher.py,sha256=WhS-rgV0fwmeldUQfcs68AkG1alD04-UpmH93_ws_50,138470
|
|
13
14
|
arrayview/_lifecycle.py,sha256=oFD05gNYAu6OBIUFcKmPfT-_Hp03KaI2oK8komUXtJY,937
|
|
14
15
|
arrayview/_overlays.py,sha256=C-qDZFBOgwJQL84XxFoMAX3qyfNa2EI5N-AlOvXeoTI,1634
|
|
15
|
-
arrayview/_platform.py,sha256=
|
|
16
|
+
arrayview/_platform.py,sha256=gWZ5_yPN1B_kqiUewRl0lKjOZmB6jGw7X71hmFxJ_PQ,18428
|
|
16
17
|
arrayview/_render.py,sha256=UCUx8YD8Q38GDTajHhnmEMUUUali-8PLvoBOHvUmaYc,28684
|
|
17
18
|
arrayview/_routes_analysis.py,sha256=ipws39Q0e6ZtYsxXk4ZcbRKlD7LPAt_Iehf_kDjo1Yo,16272
|
|
18
19
|
arrayview/_routes_export.py,sha256=Ha8mMO2E9TlRL5x25-ADV8_8xUiC-jRz9N1oWwgKOsg,3898
|
|
@@ -26,23 +27,23 @@ arrayview/_routes_state.py,sha256=TmxC__NPPsvg59jM2D_FRc_oPv1wWbRmUr6t6ukVj64,11
|
|
|
26
27
|
arrayview/_routes_vectorfield.py,sha256=P8avWU8jDH173YQ4Kfpzs8SZz5gIL4ki0-Gc6pGA9w4,7383
|
|
27
28
|
arrayview/_routes_websocket.py,sha256=7mKy52OyCSRIGcsKx02VWGY1qTxGjB22RVeS2Ev833A,16092
|
|
28
29
|
arrayview/_segmentation.py,sha256=aENWHmefrKDScXhcZL1X3KgYXE784dpMkmFFo83qnrw,7377
|
|
29
|
-
arrayview/_server.py,sha256=
|
|
30
|
+
arrayview/_server.py,sha256=x9_B3Su5BGxfpEho3RknXeG06_QIrvMIhmSU0SaEWKU,7791
|
|
30
31
|
arrayview/_session.py,sha256=OD3f2Q2gubZt1ftoIwfPX3SfKmCvPYy_FbplmmSnLSg,17427
|
|
31
32
|
arrayview/_shell.html,sha256=xzHIB-kRX12okubZoqdrgsWDY8CCLteg5jtXxDQru8U,11624
|
|
32
33
|
arrayview/_stdio_server.py,sha256=ECwUGHuO6KUx4TlQPPzqFZoL48HOTFc0n2ckCPo69Mc,33954
|
|
33
34
|
arrayview/_synthetic_mri.py,sha256=zb613sIke-yljrILKYn_f-1SB6T4XsB4f916STgAp4M,6756
|
|
34
35
|
arrayview/_torch.py,sha256=13sqIMCUu_-b9SQ9KfDCExDz2UMStL87EDVjnbJxAEo,7536
|
|
35
36
|
arrayview/_vectorfield.py,sha256=eDqMCMEGjO8Jdt-6Auh01cz3WD9dbF7xYUWD9xHAf1E,8115
|
|
36
|
-
arrayview/_viewer.html,sha256=
|
|
37
|
+
arrayview/_viewer.html,sha256=GuK-5KpSOpAvrkMiEiQZ1xs1v-Au8HyYQgc2-eQm0iE,1539699
|
|
37
38
|
arrayview/_vscode.py,sha256=laTHzOL6YnUsZYFX0fAR3aAMBFpPLL6cF9KArBl3nAI,988
|
|
38
39
|
arrayview/_vscode_browser.py,sha256=EzYEl4ZdxD_njqIfHywqw3ovy3p2SaWX9c03Kzd_Xh0,7458
|
|
39
40
|
arrayview/_vscode_extension.py,sha256=v17e4IVJkTpHmuJMhuL2VPPWwbmsCCDWnbEzdONwH3I,12953
|
|
40
41
|
arrayview/_vscode_shm.py,sha256=Rm5DapeorNwhnLnVz5fkXcctkQqTzii9uLxnOxxIMU4,2238
|
|
41
|
-
arrayview/_vscode_signal.py,sha256=
|
|
42
|
+
arrayview/_vscode_signal.py,sha256=xy7HcQdLrbcUSDLuDX5pERzwrfHtKfaJ2nry_Su76tI,33072
|
|
42
43
|
arrayview/arrayview-opener.vsix,sha256=uxIm_06uI9RC7N54SpckwhkjIh0yKLXDv613hvJTgV0,19098
|
|
43
44
|
arrayview/gsap.min.js,sha256=VGihm4idI0E1uqrWezWsiScB9F_jMosn7qs6pPSkeac,72304
|
|
44
|
-
arrayview-0.26.
|
|
45
|
-
arrayview-0.26.
|
|
46
|
-
arrayview-0.26.
|
|
47
|
-
arrayview-0.26.
|
|
48
|
-
arrayview-0.26.
|
|
45
|
+
arrayview-0.26.3.dist-info/METADATA,sha256=Fp9WT0LXPGkC7qk8uUxly06qYvYxH9050iIFKhK_73Y,2161
|
|
46
|
+
arrayview-0.26.3.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
47
|
+
arrayview-0.26.3.dist-info/entry_points.txt,sha256=FQSz3M11B-zaVLDyXofKUDvMhHASz7OjmoLgI4nn7ko,105
|
|
48
|
+
arrayview-0.26.3.dist-info/licenses/LICENSE,sha256=34OivjM9faB2QaPjZpMRTIiSx31V3U9cEyQ6F2k-bH0,1062
|
|
49
|
+
arrayview-0.26.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|