pulserver 0.1.2__tar.gz → 0.1.3__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.
Files changed (129) hide show
  1. {pulserver-0.1.2 → pulserver-0.1.3}/.github/workflows/tags-release.yml +20 -20
  2. {pulserver-0.1.2 → pulserver-0.1.3}/PKG-INFO +2 -3
  3. {pulserver-0.1.2 → pulserver-0.1.3}/docs/api.rst +0 -1
  4. {pulserver-0.1.2 → pulserver-0.1.3}/docs/hands-on.ipynb +7 -7
  5. {pulserver-0.1.2 → pulserver-0.1.3}/pyproject.toml +1 -2
  6. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/__init__.py +4 -3
  7. pulserver-0.1.3/src/pulserver/_apps/SPGR2D.py +23 -0
  8. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/_cli.py +20 -16
  9. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/_core/__init__.py +1 -2
  10. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/_core/_ceq.py +43 -161
  11. pulserver-0.1.3/src/pulserver/_core/_header.py +566 -0
  12. pulserver-0.1.3/src/pulserver/_core/_sequence.py +487 -0
  13. pulserver-0.1.3/src/pulserver/_opts.py +123 -0
  14. pulserver-0.1.3/src/pulserver/_safety/__init__.py +7 -0
  15. pulserver-0.1.3/src/pulserver/_safety/_gehc.py +302 -0
  16. pulserver-0.1.3/src/pulserver/_server/_server.py +276 -0
  17. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/_version.py +2 -2
  18. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/__init__.py +1 -0
  19. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_misc/__init__.py +2 -0
  20. pulserver-0.1.3/src/pulserver/blocks/_misc/_calc_delay.py +53 -0
  21. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_misc/_make_spoiler_gradient.py +3 -4
  22. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_readout/_make_line_readout.py +9 -4
  23. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_rfpulse/__init__.py +2 -2
  24. pulserver-0.1.3/src/pulserver/blocks/_rfpulse/_make_slr_pulse.py +294 -0
  25. pulserver-0.1.3/src/pulserver/parsing/__init__.py +16 -0
  26. pulserver-0.1.3/src/pulserver/parsing/_base.py +299 -0
  27. pulserver-0.1.3/src/pulserver/parsing/_cartesian_params.py +204 -0
  28. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/plan/_iterators/_cartesian2D_iterator.py +1 -1
  29. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/plan/_iterators/_noncartesian2D_iterator.py +1 -1
  30. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/sequences/_design_2D_spgr.py +100 -42
  31. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver.egg-info/PKG-INFO +2 -3
  32. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver.egg-info/SOURCES.txt +12 -4
  33. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver.egg-info/requires.txt +1 -2
  34. pulserver-0.1.3/tests/_core/test_header.py +146 -0
  35. {pulserver-0.1.2 → pulserver-0.1.3}/tests/_server/test_server.py +53 -67
  36. pulserver-0.1.3/tests/parsing/test_paramsparser.py +71 -0
  37. pulserver-0.1.3/tests/test_opts.py +78 -0
  38. pulserver-0.1.2/src/pulserver/_apps/SPGR2D.py +0 -206
  39. pulserver-0.1.2/src/pulserver/_apps/SPGR3D.py +0 -184
  40. pulserver-0.1.2/src/pulserver/_apps/bar.py +0 -15
  41. pulserver-0.1.2/src/pulserver/_apps/foo.py +0 -15
  42. pulserver-0.1.2/src/pulserver/_core/_sequence.py +0 -313
  43. pulserver-0.1.2/src/pulserver/_server/_server.py +0 -197
  44. pulserver-0.1.2/tests/_core/test_seqparams.py +0 -110
  45. {pulserver-0.1.2 → pulserver-0.1.3}/.github/workflows/docker.yml +0 -0
  46. {pulserver-0.1.2 → pulserver-0.1.3}/.github/workflows/style.yml +0 -0
  47. {pulserver-0.1.2 → pulserver-0.1.3}/.github/workflows/test-ci.yml +0 -0
  48. {pulserver-0.1.2 → pulserver-0.1.3}/.gitignore +0 -0
  49. {pulserver-0.1.2 → pulserver-0.1.3}/LICENSE.txt +0 -0
  50. {pulserver-0.1.2 → pulserver-0.1.3}/README.rst +0 -0
  51. {pulserver-0.1.2 → pulserver-0.1.3}/docker/Dockerfile +0 -0
  52. {pulserver-0.1.2 → pulserver-0.1.3}/docker/install_pulserver.sh +0 -0
  53. {pulserver-0.1.2 → pulserver-0.1.3}/docs/Makefile +0 -0
  54. {pulserver-0.1.2 → pulserver-0.1.3}/docs/_templates/autosummary/base.rst +0 -0
  55. {pulserver-0.1.2 → pulserver-0.1.3}/docs/_templates/autosummary/class.rst +0 -0
  56. {pulserver-0.1.2 → pulserver-0.1.3}/docs/_templates/autosummary/function.rst +0 -0
  57. {pulserver-0.1.2 → pulserver-0.1.3}/docs/_templates/autosummary/module.rst +0 -0
  58. {pulserver-0.1.2 → pulserver-0.1.3}/docs/binder/apt.txt +0 -0
  59. {pulserver-0.1.2 → pulserver-0.1.3}/docs/binder/environment.yml +0 -0
  60. {pulserver-0.1.2 → pulserver-0.1.3}/docs/client_server_architecture +0 -0
  61. {pulserver-0.1.2 → pulserver-0.1.3}/docs/conf.py +0 -0
  62. {pulserver-0.1.2 → pulserver-0.1.3}/docs/getting_started.rst +0 -0
  63. {pulserver-0.1.2 → pulserver-0.1.3}/docs/index.rst +0 -0
  64. {pulserver-0.1.2 → pulserver-0.1.3}/docs/misc/code_of_conduct.rst +0 -0
  65. {pulserver-0.1.2 → pulserver-0.1.3}/docs/misc/contributors.rst +0 -0
  66. {pulserver-0.1.2 → pulserver-0.1.3}/docs/misc/development.rst +0 -0
  67. {pulserver-0.1.2 → pulserver-0.1.3}/docs/misc/license.rst +0 -0
  68. {pulserver-0.1.2 → pulserver-0.1.3}/docs/requirements.txt +0 -0
  69. {pulserver-0.1.2 → pulserver-0.1.3}/docs/sequence_representation +0 -0
  70. {pulserver-0.1.2 → pulserver-0.1.3}/docs/sg_execution_times.rst +0 -0
  71. {pulserver-0.1.2 → pulserver-0.1.3}/docs/sphinx_add_colab_link.py +0 -0
  72. {pulserver-0.1.2 → pulserver-0.1.3}/setup.cfg +0 -0
  73. {pulserver-0.1.2 → pulserver-0.1.3}/setup.py +0 -0
  74. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/_core/_autoseg.py +0 -0
  75. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/_server/__init__.py +0 -0
  76. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_grad/_make_spiral.py +0 -0
  77. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_phaseenc/__init__.py +0 -0
  78. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_phaseenc/_make_phase_blip.py +0 -0
  79. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_phaseenc/_make_phase_encoding.py +0 -0
  80. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_readout/__init__.py +0 -0
  81. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_readout/_make_spiral_readout.py +0 -0
  82. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_readout/rosette_readout.py +0 -0
  83. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_rfpulse/_make_spsp_pulse.py +0 -0
  84. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_rfpulse/_plot.py +0 -0
  85. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_rfpulse/_spsp/__init__.py +0 -0
  86. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_rfpulse/_spsp/_slr.py +0 -0
  87. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_rfpulse/_spsp/_utils.py +0 -0
  88. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_rfpulse/_spsp/rf_ripple.py +0 -0
  89. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_rfpulse/_spsp/spec_interp.py +0 -0
  90. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_rfpulse/_spsp/ss_alias.py +0 -0
  91. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_rfpulse/_spsp/ss_design.py +0 -0
  92. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_rfpulse/_spsp/ss_ep.py +0 -0
  93. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_rfpulse/_spsp/ss_filters.py +0 -0
  94. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_rfpulse/_spsp/ss_flyback.py +0 -0
  95. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_rfpulse/_spsp/ss_fourier.py +0 -0
  96. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_rfpulse/_spsp/ss_globals.py +0 -0
  97. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_rfpulse/_spsp/ss_grad.py +0 -0
  98. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_rfpulse/_spsp/ss_spect_correct.py +0 -0
  99. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/blocks/_rfpulse/_spsp/ss_verse.py +0 -0
  100. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/plan/__init__.py +0 -0
  101. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/plan/_cartesian2D.py +0 -0
  102. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/plan/_cartesian3D.py +0 -0
  103. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/plan/_iterators/__init__.py +0 -0
  104. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/plan/_iterators/_base.py +0 -0
  105. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/plan/_iterators/_cartesian3D_iterator.py +0 -0
  106. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/plan/_iterators/_noncartesian3D_iterator.py +0 -0
  107. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/plan/_iterators/_stack3D_iterator.py +0 -0
  108. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/plan/_noncartesian2D.py +0 -0
  109. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/plan/_noncartesian3D.py +0 -0
  110. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/plan/_ordering.py +0 -0
  111. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/plan/_phase_cycling.py +0 -0
  112. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/plan/_sampling/__init__.py +0 -0
  113. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/plan/_sampling/_grid_sampling.py +0 -0
  114. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/plan/_sampling/_partial_fourier.py +0 -0
  115. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/plan/_sampling/_poisson_sampling.py +0 -0
  116. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/plan/_sampling/_tilt.py +0 -0
  117. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/plan/_stack3D.py +0 -0
  118. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/sequences/__init__.py +0 -0
  119. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver/sequences/_design_3D_spgr.py +0 -0
  120. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver.egg-info/dependency_links.txt +0 -0
  121. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver.egg-info/entry_points.txt +0 -0
  122. {pulserver-0.1.2 → pulserver-0.1.3}/src/pulserver.egg-info/top_level.txt +0 -0
  123. {pulserver-0.1.2 → pulserver-0.1.3}/tests/_core/test_autoseg.py +0 -0
  124. {pulserver-0.1.2 → pulserver-0.1.3}/tests/_core/test_ceq.py +0 -0
  125. {pulserver-0.1.2 → pulserver-0.1.3}/tests/_core/test_sequence.py +0 -0
  126. {pulserver-0.1.2 → pulserver-0.1.3}/tests/helpers/__init__.py +0 -0
  127. {pulserver-0.1.2 → pulserver-0.1.3}/tests/helpers/asserts.py +0 -0
  128. {pulserver-0.1.2 → pulserver-0.1.3}/tests/plan/test_cartesian.py +0 -0
  129. {pulserver-0.1.2 → pulserver-0.1.3}/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.2
