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 CHANGED
@@ -29,4 +29,4 @@ from seqeyes._plot import set, reset
29
29
  from seqeyes._renderer import SeqEyesViewer
30
30
 
31
31
  __all__ = ["set", "reset", "SeqEyesViewer"]
32
- __version__ = "0.2.2"
32
+ __version__ = "0.2.7"
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
- seq_text: str,
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
- seq_text : str
78
- Raw .seq file content.
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
- seq_b64 = base64.b64encode(seq_text.encode("utf-8")).decode("ascii")
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(seq_b64)};",
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
- seq_text : str
148
- Raw .seq file content as a string.
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
- seq_text: str,
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
- self._seq_text = seq_text
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._seq_text,
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._seq_text,
257
+ self._sequence_source,
235
258
  theme=self._theme,
236
259
  inject_bundle=inject_bundle,
237
260
  label=self._label,