Source code for pypulseq.make_extended_trapezoid

from types import SimpleNamespace
from typing import Union

import numpy as np

from pypulseq import eps
from pypulseq.make_arbitrary_grad import make_arbitrary_grad
from pypulseq.opts import Opts
from pypulseq.points_to_waveform import points_to_waveform
from pypulseq.utils.tracing import trace, trace_enabled


[docs] def make_extended_trapezoid( channel: str, amplitudes: Union[np.ndarray, None] = None, convert_to_arbitrary: bool = False, max_grad: float = 0.0, max_slew: float = 0.0, skip_check: bool = False, system: Union[Opts, None] = None, times: Union[np.ndarray, None] = None, ) -> SimpleNamespace: """ Create a gradient by specifying a set of points (amplitudes) at specified time points(times) at a given channel with given system limits. Returns an arbitrary gradient object. See Also -------- - `pypulseq.Sequence.sequence.Sequence.add_block()` - `pypulseq.opts.Opts` - `pypulseq.make_trapezoid.make_trapezoid()` Parameters ---------- channel : str Orientation of extended trapezoidal gradient event. Must be one of 'x', 'y' or 'z'. convert_to_arbitrary : bool, default=False Boolean flag to indicate if the extended trapezoid gradient has to be converted into an arbitrary gradient. amplitudes : numpy.ndarray, default=09 Values defined at `times` time indices. max_grad : float, default=0 Maximum gradient strength. max_slew : float, default=0 Maximum slew rate. system : Opts, default=Opts() System limits. skip_check : bool, default=False Boolean flag to indicate if amplitude check is to be skipped. times : numpy.ndarray, default=np.zeros(1) Time points at which `amplitudes` defines amplitude values. Returns ------- grad : SimpleNamespace Extended trapezoid gradient event. Raises ------ ValueError If invalid `channel` is passed. Must be one of 'x', 'y' or 'z'. If all elements in `times` are zero. If elements in `times` are not in ascending order or not distinct. If all elements in `amplitudes` are zero. If first amplitude of a gradient is non-ero and does not connect to a previous block. """ if amplitudes is None: amplitudes = np.zeros(1) if times is None: times = np.zeros(1) if system is None: system = Opts.default if channel not in ['x', 'y', 'z']: raise ValueError(f"Invalid channel. Must be one of 'x', 'y' or 'z'. Passed: {channel}") times = np.asarray(times) amplitudes = np.asarray(amplitudes) if len(times) != len(amplitudes): raise ValueError('Times and amplitudes must have the same length.') if np.all(times == 0): raise ValueError('At least one of the given times must be non-zero') if np.any(np.diff(times) <= 0): raise ValueError('Times must be in ascending order and all times must be distinct') if abs(round(times[-1] / system.grad_raster_time) * system.grad_raster_time - times[-1]) > eps: raise ValueError('The last time point must be on a gradient raster') if skip_check is False and times[0] > 0 and amplitudes[0] != 0: raise ValueError('If first amplitude of a gradient is non-zero, it must connect to previous block') if max_grad <= 0: max_grad = system.max_grad if max_slew <= 0: max_slew = system.max_slew if convert_to_arbitrary: # Represent the extended trapezoid on the regularly sampled time grid waveform = points_to_waveform(times=times, amplitudes=amplitudes, grad_raster_time=system.grad_raster_time) grad = make_arbitrary_grad( channel=channel, waveform=waveform, system=system, max_slew=max_slew, max_grad=max_grad, delay=times[0], ) else: # Keep the original possibly irregular sampling if np.any(np.abs(np.round(times / system.grad_raster_time) * system.grad_raster_time - times) > eps): raise ValueError( 'All time points must be on a gradient raster or "convert_to_arbitrary" option must be used.' ) grad = SimpleNamespace() grad.type = 'grad' grad.channel = channel grad.waveform = amplitudes grad.delay = round(times[0] / system.grad_raster_time) * system.grad_raster_time grad.tt = times - grad.delay grad.shape_dur = grad.tt[-1] grad.area = 0.5 * ((grad.tt[1:] - grad.tt[:-1]) * (grad.waveform[1:] + grad.waveform[:-1])).sum() grad.first = amplitudes[0] grad.last = amplitudes[-1] slew = np.diff(grad.waveform) / np.diff(grad.tt) if max(abs(slew)) > max_slew * (1 + eps): raise ValueError(f'Slew rate violation {max(abs(slew)) / max_slew * 100:.2f}%') if max(abs(grad.waveform)) > max_grad + eps: raise ValueError(f'Gradient amplitude violation {max(abs(grad.waveform)) / max_grad * 100:.2f}%') if trace_enabled(): grad.trace = trace() return grad