Skip to content

Write Pulseq 1.5#718

Open
pvillacorta wants to merge 35 commits intomasterfrom
write-pulseq
Open

Write Pulseq 1.5#718
pvillacorta wants to merge 35 commits intomasterfrom
write-pulseq

Conversation

@pvillacorta
Copy link
Copy Markdown
Collaborator

@pvillacorta pvillacorta commented Jan 28, 2026

This PR fixes #152 and replaces #284 from @beorostica.

First of all, thanks to @beorostica for the original work and the time invested in that PR. Given the changes in master since then, I personally found it easier to reimplement the solution from scratch based on the current state of the master branch. The goal remains the same, with an updated implementation.

Round-trip tests (Koma Sequencewrite_seq → .seq → read_seq → Koma Sequence) are still in progress. For now, a simple (RF + Grad + ADC) sequence is used, but it needs to be improved (more blocks, events, time-shaped waveforms, extensions, …).

Some additional functions and keyword arguments have been added compared to #614. We could take a deeper look at them in a meeting.

- Added comparison methods for `Sequence` and `Extension` types to facilitate equality checks.
- Updated `read_seq` and `get_block` functions to include a `simplify_shapes` keyword argument for improved shape handling.
- New `write_seq` function and new structures for managing Pulseq assets.
- Introduced utility functions for quantizing time and managing Pulseq export tests, ensuring compatibility.
- Added tests for round-trip sequence reading and writing to validate the new functionality.
@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 28, 2026

Codecov Report

❌ Patch coverage is 95.49955% with 50 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.61%. Comparing base (63dc3d0) to head (294f80e).

Files with missing lines Patch % Lines
KomaMRIFiles/src/Sequence/pulseq/Signature.jl 87.35% 11 Missing ⚠️
KomaMRIFiles/src/Sequence/pulseq/ReadPulseq.jl 97.58% 10 Missing ⚠️
KomaMRIFiles/src/Sequence/pulseq/WritePulseq.jl 98.19% 9 Missing ⚠️
KomaMRIBase/src/datatypes/Sequence.jl 92.30% 5 Missing ⚠️
...IBase/src/datatypes/sequence/extensions/Trigger.jl 0.00% 4 Missing ⚠️
...Base/src/datatypes/sequence/extensions/LabelInc.jl 0.00% 3 Missing ⚠️
...Base/src/datatypes/sequence/extensions/LabelSet.jl 0.00% 3 Missing ⚠️
KomaMRIFiles/src/Sequence/pulseq/PulseqEvents.jl 81.25% 3 Missing ⚠️
KomaMRIBase/src/datatypes/sequence/EXT.jl 83.33% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #718      +/-   ##
==========================================
- Coverage   91.11%   90.61%   -0.51%     
==========================================
  Files          62       64       +2     
  Lines        3389     3986     +597     
==========================================
+ Hits         3088     3612     +524     
- Misses        301      374      +73     
Flag Coverage Δ
base 91.25% <82.29%> (+0.38%) ⬆️
core 77.44% <ø> (-11.33%) ⬇️
files 96.92% <96.74%> (+1.28%) ⬆️
komamri 88.13% <ø> (ø)
plots 91.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
KomaMRIBase/src/KomaMRIBase.jl 100.00% <ø> (ø)
KomaMRIBase/src/datatypes/sequence/Grad.jl 94.44% <100.00%> (-0.11%) ⬇️
KomaMRIBase/src/datatypes/sequence/RF.jl 91.54% <100.00%> (+13.21%) ⬆️
KomaMRIFiles/src/KomaMRIFiles.jl 100.00% <ø> (ø)
KomaMRIBase/src/datatypes/sequence/EXT.jl 83.33% <83.33%> (+83.33%) ⬆️
...Base/src/datatypes/sequence/extensions/LabelInc.jl 16.66% <0.00%> (-8.34%) ⬇️
...Base/src/datatypes/sequence/extensions/LabelSet.jl 16.66% <0.00%> (-8.34%) ⬇️
KomaMRIFiles/src/Sequence/pulseq/PulseqEvents.jl 91.42% <81.25%> (ø)
...IBase/src/datatypes/sequence/extensions/Trigger.jl 16.66% <0.00%> (-8.34%) ⬇️
KomaMRIBase/src/datatypes/Sequence.jl 93.56% <92.30%> (+0.42%) ⬆️
... and 3 more