3
+ Version: 0.1.3
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: ismrmrd
25
+ Requires-Dist: mrd-python
27
26
  Requires-Dist: ipywidgets
28
27
  Provides-Extra: dev
29
28
  Requires-Dist: black; extra == "dev"
@@ -10,7 +10,6 @@ Core sequence representation.
10
10
  :nosignatures:
11
11
 
12
12
  pulserver.Sequence
13
- pulserver.SequenceParams
14
13
  pulserver.Opts
15
14
 
16
15
  Blocks
@@ -4,7 +4,7 @@
4
4
  "cell_type": "markdown",
5
5
  "metadata": {},
6
6
  "source": [
7
- "# Hands-on: a 2D GRE (Gradient-Recalled Echo) Sequence Example\n",
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 # Slab thickness in mm"
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 [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 conversions steps required to adapt the format to different platforms."
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
- ") # 'format' specify the internal Sequence"
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 block consists of the simultaneous execution of the readout gradient (x-axis) and the ADC event (to record the MR signal).\n",
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 routine 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",
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
- " ) # 'format' specify the internal Sequence\n",
673
+ " ) # 'platform' specifies the internal Sequence type\n",
674
674
  "\n",
675
675
  " # Define Blocks\n",
676
676
  " # -------------\n",
@@ -41,14 +41,13 @@ classifiers = [ # Optional
41
41
  ]
42
42
 
43
43
  dependencies = [
44
- "pyyaml",
45
44
  "numpy<2.0",
46
45
  "scipy<1.14",
47
46
  "numba",
48
47
  "pypulseq",
49
48
  "pulpy",
50
49
  "mri-nufft",
51
- "ismrmrd",
50
+ "mrd-python",
52
51
  "ipywidgets",
53
52
  ]
54
53
 
@@ -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 ._core import SequenceParams # noqa
15
+ from ._opts import get_opts # noqa
15
16
 
16
- __all__.append("Opts")
17
- __all__.extend(["Sequence", "SequenceParams"])
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
- if config_path is not None:
30
- os.environ["PULSEFORGE_CONFIG"] = config_path
31
- else:
32
- if scanner_address is not None:
33
- os.environ["PULSEFORGE_SCANNER_ADDRESS"] = scanner_address
34
- if scanner_port is not None:
35
- os.environ["PULSEFORGE_SCANNER_PORT"] = scanner_port
36
- if recon_server_address is not None:
37
- os.environ["PULSEFORGE_RECON_SERVER_ADDRESS"] = recon_server_address
38
- if recon_server_port is not None:
39
- os.environ["PULSEFORGE_RECON_SERVER_PORT"] = recon_server_port
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__
@@ -6,7 +6,6 @@ native pypulseq Sequence and Ceq structure.
6
6
 
7
7
  __all__ = []
8
8
 
9
- from ._ceq import SequenceParams # noqa
10
9
  from ._sequence import Sequence # noqa
11
10
 
12
- __all__.extend(["Sequence", "SequenceParams"])
11
+ __all__.append("Sequence")
@@ -1,11 +1,9 @@
1
1
  """Ceq structure definition."""
2
2
 
3
- __all__ = ["Ceq", "PulseqBlock", "SequenceParams"]
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 *= 32767 / self.amplitude
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
- rho = np.abs(data.signal)
112
- theta = np.angle(data.signal)
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