pypulseq
pypulseq copied to clipboard
Implement soft delay extension
Work in progress. Effort to implement soft delay extension for file format 1.5.0 compatibility.
TODO:
- [x] Add
soft_delayhandling toaddBlock(). - [x] Implement
apply_soft_delay(). - [x] Add
soft_delayhandling towrite(). - [x] Add
soft_delayhandling toread(). - [x] Implement
get_default_soft_delay_values() - [ ] Update
check_timing(). - [x] Implement
register_soft_delay_event() - [ ] Add tests for soft delay.
- [ ] Add example sequence.
Challenges/Concerns:
- [ ] Adding soft delays seemed to change some extension ID's, causing sequence tests to fail as the
.seqfiles do not match. Invesigate/fix.
Coverage Report
| Tests | Skipped | Failures | Errors | Time |
|---|---|---|---|---|
| 1335 | 21 :zzz: | 0 :x: | 0 :fire: | 3m 28s :stopwatch: |
Regarding the failing sequence tests, you can set the environment variable SAVE_EXPECTED=1 and run pytest to locally rewrite the .seq files. This will automatically pass the write tests, but then allows you to do a diff with the previous .seq files (e.g. git diff, or make a copy of the .seq files first). Then if you manually verify that the change of IDs is expected, commit the new .seq files. I know manually checking this is a bit of a pain, but I think aiming for a exact match of the .seq files is a good standard, better safe than sorry.
I think this is ready for testing, but there are several things that may require polishing.
- I did not add any tests because I could not come up with reasonable unit tests for this.
- I did not see specific changes to
plot()orcheckTiming()functions in Matlab version, so I couldn't figure out how/if they handle the plotting of soft delays. - This required allowing float input to
add_block()function to mimic the behavior in Matlab, which to my understanding is to mainly setrequired_durationvariable. I feel like this could be a separate function parameter with a default valueNone. My only concern with that is this variable only seems to be used for soft delay anyways, so why is this not a member of soft delay object? How much to diverge from Matlab? - Should we merge this to main, or a separate 1.5.0 branch? With the example scripts, without using any soft delays, there is no change to .seq files, so I hope it is safe to merge to main.
regarding check timings: for it to make sense, it would need to get the information about the values that should be used during the check, correct? maybe a dict of values? and check_timings should warn that the timings are only checked for this particular setting of delay values, other settings in the scanner UI can break.
or would the expected workflow be save seq, apply_soft_delay for all delays, check_timings ? then check_timings should complain if there are any "unapplied" soft delays. or am i misunderstanding how soft delays are supposed to work?
PS: I am stricly in favor of diverting from matlab to make parameters and functions more explicit and pythonic.
General comment: It's probably a good idea to add write_gre_label_softdelay to the example sequence tests (test_sequence.py line 215).
@FrankZijlstra @fzimmermann89 Many thanks for the comments. It looks like #288 takes this PR and has many changes on top of this. I am not sure how to proceed with this without duplicating effort and creating some conflicts.
It looks like there are some changes to soft delay part, but most changes on #288 are for other features. If @mcencini wants to adress these feedback on there I can close this PR.
@btasdelen apologies, I did not mean to create confusion. The only changes in the soft-delay-part are some bug fixes and a simple test case that I wanted to add to this PR (see btasdelen/pypulseq#2). The idea was to develop the remaining v1.5.0 features mentioned in #258, but wait until this PR was merged.
That said, I am happy to help polishing this. If you think you don't need to have soft delay extension in 1.4.x, I can address it in #288. The only issue is that the v1.5.x changes are quite a lot and I unfortunately I do not see an easy way to split it in multiple smaller PR for easy review, except for soft delay which is quite independent from the rest. Suggestions are welcome, either in #288 or #258 threads! :D
@mcencini No worries. That is why I started with soft delays, it was pretty much independent of the rest of the stuff.
I can try cherry-picking your relevant commits. If I make changes and merge this, I believe you will have to deal with some conflicts when you update your branch.
I am fine either way. My opinion is, regardless who deals with the rest, it is better to divide things into smaller PRs, even if the individual PRs are not complete v1.5.0 compatible.
@fzimmermann89 @FrankZijlstra I had a crack at making numID optional, and making soft_delay_hints a normal dictionary to always refer soft delay's by their hints. I realized it is done to keep EventLibrary's data integer only. We don't have that restriction in Python, so I save the hint as string now. soft_delay_hints is a normal dict now, and its only purpose is to make sure numID -> hint mapping is unique.
Another way to approach this is to make use of the soft_delay_hints dictionary, and just not store hint in EventLibrary, instead we can refer to delays by their numID. For this, though, I believe I need to make soft_delay_hints a bijective dictionary again to fetch numID when user provided hint.
Also, I did not remove numID input but made it optional. Let me know if this looks reasonable.
@fzimmermann89 @FrankZijlstra I think this is the only thing left before the v1.5.0 release. Let me know if you have any further comments. I know it is not perfect, but one option to be just merge this and see if folks use it and have any bug reports on it. Since this is pretty much decoupled from the rest of the code, it should be okay.
I think it would be okay to merge it with a few minor changes:
- Add write_gre_label_softdelay to the example sequence tests in
test_sequence. This checks basic things like writing and reading the sequence, which is good to test the soft delay extension blocks in the sequence file. - Fix
get_default_soft_delay_valuesso it does not contain the error report stuff. I do not fully understand the checks being done, but it seems to me it should be incheck_timing(or maybe it can be checked immediately when the block gets added?). These can be left as placeholders wherever it makes most sense to put these checks. Note thatcheck_timingautomatically gets called before writing a sequence, so it is the most safe for any sequence checks that would breaks things on the scanner.
@FrankZijlstra
- Done.
- I moved the remaining timing check into
check_timingand removed the error_report stuff. I kept the other checks as they are not timing checks, but I can remove them if you want to. I can even remove the wholeget_default_soft_delay_valuesfunction if it bothers you, because I don't really understand its purpose or if it works properly...
@schuenke I reviewed the changes. I think this PR looks pretty good at the moment.
I agree soft_delay_simple_example is useful. I wouldn't worry about it looks different from other examples.
I am fine with this being merged if there are no objections.
I added the soft delays to sequence.plot() now. I decided to add it to the RF phase subplot (this is usually pretty empty), but we could also add it to all subplots because soft delay blocks have to be empty anyway. For the duration I use the default duration. This is how it looks for realistic cases with a long and a very short default duration:
What's your opinion on this @btasdelen
Edit: here is the code to generate the plot
import pypulseq as pp
seq = pp.Sequence()
rf, gz, gzr = pp.make_sinc_pulse(flip_angle=0.5, duration=2.56e-3, slice_thickness=1e-3, return_gz=True)
seq.add_block(rf, gz)
seq.add_block(gzr)
seq.add_block(pp.make_soft_delay('TE', default_duration=1e-6))
gx = pp.make_trapezoid('x', flat_area=100, flat_time=5e-3)
adc = pp.make_adc(num_samples=128, duration=5e-3, delay=gx.rise_time)
seq.add_block(gx, adc)
seq.plot()
@schuenke How about this:
Shade is on all axes, but annotation is on Phase only. Is just using the delay hint to annotate too ambiguous?
Alternative annotation: SD: "TE"