pulserver 0.1.2__tar.gz → 0.1.4__tar.gz
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.
- {pulserver-0.1.2 → pulserver-0.1.4}/.github/workflows/tags-release.yml +20 -20
- {pulserver-0.1.2 → pulserver-0.1.4}/PKG-INFO +2 -3
- {pulserver-0.1.2 → pulserver-0.1.4}/docs/api.rst +0 -1
- {pulserver-0.1.2 → pulserver-0.1.4}/docs/hands-on.ipynb +7 -7
- {pulserver-0.1.2 → pulserver-0.1.4}/pyproject.toml +1 -2
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/__init__.py +4 -3
- pulserver-0.1.4/src/pulserver/_apps/SPGR2D.py +23 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/_cli.py +20 -16
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/_core/__init__.py +1 -2
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/_core/_ceq.py +43 -161
- pulserver-0.1.4/src/pulserver/_core/_header.py +566 -0
- pulserver-0.1.4/src/pulserver/_core/_sequence.py +487 -0
- pulserver-0.1.4/src/pulserver/_opts.py +123 -0
- pulserver-0.1.4/src/pulserver/_safety/__init__.py +7 -0
- pulserver-0.1.4/src/pulserver/_safety/_gehc.py +302 -0
- pulserver-0.1.4/src/pulserver/_server/_server.py +276 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/_version.py +2 -2
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/__init__.py +1 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_misc/__init__.py +2 -0
- pulserver-0.1.4/src/pulserver/blocks/_misc/_calc_delay.py +53 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_misc/_make_spoiler_gradient.py +3 -4
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_readout/_make_line_readout.py +9 -4
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_rfpulse/__init__.py +2 -2
- pulserver-0.1.4/src/pulserver/blocks/_rfpulse/_make_slr_pulse.py +294 -0
- pulserver-0.1.4/src/pulserver/parsing/__init__.py +16 -0
- pulserver-0.1.4/src/pulserver/parsing/_base.py +298 -0
- pulserver-0.1.4/src/pulserver/parsing/_cartesian_params.py +204 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/plan/_iterators/_cartesian2D_iterator.py +1 -1
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/plan/_iterators/_noncartesian2D_iterator.py +1 -1
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/sequences/_design_2D_spgr.py +100 -42
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver.egg-info/PKG-INFO +2 -3
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver.egg-info/SOURCES.txt +12 -4
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver.egg-info/requires.txt +1 -2
- pulserver-0.1.4/tests/_core/test_header.py +146 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/tests/_server/test_server.py +53 -67
- pulserver-0.1.4/tests/parsing/test_paramsparser.py +71 -0
- pulserver-0.1.4/tests/test_opts.py +78 -0
- pulserver-0.1.2/src/pulserver/_apps/SPGR2D.py +0 -206
- pulserver-0.1.2/src/pulserver/_apps/SPGR3D.py +0 -184
- pulserver-0.1.2/src/pulserver/_apps/bar.py +0 -15
- pulserver-0.1.2/src/pulserver/_apps/foo.py +0 -15
- pulserver-0.1.2/src/pulserver/_core/_sequence.py +0 -313
- pulserver-0.1.2/src/pulserver/_server/_server.py +0 -197
- pulserver-0.1.2/tests/_core/test_seqparams.py +0 -110
- {pulserver-0.1.2 → pulserver-0.1.4}/.github/workflows/docker.yml +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/.github/workflows/style.yml +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/.github/workflows/test-ci.yml +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/.gitignore +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/LICENSE.txt +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/README.rst +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/docker/Dockerfile +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/docker/install_pulserver.sh +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/docs/Makefile +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/docs/_templates/autosummary/base.rst +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/docs/_templates/autosummary/class.rst +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/docs/_templates/autosummary/function.rst +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/docs/_templates/autosummary/module.rst +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/docs/binder/apt.txt +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/docs/binder/environment.yml +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/docs/client_server_architecture +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/docs/conf.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/docs/getting_started.rst +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/docs/index.rst +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/docs/misc/code_of_conduct.rst +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/docs/misc/contributors.rst +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/docs/misc/development.rst +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/docs/misc/license.rst +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/docs/requirements.txt +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/docs/sequence_representation +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/docs/sg_execution_times.rst +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/docs/sphinx_add_colab_link.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/setup.cfg +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/setup.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/_core/_autoseg.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/_server/__init__.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_grad/_make_spiral.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_phaseenc/__init__.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_phaseenc/_make_phase_blip.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_phaseenc/_make_phase_encoding.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_readout/__init__.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_readout/_make_spiral_readout.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_readout/rosette_readout.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_rfpulse/_make_spsp_pulse.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_rfpulse/_plot.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_rfpulse/_spsp/__init__.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_rfpulse/_spsp/_slr.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_rfpulse/_spsp/_utils.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_rfpulse/_spsp/rf_ripple.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_rfpulse/_spsp/spec_interp.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_rfpulse/_spsp/ss_alias.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_rfpulse/_spsp/ss_design.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_rfpulse/_spsp/ss_ep.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_rfpulse/_spsp/ss_filters.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_rfpulse/_spsp/ss_flyback.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_rfpulse/_spsp/ss_fourier.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_rfpulse/_spsp/ss_globals.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_rfpulse/_spsp/ss_grad.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_rfpulse/_spsp/ss_spect_correct.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/blocks/_rfpulse/_spsp/ss_verse.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/plan/__init__.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/plan/_cartesian2D.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/plan/_cartesian3D.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/plan/_iterators/__init__.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/plan/_iterators/_base.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/plan/_iterators/_cartesian3D_iterator.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/plan/_iterators/_noncartesian3D_iterator.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/plan/_iterators/_stack3D_iterator.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/plan/_noncartesian2D.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/plan/_noncartesian3D.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/plan/_ordering.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/plan/_phase_cycling.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/plan/_sampling/__init__.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/plan/_sampling/_grid_sampling.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/plan/_sampling/_partial_fourier.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/plan/_sampling/_poisson_sampling.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/plan/_sampling/_tilt.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/plan/_stack3D.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/sequences/__init__.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver/sequences/_design_3D_spgr.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver.egg-info/dependency_links.txt +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver.egg-info/entry_points.txt +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/src/pulserver.egg-info/top_level.txt +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/tests/_core/test_autoseg.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/tests/_core/test_ceq.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/tests/_core/test_sequence.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/tests/helpers/__init__.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/tests/helpers/asserts.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/tests/plan/test_cartesian.py +0 -0
- {pulserver-0.1.2 → pulserver-0.1.4}/tests/plan/test_noncartesian.py +0 -0
|
@@ -95,26 +95,26 @@ jobs:
|
|
|
95
95
|
'${{ github.ref_name }}' dist/**
|
|
96
96
|
--repo '${{ github.repository }}'
|
|
97
97
|
|
|
98
|
-
publish-to-testpypi:
|
|
99
|
-
name: Publish Python 🐍 distribution 📦 to TestPyPI
|
|
100
|
-
needs:
|
|
101
|
-
- build
|
|
102
|
-
runs-on: ubuntu-latest
|
|
98
|
+
# publish-to-testpypi:
|
|
99
|
+
# name: Publish Python 🐍 distribution 📦 to TestPyPI
|
|
100
|
+
# needs:
|
|
101
|
+
# - build
|
|
102
|
+
# runs-on: ubuntu-latest
|
|
103
103
|
|
|
104
|
-
environment:
|
|
105
|
-
name: testpypi
|
|
106
|
-
url: https://test.pypi.org/p/<package-name>
|
|
104
|
+
# environment:
|
|
105
|
+
# name: testpypi
|
|
106
|
+
# url: https://test.pypi.org/p/<package-name>
|
|
107
107
|
|
|
108
|
-
permissions:
|
|
109
|
-
id-token: write # IMPORTANT: mandatory for trusted publishing
|
|
108
|
+
# permissions:
|
|
109
|
+
# id-token: write # IMPORTANT: mandatory for trusted publishing
|
|
110
110
|
|
|
111
|
-
steps:
|
|
112
|
-
- name: Download all the dists
|
|
113
|
-
uses: actions/download-artifact@v4
|
|
114
|
-
with:
|
|
115
|
-
name: python-package-distributions
|
|
116
|
-
path: dist/
|
|
117
|
-
- name: Publish distribution 📦 to TestPyPI
|
|
118
|
-
uses: pypa/gh-action-pypi-publish@release/v1
|
|
119
|
-
with:
|
|
120
|
-
repository-url: https://test.pypi.org/legacy/
|
|
111
|
+
# steps:
|
|
112
|
+
# - name: Download all the dists
|
|
113
|
+
# uses: actions/download-artifact@v4
|
|
114
|
+
# with:
|
|
115
|
+
# name: python-package-distributions
|
|
116
|
+
# path: dist/
|
|
117
|
+
# - name: Publish distribution 📦 to TestPyPI
|
|
118
|
+
# uses: pypa/gh-action-pypi-publish@release/v1
|
|
119
|
+
# with:
|
|
120
|
+
# repository-url: https://test.pypi.org/legacy/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pulserver
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: A PyPulseq-based client/server MR sequence design toolbox.
|
|
5
5
|
Author-email: Matteo Cencini <matteo.cencini@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/INFN-MRI/pulserver
|
|
@@ -16,14 +16,13 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
16
16
|
Requires-Python: >=3.10
|
|
17
17
|
Description-Content-Type: text/x-rst
|
|
18
18
|
License-File: LICENSE.txt
|
|
19
|
-
Requires-Dist: pyyaml
|
|
20
19
|
Requires-Dist: numpy<2.0
|
|
21
20
|
Requires-Dist: scipy<1.14
|
|
22
21
|
Requires-Dist: numba
|
|
23
22
|
Requires-Dist: pypulseq
|
|
24
23
|
Requires-Dist: pulpy
|
|
25
24
|
Requires-Dist: mri-nufft
|
|
26
|
-
Requires-Dist:
|
|
25
|
+
Requires-Dist: mrd-python
|
|
27
26
|
Requires-Dist: ipywidgets
|
|
28
27
|
Provides-Extra: dev
|
|
29
28
|
Requires-Dist: black; extra == "dev"
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"cell_type": "markdown",
|
|
5
5
|
"metadata": {},
|
|
6
6
|
"source": [
|
|
7
|
-
"# Hands-on: a 2D
|
|
7
|
+
"# Hands-on: a 2D SPGR (Spoiled Gradient-Recalled Echo) Sequence Example\n",
|
|
8
8
|
"\n",
|
|
9
9
|
"This tutorial aims to illustrate the sequence design features of Pulserver through building a minimal 2D Spoiled Gradient Recalled Echo (2D SPGR) sequence.\n",
|
|
10
10
|
"We will demonstrate the built-in high-level design functions as well as show how to customize the sequence using lower-level routines, assuming a basic knowledge of the [(Py)Pulseq](https://github.com/imr-framework/pypulseq/tree/dev) formalism.\n",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"n_slices = 1 # Single slice acquisition\n",
|
|
31
31
|
"\n",
|
|
32
32
|
"flip_angle = 15.0 # Flip angle in degrees\n",
|
|
33
|
-
"slice_thickness = 5.0 #
|
|
33
|
+
"slice_thickness = 5.0 # Slice thickness in mm"
|
|
34
34
|
]
|
|
35
35
|
},
|
|
36
36
|
{
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"You can notice that gradient and slew rate are automatically converted to ``[Hz]`` and ``[Hz/m/s]``, respectively.\n",
|
|
85
85
|
"\n",
|
|
86
86
|
"## 2. Instantiate the Sequence object\n",
|
|
87
|
-
"Now, we instantiate the sequence object. This is designed to provide a similar use experience as native Pulseq's `Sequence` class, but is effectively a
|
|
87
|
+
"Now, we instantiate the sequence object. This is designed to provide a similar use experience as native Pulseq's `Sequence` class, but is effectively a `builder <(https://en.wikipedia.org/wiki/Builder_pattern>` we use to create the actual PyPulseq `Sequence` object or the equivalent representations for other Pulseq interpreters (for now, GEHC TOPPE). The aim is providing a familiar syntax to Pulseq users, while minimizing the computationally expensive conversion steps required to adapt the format to different platforms."
|
|
88
88
|
]
|
|
89
89
|
},
|
|
90
90
|
{
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"source": [
|
|
96
96
|
"seq = pulserver.Sequence(\n",
|
|
97
97
|
" system_limits, platform=\"pulseq\"\n",
|
|
98
|
-
") # '
|
|
98
|
+
") # 'platform' specifies the internal Sequence type"
|
|
99
99
|
]
|
|
100
100
|
},
|
|
101
101
|
{
|
|
@@ -253,7 +253,7 @@
|
|
|
253
253
|
"- **RF Excitation**:this consists of the simultaneous execution of an RF event and its accompanying slice selection gradient on z-axis.\n",
|
|
254
254
|
"- **Slice Rephasing**: this consists of the slice rephasing gradient event on z-axis.\n",
|
|
255
255
|
"- **Phase encoding**: this consists of simultaneous execution of the phase encoding gradient event on y-axis and the readout pre-winder event on the x-axis.\n",
|
|
256
|
-
"- **Readout**: this
|
|
256
|
+
"- **Readout**: this consists of the simultaneous execution of the readout gradient (x-axis) and the ADC event (to record the MR signal).\n",
|
|
257
257
|
"- **Rewinder**: this is a repetition of the **Phase encoding**, with a scaling factor equal to -1.0 for the y-axis gradient event (reverse polarity).\n",
|
|
258
258
|
"- **Spoiler**: this consists of the spoiling gradient event on z-axis.\n",
|
|
259
259
|
"\n",
|
|
@@ -363,7 +363,7 @@
|
|
|
363
363
|
"\n",
|
|
364
364
|
"Throughout the scan loop, the basic sequence structure is repeated while changing the phase $\\Phi_n$ phase of the RF pulse (RF spoiling), its frequency offset (to excite different slices) and the scaling of the y-gradient events in both **Phase encoding** and **Rewinder** blocks to sample different ky lines in k-space. The gradient amplitude scaling and RF phase and frequency offsets are examples of dynamic sequence parameters, which also include RF amplitude scaling, gradient rotations and variable delay times, that are used to control the image contrast and k-space sampling pattern.\n",
|
|
365
365
|
"\n",
|
|
366
|
-
"Hence, the user needs to create the schedule for these dynamic sequence parameters. As this require a lot of effort, especially to obtain specific sampling pattern for Parallel Imaging and Compressed Sensing (e.g., CAIPIRINHA and Poisson Disk sampling), we include support
|
|
366
|
+
"Hence, the user needs to create the schedule for these dynamic sequence parameters. As this require a lot of effort, especially to obtain specific sampling pattern for Parallel Imaging and Compressed Sensing (e.g., CAIPIRINHA and Poisson Disk sampling), we include support routines to 1) generate such sampling masks and RF modulation patterns and 2) retrieve the corresponding gradient / RF amplitude settings at each TR. These support routines are included in `pulserver.plan` subpackage.\n",
|
|
367
367
|
"\n",
|
|
368
368
|
"We now use this package to create a Cartesian sampling mask and the RF phase list required to obtain RF spoiling:"
|
|
369
369
|
]
|
|
@@ -670,7 +670,7 @@
|
|
|
670
670
|
" # initialize sequence object\n",
|
|
671
671
|
" seq = pulserver.Sequence(\n",
|
|
672
672
|
" system_limits, platform=\"pulseq\"\n",
|
|
673
|
-
" ) # '
|
|
673
|
+
" ) # 'platform' specifies the internal Sequence type\n",
|
|
674
674
|
"\n",
|
|
675
675
|
" # Define Blocks\n",
|
|
676
676
|
" # -------------\n",
|
|
@@ -5,13 +5,14 @@ __all__ = []
|
|
|
5
5
|
from pypulseq import Opts # noqa
|
|
6
6
|
|
|
7
7
|
from . import blocks # noqa
|
|
8
|
+
from . import parsing # noqa
|
|
8
9
|
from . import plan # noqa
|
|
9
10
|
from . import sequences # noqa
|
|
10
11
|
|
|
11
12
|
from . import _server # noqa
|
|
12
13
|
|
|
13
14
|
from ._core import Sequence # noqa
|
|
14
|
-
from .
|
|
15
|
+
from ._opts import get_opts # noqa
|
|
15
16
|
|
|
16
|
-
__all__.
|
|
17
|
-
__all__.extend(["Sequence", "
|
|
17
|
+
__all__.extend(["Opts", "get_opts"])
|
|
18
|
+
__all__.extend(["Sequence", "ParamsParser"])
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""2D Spoiled Gradient Echo sequence."""
|
|
2
|
+
|
|
3
|
+
__all__ = ["SPGR2D"]
|
|
4
|
+
|
|
5
|
+
from pulserver.parsing import Cartesian2DParams
|
|
6
|
+
from pulserver.sequences import design_2D_spgr
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def SPGR2D(kwargs):
|
|
10
|
+
"""
|
|
11
|
+
Generate a 2D Spoiled Gradient Recalled Echo (SPGR) pulse sequence.
|
|
12
|
+
|
|
13
|
+
This function wraps ``pulserver.sequences.design_2D_spgr`` - see
|
|
14
|
+
the corresponding docstrings for more information.
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
# parse parameters
|
|
18
|
+
params = Cartesian2DParams(**kwargs, fudge_factor=0.9)
|
|
19
|
+
|
|
20
|
+
# call design function (exclude header for now)
|
|
21
|
+
seq, _ = design_2D_spgr(**params.asdict(), platform="gehc")
|
|
22
|
+
|
|
23
|
+
return seq.export(), None
|
|
@@ -8,7 +8,7 @@ from . import _server
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
@click.group()
|
|
11
|
-
def cli():
|
|
11
|
+
def cli(): # noqa
|
|
12
12
|
pass
|
|
13
13
|
|
|
14
14
|
|
|
@@ -22,24 +22,25 @@ def cli():
|
|
|
22
22
|
@click.option("--recon-server-port", default=None, help="Reconstruction server port.")
|
|
23
23
|
def start(
|
|
24
24
|
config_path, scanner_address, scanner_port, recon_server_address, recon_server_port
|
|
25
|
-
):
|
|
25
|
+
): # noqa
|
|
26
26
|
"""
|
|
27
27
|
Start sequence design server.
|
|
28
28
|
"""
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
29
|
+
# Get configuration
|
|
30
|
+
config = _server.load_config()
|
|
31
|
+
|
|
32
|
+
# Replace with provided arguments
|
|
33
|
+
if scanner_address is not None:
|
|
34
|
+
os.environ["SCANNER_ADDRESS"] = scanner_address
|
|
35
|
+
if scanner_port is not None:
|
|
36
|
+
os.environ["SCANNER_PORT"] = scanner_port
|
|
37
|
+
if recon_server_address is not None:
|
|
38
|
+
os.environ["RECON_SERVER_ADDRESS"] = recon_server_address
|
|
39
|
+
if recon_server_port is not None:
|
|
40
|
+
os.environ["RECON_SERVER_PORT"] = recon_server_port
|
|
40
41
|
|
|
41
42
|
# start the server
|
|
42
|
-
_server.start_server()
|
|
43
|
+
_server.start_server(config)
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
@click.command()
|
|
@@ -48,12 +49,15 @@ def start(
|
|
|
48
49
|
default=None,
|
|
49
50
|
help="Function whose docstring we want to see. If not provided, print list of available commands.",
|
|
50
51
|
)
|
|
51
|
-
def apps(name):
|
|
52
|
+
def apps(name): # noqa
|
|
52
53
|
"""
|
|
53
54
|
Design function documentations
|
|
54
55
|
"""
|
|
56
|
+
# Get configuration
|
|
57
|
+
config = _server.load_config()
|
|
58
|
+
|
|
55
59
|
# Load plugin list
|
|
56
|
-
plugins = _server.load_plugins()
|
|
60
|
+
plugins = _server.load_plugins(config)
|
|
57
61
|
if name is None:
|
|
58
62
|
for fun in plugins.values():
|
|
59
63
|
name = fun.__name__
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
"""Ceq structure definition."""
|
|
2
2
|
|
|
3
|
-
__all__ = ["Ceq", "PulseqBlock"
|
|
3
|
+
__all__ = ["Ceq", "PulseqBlock"]
|
|
4
4
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
-
from dataclasses import asdict as _asdict
|
|
7
6
|
from types import SimpleNamespace
|
|
8
|
-
from typing import Optional
|
|
9
7
|
|
|
10
8
|
import struct
|
|
11
9
|
import numpy as np
|
|
@@ -37,7 +35,7 @@ class PulseqShapeArbitrary:
|
|
|
37
35
|
|
|
38
36
|
# determine amplitude and normalize waveform
|
|
39
37
|
self.amplitude = abs(self.magnitude).max()
|
|
40
|
-
self.magnitude
|
|
38
|
+
self.magnitude /= self.amplitude
|
|
41
39
|
|
|
42
40
|
def to_bytes(self, endian=">") -> bytes:
|
|
43
41
|
_bytes = struct.pack(endian + "i", self.n_samples) + struct.pack(
|
|
@@ -80,6 +78,7 @@ class PulseqShapeTrap:
|
|
|
80
78
|
@dataclass
|
|
81
79
|
class PulseqRF:
|
|
82
80
|
type: int
|
|
81
|
+
complexflag: int
|
|
83
82
|
wav: PulseqShapeArbitrary
|
|
84
83
|
duration: float
|
|
85
84
|
delay: float
|
|
@@ -87,6 +86,7 @@ class PulseqRF:
|
|
|
87
86
|
def to_bytes(self, endian=">") -> bytes:
|
|
88
87
|
return (
|
|
89
88
|
struct.pack(endian + "h", self.type)
|
|
89
|
+
+ struct.pack(endian + "h", self.complexflag)
|
|
90
90
|
+ self.wav.to_bytes(endian)
|
|
91
91
|
+ struct.pack(endian + "f", self.duration)
|
|
92
92
|
+ struct.pack(endian + "f", self.delay)
|
|
@@ -108,11 +108,21 @@ class PulseqRF:
|
|
|
108
108
|
raster = 0.0
|
|
109
109
|
time = data.t
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
sum_rf_real = sum(abs(data.signal.real))
|
|
112
|
+
sum_rf_imag = sum(abs(data.signal.imag))
|
|
113
|
+
|
|
114
|
+
if sum_rf_real > 100 * sum_rf_imag:
|
|
115
|
+
complexflag = 0
|
|
116
|
+
rho = data.signal.real
|
|
117
|
+
theta = None
|
|
118
|
+
else:
|
|
119
|
+
complexflag = 1
|
|
120
|
+
rho = np.abs(data.signal)
|
|
121
|
+
theta = np.angle(data.signal)
|
|
113
122
|
|
|
114
123
|
return cls(
|
|
115
124
|
type=type,
|
|
125
|
+
complexflag=complexflag,
|
|
116
126
|
wav=PulseqShapeArbitrary(n_samples, raster, time, rho, theta),
|
|
117
127
|
duration=data.shape_dur,
|
|
118
128
|
delay=data.delay,
|
|
@@ -134,7 +144,6 @@ class PulseqGrad:
|
|
|
134
144
|
|
|
135
145
|
@classmethod
|
|
136
146
|
def from_struct(cls, data: SimpleNamespace) -> "PulseqGrad":
|
|
137
|
-
print(data)
|
|
138
147
|
if data.type == "trap":
|
|
139
148
|
type = 1
|
|
140
149
|
shape_obj = PulseqShapeTrap(
|
|
@@ -147,12 +156,10 @@ class PulseqGrad:
|
|
|
147
156
|
dt = np.unique(np.diff(np.round(data.tt * 1e6))) / 1e6
|
|
148
157
|
|
|
149
158
|
if len(dt) == 1: # uniform raster -> arbitrary shape
|
|
150
|
-
print("arbitrary")
|
|
151
159
|
type = 2
|
|
152
160
|
raster = dt.item()
|
|
153
161
|
time = None
|
|
154
162
|
else: # non-uniform raster -> extended trapezoid
|
|
155
|
-
print("extended")
|
|
156
163
|
type = 3
|
|
157
164
|
raster = 0.0
|
|
158
165
|
time = data.tt
|
|
@@ -300,32 +307,41 @@ class Ceq:
|
|
|
300
307
|
segments = _build_segments(loop, sections_edges)
|
|
301
308
|
|
|
302
309
|
# build CEQ structure
|
|
303
|
-
self.n_max = loop.shape[0]
|
|
304
310
|
self.n_parent_blocks = len(parent_blocks)
|
|
305
|
-
self.n_segments = len(segments)
|
|
306
311
|
self.parent_blocks = parent_blocks
|
|
312
|
+
self.n_segments = len(segments)
|
|
307
313
|
self.segments = segments
|
|
314
|
+
self.n_max = loop.shape[0]
|
|
308
315
|
self.n_columns_in_loop_array = loop.shape[1] - 2 # discard "hasrot", "hasadc"
|
|
309
316
|
self.loop = loop[:, :-2]
|
|
317
|
+
|
|
318
|
+
# Safety, RF scaling and scan duration info
|
|
319
|
+
self.max_rf_power = 0.0
|
|
310
320
|
self.max_b1 = _find_b1_max(parent_blocks)
|
|
321
|
+
self.max_grad = _find_grad_max(parent_blocks)
|
|
322
|
+
self.max_slew = _find_slew_max(parent_blocks)
|
|
311
323
|
self.duration = _calc_duration(self.loop[:, 0], self.loop[:, 9])
|
|
312
324
|
self.n_readouts = int(np.sum(loop[:, -2]))
|
|
325
|
+
self.n_gain = 0
|
|
313
326
|
|
|
314
327
|
def to_bytes(self, endian=">") -> bytes: # noqa
|
|
315
|
-
bytes_data = (
|
|
316
|
-
struct.pack(endian + "i", self.n_max)
|
|
317
|
-
+ struct.pack(endian + "h", self.n_parent_blocks)
|
|
318
|
-
+ struct.pack(endian + "h", self.n_segments)
|
|
319
|
-
)
|
|
328
|
+
bytes_data = struct.pack(endian + "h", self.n_parent_blocks)
|
|
320
329
|
for block in self.parent_blocks:
|
|
321
330
|
bytes_data += block.to_bytes(endian)
|
|
331
|
+
bytes_data += struct.pack(endian + "h", self.n_segments)
|
|
322
332
|
for segment in self.segments:
|
|
323
333
|
bytes_data += segment.to_bytes(endian)
|
|
334
|
+
bytes_data += struct.pack(endian + "i", self.n_max)
|
|
324
335
|
bytes_data += struct.pack(endian + "h", self.n_columns_in_loop_array)
|
|
325
336
|
bytes_data += self.loop.astype(endian + "f4").tobytes()
|
|
337
|
+
|
|
338
|
+
bytes_data += struct.pack(endian + "f", self.max_rf_power)
|
|
326
339
|
bytes_data += struct.pack(endian + "f", self.max_b1)
|
|
340
|
+
bytes_data += struct.pack(endian + "f", self.max_grad)
|
|
341
|
+
bytes_data += struct.pack(endian + "f", self.max_slew)
|
|
327
342
|
bytes_data += struct.pack(endian + "f", self.duration)
|
|
328
343
|
bytes_data += struct.pack(endian + "i", self.n_readouts)
|
|
344
|
+
bytes_data += struct.pack(endian + "i", self.n_gain)
|
|
329
345
|
|
|
330
346
|
return bytes_data
|
|
331
347
|
|
|
@@ -336,150 +352,6 @@ class Ceq:
|
|
|
336
352
|
return self.to_bytes(endian="<")
|
|
337
353
|
|
|
338
354
|
|
|
339
|
-
@dataclass
|
|
340
|
-
class SequenceParams:
|
|
341
|
-
"""
|
|
342
|
-
Python representation of the C SequenceParams struct.
|
|
343
|
-
|
|
344
|
-
Attributes
|
|
345
|
-
----------
|
|
346
|
-
FOVx : Optional[float]
|
|
347
|
-
Field of view in mm (x-direction).
|
|
348
|
-
FOVy : Optional[float]
|
|
349
|
-
Field of view in mm (y-direction).
|
|
350
|
-
Nx : Optional[int]
|
|
351
|
-
Matrix size (x-direction).
|
|
352
|
-
Ny : Optional[int]
|
|
353
|
-
Matrix size (y-direction).
|
|
354
|
-
Nslices : Optional[int]
|
|
355
|
-
Number of slices.
|
|
356
|
-
Nechoes : Optional[int]
|
|
357
|
-
Number of echoes.
|
|
358
|
-
Nphases : Optional[int]
|
|
359
|
-
Number of phases.
|
|
360
|
-
slice_thickness : Optional[float]
|
|
361
|
-
Thickness of each slice (mm).
|
|
362
|
-
slice_spacing : Optional[float]
|
|
363
|
-
Spacing between slices (mm).
|
|
364
|
-
Rplane : Optional[float]
|
|
365
|
-
In-plane undersampling factor.
|
|
366
|
-
Rplane2 : Optional[float]
|
|
367
|
-
Additional in-plane undersampling factor.
|
|
368
|
-
Rslice : Optional[float]
|
|
369
|
-
Through-plane undersampling factor.
|
|
370
|
-
PFplane : Optional[float]
|
|
371
|
-
In-plane partial fourier.
|
|
372
|
-
PFslice : Optional[float]
|
|
373
|
-
Through-plane partial fourier.
|
|
374
|
-
ETL : Optional[int]
|
|
375
|
-
Number of k-space shots per readout.
|
|
376
|
-
TE : Optional[float]
|
|
377
|
-
Echo time (ms).
|
|
378
|
-
TE0 : Optional[float]
|
|
379
|
-
First echo time (ms) for multiecho.
|
|
380
|
-
TR : Optional[float]
|
|
381
|
-
Repetition time (ms).
|
|
382
|
-
Tprep : Optional[float]
|
|
383
|
-
Preparation time (ms).
|
|
384
|
-
Trecovery : Optional[float]
|
|
385
|
-
Recovery time (ms).
|
|
386
|
-
flip : Optional[float]
|
|
387
|
-
Flip angle in degrees.
|
|
388
|
-
flip2 : Optional[float]
|
|
389
|
-
Second flip angle in degrees.
|
|
390
|
-
refoc_flip : Optional[float]
|
|
391
|
-
Refocusing flip angle in degrees.
|
|
392
|
-
freq_dir : Optional[int]
|
|
393
|
-
Frequency direction (0: A/P; 1: S/L).
|
|
394
|
-
freq_verse : Optional[int]
|
|
395
|
-
Frequency verse (1: normal; -1: swapped).
|
|
396
|
-
phase_verse : Optional[int]
|
|
397
|
-
Phase verse (1: normal; -1: swapped).
|
|
398
|
-
bipolar_echoes : Optional[int]
|
|
399
|
-
Bipolar echoes (0: false, 1: true).
|
|
400
|
-
dwell : Optional[float]
|
|
401
|
-
ADC dwell time (s).
|
|
402
|
-
raster : Optional[float]
|
|
403
|
-
Waveform raster time (s).
|
|
404
|
-
gmax : Optional[float]
|
|
405
|
-
Maximum gradient strength (mT/m).
|
|
406
|
-
smax : Optional[float]
|
|
407
|
-
Maximum gradient slew rate (T/m/s).
|
|
408
|
-
b1_max : Optional[float]
|
|
409
|
-
Maximum RF value (uT).
|
|
410
|
-
b0_field : Optional[float]
|
|
411
|
-
System field strength (T).
|
|
412
|
-
"""
|
|
413
|
-
|
|
414
|
-
FOVx: float | None = None
|
|
415
|
-
FOVy: float | None = None
|
|
416
|
-
Nx: int | None = None
|
|
417
|
-
Ny: int | None = None
|
|
418
|
-
Nslices: int | None = None
|
|
419
|
-
Nechoes: int | None = None
|
|
420
|
-
Nphases: int | None = None
|
|
421
|
-
slice_thickness: float | None = None
|
|
422
|
-
slice_spacing: float | None = None
|
|
423
|
-
Rplane: float | None = None
|
|
424
|
-
Rplane2: float | None = None
|
|
425
|
-
Rslice: float | None = None
|
|
426
|
-
PFplane: float | None = None
|
|
427
|
-
PFslice: float | None = None
|
|
428
|
-
ETL: int | None = None
|
|
429
|
-
TE: float | None = None
|
|
430
|
-
TE0: float | None = None
|
|
431
|
-
TR: float | None = None
|
|
432
|
-
Tprep: float | None = None
|
|
433
|
-
Trecovery: float | None = None
|
|
434
|
-
flip: float | None = None
|
|
435
|
-
flip2: float | None = None
|
|
436
|
-
refoc_flip: float | None = None
|
|
437
|
-
freq_dir: int | None = None
|
|
438
|
-
freq_verse: int | None = None
|
|
439
|
-
phase_verse: int | None = None
|
|
440
|
-
bipolar_echoes: int | None = None
|
|
441
|
-
dwell: float | None = None
|
|
442
|
-
raster: float | None = None
|
|
443
|
-
gmax: float | None = None
|
|
444
|
-
smax: float | None = None
|
|
445
|
-
b1_max: float | None = None
|
|
446
|
-
b0_field: float | None = None
|
|
447
|
-
|
|
448
|
-
@classmethod
|
|
449
|
-
def from_bytes(cls, data: bytes) -> "SequenceParams":
|
|
450
|
-
"""
|
|
451
|
-
Deserialize from a byte array into a SequenceParams object.
|
|
452
|
-
|
|
453
|
-
Parameters
|
|
454
|
-
----------
|
|
455
|
-
data : bytes
|
|
456
|
-
A byte array representing a serialized SequenceParams object.
|
|
457
|
-
|
|
458
|
-
Returns
|
|
459
|
-
-------
|
|
460
|
-
SequenceParams
|
|
461
|
-
A SequenceParams instance with attributes filled.
|
|
462
|
-
"""
|
|
463
|
-
format_string = "2f 5h 2f 5f h 5f 3f 3h h 6f"
|
|
464
|
-
unpacked = struct.unpack(format_string, data)
|
|
465
|
-
|
|
466
|
-
# Replace -1 values with None
|
|
467
|
-
unpacked = [None if x == -1 or x == -1.0 else x for x in unpacked]
|
|
468
|
-
|
|
469
|
-
return cls(*unpacked)
|
|
470
|
-
|
|
471
|
-
def asdict(self) -> dict:
|
|
472
|
-
"""
|
|
473
|
-
Return a dictionary of the dataclass, excluding None values.
|
|
474
|
-
|
|
475
|
-
Returns
|
|
476
|
-
-------
|
|
477
|
-
dict
|
|
478
|
-
A dictionary of the dataclass fields, excluding None values.
|
|
479
|
-
"""
|
|
480
|
-
return {k: v for k, v in _asdict(self).items() if v is not None}
|
|
481
|
-
|
|
482
|
-
|
|
483
355
|
# %% local subroutines
|
|
484
356
|
def _build_segments(loop, sections_edges):
|
|
485
357
|
hasrot = np.ascontiguousarray(loop[:, -1]).astype(int)
|
|
@@ -531,11 +403,21 @@ def _find_b1_max(parent_blocks):
|
|
|
531
403
|
)
|
|
532
404
|
|
|
533
405
|
|
|
406
|
+
# TODO: implement this
|
|
407
|
+
def _find_grad_max(parent_blocks):
|
|
408
|
+
return 0.0
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
# TODO: implement this
|
|
412
|
+
def _find_slew_max(parent_blocks):
|
|
413
|
+
return 0.0
|
|
414
|
+
|
|
415
|
+
|
|
534
416
|
def _calc_duration(segment_id, block_duration):
|
|
535
417
|
block_duration = block_duration.sum()
|
|
536
418
|
|
|
537
419
|
# total segment ringdown
|
|
538
|
-
n_seg_boundaries = (np.diff(segment_id) != 0).sum()
|
|
420
|
+
n_seg_boundaries = (np.diff(segment_id + 1) != 0).sum()
|
|
539
421
|
seg_ringdown_duration = SEGMENT_RINGDOWN_TIME * n_seg_boundaries
|
|
540
422
|
|
|
541
423
|
return block_duration + seg_ringdown_duration
|