Skip to content

Rounding error in add_gradients leads to incorrect gradient waveforms #186

@felixlandmeyer

Description

@felixlandmeyer

Describe the bug
(Note: This is my first issue report in a public repository, I apologise in advance for any mistakes.)
1.
Line 205 in add_gradients returns an error because of dimension inconsistency.
Terminal

I changed line 205 from
waveforms[ii] = np.concatenate(([t_delay], waveforms[ii]))
to
waveforms[ii] = np.concatenate((t_delay, waveforms[ii]))
to resolve the error (waveforms[ii] should always have shape (N,) - I think-)

When adding gradients, sometimes too many gradient raster times are added as a delay in add_gradients.
The returned gradient waveform has a jump to zero and back when connecting the individual gradients.
With add_gradients():
add_grads
Gradients in single blocks:
single_grads

To Reproduce

run this script:


from math import ceil, isclose
from math import pi as PI
import numpy as np
import pypulseq as pp


# Set up system
system = pp.Opts(
    grad_unit='mT/m', max_grad=50, slew_unit='T/m/s',
    max_slew=180, grad_raster_time=10e-6)

Seq = pp.Sequence(system=system)
Seq1 = pp.Sequence(system=system)

amplitudes = np.linspace(0.003, 0.04, 15)  # [T/m]
readout_dur = 4e-3  # [s]
readout_tps = np.arange(0, readout_dur, system.grad_raster_time) 

for ampl in amplitudes:
    # Create readout gradient
    waveform = ampl * np.cos(2*PI/readout_dur * readout_tps)
    g_read = pp.make_arbitrary_grad(
        channel='x', waveform=waveform*system.gamma, system=system)

    # Create rampup gradient
    ramp_dur = g_read.waveform[0]/system.max_slew
    ramp_dur = ceil(
        ramp_dur/system.grad_raster_time
    ) * system.grad_raster_time
    
    g_ramp = pp.make_extended_trapezoid(
        channel='x', amplitudes=[0, g_read.waveform[-1]],
        times=[0, ramp_dur], system=system)
    
    # Create moment nulling gradient for rampup
    g_nullmom = pp.make_trapezoid(
        channel='x', area=-g_ramp.area, system=system)

    single_grads_duration = (
        pp.calc_duration(g_nullmom)
        + pp.calc_duration(g_ramp)
        + pp.calc_duration(g_read)
    )

    # Add native gradients to sequence    
    Seq.add_block(g_nullmom)
    Seq.add_block(g_ramp)
    Seq.add_block(g_read)
    Seq.add_block(pp.make_delay(1e-3))
    
    # Add gradients
    g_ramp.delay = pp.calc_duration(g_nullmom)
    g_read.delay = pp.calc_duration(g_ramp)
    g_new = pp.add_gradients(
        grads=[g_nullmom, g_ramp, g_read],
        max_slew=np.inf,  # Otherwise slew rate conflict 
        system=system
    )

    # Add gradients to sequences
    Seq1.add_block(g_new)
    Seq1.add_block(pp.make_delay(1e-3))

    # Check if gradient timing has changed
    if single_grads_duration == pp.calc_duration(g_new):
        print(f'Duration does not change with added gradients for amplitude {ampl:.4f}')
    else:
        print(f'Duration DOES change with added gradients for amplitude {ampl:.4f}')
        # add_gradients() calc:
        t_delay = np.arange(0, g_read.delay-0.0, step=system.grad_raster_time)
        if t_delay.size != round(g_read.delay/system.grad_raster_time):
            print('\t GRT added for delay in add_gradients():', t_delay.size)
            print('\t Delay to add in units of GRT:', g_read.delay/system.grad_raster_time)

# Show sequence diagramms
dur_seq = Seq.duration()[0]
dur_seq1 = Seq.duration()[0]

Seq.plot(
    show_blocks=False, time_range=(0, dur_seq),
    time_disp='ms', grad_disp='mT/m', plot_now=True)

Seq1.plot(
    show_blocks=False, time_range=(0, dur_seq),
    time_disp='ms', grad_disp='mT/m', plot_now=True)

Expected behavior
Added gradients without a jump at the connection point.

Desktop (please complete the following information):

  • OS: macOS
  • OS Version: Sonoma 14.5
  • pypulseq version: 1.4.2 and 1.4.0

Additional context
The numpy.arange() can add one zero too many to the gradient waveform in some cases due to rounding errors.
In the script above, this happens to some of the readouts (e.g. readout two and five).

I'm still kind of a newbie to pulseq so it might be an issue with my code instead of pypulseq itself.
Other than that, I have fixed the rounding issue in my pypulseq installation and can create a PR if you want.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions