Source code for pypulseq.split_gradient_at

from copy import deepcopy
from types import SimpleNamespace
from typing import Tuple, Union

import numpy as np

from pypulseq.make_extended_trapezoid import make_extended_trapezoid
from pypulseq.opts import Opts
from pypulseq.utils.tracing import trace, trace_enabled


[docs] def split_gradient_at( grad: SimpleNamespace, time_point: float, system: Union[Opts, None] = None ) -> Union[SimpleNamespace, Tuple[SimpleNamespace, SimpleNamespace]]: """ Splits a trapezoidal gradient into two extended trapezoids defined by the cut line. Returns the two gradient parts by cutting the original 'grad' at 'time_point'. For the input type 'trapezoid' the results are returned as extended trapezoids, for 'arb' as arbitrary gradient objects. The delays in the individual gradient events are adapted such that add_gradients(...) produces a gradient equivalent to 'grad'. See Also -------- - `pypulseq.split_gradient()` - `pypulseq.make_extended_trapezoid()` - `pypulseq.make_trapezoid()` - `pypulseq.Sequence.sequence.Sequence.add_block()` - `pypulseq.opts.Opts` Parameters ---------- grad : SimpleNamespace Gradient event to be split into two gradient events. time_point : float Time point at which `grad` will be split into two gradient waveforms. system : Opts, default=Opts() System limits. Returns ------- grad1, grad2 : SimpleNamespace Gradient waveforms after splitting. Raises ------ ValueError If non-gradient event is passed. """ if system is None: system = Opts.default # copy() to emulate pass-by-value; otherwise passed grad is modified grad = deepcopy(grad) grad_raster_time = system.grad_raster_time time_index = round(time_point / grad_raster_time) # Work around floating-point arithmetic limitation time_point = round(time_index * grad_raster_time, 6) channel = grad.channel if grad.type == 'grad': # Check if we have an arbitrary gradient or an extended trapezoid if abs(grad.tt[-1] - 0.5 * grad_raster_time) < 1e-10 and np.all( abs(grad.tt[1:] - grad.tt[:-1] - grad_raster_time) < 1e-10 ): # Arbitrary gradient -- trivial conversion # If time point is out of range we have nothing to do if time_index == 0 or time_index >= len(grad.tt): return grad else: grad1 = grad grad2 = grad grad1.last = 0.5 * (grad.waveform[time_index - 1] + grad.waveform[time_index]) grad2.first = grad1.last grad2.delay = grad.delay + grad.t[time_index] grad1.t = grad.t[:time_index] grad1.waveform = grad.waveform[:time_index] grad2.t = grad.t[time_index:] - time_point grad2.waveform = grad.waveform[time_index:] if trace_enabled(): t = trace() grad1.trace = t grad2.trace = t return grad1, grad2 else: # Extended trapezoid times = grad.tt amplitudes = grad.waveform elif grad.type == 'trap': grad.delay = round(grad.delay / grad_raster_time) * grad_raster_time grad.rise_time = round(grad.rise_time / grad_raster_time) * grad_raster_time grad.flat_time = round(grad.flat_time / grad_raster_time) * grad_raster_time grad.fall_time = round(grad.fall_time / grad_raster_time) * grad_raster_time # Prepare the extended trapezoid structure if grad.flat_time == 0: times = [0, grad.rise_time, grad.rise_time + grad.fall_time] amplitudes = [0, grad.amplitude, 0] else: times = [ 0, grad.rise_time, grad.rise_time + grad.flat_time, grad.rise_time + grad.flat_time + grad.fall_time, ] amplitudes = [0, grad.amplitude, grad.amplitude, 0] else: raise ValueError('Splitting of unsupported event.') # If the split line is behind the gradient, there is no second gradient to create if time_point >= grad.delay + times[-1]: raise ValueError('Splitting of gradient at time point after the end of gradient.') # If the split line goes through the delay if time_point < grad.delay: times = np.concatenate(([0], grad.delay + times)) amplitudes = [0, amplitudes] grad.delay = 0 else: time_point -= grad.delay amplitudes = np.array(amplitudes) times = np.array(times).round(6) # Work around floating-point arithmetic limitation # Sample at time point amp_tp = np.interp(x=time_point, xp=times, fp=amplitudes) t_eps = 1e-10 times1 = np.concatenate((times[np.where(times < time_point - t_eps)], [time_point])) amplitudes1 = np.concatenate((amplitudes[np.where(times < time_point - t_eps)], [amp_tp])) times2 = np.concatenate(([time_point], times[times > time_point + t_eps])) - time_point amplitudes2 = np.concatenate(([amp_tp], amplitudes[times > time_point + t_eps])) # Recreate gradients grad1 = make_extended_trapezoid( channel=channel, system=system, times=times1, amplitudes=amplitudes1, skip_check=True, ) grad1.delay = grad.delay grad2 = make_extended_trapezoid( channel=channel, system=system, times=times2, amplitudes=amplitudes2, skip_check=True, ) grad2.delay = time_point if trace_enabled(): t = trace() grad1.trace = t grad2.trace = t return grad1, grad2