pulserver 0.2.1__tar.gz → 0.2.2__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 (504) hide show
  1. {pulserver-0.2.1 → pulserver-0.2.2}/.github/workflows/docs.yml +1 -1
  2. {pulserver-0.2.1 → pulserver-0.2.2}/.github/workflows/tags-release.yml +6 -5
  3. {pulserver-0.2.1 → pulserver-0.2.2}/.github/workflows/test-ci.yml +1 -1
  4. {pulserver-0.2.1 → pulserver-0.2.2}/AGENTS.md +7 -3
  5. pulserver-0.2.2/PKG-INFO +245 -0
  6. pulserver-0.2.2/README.md +194 -0
  7. {pulserver-0.2.1 → pulserver-0.2.2}/docker/Dockerfile +10 -4
  8. pulserver-0.2.2/docs/_static/capabilities.js +15 -0
  9. pulserver-0.2.2/docs/_static/context-dark.svg +39 -0
  10. pulserver-0.2.2/docs/_static/context.svg +39 -0
  11. {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/pulserver.css +55 -0
  12. pulserver-0.2.2/docs/_static/safety-dark.svg +42 -0
  13. pulserver-0.2.2/docs/_static/safety.svg +42 -0
  14. pulserver-0.2.2/docs/_static/session-dark.svg +40 -0
  15. pulserver-0.2.2/docs/_static/session.svg +40 -0
  16. pulserver-0.2.2/docs/_static/virtual-dark.svg +40 -0
  17. pulserver-0.2.2/docs/_static/virtual.svg +40 -0
  18. {pulserver-0.2.1 → pulserver-0.2.2}/docs/api/design.md +16 -0
  19. {pulserver-0.2.1 → pulserver-0.2.2}/docs/api/host.md +1 -1
  20. pulserver-0.2.2/docs/api/index.md +34 -0
  21. {pulserver-0.2.1 → pulserver-0.2.2}/docs/api/mrd.md +3 -1
  22. {pulserver-0.2.1 → pulserver-0.2.2}/docs/api/protocol.md +4 -3
  23. {pulserver-0.2.1 → pulserver-0.2.2}/docs/api/recon.md +1 -1
  24. pulserver-0.2.2/docs/colab.py +129 -0
  25. {pulserver-0.2.1 → pulserver-0.2.2}/docs/conf.py +17 -0
  26. {pulserver-0.2.1 → pulserver-0.2.2}/docs/developer-guide/documentation.md +17 -10
  27. pulserver-0.2.2/docs/developer-guide/index.md +26 -0
  28. pulserver-0.2.2/docs/developer-guide/installation.md +52 -0
  29. {pulserver-0.2.1/docs/explanations → pulserver-0.2.2/docs/developer-guide/internals}/calibration.md +10 -22
  30. pulserver-0.2.2/docs/developer-guide/internals/index.md +25 -0
  31. {pulserver-0.2.1 → pulserver-0.2.2}/docs/developer-guide/internals/ir-cache.md +46 -40
  32. pulserver-0.2.2/docs/developer-guide/internals/protocol-wire.md +150 -0
  33. pulserver-0.2.1/docs/explanations/reconstruction.md → pulserver-0.2.2/docs/developer-guide/internals/reconstruction-proxy.md +208 -147
  34. pulserver-0.2.2/docs/developer-guide/internals/sar-ratios.md +57 -0
  35. {pulserver-0.2.1 → pulserver-0.2.2}/docs/developer-guide/internals/virtual-scanner.md +280 -43
  36. pulserver-0.2.2/docs/developer-guide/releases.md +14 -0
  37. {pulserver-0.2.1 → pulserver-0.2.2}/docs/developer-guide/style.md +15 -10
  38. {pulserver-0.2.1 → pulserver-0.2.2}/docs/developer-guide/terminology.md +12 -6
  39. pulserver-0.2.2/docs/developer-guide/workflow.md +39 -0
  40. pulserver-0.2.2/docs/examples/course.md +35 -0
  41. pulserver-0.2.2/docs/examples/index.md +37 -0
  42. pulserver-0.2.2/docs/examples/tours.md +18 -0
  43. pulserver-0.2.2/docs/explanations/architecture.md +124 -0
  44. pulserver-0.2.2/docs/explanations/index.md +34 -0
  45. pulserver-0.2.2/docs/explanations/protocol.md +99 -0
  46. pulserver-0.2.2/docs/explanations/raw-data.md +97 -0
  47. pulserver-0.2.2/docs/explanations/reconstruction-session.md +113 -0
  48. pulserver-0.2.2/docs/explanations/reconstruction.md +114 -0
  49. pulserver-0.2.2/docs/explanations/safety-checks.md +122 -0
  50. pulserver-0.2.2/docs/explanations/scanner-representation.md +192 -0
  51. pulserver-0.2.2/docs/explanations/virtual-scanner.md +110 -0
  52. pulserver-0.2.2/docs/user-guide/index.md +97 -0
  53. {pulserver-0.2.1 → pulserver-0.2.2}/docs/user-guide/reconstruction-client.md +20 -16
  54. {pulserver-0.2.1 → pulserver-0.2.2}/docs/user-guide/reconstruction-plugins.md +150 -118
  55. pulserver-0.2.2/docs/user-guide/running.md +438 -0
  56. pulserver-0.2.2/docs/user-guide/scanner-sequences.md +540 -0
  57. {pulserver-0.2.1 → pulserver-0.2.2}/docs/user-guide/virtual-scanner.md +183 -177
  58. pulserver-0.2.2/gallery/01-course/01_protocol_to_image.py +242 -0
  59. pulserver-0.2.2/gallery/01-course/02_sequence_plugin.py +447 -0
  60. pulserver-0.2.2/gallery/01-course/03_scanner_representation.py +285 -0
  61. pulserver-0.2.2/gallery/01-course/04_reconstruction_plugin.py +304 -0
  62. pulserver-0.2.2/gallery/01-course/05_recon_plugin.py +249 -0
  63. pulserver-0.2.2/gallery/01-course/06_testing_on_the_virtual_scanner.py +340 -0
  64. {pulserver-0.2.1 → pulserver-0.2.2}/gallery/02-tours/01_protocol_resolution.py +45 -44
  65. {pulserver-0.2.1 → pulserver-0.2.2}/gallery/02-tours/02_segmentation.py +24 -25
  66. {pulserver-0.2.1 → pulserver-0.2.2}/gallery/02-tours/03_fov_offset_enrichment.py +36 -35
  67. {pulserver-0.2.1 → pulserver-0.2.2}/pyproject.toml +7 -9
  68. {pulserver-0.2.1 → pulserver-0.2.2}/scripts/make_artwork.py +206 -0
  69. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseg/pulseg_protocol.h +157 -75
  70. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/io/pulseg_protocol.c +92 -15
  71. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_accelerators.py +1 -1
  72. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_plugins.py +4 -0
  73. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/_evaluation.py +62 -28
  74. pulserver-0.2.2/src/pulserver/_zoo/_saturation.py +118 -0
  75. pulserver-0.2.2/src/pulserver/_zoo/_slab.py +37 -0
  76. pulserver-0.2.2/src/pulserver/_zoo/_user.py +160 -0
  77. pulserver-0.2.2/src/pulserver/_zoo/parameters/bssfp2d/retrospective.toml +2 -0
  78. pulserver-0.2.2/src/pulserver/_zoo/parameters/fse3d/dual_region.toml +2 -0
  79. pulserver-0.2.2/src/pulserver/_zoo/parameters/fse3d/navigator.toml +2 -0
  80. pulserver-0.2.2/src/pulserver/_zoo/parameters/fse3d/optimized.toml +2 -0
  81. pulserver-0.2.2/src/pulserver/_zoo/parameters/fse3d/shuffling.toml +2 -0
  82. pulserver-0.2.2/src/pulserver/_zoo/parameters/fse3d/wave.toml +2 -0
  83. pulserver-0.2.2/src/pulserver/_zoo/parameters/gre3d/full_plane.toml +2 -0
  84. pulserver-0.2.2/src/pulserver/_zoo/parameters/gre3d/wave.toml +2 -0
  85. pulserver-0.2.2/src/pulserver/_zoo/parameters/gre_multiecho2d/bipolar.toml +2 -0
  86. pulserver-0.2.2/src/pulserver/_zoo/parameters/gre_multiecho3d/bipolar.toml +2 -0
  87. pulserver-0.2.2/src/pulserver/_zoo/parameters/gre_multiecho3d/full_plane.toml +2 -0
  88. pulserver-0.2.2/src/pulserver/_zoo/parameters/gre_multiecho3d/wave.toml +2 -0
  89. pulserver-0.2.2/src/pulserver/_zoo/parameters/gre_spiral2d/variable_density.toml +2 -0
  90. pulserver-0.2.2/src/pulserver/_zoo/parameters/gre_stack_of_spirals3d/variable_density.toml +2 -0
  91. pulserver-0.2.2/src/pulserver/_zoo/parameters/mprage3d/navigator.toml +2 -0
  92. pulserver-0.2.2/src/pulserver/_zoo/parameters/mprage3d/shuffling.toml +2 -0
  93. pulserver-0.2.2/src/pulserver/_zoo/parameters/mprage3d/wave.toml +2 -0
  94. pulserver-0.2.2/src/pulserver/_zoo/parameters/mprage_stack_of_spirals3d/variable_density.toml +2 -0
  95. pulserver-0.2.2/src/pulserver/_zoo/parameters/se3d/full_plane.toml +2 -0
  96. pulserver-0.2.2/src/pulserver/_zoo/parameters/se_spiral2d/variable_density.toml +2 -0
  97. pulserver-0.2.2/src/pulserver/_zoo/parameters/se_stack_of_spirals3d/variable_density.toml +2 -0
  98. pulserver-0.2.2/src/pulserver/_zoo/parameters/zte3d/meridian.toml +2 -0
  99. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/bssfp2d.py +23 -4
  100. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/bssfp3d.py +25 -7
  101. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/epi2d.py +32 -86
  102. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/epi3d.py +31 -5
  103. pulserver-0.2.2/src/pulserver/_zoo/sequences/fse3d.py +205 -0
  104. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/gre2d.py +29 -3
  105. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/gre3d.py +37 -13
  106. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/gre_multiecho2d.py +30 -3
  107. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/gre_multiecho3d.py +41 -13
  108. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/gre_propeller2d.py +21 -2
  109. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/gre_radial2d.py +21 -2
  110. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/gre_spiral2d.py +19 -4
  111. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/gre_stack_of_blades3d.py +29 -3
  112. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/gre_stack_of_spirals3d.py +30 -6
  113. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/gre_stack_of_stars3d.py +28 -3
  114. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/mprage3d.py +60 -29
  115. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/mprage_stack_of_spirals3d.py +34 -8
  116. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/mprage_stack_of_stars3d.py +31 -3
  117. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/se2d.py +29 -3
  118. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/se3d.py +32 -3
  119. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/se_epi_propeller2d.py +24 -3
  120. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/se_propeller2d.py +21 -2
  121. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/se_radial2d.py +21 -2
  122. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/se_spiral2d.py +19 -4
  123. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/se_stack_of_blades3d.py +29 -3
  124. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/se_stack_of_spirals3d.py +30 -6
  125. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/se_stack_of_stars3d.py +28 -3
  126. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/zte3d.py +12 -2
  127. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/design/__init__.py +8 -0
  128. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/design/_entries.py +180 -74
  129. pulserver-0.2.2/src/pulserver/design/_options.py +133 -0
  130. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/design/_plugin.py +76 -30
  131. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/design/_rf.py +3 -3
  132. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/host/__init__.py +1 -1
  133. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/host/_blocks.py +29 -1
  134. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/host/_command.py +17 -0
  135. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/host/_limits.py +17 -15
  136. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/host/_server.py +13 -7
  137. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/host/_service.py +139 -40
  138. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/ir/_checks.py +27 -23
  139. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/ir/_convert.py +74 -42
  140. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/ir/_playout.py +17 -1
  141. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/ir/_source.py +3 -2
  142. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/ir/_vendor.py +10 -1
  143. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/ir/_waves.py +36 -1
  144. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/mrd/__init__.py +4 -0
  145. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/mrd/_acquisitions.py +6 -6
  146. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/mrd/_header.py +20 -13
  147. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/mrd/_images.py +5 -5
  148. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/mrd/_metadata.py +32 -2
  149. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/mrd/_sequence.py +23 -11
  150. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/protocol/__init__.py +2 -0
  151. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/protocol/_keys.py +158 -31
  152. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/protocol/_prescription.py +1 -1
  153. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/protocol/_wire.py +7 -3
  154. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/_designs.py +1 -1
  155. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/_enrich.py +151 -14
  156. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/_held.py +3 -2
  157. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/_intake.py +4 -4
  158. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/_local.py +2 -2
  159. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/_motion.py +8 -12
  160. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/_proxy.py +78 -69
  161. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/_seqdesc.py +15 -17
  162. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/_workers.py +22 -8
  163. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_buffers.py +28 -26
  164. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_calibration.py +6 -7
  165. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_correct.py +17 -17
  166. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_runtime/concurrency.py +12 -14
  167. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_runtime/connection.py +12 -8
  168. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_runtime/exam.py +2 -2
  169. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_runtime/mrd2dicom.py +39 -39
  170. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_units.py +9 -5
  171. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/gadgets.py +5 -6
  172. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/handlers/epi.py +33 -17
  173. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/handlers/nufft.py +61 -30
  174. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/handlers/nufft_train.py +2 -2
  175. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/handlers/pics.py +21 -8
  176. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/handlers/pics_train.py +2 -2
  177. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/handlers/pmc.py +4 -2
  178. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/plugin.py +31 -35
  179. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/validate/_compare.py +55 -38
  180. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/validate/_validate.py +22 -21
  181. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/validate/_xml.py +12 -9
  182. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_brainweb.py +11 -18
  183. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_coils.py +35 -27
  184. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_command.py +9 -5
  185. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_console.py +57 -38
  186. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_export.py +7 -7
  187. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_fourier.py +241 -76
  188. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_girf.py +2 -2
  189. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_localizer.py +11 -10
  190. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_motion.py +4 -4
  191. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_phantom.py +6 -6
  192. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_stream.py +150 -73
  193. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_timeline.py +241 -30
  194. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_tissue.py +3 -1
  195. {pulserver-0.2.1 → pulserver-0.2.2}/tests/_analytic.py +1 -1
  196. {pulserver-0.2.1 → pulserver-0.2.2}/tests/_host.py +9 -2
  197. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_buffers.py +22 -0
  198. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_epi.py +45 -0
  199. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_mrd2dicom.py +42 -3
  200. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_nufft.py +15 -1
  201. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_pics.py +24 -0
  202. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_console.py +31 -5
  203. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_console_sequences.py +51 -1
  204. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_design_service.py +31 -2
  205. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_docstring_defaults.py +11 -4
  206. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_enrich.py +116 -3
  207. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir.py +15 -0
  208. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_keys.py +33 -6
  209. pulserver-0.2.2/tests/test_plugin_options.py +240 -0
  210. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_protocol.py +28 -0
  211. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_sequence_plugin.py +97 -1
  212. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_virtual.py +14 -7
  213. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_virtual_brainweb.py +0 -6
  214. pulserver-0.2.2/tests/test_virtual_clock.py +205 -0
  215. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_virtual_coils.py +0 -13
  216. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_virtual_fourier.py +90 -1
  217. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_virtual_localizer.py +9 -7
  218. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_zoo_evaluation.py +476 -62
  219. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_zoo_wave.py +17 -8
  220. pulserver-0.2.1/PKG-INFO +0 -223
  221. pulserver-0.2.1/README.md +0 -166
  222. pulserver-0.2.1/docs/api/index.md +0 -34
  223. pulserver-0.2.1/docs/developer-guide/code-of-conduct.md +0 -6
  224. pulserver-0.2.1/docs/developer-guide/index.md +0 -33
  225. pulserver-0.2.1/docs/developer-guide/installation.md +0 -13
  226. pulserver-0.2.1/docs/developer-guide/internals/index.md +0 -21
  227. pulserver-0.2.1/docs/developer-guide/internals/protocol-wire.md +0 -81
  228. pulserver-0.2.1/docs/developer-guide/internals/reconstruction-proxy.md +0 -113
  229. pulserver-0.2.1/docs/developer-guide/pre-commit.md +0 -14
  230. pulserver-0.2.1/docs/developer-guide/prerequisites.md +0 -9
  231. pulserver-0.2.1/docs/developer-guide/pull-requests.md +0 -6
  232. pulserver-0.2.1/docs/developer-guide/releases.md +0 -11
  233. pulserver-0.2.1/docs/developer-guide/workflow.md +0 -12
  234. pulserver-0.2.1/docs/examples/course.md +0 -25
  235. pulserver-0.2.1/docs/examples/index.md +0 -36
  236. pulserver-0.2.1/docs/examples/tours.md +0 -18
  237. pulserver-0.2.1/docs/explanations/architecture.md +0 -118
  238. pulserver-0.2.1/docs/explanations/designs.md +0 -197
  239. pulserver-0.2.1/docs/explanations/index.md +0 -32
  240. pulserver-0.2.1/docs/explanations/protocol.md +0 -186
  241. pulserver-0.2.1/docs/explanations/scanner-representation.md +0 -160
  242. pulserver-0.2.1/docs/explanations/virtual-scanner.md +0 -253
  243. pulserver-0.2.1/docs/user-guide/index.md +0 -99
  244. pulserver-0.2.1/docs/user-guide/running.md +0 -400
  245. pulserver-0.2.1/docs/user-guide/scanner-sequences.md +0 -383
  246. pulserver-0.2.1/gallery/01-course/01_protocol_to_image.py +0 -161
  247. pulserver-0.2.1/gallery/01-course/02_sequence_plugin.py +0 -160
  248. pulserver-0.2.1/gallery/01-course/03_scanner_representation.py +0 -135
  249. pulserver-0.2.1/gallery/01-course/04_reconstruction_plugin.py +0 -185
  250. pulserver-0.2.1/gallery/01-course/05_testing_on_the_virtual_scanner.py +0 -148
  251. pulserver-0.2.1/src/pulserver/_zoo/sequences/fse3d.py +0 -148
  252. {pulserver-0.2.1 → pulserver-0.2.2}/.claude/skills/build-and-test/SKILL.md +0 -0
  253. {pulserver-0.2.1 → pulserver-0.2.2}/.claude/skills/write-documentation/SKILL.md +0 -0
  254. {pulserver-0.2.1 → pulserver-0.2.2}/.dockerignore +0 -0
  255. {pulserver-0.2.1 → pulserver-0.2.2}/.gitattributes +0 -0
  256. {pulserver-0.2.1 → pulserver-0.2.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  257. {pulserver-0.2.1 → pulserver-0.2.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  258. {pulserver-0.2.1 → pulserver-0.2.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  259. {pulserver-0.2.1 → pulserver-0.2.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  260. {pulserver-0.2.1 → pulserver-0.2.2}/.github/dependabot.yml +0 -0
  261. {pulserver-0.2.1 → pulserver-0.2.2}/.github/workflows/codeql.yml +0 -0
  262. {pulserver-0.2.1 → pulserver-0.2.2}/.github/workflows/console.yml +0 -0
  263. {pulserver-0.2.1 → pulserver-0.2.2}/.github/workflows/fields.yml +0 -0
  264. {pulserver-0.2.1 → pulserver-0.2.2}/.github/workflows/image.yml +0 -0
  265. {pulserver-0.2.1 → pulserver-0.2.2}/.github/workflows/koma.yml +0 -0
  266. {pulserver-0.2.1 → pulserver-0.2.2}/.github/workflows/style.yml +0 -0
  267. {pulserver-0.2.1 → pulserver-0.2.2}/.github/workflows/wheels.yml +0 -0
  268. {pulserver-0.2.1 → pulserver-0.2.2}/.gitignore +0 -0
  269. {pulserver-0.2.1 → pulserver-0.2.2}/.pre-commit-config.yaml +0 -0
  270. {pulserver-0.2.1 → pulserver-0.2.2}/CLAUDE.md +0 -0
  271. {pulserver-0.2.1 → pulserver-0.2.2}/CMakeLists.txt +0 -0
  272. {pulserver-0.2.1 → pulserver-0.2.2}/CODE_OF_CONDUCT.md +0 -0
  273. {pulserver-0.2.1 → pulserver-0.2.2}/CONTRIBUTING.md +0 -0
  274. {pulserver-0.2.1 → pulserver-0.2.2}/GEMINI.md +0 -0
  275. {pulserver-0.2.1 → pulserver-0.2.2}/LICENSE +0 -0
  276. {pulserver-0.2.1 → pulserver-0.2.2}/LICENSES/gadgetron-python-MIT.txt +0 -0
  277. {pulserver-0.2.1 → pulserver-0.2.2}/LICENSES/python-ismrmrd-server-MIT.txt +0 -0
  278. {pulserver-0.2.1 → pulserver-0.2.2}/NEXT.md +0 -0
  279. {pulserver-0.2.1 → pulserver-0.2.2}/SECURITY.md +0 -0
  280. {pulserver-0.2.1 → pulserver-0.2.2}/SKILLS.md +0 -0
  281. {pulserver-0.2.1 → pulserver-0.2.2}/codecov.yml +0 -0
  282. {pulserver-0.2.1 → pulserver-0.2.2}/docker/compose.yaml +0 -0
  283. {pulserver-0.2.1 → pulserver-0.2.2}/docker/entrypoint.sh +0 -0
  284. {pulserver-0.2.1 → pulserver-0.2.2}/docker/limits/limits.txt +0 -0
  285. {pulserver-0.2.1 → pulserver-0.2.2}/docker/smoke.py +0 -0
  286. {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/architecture-dark.svg +0 -0
  287. {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/architecture.svg +0 -0
  288. {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/pulseg-dark.svg +0 -0
  289. {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/pulseg.svg +0 -0
  290. {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/pulserver-logo-dark.svg +0 -0
  291. {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/pulserver-logo.svg +0 -0
  292. {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/pulserver-mark-dark.svg +0 -0
  293. {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/pulserver-mark.svg +0 -0
  294. {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/repetition-dark.svg +0 -0
  295. {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/repetition.svg +0 -0
  296. {pulserver-0.2.1 → pulserver-0.2.2}/docs/_templates/autosummary/base.rst +0 -0
  297. {pulserver-0.2.1 → pulserver-0.2.2}/docs/_templates/autosummary/class.rst +0 -0
  298. {pulserver-0.2.1 → pulserver-0.2.2}/docs/_templates/autosummary/enum.rst +0 -0
  299. {pulserver-0.2.1 → pulserver-0.2.2}/docs/_templates/autosummary/function.rst +0 -0
  300. {pulserver-0.2.1 → pulserver-0.2.2}/docs/api/ir.md +0 -0
  301. {pulserver-0.2.1 → pulserver-0.2.2}/docs/api/proxy.md +0 -0
  302. {pulserver-0.2.1 → pulserver-0.2.2}/docs/api/validate.md +0 -0
  303. {pulserver-0.2.1 → pulserver-0.2.2}/docs/api/virtual.md +0 -0
  304. {pulserver-0.2.1 → pulserver-0.2.2}/docs/api_objects.py +0 -0
  305. {pulserver-0.2.1 → pulserver-0.2.2}/docs/figure_style.py +0 -0
  306. {pulserver-0.2.1 → pulserver-0.2.2}/docs/index.md +0 -0
  307. {pulserver-0.2.1 → pulserver-0.2.2}/docs/misc/contributors.md +0 -0
  308. {pulserver-0.2.1 → pulserver-0.2.2}/docs/misc/index.md +0 -0
  309. {pulserver-0.2.1 → pulserver-0.2.2}/docs/misc/license.md +0 -0
  310. {pulserver-0.2.1 → pulserver-0.2.2}/docs/misc/related.md +0 -0
  311. {pulserver-0.2.1 → pulserver-0.2.2}/gallery/01-course/README.rst +0 -0
  312. {pulserver-0.2.1 → pulserver-0.2.2}/gallery/01-course/_gallery_header.md +0 -0
  313. {pulserver-0.2.1 → pulserver-0.2.2}/gallery/02-tours/README.rst +0 -0
  314. {pulserver-0.2.1 → pulserver-0.2.2}/gallery/02-tours/_gallery_header.md +0 -0
  315. {pulserver-0.2.1 → pulserver-0.2.2}/gallery/README.rst +0 -0
  316. {pulserver-0.2.1 → pulserver-0.2.2}/gallery/_gallery_header.md +0 -0
  317. {pulserver-0.2.1 → pulserver-0.2.2}/scripts/build_docs.sh +0 -0
  318. {pulserver-0.2.1 → pulserver-0.2.2}/scripts/check_throughput.py +0 -0
  319. {pulserver-0.2.1 → pulserver-0.2.2}/scripts/format_and_lint.sh +0 -0
  320. {pulserver-0.2.1 → pulserver-0.2.2}/scripts/publish_docs.py +0 -0
  321. {pulserver-0.2.1 → pulserver-0.2.2}/setup.py +0 -0
  322. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/CMakeLists.txt +0 -0
  323. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/cache/pulseg_cache.c +0 -0
  324. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/core/pulseg_core.c +0 -0
  325. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/core/pulseg_error.c +0 -0
  326. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/core/pulseg_math.c +0 -0
  327. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseg/pulseg.h +0 -0
  328. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseg/pulseg_cache.h +0 -0
  329. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseg/pulseg_collection.h +0 -0
  330. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseg/pulseg_config.h +0 -0
  331. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseg/pulseg_errors.h +0 -0
  332. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseg/pulseg_io.h +0 -0
  333. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseg/pulseg_playout.h +0 -0
  334. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseg/pulseg_types.h +0 -0
  335. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseq/pulseq.h +0 -0
  336. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseq/pulseq_config.h +0 -0
  337. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseq/pulseq_types.h +0 -0
  338. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/io/pulseg_opts.c +0 -0
  339. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/playout/pulseg_playout.c +0 -0
  340. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/pulseg_internal.h +0 -0
  341. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/pulseq/pulseq_internal.h +0 -0
  342. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/pulseq/pulseq_shape.c +0 -0
  343. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/structure/pulseg_descriptor.c +0 -0
  344. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/structure/pulseg_getters.c +0 -0
  345. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/structure/pulseg_repetition.c +0 -0
  346. {pulserver-0.2.1 → pulserver-0.2.2}/src/c/structure/pulseg_waves.c +0 -0
  347. {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/fourier/bindings.cpp +0 -0
  348. {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/fourier/bindings.hpp +0 -0
  349. {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/fourier/parallel.hpp +0 -0
  350. {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/fourier/timeline.cpp +0 -0
  351. {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/fourier/timeline.hpp +0 -0
  352. {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/collection.cpp +0 -0
  353. {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/dedup.cpp +0 -0
  354. {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/from_libraries.cpp +0 -0
  355. {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/from_libraries.hpp +0 -0
  356. {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/pulseg_convert.h +0 -0
  357. {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/pulseq_file.cpp +0 -0
  358. {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/pulseq_file.h +0 -0
  359. {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/repetition.cpp +0 -0
  360. {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/structure.cpp +0 -0
  361. {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/wave_plan.cpp +0 -0
  362. {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/waves.cpp +0 -0
  363. {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/module.cpp +0 -0
  364. {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/native.hpp +0 -0
  365. {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/playout.cpp +0 -0
  366. {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/playout.hpp +0 -0
  367. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/__init__.py +0 -0
  368. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_cli.py +0 -0
  369. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_labels.py +0 -0
  370. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_logs.py +0 -0
  371. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_memory.py +0 -0
  372. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/__init__.py +0 -0
  373. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/_inversion.py +0 -0
  374. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/_pmc.py +0 -0
  375. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/recon/epi.py +0 -0
  376. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/recon/nufft.py +0 -0
  377. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/recon/nufft_train.py +0 -0
  378. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/recon/pics.py +0 -0
  379. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/recon/pics_train.py +0 -0
  380. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/recon/pmc.py +0 -0
  381. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/host/__main__.py +0 -0
  382. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/host/_push.py +0 -0
  383. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/host/_store.py +0 -0
  384. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/ir/__init__.py +0 -0
  385. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/protocol/_schema.py +0 -0
  386. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/__init__.py +0 -0
  387. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/__main__.py +0 -0
  388. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/_queue.py +0 -0
  389. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/__init__.py +0 -0
  390. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/__main__.py +0 -0
  391. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_exam_image.py +0 -0
  392. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_loader.py +0 -0
  393. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_runtime/__init__.py +0 -0
  394. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_runtime/application.py +0 -0
  395. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_runtime/constants.py +0 -0
  396. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_runtime/offline.py +0 -0
  397. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_runtime/readers.py +0 -0
  398. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_runtime/writers.py +0 -0
  399. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/handlers/__init__.py +0 -0
  400. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/validate/__init__.py +0 -0
  401. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/validate/_command.py +0 -0
  402. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/__init__.py +0 -0
  403. {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_client.py +0 -0
  404. {pulserver-0.2.1 → pulserver-0.2.2}/tests/_seqtext.py +0 -0
  405. {pulserver-0.2.1 → pulserver-0.2.2}/tests/_synthetic.py +0 -0
  406. {pulserver-0.2.1 → pulserver-0.2.2}/tests/_virtual.py +0 -0
  407. {pulserver-0.2.1 → pulserver-0.2.2}/tests/_zoo.py +0 -0
  408. {pulserver-0.2.1 → pulserver-0.2.2}/tests/conftest.py +0 -0
  409. {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/bloch/bssfp.npy +0 -0
  410. {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/bloch/epi_2d_main.npy +0 -0
  411. {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/bloch/gre_2d_3sl.npy +0 -0
  412. {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/bloch/steady_dess_hard_0hz.npy +0 -0
  413. {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/bloch/steady_dess_hard_30hz.npy +0 -0
  414. {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/bloch/steady_echo_hard_0hz.npy +0 -0
  415. {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/bloch/steady_echo_hard_30hz.npy +0 -0
  416. {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/bloch/steady_echo_selective_0hz.npy +0 -0
  417. {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/bloch/steady_fid_hard_0hz.npy +0 -0
  418. {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/sequences/dedup_gre_pair.seq +0 -0
  419. {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/sequences/dedup_gre_pair_b.seq +0 -0
  420. {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/sequences/epi_2d_main.seq +0 -0
  421. {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/sequences/gre_2d_3sl.seq +0 -0
  422. {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/sequences/mprage_stack_of_spirals_3d.seq +0 -0
  423. {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/sequences/zte_3d.seq +0 -0
  424. {pulserver-0.2.1 → pulserver-0.2.2}/tests/koma/Project.toml +0 -0
  425. {pulserver-0.2.1 → pulserver-0.2.2}/tests/koma/simulate.jl +0 -0
  426. {pulserver-0.2.1 → pulserver-0.2.2}/tests/native/check_initializers.c +0 -0
  427. {pulserver-0.2.1 → pulserver-0.2.2}/tests/native/print_wave_samples.c +0 -0
  428. {pulserver-0.2.1 → pulserver-0.2.2}/tests/native/read_cache_summary.c +0 -0
  429. {pulserver-0.2.1 → pulserver-0.2.2}/tests/native/read_rf_definitions.c +0 -0
  430. {pulserver-0.2.1 → pulserver-0.2.2}/tests/native/replay_an_instance.c +0 -0
  431. {pulserver-0.2.1 → pulserver-0.2.2}/tests/plugins/crash.py +0 -0
  432. {pulserver-0.2.1 → pulserver-0.2.2}/tests/plugins/gre2d.py +0 -0
  433. {pulserver-0.2.1 → pulserver-0.2.2}/tests/plugins/gre2d_raw.py +0 -0
  434. {pulserver-0.2.1 → pulserver-0.2.2}/tests/plugins/rf_train.py +0 -0
  435. {pulserver-0.2.1 → pulserver-0.2.2}/tests/plugins/stall.py +0 -0
  436. {pulserver-0.2.1 → pulserver-0.2.2}/tests/plugins/strong.py +0 -0
  437. {pulserver-0.2.1 → pulserver-0.2.2}/tests/plugins/tiny.py +0 -0
  438. {pulserver-0.2.1 → pulserver-0.2.2}/tests/plugins/typed.py +0 -0
  439. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_app.py +0 -0
  440. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_calibration.py +0 -0
  441. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_coil_compression.py +0 -0
  442. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_connection.py +0 -0
  443. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_correct.py +0 -0
  444. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_exam_image.py +0 -0
  445. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_flags.py +0 -0
  446. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_gadgets.py +0 -0
  447. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_handlers.py +0 -0
  448. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_images.py +0 -0
  449. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_loader.py +0 -0
  450. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_pmc.py +0 -0
  451. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_prewhiten.py +0 -0
  452. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_readers_writers.py +0 -0
  453. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_slots.py +0 -0
  454. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_units.py +0 -0
  455. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon_plugins/children.py +0 -0
  456. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon_plugins/crash.py +0 -0
  457. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon_plugins/device.py +0 -0
  458. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon_plugins/exam.py +0 -0
  459. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon_plugins/gre2d.py +0 -0
  460. {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon_plugins/pose.py +0 -0
  461. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_design_push.py +0 -0
  462. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_design_server.py +0 -0
  463. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_docs.py +0 -0
  464. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_docstrings.py +0 -0
  465. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_exam_artifacts.py +0 -0
  466. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_exam_calibration.py +0 -0
  467. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_exam_sharing.py +0 -0
  468. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_host_slots.py +0 -0
  469. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_image.py +0 -0
  470. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_checks.py +0 -0
  471. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_designed.py +0 -0
  472. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_extensions.py +0 -0
  473. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_grouping.py +0 -0
  474. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_groups.py +0 -0
  475. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_labels.py +0 -0
  476. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_playout.py +0 -0
  477. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_profile.py +0 -0
  478. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_repetition.py +0 -0
  479. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_repetition_gradients.py +0 -0
  480. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_rf.py +0 -0
  481. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_segments.py +0 -0
  482. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_source.py +0 -0
  483. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_steady_excitation.py +0 -0
  484. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_vendor.py +0 -0
  485. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_waves.py +0 -0
  486. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_koma.py +0 -0
  487. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_limits.py +0 -0
  488. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_logs.py +0 -0
  489. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_memory.py +0 -0
  490. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_motion.py +0 -0
  491. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_mrd_images.py +0 -0
  492. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_mrd_sequence.py +0 -0
  493. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_plugins.py +0 -0
  494. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_proxy.py +0 -0
  495. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_publish_docs.py +0 -0
  496. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_resolution.py +0 -0
  497. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_rf_layout.py +0 -0
  498. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_scan_command.py +0 -0
  499. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_seqdesc.py +0 -0
  500. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_smoke.py +0 -0
  501. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_validate.py +0 -0
  502. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_virtual_export.py +0 -0
  503. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_virtual_sequences.py +0 -0
  504. {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_wire.py +0 -0
@@ -76,7 +76,7 @@ jobs:
76
76
  run: |
77
77
  python -m pip install --upgrade pip
78
78
  python -m pip install torch --index-url https://download.pytorch.org/whl/cpu
79
- python -m pip install '.[doc,coils]'
79
+ python -m pip install '.[doc]'
80
80
 
81
81
  - name: Build
82
82
  env:
@@ -51,8 +51,9 @@ jobs:
51
51
  - env:
52
52
  GITHUB_TOKEN: ${{ github.token }}
53
53
  run: |
54
- gh release create '${{ github.ref_name }}' \
55
- --repo '${{ github.repository }}' \
56
- --title '${{ github.ref_name }}' \
57
- --generate-notes
58
- gh release upload '${{ github.ref_name }}' dist/** --repo '${{ github.repository }}'
54
+ gh release view '${{ github.ref_name }}' --repo '${{ github.repository }}' >/dev/null 2>&1 ||
55
+ gh release create '${{ github.ref_name }}' \
56
+ --repo '${{ github.repository }}' \
57
+ --title '${{ github.ref_name }}' \
58
+ --generate-notes
59
+ gh release upload '${{ github.ref_name }}' dist/** --clobber --repo '${{ github.repository }}'
@@ -84,7 +84,7 @@ jobs:
84
84
  run: |
85
85
  python -m pip install --upgrade pip
86
86
  python -m pip install torch --index-url https://download.pytorch.org/whl/cpu
87
- python -m pip install .[dev,coils,design]
87
+ python -m pip install .[dev]
88
88
  python -c "import bartorch.tools, blochsim"
89
89
 
90
90
  - name: Run the coil tests
@@ -261,8 +261,11 @@ above govern docstrings.
261
261
  | `docs/misc/` | Licensing, related projects, contributors | |
262
262
 
263
263
  The prose style of one type is not carried into another. An explanation page
264
- proceeds from the concept to its model, its consequences and the software
265
- abstraction, and ends with a *See also* list. Explanations are conceptual:
264
+ is a TL;DR, a short introduction, *Capabilities* (a list, one
265
+ bold line each, with its code and tests behind a toggle), *Division of
266
+ responsibility* (what the scanner or the engines do), *Method* (a figure and
267
+ a few short subsections) and *Examples*; every heading names a concept; `docs/developer-guide/documentation.md` states the layout.
268
+ Explanations are conceptual:
266
269
  implementation design, file layouts, tolerances and test inventories go to
267
270
  `docs/developer-guide/internals/`. The explanations are one flat list, in the
268
271
  order of the data flow.
@@ -285,7 +288,8 @@ table and a Tours table, which `tests/test_docs.py` holds to the gallery.
285
288
 
286
289
  An explanation page with more than one `##` section opens with a TL;DR
287
290
  admonition (```` ```{admonition} TL;DR ```` with `:class: tldr`) directly
288
- under its title, stating only what the page establishes; landing, API and
291
+ under its title, at most three one-line bullets stating only what the page
292
+ establishes; landing, API and
289
293
  example pages have none, and `tests/test_docs.py` holds both.
290
294
 
291
295
  Figures take their typography, size and layout from `docs/figure_style.py`,
@@ -0,0 +1,245 @@
1
+ Metadata-Version: 2.4
2
+ Name: pulserver
3
+ Version: 0.2.2
4
+ Summary: Orchestrator for MR acquisitions: sequence design, scanner preparation and reconstruction.
5
+ Keywords: mri,pulseq,mrd,ismrmrd,reconstruction,scanner
6
+ Author-Email: Matteo Cencini <matteo.cencini@gmail.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ License-File: LICENSES/gadgetron-python-MIT.txt
10
+ License-File: LICENSES/python-ismrmrd-server-MIT.txt
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Project-URL: Homepage, https://github.com/pulserver/pulserver
20
+ Project-URL: Documentation, https://pulserver.github.io/pulserver/
21
+ Project-URL: Issues, https://github.com/pulserver/pulserver/issues
22
+ Project-URL: Source, https://github.com/pulserver/pulserver
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: pypulseqpp>=0.0.40
25
+ Requires-Dist: numpy>=2.2.6
26
+ Requires-Dist: ismrmrd>=1.15.0
27
+ Requires-Dist: pydicom>=3.0.2
28
+ Requires-Dist: torch>=2.4
29
+ Requires-Dist: blochsim>=0.0.12
30
+ Requires-Dist: bartorch>=0.0.14
31
+ Requires-Dist: brainweb-dl>=0.4.6
32
+ Requires-Dist: websockets>=16.1.1
33
+ Requires-Dist: tomli>=2.0.1; python_version < "3.11"
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest>=9.1.1; extra == "dev"
36
+ Requires-Dist: pytest-cov>=7.1.0; extra == "dev"
37
+ Requires-Dist: pytest-xdist>=3; extra == "dev"
38
+ Requires-Dist: coverage>=7.16.2; extra == "dev"
39
+ Requires-Dist: ruff==0.16.10; extra == "dev"
40
+ Requires-Dist: pre-commit>=4.6.2; extra == "dev"
41
+ Requires-Dist: build; extra == "dev"
42
+ Provides-Extra: doc
43
+ Requires-Dist: sphinx>=8.1.3; extra == "doc"
44
+ Requires-Dist: sphinx-book-theme>=1.1.4; extra == "doc"
45
+ Requires-Dist: myst-parser>=4.0.1; extra == "doc"
46
+ Requires-Dist: linkify-it-py>=2.2.0; extra == "doc"
47
+ Requires-Dist: sphinx-copybutton>=0.5.2; extra == "doc"
48
+ Requires-Dist: sphinx-gallery>=0.22.1; extra == "doc"
49
+ Requires-Dist: matplotlib>=3.10.9; extra == "doc"
50
+ Description-Content-Type: text/markdown
51
+
52
+ [![Tests](https://github.com/pulserver/pulserver/actions/workflows/test-ci.yml/badge.svg)](https://github.com/pulserver/pulserver/actions/workflows/test-ci.yml)
53
+ [![PyPI](https://img.shields.io/pypi/v/pulserver.svg)](https://pypi.org/project/pulserver/)
54
+ [![Docs: stable](https://img.shields.io/badge/docs-stable-2b76ad)](https://pulserver.github.io/pulserver/stable/)
55
+ [![Virtual scanner: try it](https://img.shields.io/badge/virtual%20scanner-try%20it-ffbd28)](https://pulserver.github.io/MaRGE/)
56
+ [![License: MIT](https://img.shields.io/badge/license-MIT-ffbd28.svg)](https://github.com/pulserver/pulserver/blob/main/LICENSE)
57
+
58
+ <p align="center"><picture>
59
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/pulserver/pulserver/main/docs/_static/pulserver-logo-dark.svg">
60
+ <img src="https://raw.githubusercontent.com/pulserver/pulserver/main/docs/_static/pulserver-logo.svg" alt="pulserver" width="580">
61
+ </picture></p>
62
+
63
+ pulserver lets you run your own sequence on a clinical scanner as if it were
64
+ a product sequence: the operator edits it in the scanner UI, and the images
65
+ come back to the console. Sequences are exchanged as Pulseq, so any tool that
66
+ writes a `.seq` file (pypulseqpp, MATLAB Pulseq, BART's `seq`...) can provide
67
+ one; pypulseqpp is the one pulserver uses natively.
68
+
69
+ You write two Python plugins, one that designs the sequence and one that
70
+ reconstructs it. pulserver does everything in between: it answers the scanner
71
+ UI, checks the sequence, converts it into the form the scanner plays, and
72
+ routes the raw data back to your reconstruction.
73
+
74
+ <p align="center"><picture>
75
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/pulserver/pulserver/main/docs/_static/architecture-dark.svg">
76
+ <img src="https://raw.githubusercontent.com/pulserver/pulserver/main/docs/_static/architecture.svg" alt="pulserver architecture" width="900">
77
+ </picture></p>
78
+
79
+ The scanner side is a vendor interpreter, a small program on the scanner that
80
+ calls pulserver. pulserver itself never touches the hardware.
81
+
82
+ ## How it works
83
+
84
+ 1. The operator opens your sequence. The scanner asks pulserver which
85
+ parameters to show (TE, TR, flip angle, matrix...), and pulserver reads them
86
+ from your sequence plugin.
87
+ 2. Every time the operator changes a value, pulserver asks your plugin
88
+ whether the protocol is feasible and what it achieves (the TE, the TR, the
89
+ scan time), and sends that back to the scanner. How your plugin answers is
90
+ up to you: design one TR, or compute the times from the block durations.
91
+ If the value is impossible, the scanner shows your error message instead.
92
+ 3. When the operator presses *Scan*, pulserver designs the whole sequence,
93
+ runs the checks (timing, gradient and slew limits, PNS, mechanical
94
+ resonance, acoustic noise) and converts it into the cache the interpreter
95
+ plays, shifted to the prescribed field of view.
96
+ 4. The scanner plays it and streams the raw data to pulserver.
97
+ 5. pulserver fills in everything the sequence knows and the scanner does not:
98
+ the MRD header (encoding spaces, matrix, field of view), and on every
99
+ readout its acquisition header, flags, encoding counters from the
100
+ sequence's labels, and trajectory. Your recon plugin receives these
101
+ enriched readouts, sorts them into k-space and reconstructs as soon as a
102
+ slice or volume is complete.
103
+ 6. Your plugin returns images; pulserver turns them into DICOM and sends them
104
+ back to the console.
105
+
106
+ ## The sequence plugin
107
+
108
+ A sequence plugin takes a pypulseqpp sequence function, a function that returns
109
+ a `Sequence`, and tells the scanner which of its arguments the operator can
110
+ edit:
111
+
112
+ ```python
113
+ # sequences/gre.py
114
+ from pypulseqpp.sequences.sequence.gre2D_sequence import gre2d
115
+ from pulserver.design import Evaluation, FloatParam, SequencePlugin, TimeParam, UIParam
116
+
117
+
118
+ class Gre(SequencePlugin):
119
+ app = gre2d
120
+ protocol = {
121
+ # ms on the scanner UI, µs on the wire, s for gre2d
122
+ UIParam.TE: TimeParam("te", range_min=2000, range_max=20000),
123
+ UIParam.FLIP: FloatParam(
124
+ "flip_angle_deg", unit="deg", range_min=1, range_max=90
125
+ ),
126
+ }
127
+
128
+ def evaluate(self, system, protocol):
129
+ arguments = protocol.arguments # SI units, as gre2d takes them
130
+ one_line = arguments | {"ry": 128, "n_acs_y": 0, "n_dummy": 0} # one TR
131
+ seq = self.app(system, **one_line)
132
+ return Evaluation(protocol.replace({UIParam.TE: seq.definitions["TE"][0]}))
133
+ ```
134
+
135
+ - `protocol` maps each scanner UI button (a `UIParam` key) to the argument of
136
+ your function it sets, with its unit and range (a `*Param` value).
137
+ Arguments you leave out keep the function's defaults.
138
+ - `evaluate` runs on every edit, so keep it fast. Here it designs one line
139
+ and reads back the TE; it could equally compute the TR from block durations,
140
+ or accept the requested TR when it is feasible. To refuse a value, raise;
141
+ the message goes to the operator.
142
+ - The whole scan is built only when the operator starts it, by calling your
143
+ function with the final protocol.
144
+
145
+ pulserver ships a plugin for each of pypulseqpp's 28 built-in sequences
146
+ (Cartesian, radial, spiral, PROPELLER, EPI, FSE, MPRAGE, bSSFP, ZTE), so
147
+ `--plugin gre2d` works without writing anything.
148
+
149
+ ## The recon plugin
150
+
151
+ The reconstruction side follows Gadgetron and ISMRMRD: the data arrive as an
152
+ MRD stream (a header, then one acquisition per readout), and a recon plugin
153
+ handles them as they arrive, much like a Gadgetron gadget chain. Any framework
154
+ can do the maths; this one uses bartorch:
155
+
156
+ ```python
157
+ # recon/pics.py
158
+ import torch
159
+ import bartorch.tools as bt
160
+
161
+ from bartorch import apps, priors
162
+ from pulserver import recon
163
+
164
+
165
+ class Pics(recon.ReconPlugin):
166
+ def recon(self, context, branch, data):
167
+ kspace = torch.from_numpy(data.data.kspace)[:, None] # (coils, z, y, x)
168
+ maps = bt.ecalib(kspace, maps=1)
169
+ image = apps.pics(kspace, maps, regularizers=priors.Wavelet((-1, -2), 0.005))
170
+ return recon.ReconResult(image.abs().squeeze(0).numpy())
171
+
172
+
173
+ PLUGIN = Pics()
174
+ ```
175
+
176
+ - The plugin places each readout in k-space as it arrives, by its encoding
177
+ counters, and `recon` is called once per slice or volume, when the flag that
178
+ closes it arrives. By default that is the end of the scan; pass
179
+ `triggers={"imaging": AcquisitionFlag.LAST_IN_SLICE}` to reconstruct slice
180
+ by slice.
181
+ - The flags and counters come from the labels in your sequence
182
+ (`pp.make_label`, as in PyPulseq), so a correctly labelled sequence needs no
183
+ sorting code in the reconstruction.
184
+ - Return a `ReconResult`; pulserver writes the image header (position,
185
+ orientation, field of view) from the sequence and the scanner.
186
+
187
+ The scanner chooses the recon by name, independently of the sequence, so one
188
+ recon can serve many sequences.
189
+
190
+ ## Try it without a scanner
191
+
192
+ The virtual scanner plays your sequence on a phantom and streams the data to
193
+ your recon, through exactly the path a real scan takes:
194
+
195
+ ```bash
196
+ pip install pulserver bartorch
197
+ printf '[Limits]\nB0: 3.0\n[Limits End]\n' > system.txt
198
+ pulserver proxy --store designs --port 9002 --plugins recon &
199
+ pulserver scan --plugins sequences --plugin gre --reconstruction pics \
200
+ --limits system.txt --store designs --recon 127.0.0.1:9002 --output images
201
+ ```
202
+
203
+ Or skip the install and try it in your browser:
204
+ <https://pulserver.github.io/MaRGE/>.
205
+
206
+ ## On a real scanner
207
+
208
+ You need the vendor interpreter for your scanner, which calls
209
+ `pulserver design` for steps 1 to 3 and streams the raw data to
210
+ `pulserver proxy` for steps 4 to 6. [Running the services](https://pulserver.github.io/pulserver/stable/user-guide/running.html)
211
+ explains how to set both up.
212
+
213
+ pulserver's checks compute estimates. Passing them does not establish scanner
214
+ or patient safety: SAR and RF and gradient heating are checked by the
215
+ scanner's own routines.
216
+
217
+ ## Learn more
218
+
219
+ - [Course](https://pulserver.github.io/pulserver/stable/examples/course.html):
220
+ six lessons from a protocol to an image.
221
+ - [Documentation](https://pulserver.github.io/pulserver/stable/), for every
222
+ option and API detail.
223
+
224
+ ## How to cite
225
+
226
+ If you use pulserver in your work, please cite:
227
+
228
+ ```bibtex
229
+ @inproceedings{cencini2025pulserver,
230
+ title = {Pulserver: an open-source Pulseq-based client-server framework for vendor agnostic, interactive {MR} sequence design},
231
+ author = {Cencini, Matteo and Wang, Kang and Huang, Sherry and Schulte, Rolf F. and Sprenger, Tim
232
+ and Noll, Douglas C. and Tosetti, Michela and Nielsen, Jon-Fredrik},
233
+ booktitle = {Proceedings of the International Society for Magnetic Resonance in Medicine},
234
+ pages = {1275},
235
+ year = {2025}
236
+ }
237
+ ```
238
+
239
+ pulserver builds on Pulseq ([Layton et al., 2017](https://doi.org/10.1002/mrm.26235))
240
+ and the ISMRM raw data format ([Inati et al., 2017](https://doi.org/10.1002/mrm.26089));
241
+ please cite those too.
242
+
243
+ ## License
244
+
245
+ MIT, see [LICENSE](https://github.com/pulserver/pulserver/blob/main/LICENSE).
@@ -0,0 +1,194 @@
1
+ [![Tests](https://github.com/pulserver/pulserver/actions/workflows/test-ci.yml/badge.svg)](https://github.com/pulserver/pulserver/actions/workflows/test-ci.yml)
2
+ [![PyPI](https://img.shields.io/pypi/v/pulserver.svg)](https://pypi.org/project/pulserver/)
3
+ [![Docs: stable](https://img.shields.io/badge/docs-stable-2b76ad)](https://pulserver.github.io/pulserver/stable/)
4
+ [![Virtual scanner: try it](https://img.shields.io/badge/virtual%20scanner-try%20it-ffbd28)](https://pulserver.github.io/MaRGE/)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-ffbd28.svg)](https://github.com/pulserver/pulserver/blob/main/LICENSE)
6
+
7
+ <p align="center"><picture>
8
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/pulserver/pulserver/main/docs/_static/pulserver-logo-dark.svg">
9
+ <img src="https://raw.githubusercontent.com/pulserver/pulserver/main/docs/_static/pulserver-logo.svg" alt="pulserver" width="580">
10
+ </picture></p>
11
+
12
+ pulserver lets you run your own sequence on a clinical scanner as if it were
13
+ a product sequence: the operator edits it in the scanner UI, and the images
14
+ come back to the console. Sequences are exchanged as Pulseq, so any tool that
15
+ writes a `.seq` file (pypulseqpp, MATLAB Pulseq, BART's `seq`...) can provide
16
+ one; pypulseqpp is the one pulserver uses natively.
17
+
18
+ You write two Python plugins, one that designs the sequence and one that
19
+ reconstructs it. pulserver does everything in between: it answers the scanner
20
+ UI, checks the sequence, converts it into the form the scanner plays, and
21
+ routes the raw data back to your reconstruction.
22
+
23
+ <p align="center"><picture>
24
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/pulserver/pulserver/main/docs/_static/architecture-dark.svg">
25
+ <img src="https://raw.githubusercontent.com/pulserver/pulserver/main/docs/_static/architecture.svg" alt="pulserver architecture" width="900">
26
+ </picture></p>
27
+
28
+ The scanner side is a vendor interpreter, a small program on the scanner that
29
+ calls pulserver. pulserver itself never touches the hardware.
30
+
31
+ ## How it works
32
+
33
+ 1. The operator opens your sequence. The scanner asks pulserver which
34
+ parameters to show (TE, TR, flip angle, matrix...), and pulserver reads them
35
+ from your sequence plugin.
36
+ 2. Every time the operator changes a value, pulserver asks your plugin
37
+ whether the protocol is feasible and what it achieves (the TE, the TR, the
38
+ scan time), and sends that back to the scanner. How your plugin answers is
39
+ up to you: design one TR, or compute the times from the block durations.
40
+ If the value is impossible, the scanner shows your error message instead.
41
+ 3. When the operator presses *Scan*, pulserver designs the whole sequence,
42
+ runs the checks (timing, gradient and slew limits, PNS, mechanical
43
+ resonance, acoustic noise) and converts it into the cache the interpreter
44
+ plays, shifted to the prescribed field of view.
45
+ 4. The scanner plays it and streams the raw data to pulserver.
46
+ 5. pulserver fills in everything the sequence knows and the scanner does not:
47
+ the MRD header (encoding spaces, matrix, field of view), and on every
48
+ readout its acquisition header, flags, encoding counters from the
49
+ sequence's labels, and trajectory. Your recon plugin receives these
50
+ enriched readouts, sorts them into k-space and reconstructs as soon as a
51
+ slice or volume is complete.
52
+ 6. Your plugin returns images; pulserver turns them into DICOM and sends them
53
+ back to the console.
54
+
55
+ ## The sequence plugin
56
+
57
+ A sequence plugin takes a pypulseqpp sequence function, a function that returns
58
+ a `Sequence`, and tells the scanner which of its arguments the operator can
59
+ edit:
60
+
61
+ ```python
62
+ # sequences/gre.py
63
+ from pypulseqpp.sequences.sequence.gre2D_sequence import gre2d
64
+ from pulserver.design import Evaluation, FloatParam, SequencePlugin, TimeParam, UIParam
65
+
66
+
67
+ class Gre(SequencePlugin):
68
+ app = gre2d
69
+ protocol = {
70
+ # ms on the scanner UI, µs on the wire, s for gre2d
71
+ UIParam.TE: TimeParam("te", range_min=2000, range_max=20000),
72
+ UIParam.FLIP: FloatParam(
73
+ "flip_angle_deg", unit="deg", range_min=1, range_max=90
74
+ ),
75
+ }
76
+
77
+ def evaluate(self, system, protocol):
78
+ arguments = protocol.arguments # SI units, as gre2d takes them
79
+ one_line = arguments | {"ry": 128, "n_acs_y": 0, "n_dummy": 0} # one TR
80
+ seq = self.app(system, **one_line)
81
+ return Evaluation(protocol.replace({UIParam.TE: seq.definitions["TE"][0]}))
82
+ ```
83
+
84
+ - `protocol` maps each scanner UI button (a `UIParam` key) to the argument of
85
+ your function it sets, with its unit and range (a `*Param` value).
86
+ Arguments you leave out keep the function's defaults.
87
+ - `evaluate` runs on every edit, so keep it fast. Here it designs one line
88
+ and reads back the TE; it could equally compute the TR from block durations,
89
+ or accept the requested TR when it is feasible. To refuse a value, raise;
90
+ the message goes to the operator.
91
+ - The whole scan is built only when the operator starts it, by calling your
92
+ function with the final protocol.
93
+
94
+ pulserver ships a plugin for each of pypulseqpp's 28 built-in sequences
95
+ (Cartesian, radial, spiral, PROPELLER, EPI, FSE, MPRAGE, bSSFP, ZTE), so
96
+ `--plugin gre2d` works without writing anything.
97
+
98
+ ## The recon plugin
99
+
100
+ The reconstruction side follows Gadgetron and ISMRMRD: the data arrive as an
101
+ MRD stream (a header, then one acquisition per readout), and a recon plugin
102
+ handles them as they arrive, much like a Gadgetron gadget chain. Any framework
103
+ can do the maths; this one uses bartorch:
104
+
105
+ ```python
106
+ # recon/pics.py
107
+ import torch
108
+ import bartorch.tools as bt
109
+
110
+ from bartorch import apps, priors
111
+ from pulserver import recon
112
+
113
+
114
+ class Pics(recon.ReconPlugin):
115
+ def recon(self, context, branch, data):
116
+ kspace = torch.from_numpy(data.data.kspace)[:, None] # (coils, z, y, x)
117
+ maps = bt.ecalib(kspace, maps=1)
118
+ image = apps.pics(kspace, maps, regularizers=priors.Wavelet((-1, -2), 0.005))
119
+ return recon.ReconResult(image.abs().squeeze(0).numpy())
120
+
121
+
122
+ PLUGIN = Pics()
123
+ ```
124
+
125
+ - The plugin places each readout in k-space as it arrives, by its encoding
126
+ counters, and `recon` is called once per slice or volume, when the flag that
127
+ closes it arrives. By default that is the end of the scan; pass
128
+ `triggers={"imaging": AcquisitionFlag.LAST_IN_SLICE}` to reconstruct slice
129
+ by slice.
130
+ - The flags and counters come from the labels in your sequence
131
+ (`pp.make_label`, as in PyPulseq), so a correctly labelled sequence needs no
132
+ sorting code in the reconstruction.
133
+ - Return a `ReconResult`; pulserver writes the image header (position,
134
+ orientation, field of view) from the sequence and the scanner.
135
+
136
+ The scanner chooses the recon by name, independently of the sequence, so one
137
+ recon can serve many sequences.
138
+
139
+ ## Try it without a scanner
140
+
141
+ The virtual scanner plays your sequence on a phantom and streams the data to
142
+ your recon, through exactly the path a real scan takes:
143
+
144
+ ```bash
145
+ pip install pulserver bartorch
146
+ printf '[Limits]\nB0: 3.0\n[Limits End]\n' > system.txt
147
+ pulserver proxy --store designs --port 9002 --plugins recon &
148
+ pulserver scan --plugins sequences --plugin gre --reconstruction pics \
149
+ --limits system.txt --store designs --recon 127.0.0.1:9002 --output images
150
+ ```
151
+
152
+ Or skip the install and try it in your browser:
153
+ <https://pulserver.github.io/MaRGE/>.
154
+
155
+ ## On a real scanner
156
+
157
+ You need the vendor interpreter for your scanner, which calls
158
+ `pulserver design` for steps 1 to 3 and streams the raw data to
159
+ `pulserver proxy` for steps 4 to 6. [Running the services](https://pulserver.github.io/pulserver/stable/user-guide/running.html)
160
+ explains how to set both up.
161
+
162
+ pulserver's checks compute estimates. Passing them does not establish scanner
163
+ or patient safety: SAR and RF and gradient heating are checked by the
164
+ scanner's own routines.
165
+
166
+ ## Learn more
167
+
168
+ - [Course](https://pulserver.github.io/pulserver/stable/examples/course.html):
169
+ six lessons from a protocol to an image.
170
+ - [Documentation](https://pulserver.github.io/pulserver/stable/), for every
171
+ option and API detail.
172
+
173
+ ## How to cite
174
+
175
+ If you use pulserver in your work, please cite:
176
+
177
+ ```bibtex
178
+ @inproceedings{cencini2025pulserver,
179
+ title = {Pulserver: an open-source Pulseq-based client-server framework for vendor agnostic, interactive {MR} sequence design},
180
+ author = {Cencini, Matteo and Wang, Kang and Huang, Sherry and Schulte, Rolf F. and Sprenger, Tim
181
+ and Noll, Douglas C. and Tosetti, Michela and Nielsen, Jon-Fredrik},
182
+ booktitle = {Proceedings of the International Society for Magnetic Resonance in Medicine},
183
+ pages = {1275},
184
+ year = {2025}
185
+ }
186
+ ```
187
+
188
+ pulserver builds on Pulseq ([Layton et al., 2017](https://doi.org/10.1002/mrm.26235))
189
+ and the ISMRM raw data format ([Inati et al., 2017](https://doi.org/10.1002/mrm.26089));
190
+ please cite those too.
191
+
192
+ ## License
193
+
194
+ MIT, see [LICENSE](https://github.com/pulserver/pulserver/blob/main/LICENSE).
@@ -1,6 +1,6 @@
1
- # pulserver with its console, BrainWeb and coil extras, BrainWeb's normal
2
- # brain, and the field maps and VOPs of the virtual scanner's coils solved in
3
- # it, on which every exam is scanned. Run without a command, it serves the
1
+ # pulserver with PyHySCO,
2
+ # BrainWeb's normal brain, and the field maps and VOPs of the virtual
3
+ # scanner's coils solved in it, on which every exam is scanned. Run without a command, it serves the
4
4
  # console of the virtual scanner on port 8765, reconstructing each scan in its
5
5
  # own process, for MaRGE's page:
6
6
  #
@@ -29,9 +29,15 @@ FROM python:3.12-slim
29
29
  RUN pip install --no-cache-dir torch --index-url https://download.pytorch.org/whl/cpu
30
30
 
31
31
  COPY --from=build /wheels /wheels
32
- RUN pip install --no-cache-dir "$(echo /wheels/pulserver-*.whl)[console,brainweb,coils]" \
32
+ RUN pip install --no-cache-dir "$(echo /wheels/pulserver-*.whl)" \
33
33
  && rm -rf /wheels
34
34
 
35
+ # PyHySCO, with which the EPI reconstruction corrects each image and volume
36
+ # for susceptibility distortion against its phase-encode-reversed reference.
37
+ # It is GPL-3.0-only: bartorch never ships it, and the image carries it as
38
+ # installed from PyPI, where its source is.
39
+ RUN pip install --no-cache-dir "bartorch[pyhysco]"
40
+
35
41
  # BrainWeb's normal brain, downloaded while the image is built, so that no
36
42
  # exam reaches BrainWeb's server; brainweb-dl reads it from BRAINWEB_DIR.
37
43
  ENV BRAINWEB_DIR=/opt/brainweb
@@ -0,0 +1,15 @@
1
+ // Collapse the code-and-test line under each capability behind one toggle.
2
+ document.addEventListener("DOMContentLoaded", () => {
3
+ for (const list of document.querySelectorAll(".capabilities")) {
4
+ list.classList.add("collapsed");
5
+ const button = document.createElement("button");
6
+ button.className = "capabilities-toggle";
7
+ button.type = "button";
8
+ button.textContent = "Show code and tests";
9
+ button.addEventListener("click", () => {
10
+ const collapsed = list.classList.toggle("collapsed");
11
+ button.textContent = collapsed ? "Show code and tests" : "Hide code and tests";
12
+ });
13
+ list.before(button);
14
+ }
15
+ });
@@ -0,0 +1,39 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 370" role="img" aria-labelledby="t d">
2
+ <title id="t">Calibration shared across an exam</title><desc id="d">A noise series and a calibration series store their whitening and coil maps in the exam cache; an imaging series reads them, and stored maps are reused only when every recorded field equals the unit's, otherwise refused with the differing field named, and never resampled</desc>
3
+ <style>
4
+ .box{fill:#13293a;stroke:#5b9bd0;stroke-width:2}
5
+ .core{fill:#2b76ad;stroke:#8fc3e8;stroke-width:2}
6
+ .a{fill:#2a2415;stroke:#ffbe2e;stroke-width:2}
7
+ .txt{font:22px Arial,sans-serif;fill:#dce7f0}
8
+ .white{font:23px Arial,sans-serif;fill:#ffffff;font-weight:bold}
9
+ .small{font:18px Arial,sans-serif;fill:#dce7f0}
10
+ .label{font:16px Arial,sans-serif;fill:#93a7b5}
11
+ .arrow{stroke:#93a7b5;stroke-width:3;marker-end:url(#m)}
12
+ .head{fill:#93a7b5}
13
+ .ink{fill:none;stroke:#e8eef3;stroke-width:5;stroke-linejoin:miter;stroke-linecap:butt}
14
+ .blue{fill:#5b9bd0}
15
+ .amber{fill:#ffbe2e}
16
+ .word{fill:#79bba4}
17
+ .orange{fill:#ffa00f}
18
+ .unit{stroke:#e6ece9;stroke-width:4}
19
+ .trace{fill:none;stroke:#e6ece9;stroke-width:4;stroke-linejoin:round;stroke-linecap:round}
20
+ .title{font:bold 20px Arial,sans-serif;fill:#dce7f0}
21
+ .wave{fill:none;stroke:#5b9bd0;stroke-width:3;stroke-linejoin:round}
22
+ .rf{fill:none;stroke:#ffbe2e;stroke-width:3;stroke-linejoin:round}
23
+ .adc{fill:#ffbe2e}
24
+ .base{fill:none;stroke:#93a7b5;stroke-width:1.5}
25
+ .bracket{fill:none;stroke:#93a7b5;stroke-width:2}
26
+ </style>
27
+ <defs><marker id="m" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto"><path class="head" d="M0,0 L0,6 L9,3 z"/></marker></defs>
28
+ <rect class="a" x="40" y="20" rx="12" width="280" height="70"/><text class="small" x="180" y="49.5" text-anchor="middle">Noise series</text><text class="small" x="180" y="74.5" text-anchor="middle">publishes the whitening</text>
29
+ <rect class="a" x="360" y="20" rx="12" width="280" height="70"/><text class="small" x="500" y="49.5" text-anchor="middle">Calibration series</text><text class="small" x="500" y="74.5" text-anchor="middle">publishes coil maps</text>
30
+ <rect class="a" x="680" y="20" rx="12" width="280" height="70"/><text class="small" x="820" y="49.5" text-anchor="middle">Imaging series</text><text class="small" x="820" y="74.5" text-anchor="middle">reads both</text>
31
+ <line class="arrow" x1="180" y1="90" x2="180" y2="137"/><text class="label" x="190" y="118.5">stores</text>
32
+ <line class="arrow" x1="500" y1="90" x2="500" y2="137"/><text class="label" x="510" y="118.5">stores</text>
33
+ <line class="arrow" x1="820" y1="137" x2="820" y2="90"/><text class="label" x="830" y="118.5">reads</text>
34
+ <rect class="core" x="40" y="140" rx="12" width="920" height="60"/><text class="white" x="500" y="177" text-anchor="middle">Exam cache: noise covariance, coil maps, B0 and B1 maps</text>
35
+ <line class="arrow" x1="250" y1="200" x2="250" y2="277"/><text class="label" x="260" y="243.5">every field equal</text>
36
+ <line class="arrow" x1="750" y1="200" x2="750" y2="277"/><text class="label" x="760" y="243.5">a field differs</text>
37
+ <rect class="box" x="40" y="280" rx="12" width="420" height="70"/><text class="txt" x="250" y="309.5" text-anchor="middle">Maps reused</text><text class="txt" x="250" y="334.5" text-anchor="middle">as measured</text>
38
+ <rect class="box" x="540" y="280" rx="12" width="420" height="70"/><text class="txt" x="750" y="309.5" text-anchor="middle">Refused, naming the field;</text><text class="txt" x="750" y="334.5" text-anchor="middle">nothing resampled</text>
39
+ </svg>
@@ -0,0 +1,39 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 370" role="img" aria-labelledby="t d">
2
+ <title id="t">Calibration shared across an exam</title><desc id="d">A noise series and a calibration series store their whitening and coil maps in the exam cache; an imaging series reads them, and stored maps are reused only when every recorded field equals the unit's, otherwise refused with the differing field named, and never resampled</desc>
3
+ <style>
4
+ .box{fill:#eef4fa;stroke:#2b76ad;stroke-width:2}
5
+ .core{fill:#2b76ad;stroke:#174b70;stroke-width:2}
6
+ .a{fill:#fdf6e6;stroke:#ffb91f;stroke-width:2}
7
+ .txt{font:22px Arial,sans-serif;fill:#173042}
8
+ .white{font:23px Arial,sans-serif;fill:#ffffff;font-weight:bold}
9
+ .small{font:18px Arial,sans-serif;fill:#173042}
10
+ .label{font:16px Arial,sans-serif;fill:#657786}
11
+ .arrow{stroke:#657786;stroke-width:3;marker-end:url(#m)}
12
+ .head{fill:#657786}
13
+ .ink{fill:none;stroke:#12212b;stroke-width:5;stroke-linejoin:miter;stroke-linecap:butt}
14
+ .blue{fill:#3a77a8}
15
+ .amber{fill:#ffbe2e}
16
+ .word{fill:#356a57}
17
+ .orange{fill:#ffa00f}
18
+ .unit{stroke:#2a2a2a;stroke-width:4}
19
+ .trace{fill:none;stroke:#2a2a2a;stroke-width:4;stroke-linejoin:round;stroke-linecap:round}
20
+ .title{font:bold 20px Arial,sans-serif;fill:#173042}
21
+ .wave{fill:none;stroke:#3a77a8;stroke-width:3;stroke-linejoin:round}
22
+ .rf{fill:none;stroke:#ffbe2e;stroke-width:3;stroke-linejoin:round}
23
+ .adc{fill:#ffbe2e}
24
+ .base{fill:none;stroke:#657786;stroke-width:1.5}
25
+ .bracket{fill:none;stroke:#657786;stroke-width:2}
26
+ </style>
27
+ <defs><marker id="m" markerWidth="10" markerHeight="10" refX="9" refY="3" orient="auto"><path class="head" d="M0,0 L0,6 L9,3 z"/></marker></defs>
28
+ <rect class="a" x="40" y="20" rx="12" width="280" height="70"/><text class="small" x="180" y="49.5" text-anchor="middle">Noise series</text><text class="small" x="180" y="74.5" text-anchor="middle">publishes the whitening</text>
29
+ <rect class="a" x="360" y="20" rx="12" width="280" height="70"/><text class="small" x="500" y="49.5" text-anchor="middle">Calibration series</text><text class="small" x="500" y="74.5" text-anchor="middle">publishes coil maps</text>
30
+ <rect class="a" x="680" y="20" rx="12" width="280" height="70"/><text class="small" x="820" y="49.5" text-anchor="middle">Imaging series</text><text class="small" x="820" y="74.5" text-anchor="middle">reads both</text>
31
+ <line class="arrow" x1="180" y1="90" x2="180" y2="137"/><text class="label" x="190" y="118.5">stores</text>
32
+ <line class="arrow" x1="500" y1="90" x2="500" y2="137"/><text class="label" x="510" y="118.5">stores</text>
33
+ <line class="arrow" x1="820" y1="137" x2="820" y2="90"/><text class="label" x="830" y="118.5">reads</text>
34
+ <rect class="core" x="40" y="140" rx="12" width="920" height="60"/><text class="white" x="500" y="177" text-anchor="middle">Exam cache: noise covariance, coil maps, B0 and B1 maps</text>
35
+ <line class="arrow" x1="250" y1="200" x2="250" y2="277"/><text class="label" x="260" y="243.5">every field equal</text>
36
+ <line class="arrow" x1="750" y1="200" x2="750" y2="277"/><text class="label" x="760" y="243.5">a field differs</text>
37
+ <rect class="box" x="40" y="280" rx="12" width="420" height="70"/><text class="txt" x="250" y="309.5" text-anchor="middle">Maps reused</text><text class="txt" x="250" y="334.5" text-anchor="middle">as measured</text>
38
+ <rect class="box" x="540" y="280" rx="12" width="420" height="70"/><text class="txt" x="750" y="309.5" text-anchor="middle">Refused, naming the field;</text><text class="txt" x="750" y="334.5" text-anchor="middle">nothing resampled</text>
39
+ </svg>