... and 8 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

KomaMRI Benchmarks

Details
Benchmark suite Current: 17d943d Previous: 43a3ab1 Ratio
MRI Lab/Bloch/CPU/2 thread(s) 333149869 ns 335386708 ns 0.99
MRI Lab/Bloch/CPU/4 thread(s) 275996251.5 ns 274571608 ns 1.01
MRI Lab/Bloch/CPU/8 thread(s) 258167686 ns 247370806 ns 1.04
MRI Lab/Bloch/CPU/1 thread(s) 557250045 ns 555875715 ns 1.00
MRI Lab/Bloch/GPU/CUDA 20560080 ns 20834632 ns 0.99
MRI Lab/Bloch/GPU/oneAPI 73159990 ns 73526556 ns 1.00
MRI Lab/Bloch/GPU/Metal 97828958 ns 93171166 ns 1.05
MRI Lab/Bloch/GPU/AMDGPU 23782296.5 ns 23841938 ns 1.00
Slice Selection 3D/Bloch/CPU/2 thread(s) 1583519021 ns 1587890915.5 ns 1.00
Slice Selection 3D/Bloch/CPU/4 thread(s) 874887134 ns 874868400 ns 1.00
Slice Selection 3D/Bloch/CPU/8 thread(s) 554811207 ns 552711225 ns 1.00
Slice Selection 3D/Bloch/CPU/1 thread(s) 3014104944 ns 3043255379.5 ns 0.99
Slice Selection 3D/Bloch/GPU/CUDA 31425726.5 ns 30774441 ns 1.02
Slice Selection 3D/Bloch/GPU/oneAPI 117738422 ns 117283108 ns 1.00
Slice Selection 3D/Bloch/GPU/Metal 112764792 ns 108879500 ns 1.04
Slice Selection 3D/Bloch/GPU/AMDGPU 31907102 ns 31712397 ns 1.01

This comment was automatically generated by workflow using github-action-benchmark.

Comment thread KomaMRIFiles/src/Sequence/Pulseq.jl Outdated
Comment thread KomaMRIFiles/test/runtests.jl Outdated
Comment thread KomaMRIFiles/test/utils.jl Outdated
Comment thread KomaMRIFiles/test/utils.jl Outdated
@cncastillo
Copy link
Copy Markdown
Member

Also something I completely forgot, but it would be great if scanner limits where checked before writing the file, to ensure that the sequence doesnt go beyond the hardware limits.

- Enhance the `≈` operator for `Sequence` to improve event comparison, including handling of empty events and waveform interpolation.
- Update `write_seq` to return the correct ADC delay and add detailed comments for clarity.
- Improve the `register_grad!` function to handle various waveform types.
- Improve a bit (WIP) round trip test.
- Rename test directories.
- Improve phase offset calculations and ensure correct scaling of values
- Bump to 0.9.10
- Add `get_symbol_from_EXT_type` function
- `phase_equality` -> `phase_shape`
- Remove unnecessary brackets
@cncastillo cncastillo mentioned this pull request Apr 2, 2026
cncastillo and others added 4 commits April 2, 2026 15:29
- New function `check_scanner_contraints`.
  Export from KomaBase
- Modify `quantize_to_pulseq` -> `check_raster`;
  it now adapts the sequence to the Pulseq raster correctly.
  Export from KomaFiles
- Get `DEFAULT_RASTER` from default`Scanner` values
- Changes in Pulseq.jl:
      - Remove `seq` field from `PulseqExportContext` struct.
      - Add `definitions` field to `PulseqExportContext` struct.
      - New constructor for `PulseqExportContext`
      - New function `get_RasterTime`
      - Remove `trapezoids` from `PulseqExportAssets` struct
      - Const types `ArbitraryGradient` and `TrapezoidGradient`
      - New function `check_raster`
      - Add information messages and doble cheking in `write_seq`
- Unexport `check_raster`
- Add `check_scanner_constraints` to docs
- Fix Printf version dependency problem
- Change tolerance for sequence comparison
- More appropriate block duration rounding
- Add new sequences to tests:
  - Uniformly-shaped RF
  - Time-shaped RF
  - RF with phase offset
  - Adiabatic RF
