Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sequence.gradient_waveforms() does not interpolate trapezoids with gradient raster time. #72

Closed
btasdelen opened this issue Jul 19, 2022 · 9 comments · Fixed by #84
Closed
Assignees
Labels
enhancement New feature or request

Comments

@btasdelen
Copy link
Collaborator

Describe the bug
The issue arises when a trapezoid is defined by time points and amplitudes. gradient_waveforms() function directly puts gradient's waveform variable into the gradient axis without considering the timings, which results in incorrect rendering of the gradient. The responsible part seems to be in Sequence.py line 667:

grad_waveforms[j, l1:l2] = waveform

Ideally, there should be a code that interpolates the waveform using grad.tt to gradient raster time before the aforementioned line.

To Reproduce
Here is a minimal sequence that reproduces the bug:

import matplotlib.pyplot as plt
import numpy as np
from pypulseq.make_extended_trapezoid_area import make_extended_trapezoid_area
from pypulseq.opts import Opts
from pypulseq.Sequence.sequence import Sequence

# ## **SYSTEM LIMITS**
# Set the hardware limits and initialize sequence object

system = Opts(max_grad=26, grad_unit='mT/m', max_slew=45, slew_unit='T/m/s', 
              grad_raster_time=10e-6, rf_ringdown_time=10e-6, 
              rf_dead_time=100e-6)
seq = Sequence(system)

gz_spoil,gz_spoil_times,gz_spoil_amps = make_extended_trapezoid_area(channel='z', grad_start=0, grad_end=0, system=system, area=3536)

seq.add_block(gz_spoil)
            
## Waveform debug

gw = seq.gradient_waveforms()
gws = (gw[:, 1:] - gw[:, :-1]) / seq.system.grad_raster_time
gs = np.max(np.abs(gws), axis=1)/system.gamma

Expected behavior
The function should return correct gradient waveforms.

Screenshots
Here is a figure showing how the gradient is rendered. This also causes incorrect calculation of slew rate.

Figure_1

Desktop (please complete the following information):

  • OS: macOS
  • pypulseq version: dev branch
@btasdelen
Copy link
Collaborator Author

Workaround: Passing convert_to_arbitrary=True to make_extended_trapezoid() seems to work around the issue, naturally, but kind of defeats the purpose of extended trapezoids. Also, that parameter is not exposed to make_extended_trapezoid_area().

@sravan953
Copy link
Collaborator

Hi @bilal-tasdelen . Will take a look soon at the two issues you've opened, thanks!

@btasdelen
Copy link
Collaborator Author

If you are interested, I can send PRs?

@sravan953
Copy link
Collaborator

Yes PRs are most welcome! Definitely easier to maintain this framework with more helping hands :)

@btasdelen
Copy link
Collaborator Author

@sravan953 I see that both arbitrary gradients and extended trapezoids have the same type, 'grad'. Arbitrary grads assumed to be in GRT, whereas extended trapezoids are not. To fix the issue, I see two options.

  1. We can define a separate type for extended trapezoids, e.g. 'ext_trap'. This way we can easily distinguish the types. An aside, type name 'grad' is not descriptive either.
  2. We can check if the time axis of the gradients is on the GRT. I believe this is how it is done in 'waveforms_and_times()' function.
    Which one do you prefer?

@sravan953
Copy link
Collaborator

Thus far I have tried to keep PyPulseq identical to Pulseq in terms of code and structure so that users who use both platforms, or come from MATLAB to Python, don't find it jarring. Therefore, I would prefer method #2.

However, I believe waveforms_and_times() would be the more appropriate method to use here? It returns gradient values and their respective timepoints.

@btasdelen
Copy link
Collaborator Author

btasdelen commented Jul 28, 2022

Got it. I agree that overall waveforms_and_times() is more useful, but there are a couple of cases where gradients directly returned on GRT is more convenient. One such example is detecting gradient resonance frequencies, where we take the FT of the entire waveform and check if there are peaks near the forbidden frequencies. Another case is gradient moment calculations.

I will check how it is implemented in the Pulseq, and make necessary changes accordingly.

Edit: I realized that function is dysfunctional in Pulseq. I believe the correct way to implement this function is to call waveforms_and_times(), and then call points_to_waveform() on the output to raster the waveforms. It is best this way because it will reuse the existing functions and reduce the risk of introducing bugs.

However, if you also want to keep the implementations the same, as well as the interface, I will understand. In that case, users can create their own convenience functions as I described.

@sravan953
Copy link
Collaborator

Thank you for sharing example use cases, I was not aware. Your suggestion sounds like a good idea -- and I agree with reusing code to reduce the risk of introducing bugs. Have you tried chaining the two methods and does it fix the issue?

@sravan953 sravan953 added the enhancement New feature or request label Aug 9, 2022
@btasdelen
Copy link
Collaborator Author

I already did that for my fork. I did not observe any issues with my implementation yet. Creating a PR soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants