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.
- {pulserver-0.2.1 → pulserver-0.2.2}/.github/workflows/docs.yml +1 -1
- {pulserver-0.2.1 → pulserver-0.2.2}/.github/workflows/tags-release.yml +6 -5
- {pulserver-0.2.1 → pulserver-0.2.2}/.github/workflows/test-ci.yml +1 -1
- {pulserver-0.2.1 → pulserver-0.2.2}/AGENTS.md +7 -3
- pulserver-0.2.2/PKG-INFO +245 -0
- pulserver-0.2.2/README.md +194 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docker/Dockerfile +10 -4
- pulserver-0.2.2/docs/_static/capabilities.js +15 -0
- pulserver-0.2.2/docs/_static/context-dark.svg +39 -0
- pulserver-0.2.2/docs/_static/context.svg +39 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/pulserver.css +55 -0
- pulserver-0.2.2/docs/_static/safety-dark.svg +42 -0
- pulserver-0.2.2/docs/_static/safety.svg +42 -0
- pulserver-0.2.2/docs/_static/session-dark.svg +40 -0
- pulserver-0.2.2/docs/_static/session.svg +40 -0
- pulserver-0.2.2/docs/_static/virtual-dark.svg +40 -0
- pulserver-0.2.2/docs/_static/virtual.svg +40 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/api/design.md +16 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/api/host.md +1 -1
- pulserver-0.2.2/docs/api/index.md +34 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/api/mrd.md +3 -1
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/api/protocol.md +4 -3
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/api/recon.md +1 -1
- pulserver-0.2.2/docs/colab.py +129 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/conf.py +17 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/developer-guide/documentation.md +17 -10
- pulserver-0.2.2/docs/developer-guide/index.md +26 -0
- pulserver-0.2.2/docs/developer-guide/installation.md +52 -0
- {pulserver-0.2.1/docs/explanations → pulserver-0.2.2/docs/developer-guide/internals}/calibration.md +10 -22
- pulserver-0.2.2/docs/developer-guide/internals/index.md +25 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/developer-guide/internals/ir-cache.md +46 -40
- pulserver-0.2.2/docs/developer-guide/internals/protocol-wire.md +150 -0
- pulserver-0.2.1/docs/explanations/reconstruction.md → pulserver-0.2.2/docs/developer-guide/internals/reconstruction-proxy.md +208 -147
- pulserver-0.2.2/docs/developer-guide/internals/sar-ratios.md +57 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/developer-guide/internals/virtual-scanner.md +280 -43
- pulserver-0.2.2/docs/developer-guide/releases.md +14 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/developer-guide/style.md +15 -10
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/developer-guide/terminology.md +12 -6
- pulserver-0.2.2/docs/developer-guide/workflow.md +39 -0
- pulserver-0.2.2/docs/examples/course.md +35 -0
- pulserver-0.2.2/docs/examples/index.md +37 -0
- pulserver-0.2.2/docs/examples/tours.md +18 -0
- pulserver-0.2.2/docs/explanations/architecture.md +124 -0
- pulserver-0.2.2/docs/explanations/index.md +34 -0
- pulserver-0.2.2/docs/explanations/protocol.md +99 -0
- pulserver-0.2.2/docs/explanations/raw-data.md +97 -0
- pulserver-0.2.2/docs/explanations/reconstruction-session.md +113 -0
- pulserver-0.2.2/docs/explanations/reconstruction.md +114 -0
- pulserver-0.2.2/docs/explanations/safety-checks.md +122 -0
- pulserver-0.2.2/docs/explanations/scanner-representation.md +192 -0
- pulserver-0.2.2/docs/explanations/virtual-scanner.md +110 -0
- pulserver-0.2.2/docs/user-guide/index.md +97 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/user-guide/reconstruction-client.md +20 -16
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/user-guide/reconstruction-plugins.md +150 -118
- pulserver-0.2.2/docs/user-guide/running.md +438 -0
- pulserver-0.2.2/docs/user-guide/scanner-sequences.md +540 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/user-guide/virtual-scanner.md +183 -177
- pulserver-0.2.2/gallery/01-course/01_protocol_to_image.py +242 -0
- pulserver-0.2.2/gallery/01-course/02_sequence_plugin.py +447 -0
- pulserver-0.2.2/gallery/01-course/03_scanner_representation.py +285 -0
- pulserver-0.2.2/gallery/01-course/04_reconstruction_plugin.py +304 -0
- pulserver-0.2.2/gallery/01-course/05_recon_plugin.py +249 -0
- pulserver-0.2.2/gallery/01-course/06_testing_on_the_virtual_scanner.py +340 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/gallery/02-tours/01_protocol_resolution.py +45 -44
- {pulserver-0.2.1 → pulserver-0.2.2}/gallery/02-tours/02_segmentation.py +24 -25
- {pulserver-0.2.1 → pulserver-0.2.2}/gallery/02-tours/03_fov_offset_enrichment.py +36 -35
- {pulserver-0.2.1 → pulserver-0.2.2}/pyproject.toml +7 -9
- {pulserver-0.2.1 → pulserver-0.2.2}/scripts/make_artwork.py +206 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseg/pulseg_protocol.h +157 -75
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/io/pulseg_protocol.c +92 -15
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_accelerators.py +1 -1
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_plugins.py +4 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/_evaluation.py +62 -28
- pulserver-0.2.2/src/pulserver/_zoo/_saturation.py +118 -0
- pulserver-0.2.2/src/pulserver/_zoo/_slab.py +37 -0
- pulserver-0.2.2/src/pulserver/_zoo/_user.py +160 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/bssfp2d/retrospective.toml +2 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/fse3d/dual_region.toml +2 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/fse3d/navigator.toml +2 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/fse3d/optimized.toml +2 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/fse3d/shuffling.toml +2 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/fse3d/wave.toml +2 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/gre3d/full_plane.toml +2 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/gre3d/wave.toml +2 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/gre_multiecho2d/bipolar.toml +2 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/gre_multiecho3d/bipolar.toml +2 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/gre_multiecho3d/full_plane.toml +2 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/gre_multiecho3d/wave.toml +2 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/gre_spiral2d/variable_density.toml +2 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/gre_stack_of_spirals3d/variable_density.toml +2 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/mprage3d/navigator.toml +2 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/mprage3d/shuffling.toml +2 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/mprage3d/wave.toml +2 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/mprage_stack_of_spirals3d/variable_density.toml +2 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/se3d/full_plane.toml +2 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/se_spiral2d/variable_density.toml +2 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/se_stack_of_spirals3d/variable_density.toml +2 -0
- pulserver-0.2.2/src/pulserver/_zoo/parameters/zte3d/meridian.toml +2 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/bssfp2d.py +23 -4
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/bssfp3d.py +25 -7
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/epi2d.py +32 -86
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/epi3d.py +31 -5
- pulserver-0.2.2/src/pulserver/_zoo/sequences/fse3d.py +205 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/gre2d.py +29 -3
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/gre3d.py +37 -13
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/gre_multiecho2d.py +30 -3
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/gre_multiecho3d.py +41 -13
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/gre_propeller2d.py +21 -2
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/gre_radial2d.py +21 -2
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/gre_spiral2d.py +19 -4
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/gre_stack_of_blades3d.py +29 -3
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/gre_stack_of_spirals3d.py +30 -6
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/gre_stack_of_stars3d.py +28 -3
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/mprage3d.py +60 -29
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/mprage_stack_of_spirals3d.py +34 -8
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/mprage_stack_of_stars3d.py +31 -3
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/se2d.py +29 -3
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/se3d.py +32 -3
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/se_epi_propeller2d.py +24 -3
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/se_propeller2d.py +21 -2
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/se_radial2d.py +21 -2
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/se_spiral2d.py +19 -4
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/se_stack_of_blades3d.py +29 -3
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/se_stack_of_spirals3d.py +30 -6
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/se_stack_of_stars3d.py +28 -3
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/sequences/zte3d.py +12 -2
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/design/__init__.py +8 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/design/_entries.py +180 -74
- pulserver-0.2.2/src/pulserver/design/_options.py +133 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/design/_plugin.py +76 -30
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/design/_rf.py +3 -3
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/host/__init__.py +1 -1
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/host/_blocks.py +29 -1
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/host/_command.py +17 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/host/_limits.py +17 -15
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/host/_server.py +13 -7
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/host/_service.py +139 -40
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/ir/_checks.py +27 -23
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/ir/_convert.py +74 -42
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/ir/_playout.py +17 -1
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/ir/_source.py +3 -2
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/ir/_vendor.py +10 -1
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/ir/_waves.py +36 -1
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/mrd/__init__.py +4 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/mrd/_acquisitions.py +6 -6
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/mrd/_header.py +20 -13
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/mrd/_images.py +5 -5
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/mrd/_metadata.py +32 -2
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/mrd/_sequence.py +23 -11
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/protocol/__init__.py +2 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/protocol/_keys.py +158 -31
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/protocol/_prescription.py +1 -1
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/protocol/_wire.py +7 -3
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/_designs.py +1 -1
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/_enrich.py +151 -14
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/_held.py +3 -2
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/_intake.py +4 -4
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/_local.py +2 -2
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/_motion.py +8 -12
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/_proxy.py +78 -69
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/_seqdesc.py +15 -17
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/_workers.py +22 -8
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_buffers.py +28 -26
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_calibration.py +6 -7
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_correct.py +17 -17
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_runtime/concurrency.py +12 -14
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_runtime/connection.py +12 -8
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_runtime/exam.py +2 -2
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_runtime/mrd2dicom.py +39 -39
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_units.py +9 -5
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/gadgets.py +5 -6
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/handlers/epi.py +33 -17
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/handlers/nufft.py +61 -30
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/handlers/nufft_train.py +2 -2
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/handlers/pics.py +21 -8
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/handlers/pics_train.py +2 -2
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/handlers/pmc.py +4 -2
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/plugin.py +31 -35
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/validate/_compare.py +55 -38
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/validate/_validate.py +22 -21
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/validate/_xml.py +12 -9
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_brainweb.py +11 -18
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_coils.py +35 -27
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_command.py +9 -5
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_console.py +57 -38
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_export.py +7 -7
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_fourier.py +241 -76
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_girf.py +2 -2
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_localizer.py +11 -10
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_motion.py +4 -4
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_phantom.py +6 -6
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_stream.py +150 -73
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_timeline.py +241 -30
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_tissue.py +3 -1
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/_analytic.py +1 -1
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/_host.py +9 -2
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_buffers.py +22 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_epi.py +45 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_mrd2dicom.py +42 -3
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_nufft.py +15 -1
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_pics.py +24 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_console.py +31 -5
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_console_sequences.py +51 -1
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_design_service.py +31 -2
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_docstring_defaults.py +11 -4
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_enrich.py +116 -3
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir.py +15 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_keys.py +33 -6
- pulserver-0.2.2/tests/test_plugin_options.py +240 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_protocol.py +28 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_sequence_plugin.py +97 -1
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_virtual.py +14 -7
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_virtual_brainweb.py +0 -6
- pulserver-0.2.2/tests/test_virtual_clock.py +205 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_virtual_coils.py +0 -13
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_virtual_fourier.py +90 -1
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_virtual_localizer.py +9 -7
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_zoo_evaluation.py +476 -62
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_zoo_wave.py +17 -8
- pulserver-0.2.1/PKG-INFO +0 -223
- pulserver-0.2.1/README.md +0 -166
- pulserver-0.2.1/docs/api/index.md +0 -34
- pulserver-0.2.1/docs/developer-guide/code-of-conduct.md +0 -6
- pulserver-0.2.1/docs/developer-guide/index.md +0 -33
- pulserver-0.2.1/docs/developer-guide/installation.md +0 -13
- pulserver-0.2.1/docs/developer-guide/internals/index.md +0 -21
- pulserver-0.2.1/docs/developer-guide/internals/protocol-wire.md +0 -81
- pulserver-0.2.1/docs/developer-guide/internals/reconstruction-proxy.md +0 -113
- pulserver-0.2.1/docs/developer-guide/pre-commit.md +0 -14
- pulserver-0.2.1/docs/developer-guide/prerequisites.md +0 -9
- pulserver-0.2.1/docs/developer-guide/pull-requests.md +0 -6
- pulserver-0.2.1/docs/developer-guide/releases.md +0 -11
- pulserver-0.2.1/docs/developer-guide/workflow.md +0 -12
- pulserver-0.2.1/docs/examples/course.md +0 -25
- pulserver-0.2.1/docs/examples/index.md +0 -36
- pulserver-0.2.1/docs/examples/tours.md +0 -18
- pulserver-0.2.1/docs/explanations/architecture.md +0 -118
- pulserver-0.2.1/docs/explanations/designs.md +0 -197
- pulserver-0.2.1/docs/explanations/index.md +0 -32
- pulserver-0.2.1/docs/explanations/protocol.md +0 -186
- pulserver-0.2.1/docs/explanations/scanner-representation.md +0 -160
- pulserver-0.2.1/docs/explanations/virtual-scanner.md +0 -253
- pulserver-0.2.1/docs/user-guide/index.md +0 -99
- pulserver-0.2.1/docs/user-guide/running.md +0 -400
- pulserver-0.2.1/docs/user-guide/scanner-sequences.md +0 -383
- pulserver-0.2.1/gallery/01-course/01_protocol_to_image.py +0 -161
- pulserver-0.2.1/gallery/01-course/02_sequence_plugin.py +0 -160
- pulserver-0.2.1/gallery/01-course/03_scanner_representation.py +0 -135
- pulserver-0.2.1/gallery/01-course/04_reconstruction_plugin.py +0 -185
- pulserver-0.2.1/gallery/01-course/05_testing_on_the_virtual_scanner.py +0 -148
- pulserver-0.2.1/src/pulserver/_zoo/sequences/fse3d.py +0 -148
- {pulserver-0.2.1 → pulserver-0.2.2}/.claude/skills/build-and-test/SKILL.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/.claude/skills/write-documentation/SKILL.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/.dockerignore +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/.gitattributes +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/.github/dependabot.yml +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/.github/workflows/codeql.yml +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/.github/workflows/console.yml +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/.github/workflows/fields.yml +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/.github/workflows/image.yml +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/.github/workflows/koma.yml +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/.github/workflows/style.yml +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/.github/workflows/wheels.yml +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/.gitignore +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/.pre-commit-config.yaml +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/CLAUDE.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/CMakeLists.txt +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/CODE_OF_CONDUCT.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/CONTRIBUTING.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/GEMINI.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/LICENSE +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/LICENSES/gadgetron-python-MIT.txt +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/LICENSES/python-ismrmrd-server-MIT.txt +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/NEXT.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/SECURITY.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/SKILLS.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/codecov.yml +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docker/compose.yaml +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docker/entrypoint.sh +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docker/limits/limits.txt +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docker/smoke.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/architecture-dark.svg +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/architecture.svg +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/pulseg-dark.svg +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/pulseg.svg +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/pulserver-logo-dark.svg +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/pulserver-logo.svg +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/pulserver-mark-dark.svg +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/pulserver-mark.svg +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/repetition-dark.svg +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/_static/repetition.svg +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/_templates/autosummary/base.rst +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/_templates/autosummary/class.rst +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/_templates/autosummary/enum.rst +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/_templates/autosummary/function.rst +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/api/ir.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/api/proxy.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/api/validate.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/api/virtual.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/api_objects.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/figure_style.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/index.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/misc/contributors.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/misc/index.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/misc/license.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/docs/misc/related.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/gallery/01-course/README.rst +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/gallery/01-course/_gallery_header.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/gallery/02-tours/README.rst +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/gallery/02-tours/_gallery_header.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/gallery/README.rst +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/gallery/_gallery_header.md +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/scripts/build_docs.sh +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/scripts/check_throughput.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/scripts/format_and_lint.sh +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/scripts/publish_docs.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/setup.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/CMakeLists.txt +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/cache/pulseg_cache.c +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/core/pulseg_core.c +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/core/pulseg_error.c +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/core/pulseg_math.c +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseg/pulseg.h +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseg/pulseg_cache.h +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseg/pulseg_collection.h +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseg/pulseg_config.h +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseg/pulseg_errors.h +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseg/pulseg_io.h +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseg/pulseg_playout.h +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseg/pulseg_types.h +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseq/pulseq.h +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseq/pulseq_config.h +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/include/pulseq/pulseq_types.h +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/io/pulseg_opts.c +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/playout/pulseg_playout.c +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/pulseg_internal.h +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/pulseq/pulseq_internal.h +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/pulseq/pulseq_shape.c +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/structure/pulseg_descriptor.c +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/structure/pulseg_getters.c +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/structure/pulseg_repetition.c +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/c/structure/pulseg_waves.c +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/fourier/bindings.cpp +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/fourier/bindings.hpp +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/fourier/parallel.hpp +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/fourier/timeline.cpp +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/fourier/timeline.hpp +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/collection.cpp +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/dedup.cpp +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/from_libraries.cpp +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/from_libraries.hpp +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/pulseg_convert.h +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/pulseq_file.cpp +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/pulseq_file.h +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/repetition.cpp +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/structure.cpp +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/wave_plan.cpp +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/ir/waves.cpp +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/module.cpp +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/native.hpp +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/playout.cpp +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/cpp/playout.hpp +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/__init__.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_cli.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_labels.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_logs.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_memory.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/__init__.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/_inversion.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/_pmc.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/recon/epi.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/recon/nufft.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/recon/nufft_train.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/recon/pics.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/recon/pics_train.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/_zoo/recon/pmc.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/host/__main__.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/host/_push.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/host/_store.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/ir/__init__.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/protocol/_schema.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/__init__.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/__main__.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/proxy/_queue.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/__init__.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/__main__.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_exam_image.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_loader.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_runtime/__init__.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_runtime/application.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_runtime/constants.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_runtime/offline.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_runtime/readers.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/_runtime/writers.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/recon/handlers/__init__.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/validate/__init__.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/validate/_command.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/__init__.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/src/pulserver/virtual/_client.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/_seqtext.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/_synthetic.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/_virtual.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/_zoo.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/conftest.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/bloch/bssfp.npy +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/bloch/epi_2d_main.npy +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/bloch/gre_2d_3sl.npy +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/bloch/steady_dess_hard_0hz.npy +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/bloch/steady_dess_hard_30hz.npy +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/bloch/steady_echo_hard_0hz.npy +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/bloch/steady_echo_hard_30hz.npy +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/bloch/steady_echo_selective_0hz.npy +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/bloch/steady_fid_hard_0hz.npy +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/sequences/dedup_gre_pair.seq +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/sequences/dedup_gre_pair_b.seq +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/sequences/epi_2d_main.seq +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/sequences/gre_2d_3sl.seq +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/sequences/mprage_stack_of_spirals_3d.seq +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/fixtures/sequences/zte_3d.seq +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/koma/Project.toml +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/koma/simulate.jl +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/native/check_initializers.c +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/native/print_wave_samples.c +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/native/read_cache_summary.c +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/native/read_rf_definitions.c +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/native/replay_an_instance.c +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/plugins/crash.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/plugins/gre2d.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/plugins/gre2d_raw.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/plugins/rf_train.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/plugins/stall.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/plugins/strong.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/plugins/tiny.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/plugins/typed.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_app.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_calibration.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_coil_compression.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_connection.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_correct.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_exam_image.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_flags.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_gadgets.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_handlers.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_images.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_loader.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_pmc.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_prewhiten.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_readers_writers.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_slots.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon/test_units.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon_plugins/children.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon_plugins/crash.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon_plugins/device.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon_plugins/exam.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon_plugins/gre2d.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/recon_plugins/pose.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_design_push.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_design_server.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_docs.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_docstrings.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_exam_artifacts.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_exam_calibration.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_exam_sharing.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_host_slots.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_image.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_checks.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_designed.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_extensions.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_grouping.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_groups.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_labels.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_playout.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_profile.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_repetition.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_repetition_gradients.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_rf.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_segments.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_source.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_steady_excitation.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_vendor.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_ir_waves.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_koma.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_limits.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_logs.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_memory.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_motion.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_mrd_images.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_mrd_sequence.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_plugins.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_proxy.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_publish_docs.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_resolution.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_rf_layout.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_scan_command.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_seqdesc.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_smoke.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_validate.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_virtual_export.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_virtual_sequences.py +0 -0
- {pulserver-0.2.1 → pulserver-0.2.2}/tests/test_wire.py +0 -0
|
@@ -51,8 +51,9 @@ jobs:
|
|
|
51
51
|
- env:
|
|
52
52
|
GITHUB_TOKEN: ${{ github.token }}
|
|
53
53
|
run: |
|
|
54
|
-
gh release
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
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
|
-
|
|
265
|
-
|
|
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
|
|
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`,
|
pulserver-0.2.2/PKG-INFO
ADDED
|
@@ -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
|
+
[](https://github.com/pulserver/pulserver/actions/workflows/test-ci.yml)
|
|
53
|
+
[](https://pypi.org/project/pulserver/)
|
|
54
|
+
[](https://pulserver.github.io/pulserver/stable/)
|
|
55
|
+
[](https://pulserver.github.io/MaRGE/)
|
|
56
|
+
[](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
|
+
[](https://github.com/pulserver/pulserver/actions/workflows/test-ci.yml)
|
|
2
|
+
[](https://pypi.org/project/pulserver/)
|
|
3
|
+
[](https://pulserver.github.io/pulserver/stable/)
|
|
4
|
+
[](https://pulserver.github.io/MaRGE/)
|
|
5
|
+
[](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
|
|
2
|
-
# brain, and the field maps and VOPs of the virtual
|
|
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)
|
|
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>
|