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 +5 -0
- seqeyes/_version.py +34 -0
- seqeyes/bin/Qt6Core.dll +0 -0
- seqeyes/bin/Qt6Gui.dll +0 -0
- seqeyes/bin/Qt6Network.dll +0 -0
- seqeyes/bin/Qt6Svg.dll +0 -0
- seqeyes/bin/Qt6Widgets.dll +0 -0
- seqeyes/bin/seqeyes.exe +0 -0
- seqeyes/bin/vc_redist.x64.exe +0 -0
- seqeyes/py.typed +0 -0
- seqeyes/viewer.py +120 -0
- seqeyes-0.0.4.dist-info/METADATA +72 -0
- seqeyes-0.0.4.dist-info/RECORD +17 -0
- seqeyes-0.0.4.dist-info/WHEEL +5 -0
- seqeyes-0.0.4.dist-info/licenses/LICENSE +29 -0
- seqeyes-0.0.4.dist-info/top_level.txt +2 -0
- tests/test_viewer.py +164 -0
seqeyes/__init__.py
ADDED
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'
|
seqeyes/bin/Qt6Core.dll
ADDED
|
Binary file
|
seqeyes/bin/Qt6Gui.dll
ADDED
|
Binary file
|
|
Binary file
|
seqeyes/bin/Qt6Svg.dll
ADDED
|
Binary file
|
|
Binary file
|
seqeyes/bin/seqeyes.exe
ADDED
|
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
|
+

|
|
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,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.
|
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)
|