seqeyes-python 0.2.2__py3-none-any.whl → 0.2.7__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.
- seqeyes/__init__.py +1 -1
- seqeyes/_renderer.py +35 -12
- seqeyes/resources/pulseq-bundle.js +1301 -186
- seqeyes/resources/viewer.html +41 -15
- {seqeyes_python-0.2.2.dist-info → seqeyes_python-0.2.7.dist-info}/METADATA +13 -3
- seqeyes_python-0.2.7.dist-info/RECORD +8 -0
- seqeyes_python-0.2.2.dist-info/RECORD +0 -8
- {seqeyes_python-0.2.2.dist-info → seqeyes_python-0.2.7.dist-info}/WHEEL +0 -0
seqeyes/__init__.py
CHANGED
seqeyes/_renderer.py
CHANGED
|
@@ -12,7 +12,7 @@ import base64
|
|
|
12
12
|
import json
|
|
13
13
|
import os
|
|
14
14
|
from pathlib import Path
|
|
15
|
-
from typing import Optional
|
|
15
|
+
from typing import Any, Optional, Union
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
# ── Paths ─────────────────────────────────────────────────────────────────
|
|
@@ -27,6 +27,7 @@ _BUNDLE_CANDIDATES = [
|
|
|
27
27
|
Path.cwd() / "web" / "pulseq-bundle.js",
|
|
28
28
|
]
|
|
29
29
|
_VALID_GRAD_UNITS = {"Hz/m", "mT/m", "G/cm"}
|
|
30
|
+
SequenceSource = Union[str, bytes, bytearray, memoryview]
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
def _find_bundle() -> str:
|
|
@@ -60,7 +61,7 @@ def _normalize_grad_disp(unit: str) -> str:
|
|
|
60
61
|
|
|
61
62
|
|
|
62
63
|
def _build_html(
|
|
63
|
-
|
|
64
|
+
sequence_source: SequenceSource,
|
|
64
65
|
*,
|
|
65
66
|
theme: Optional[str] = None,
|
|
66
67
|
inject_bundle: bool = True,
|
|
@@ -74,8 +75,8 @@ def _build_html(
|
|
|
74
75
|
|
|
75
76
|
Parameters
|
|
76
77
|
----------
|
|
77
|
-
|
|
78
|
-
Raw
|
|
78
|
+
sequence_source : str or bytes-like
|
|
79
|
+
Raw text ``.seq`` content or binary ``.bseq`` bytes.
|
|
79
80
|
theme : str or None
|
|
80
81
|
CSS theme class to apply to ``<body>``.
|
|
81
82
|
inject_bundle : bool
|
|
@@ -93,7 +94,9 @@ def _build_html(
|
|
|
93
94
|
"""
|
|
94
95
|
template = _read_viewer_template()
|
|
95
96
|
grad_disp = _normalize_grad_disp(grad_disp)
|
|
96
|
-
|
|
97
|
+
source_is_text = isinstance(sequence_source, str)
|
|
98
|
+
source_bytes = sequence_source.encode("utf-8") if source_is_text else bytes(sequence_source)
|
|
99
|
+
source_b64 = base64.b64encode(source_bytes).decode("ascii")
|
|
97
100
|
|
|
98
101
|
# 1. Inject the pulseq-bundle.js
|
|
99
102
|
bundle_placeholder = "<!-- PULSEQ_BUNDLE_PLACEHOLDER -->"
|
|
@@ -108,7 +111,9 @@ def _build_html(
|
|
|
108
111
|
|
|
109
112
|
# 2. Build options injection block (sequence data + display opts)
|
|
110
113
|
opts = [
|
|
111
|
-
f"window.SEQEYES_RAW_B64 = {json.dumps(
|
|
114
|
+
f"window.SEQEYES_RAW_B64 = {json.dumps(source_b64)};",
|
|
115
|
+
f"window.SEQEYES_SOURCE_KIND = {json.dumps('text' if source_is_text else 'bytes')};",
|
|
116
|
+
f"window.SEQEYES_SOURCE_NAME = {json.dumps(label)};",
|
|
112
117
|
f"window.SEQEYES_LABEL = {json.dumps(label)};",
|
|
113
118
|
f"window.SEQEYES_SHOW_BLOCKS = {json.dumps(show_blocks)};",
|
|
114
119
|
f"window.SEQEYES_TIME_RANGE = {json.dumps(list(time_range))};",
|
|
@@ -144,8 +149,8 @@ class SeqEyesViewer:
|
|
|
144
149
|
|
|
145
150
|
Parameters
|
|
146
151
|
----------
|
|
147
|
-
|
|
148
|
-
Raw
|
|
152
|
+
sequence_source : str or bytes-like
|
|
153
|
+
Raw text ``.seq`` content or binary ``.bseq`` bytes.
|
|
149
154
|
label : str
|
|
150
155
|
Display label (not yet rendered on the viewer).
|
|
151
156
|
show_blocks : bool
|
|
@@ -167,7 +172,7 @@ class SeqEyesViewer:
|
|
|
167
172
|
|
|
168
173
|
def __init__(
|
|
169
174
|
self,
|
|
170
|
-
|
|
175
|
+
sequence_source: SequenceSource,
|
|
171
176
|
*,
|
|
172
177
|
label: str = "",
|
|
173
178
|
show_blocks: bool = False,
|
|
@@ -178,7 +183,9 @@ class SeqEyesViewer:
|
|
|
178
183
|
width: str = "100%",
|
|
179
184
|
height: str = "550px",
|
|
180
185
|
) -> None:
|
|
181
|
-
|
|
186
|
+
if not isinstance(sequence_source, (str, bytes, bytearray, memoryview)):
|
|
187
|
+
raise TypeError("sequence_source must be .seq text or .bseq bytes")
|
|
188
|
+
self._sequence_source = sequence_source
|
|
182
189
|
self._label = label
|
|
183
190
|
self._show_blocks = show_blocks
|
|
184
191
|
self._time_range = time_range
|
|
@@ -188,12 +195,28 @@ class SeqEyesViewer:
|
|
|
188
195
|
self._width = width
|
|
189
196
|
self._height = height
|
|
190
197
|
|
|
198
|
+
@classmethod
|
|
199
|
+
def from_file(cls, path: Union[str, os.PathLike[str]], **kwargs: Any) -> "SeqEyesViewer":
|
|
200
|
+
"""Create a viewer from a local ``.seq`` or ``.bseq`` file."""
|
|
201
|
+
source_path = Path(path)
|
|
202
|
+
if source_path.suffix.lower() not in {".seq", ".bseq"}:
|
|
203
|
+
raise ValueError("SeqEyesViewer.from_file() expects a .seq or .bseq file")
|
|
204
|
+
if not source_path.is_file():
|
|
205
|
+
raise FileNotFoundError(source_path)
|
|
206
|
+
kwargs.setdefault("label", source_path.name)
|
|
207
|
+
source: SequenceSource = (
|
|
208
|
+
source_path.read_text(encoding="utf-8")
|
|
209
|
+
if source_path.suffix.lower() == ".seq"
|
|
210
|
+
else source_path.read_bytes()
|
|
211
|
+
)
|
|
212
|
+
return cls(source, **kwargs)
|
|
213
|
+
|
|
191
214
|
# ── Jupyter integration ───────────────────────────────────────────
|
|
192
215
|
|
|
193
216
|
def _repr_html_(self) -> str:
|
|
194
217
|
"""Return an HTML iframe that Jupyter renders inline."""
|
|
195
218
|
html = _build_html(
|
|
196
|
-
self.
|
|
219
|
+
self._sequence_source,
|
|
197
220
|
theme=self._theme,
|
|
198
221
|
label=self._label,
|
|
199
222
|
show_blocks=self._show_blocks,
|
|
@@ -231,7 +254,7 @@ class SeqEyesViewer:
|
|
|
231
254
|
def to_html(self, *, inject_bundle: bool = True) -> str:
|
|
232
255
|
"""Return the full standalone HTML string (for saving to a file)."""
|
|
233
256
|
return _build_html(
|
|
234
|
-
self.
|
|
257
|
+
self._sequence_source,
|
|
235
258
|
theme=self._theme,
|
|
236
259
|
inject_bundle=inject_bundle,
|
|
237
260
|
label=self._label,
|