@pvillacorta
Copy link
Copy Markdown
Collaborator Author

pvillacorta commented Apr 9, 2026

I have tried to cover most of the requested details. I think the only pending changes are:

  • Revise sequence comparison function:
    • throw warning in case we are comparing via linear interpolation (when sequence samples don't have the same length).
    • compare extensions
  • Documentation for specific functions or keywords (e.g., simplify_shapes).
    EDIT: I have decided to delete that keyword, since it is no longer needed. It's only purpose was to avoid simplifying waveforms when comparing sequences. Now, since the new implementation of isapprox(::Sequence, ::Sequence), sequences can result in being equal even with waveforms with different number of points.
  • Ensure that samples of uniformly-shaped events are located at raster cell centers. This is needed in both read and write functions when no time_shape is provided (time_sahape_id = 0) (see this).
    EDIT: Due to differing sampling conventions (edge-based vs center-based) and integer microsecond delay quantization in Pulseq, compact uniformly-sampled shapes (time_shape_id = 0) are not used to avoid introducing temporal inaccuracies. Therefore, it is possible to read sequences with uniformly-shaped events, but they will be converted to "time-shaped" events once they are written again. This approach may increase file size, but ensures that we are not making any error derived from quantization.
  • More tests (extensions, edge cases...)
  • Bump package versions (KomaBase v0.9.10? KomaFiles v0.9.12?)

@pvillacorta pvillacorta requested a review from cncastillo April 19, 2026 21:18
@cncastillo
Copy link
Copy Markdown
Member

cncastillo commented Apr 20, 2026

Thanks! I tested it on this sequence:
fatsat_r2_5mm_mtx100x100_0_05s.seq.txt

Read takes 6ms, write takes 70ms (~10x).

I also did a "roundtrip" test: read -> write -> read and it gave me an error. I think the problem was the trigger extension. The writer saves the trigger like:

1 2.0 1.0 0.0 0.00000053

but the reader expect %i %i %f %f. For reference, in the original file we had:

1 2 1 0 530000 

so there is an error in the type and unit conversions as well.

Also, the saved DEFINITIONS are weird (we may want to ignore some # <---):

DEFINITIONS]
signature (type = "md5", hash = "e8f4f8ab105b4b52470b2c0a3b6e1e09") # <-------------
AdcRasterTime 2.0e-6
GradientRasterTime 1.0e-5
FOV [0.25, 0.25, 0.01]
Name stanford_logo
Nz 1
Nx 100
Ny 100
PulseqVersion 1.5.0 # <-------------
BlockDurationRaster 1.0e-5
FileName bSSFP_fatsatopt_r2_5mm_mtx100x100_0_05s.seq
RequiredExtensions String[] # <-------------
RadiofrequencyRasterTime 1.0e-6

@cncastillo
Copy link
Copy Markdown
Member

cncastillo commented Apr 20, 2026

Also, right now there are some pretty big differences on the output format. Like hard coding two spaces " " between columns in the block table and others things like that, instead of using printf like MATLAB's. Not really a big problem, can be fixed later.

I would also suggest to split Pulseq.jl to PulseqRead.jl and PulseqWrite.jl.

…nd update `get_slew_rate` and `get_eddy_currents` to use it. Enhance error messages in `check_scanner_constraints` for better clarity.
- Fix Trigger output format (Int)
- (Cursor) Output format improvements
@pvillacorta
Copy link
Copy Markdown
Collaborator Author

pvillacorta commented Apr 21, 2026

I made a set of improvements related to your comments:

  • Output formatting; this change has been guided by Cursor, so it can certainly be simplified. For the moment, it works well. (aac0ec6)
  • Fix Trigger output format. (aac0ec6)
  • Optimize writing by using an array for storing blocks and hash tables for shapes and events (c36121d).
  • Clean definition export. (0b10a5b)
  • Add _slew_quotient_ΔG_over_Δt function for slew rate calculations and update get_slew_rate and get_eddy_currents to use it. Now it does not produce extremely high values (I don't know why you did not get an error when exporting fatsat_r2_5mm_mtx100x100_0_05s). (760b74e)
  • File and folder redistribution as requested. (034f48d)

- Remove unsed `store_shape` and `store_event` methods
- Rename keyword to adapt to format
- Test coverage for all types of signature algorithms
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Write Pulseq files

2 participants