seqeyes 0.0.4__py3-none-win_amd64.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 ADDED
@@ -0,0 +1,5 @@
1
+ """SeqEyes Python package – Pulseq sequence viewer."""
2
+
3
+ from .viewer import seqeyes
4
+
5
+ __all__ = ["seqeyes"]
seqeyes/_version.py ADDED
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '0.0.4'
32
+ __version_tuple__ = version_tuple = (0, 0, 4)
33
+
34
+ __commit_id__ = commit_id = 'gc97e3fcb2'
Binary file
seqeyes/bin/Qt6Gui.dll ADDED
Binary file
Binary file
seqeyes/bin/Qt6Svg.dll ADDED
Binary file
Binary file
Binary file
Binary file
seqeyes/py.typed ADDED
File without changes
seqeyes/viewer.py ADDED
@@ -0,0 +1,120 @@
1
+ """Launch the SeqEyes GUI viewer from Python."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import shutil
7
+ import subprocess
8
+ import sys
9
+ import tempfile
10
+ from pathlib import Path
11
+
12
+ _exe_name = "seqeyes.exe" if sys.platform == "win32" else "seqeyes"
13
+ # Bundled binary installed by the Python wheel alongside this module.
14
+ _BUNDLED_EXE = Path(__file__).parent / "bin" / _exe_name
15
+
16
+
17
+ def _find_executable() -> str:
18
+ """Return the path to the seqeyes executable.
19
+
20
+ Checks (in order):
21
+ 1. Bundled binary shipped inside this Python package (``seqeyes/bin/``).
22
+ 2. ``seqeyes`` on the system ``PATH``.
23
+ """
24
+ if _BUNDLED_EXE.is_file():
25
+ return str(_BUNDLED_EXE)
26
+
27
+ exe = shutil.which("seqeyes")
28
+ if exe is None:
29
+ raise FileNotFoundError(
30
+ "SeqEyes executable not found. "
31
+ "Install a binary wheel via 'pip install seqeyes', "
32
+ "or add seqeyes to your PATH."
33
+ )
34
+ return exe
35
+
36
+
37
+ def seqeyes(*args) -> None:
38
+ """Launch the SeqEyes GUI viewer.
39
+
40
+ Mirrors the MATLAB ``seqeyes()`` wrapper. All preceding positional
41
+ arguments are passed as command-line options; the last positional
42
+ argument is the sequence source.
43
+
44
+ Parameters
45
+ ----------
46
+ *args :
47
+ Options followed by a sequence source. The source may be:
48
+
49
+ - a ``str`` or :class:`os.PathLike` path to a ``.seq`` file,
50
+ - an object with a ``write(filepath)`` method (e.g. a
51
+ `pypulseq <https://github.com/imr-framework/pypulseq>`_ sequence
52
+ object), or
53
+ - an option string starting with ``-`` (options-only call, e.g.
54
+ ``seqeyes.seqeyes('--help')``).
55
+
56
+ If called with no arguments the SeqEyes GUI opens with no file loaded.
57
+
58
+ Examples
59
+ --------
60
+ Open a ``.seq`` file:
61
+
62
+ >>> import seqeyes
63
+ >>> seqeyes.seqeyes('path/to/sequence.seq')
64
+
65
+ Open a pypulseq in-memory sequence:
66
+
67
+ >>> seqeyes.seqeyes(seq)
68
+
69
+ Pass extra CLI options before the source:
70
+
71
+ >>> seqeyes.seqeyes('--layout', '212', 'path/to/sequence.seq')
72
+
73
+ Raises
74
+ ------
75
+ FileNotFoundError
76
+ If the ``seqeyes`` executable cannot be found on ``PATH``.
77
+ FileNotFoundError
78
+ If a ``.seq`` filepath is given but the file does not exist.
79
+ TypeError
80
+ If the last argument is not a recognised sequence source.
81
+ """
82
+ exe = _find_executable()
83
+ cmd = [exe]
84
+
85
+ if not args:
86
+ # No-argument call: open GUI with no file loaded
87
+ subprocess.Popen(cmd)
88
+ return
89
+
90
+ last = args[-1]
91
+ options = list(args[:-1])
92
+ seq_fn = None
93
+ _tmp_path = None # keep tempfile path for reference (delete=False)
94
+
95
+ if isinstance(last, str) and last.startswith("-"):
96
+ # Options-only call (e.g. '--help')
97
+ options = list(args)
98
+ last = None
99
+ elif isinstance(last, (str, os.PathLike)):
100
+ seq_fn = os.fspath(last)
101
+ if not os.path.isfile(seq_fn):
102
+ raise FileNotFoundError(f"Seq file not found: {seq_fn}")
103
+ elif hasattr(last, "write"):
104
+ # Sequence object (e.g. pypulseq.Sequence) – write to a temp file
105
+ tmp = tempfile.NamedTemporaryFile(suffix=".seq", delete=False)
106
+ tmp.close()
107
+ _tmp_path = tmp.name
108
+ seq_fn = _tmp_path
109
+ last.write(seq_fn)
110
+ else:
111
+ raise TypeError(
112
+ "Last argument must be a .seq filepath, a sequence object with a "
113
+ f"write() method, or an option string. Got: {type(last)!r}"
114
+ )
115
+
116
+ cmd.extend(options)
117
+ if seq_fn:
118
+ cmd.append(seq_fn)
119
+
120
+ subprocess.Popen(cmd)
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.4
2
+ Name: seqeyes
3
+ Version: 0.0.4
4
+ Summary: Python wrapper for the SeqEyes Pulseq sequence viewer
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Provides-Extra: test
9
+ Requires-Dist: pytest; extra == "test"
10
+ Dynamic: license-file
11
+
12
+ # SeqEyes
13
+ Display Pulseq sequence diagram and k-space trajectory, modified from [PulseqViewer](https://github.com/xpjiang/PulseqViewer)
14
+
15
+ A brief overview of SeqEyes can be found at the [2026 pulseq virtual meeting](https://github.com/pulseq/ISMRM-Virtual-Meeting--February-24-26-2026/blob/main/slides/day2_Seqeyes_sequence_and_trajectory_viewer_tool.pdf).
16
+
17
+ ![image](./doc/ui.png)
18
+
19
+ ## Install
20
+ Download the compiled windows exe from [github releases](https://github.com/xingwangyong/seqeyes/releases), or from [artifacts in github actions](https://github.com/xingwangyong/seqeyes/actions). The latter is more frequently updated.
21
+
22
+ ## Usage
23
+ - Open GUI, load .seq file
24
+ - Use the command line interface
25
+ ```bash
26
+ seqeyes filename.seq
27
+ ```
28
+ for more options, see `seqeyes --help`
29
+ - Use the matlab wrapper `seqeyes.m`
30
+ ```matlab
31
+ seqeyes('path/to/sequence.seq');
32
+ ```
33
+ or
34
+ ```matlab
35
+ seqeyes(seq);
36
+ ```
37
+ - Use the python wrapper, install with `pip install seqeyes` and then:
38
+ ```python
39
+ import seqeyes
40
+ seqeyes.seqeyes('path/to/sequence.seq')
41
+ ```
42
+ or
43
+ ```python
44
+ seqeyes.seqeyes(seq)
45
+ ```
46
+
47
+ ## Build Instructions
48
+ Qt6 libraries and cmake are required to build the project.
49
+ ### Linux
50
+ Use the build.sh script to build the project.
51
+ ### Windows
52
+ ```
53
+ cmake -S . -B out/build/x64-Release
54
+ cmake --build out/build/x64-Release --config Release
55
+ ```
56
+ After compilation, run the following command to deploy Qt libraries:
57
+ ```bash
58
+ C:\Qt\6.5.3\msvc2019_64\bin\windeployqt.exe .\seqeyes.exe
59
+ ```
60
+
61
+ **Note**: Please use the full path to run windeployqt.exe, as the system may have multiple versions of Qt installed.
62
+
63
+ ## Known Issues
64
+
65
+ Please see [KNOWN_ISSUES.md](KNOWN_ISSUES.md) for a list of known issues and limitations.
66
+
67
+
68
+
69
+
70
+
71
+
72
+
@@ -0,0 +1,17 @@
1
+ seqeyes/__init__.py,sha256=mW_HaUrT8x3kQt6aVLJ3ufHLy2GUFYHBlVwJvn8-4l8,114
2
+ seqeyes/_version.py,sha256=Q-n6YkX67p4f-dqAuPqtFTK23GUZ_uVAoQBI6b5zZ0Y,746
3
+ seqeyes/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ seqeyes/viewer.py,sha256=pqh_mzimr6onvqZO_MNyafGM4GEepQ9_dqlbClZ_C4g,3660
5
+ seqeyes/bin/Qt6Core.dll,sha256=aXAm_ytILfFpeUvXsXpB-0fYelGHMFiMZoBF4fIaU9o,5793424
6
+ seqeyes/bin/Qt6Gui.dll,sha256=i1aFRnCILL8UI5Kf_mTyI1fOJBTs_opr8EHvqKJAi9E,8071824
7
+ seqeyes/bin/Qt6Network.dll,sha256=CwM7sE3UBSD67H2eS0Wg56WNksMCrx1QhptLrviN6nU,1373328
8
+ seqeyes/bin/Qt6Svg.dll,sha256=mDSqLPU771dLUN1Zeyg9jYOraCVkaBHOb-mjFA0n3W8,364688
9
+ seqeyes/bin/Qt6Widgets.dll,sha256=3c4mKU6q3UMLnyXy6qN6IVXplHiCOCzD7RSPolMixSo,6026384
10
+ seqeyes/bin/seqeyes.exe,sha256=Ad1V1K0WoJe5zg9MA1W45DXfJ-5bryfvndyP28JvkSU,1335808
11
+ seqeyes/bin/vc_redist.x64.exe,sha256=zA_w6x3D9RiK5jAPrvMr9b7rpL3W6ORFqRhAcglrcTs,25635768
12
+ seqeyes-0.0.4.dist-info/licenses/LICENSE,sha256=xBMh8RQt7ZZn6mlMNPKcfY8lBS3EWAA8BSpWcgMcDcA,1559
13
+ tests/test_viewer.py,sha256=ZxTwW4rm26cgc-Y-ts-VDVnCcnw8QQJcT4gm0iX7vPU,5897
14
+ seqeyes-0.0.4.dist-info/METADATA,sha256=yTKaLyYcP878xIRnWGCISrdasRXBy2H9TTTdR_3hUrI,2037
15
+ seqeyes-0.0.4.dist-info/WHEEL,sha256=QR8DNjG6Lr6bNErJWJgF4dP2dJ2N7NpY-BWly1OvcTM,97
16
+ seqeyes-0.0.4.dist-info/top_level.txt,sha256=BbsG2JoD0T4sWHA6tywWaGZvnpII5pA1Lp5iI1kl3X8,14
17
+ seqeyes-0.0.4.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-win_amd64
5
+
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2024, Zheng Liu
4
+ Copyright (c) 2025, Xingwang Yong
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,2 @@
1
+ seqeyes
2
+ tests
tests/test_viewer.py ADDED
@@ -0,0 +1,164 @@
1
+ """Tests for seqeyes.seqeyes() viewer launcher."""
2
+
3
+ import os
4
+ import pytest
5
+ from unittest.mock import patch, MagicMock
6
+
7
+
8
+ # ---------------------------------------------------------------------------
9
+ # Helpers
10
+ # ---------------------------------------------------------------------------
11
+
12
+ FAKE_EXE = "/usr/local/bin/seqeyes"
13
+
14
+
15
+ def _popen_mock():
16
+ """Return a mock that replaces subprocess.Popen."""
17
+ return MagicMock()
18
+
19
+
20
+ # ---------------------------------------------------------------------------
21
+ # Executable discovery
22
+ # ---------------------------------------------------------------------------
23
+
24
+ def test_seqeyes_raises_when_exe_not_found():
25
+ """FileNotFoundError when neither bundled binary nor PATH entry exists."""
26
+ from seqeyes.viewer import seqeyes
27
+ with patch("shutil.which", return_value=None), \
28
+ patch("pathlib.Path.is_file", return_value=False):
29
+ with pytest.raises(FileNotFoundError, match="executable not found"):
30
+ seqeyes("dummy.seq")
31
+
32
+
33
+ def test_find_executable_prefers_bundled(tmp_path):
34
+ """_find_executable returns the bundled binary when it exists."""
35
+ import sys
36
+ from seqeyes import viewer
37
+ from seqeyes.viewer import _find_executable
38
+
39
+ _exe_name = "seqeyes.exe" if sys.platform == "win32" else "seqeyes"
40
+ fake_bin = tmp_path / _exe_name
41
+ fake_bin.touch()
42
+
43
+ with patch.object(viewer, "_BUNDLED_EXE", fake_bin):
44
+ result = _find_executable()
45
+
46
+ assert result == str(fake_bin)
47
+
48
+
49
+ # ---------------------------------------------------------------------------
50
+ # No-argument call
51
+ # ---------------------------------------------------------------------------
52
+
53
+ def test_seqeyes_no_args_opens_gui(tmp_path):
54
+ """Calling seqeyes() with no args should Popen the bare executable."""
55
+ from seqeyes.viewer import seqeyes
56
+ popen = _popen_mock()
57
+ with patch("shutil.which", return_value=FAKE_EXE), \
58
+ patch("subprocess.Popen", popen):
59
+ seqeyes()
60
+ popen.assert_called_once_with([FAKE_EXE])
61
+
62
+
63
+ # ---------------------------------------------------------------------------
64
+ # File path argument
65
+ # ---------------------------------------------------------------------------
66
+
67
+ def test_seqeyes_filepath(tmp_path):
68
+ """seqeyes('file.seq') should Popen [exe, filepath]."""
69
+ from seqeyes.viewer import seqeyes
70
+ seq_file = tmp_path / "test.seq"
71
+ seq_file.write_text("[VERSION]\nmajor 1\nminor 4\n")
72
+
73
+ popen = _popen_mock()
74
+ with patch("shutil.which", return_value=FAKE_EXE), \
75
+ patch("subprocess.Popen", popen):
76
+ seqeyes(str(seq_file))
77
+ popen.assert_called_once_with([FAKE_EXE, str(seq_file)])
78
+
79
+
80
+ def test_seqeyes_filepath_not_found(tmp_path):
81
+ """seqeyes() should raise FileNotFoundError for a missing .seq file."""
82
+ from seqeyes.viewer import seqeyes
83
+ with patch("shutil.which", return_value=FAKE_EXE):
84
+ with pytest.raises(FileNotFoundError, match="not found"):
85
+ seqeyes(str(tmp_path / "missing.seq"))
86
+
87
+
88
+ def test_seqeyes_pathlike(tmp_path):
89
+ """seqeyes() should accept a pathlib.Path."""
90
+ from seqeyes.viewer import seqeyes
91
+ seq_file = tmp_path / "test.seq"
92
+ seq_file.write_text("[VERSION]\nmajor 1\nminor 4\n")
93
+
94
+ popen = _popen_mock()
95
+ with patch("shutil.which", return_value=FAKE_EXE), \
96
+ patch("subprocess.Popen", popen):
97
+ seqeyes(seq_file)
98
+ popen.assert_called_once_with([FAKE_EXE, str(seq_file)])
99
+
100
+
101
+ # ---------------------------------------------------------------------------
102
+ # Sequence object argument
103
+ # ---------------------------------------------------------------------------
104
+
105
+ def test_seqeyes_sequence_object(tmp_path):
106
+ """seqeyes(seq) should write to a temp file and Popen [exe, tempfile]."""
107
+ from seqeyes.viewer import seqeyes
108
+
109
+ written_paths = []
110
+
111
+ class FakeSeq:
112
+ def write(self, path):
113
+ written_paths.append(path)
114
+
115
+ popen = _popen_mock()
116
+ with patch("shutil.which", return_value=FAKE_EXE), \
117
+ patch("subprocess.Popen", popen):
118
+ seqeyes(FakeSeq())
119
+
120
+ assert len(written_paths) == 1
121
+ assert written_paths[0].endswith(".seq")
122
+ popen.assert_called_once()
123
+ call_args = popen.call_args[0][0]
124
+ assert call_args[0] == FAKE_EXE
125
+ assert call_args[-1] == written_paths[0]
126
+
127
+
128
+ # ---------------------------------------------------------------------------
129
+ # Options-only call
130
+ # ---------------------------------------------------------------------------
131
+
132
+ def test_seqeyes_help_option():
133
+ """seqeyes('--help') should pass --help to the executable."""
134
+ from seqeyes.viewer import seqeyes
135
+ popen = _popen_mock()
136
+ with patch("shutil.which", return_value=FAKE_EXE), \
137
+ patch("subprocess.Popen", popen):
138
+ seqeyes("--help")
139
+ popen.assert_called_once_with([FAKE_EXE, "--help"])
140
+
141
+
142
+ def test_seqeyes_options_before_file(tmp_path):
143
+ """seqeyes('--layout', '212', 'file.seq') passes options before filepath."""
144
+ from seqeyes.viewer import seqeyes
145
+ seq_file = tmp_path / "test.seq"
146
+ seq_file.write_text("[VERSION]\nmajor 1\nminor 4\n")
147
+
148
+ popen = _popen_mock()
149
+ with patch("shutil.which", return_value=FAKE_EXE), \
150
+ patch("subprocess.Popen", popen):
151
+ seqeyes("--layout", "212", str(seq_file))
152
+ popen.assert_called_once_with([FAKE_EXE, "--layout", "212", str(seq_file)])
153
+
154
+
155
+ # ---------------------------------------------------------------------------
156
+ # Bad argument
157
+ # ---------------------------------------------------------------------------
158
+
159
+ def test_seqeyes_bad_last_arg():
160
+ """seqeyes() should raise TypeError for an unrecognised last argument."""
161
+ from seqeyes.viewer import seqeyes
162
+ with patch("shutil.which", return_value=FAKE_EXE):
163
+ with pytest.raises(TypeError):
164
+ seqeyes(42)