Source code for pypulseq.rotate

from types import SimpleNamespace
from typing import List, Union

import numpy as np

from pypulseq.add_gradients import add_gradients
from pypulseq.opts import Opts
from pypulseq.scale_grad import scale_grad
from pypulseq.utils.tracing import trace, trace_enabled


def __get_grad_abs_mag(grad: SimpleNamespace) -> np.ndarray:
    if grad.type == 'trap':
        return abs(grad.amplitude)
    return np.max(np.abs(grad.waveform))


[docs] def rotate(*args: SimpleNamespace, angle: float, axis: str, system: Union[Opts, None] = None) -> List[SimpleNamespace]: """ Rotates the corresponding gradient(s) about the given axis by the specified amount. Gradients parallel to the rotation axis and non-gradient(s) are not affected. Possible rotation axes are 'x', 'y' or 'z'. See also `pypulseq.Sequence.sequence.add_block()`. Parameters ---------- axis : str Axis about which the gradient(s) will be rotated. angle : float Angle by which the gradient(s) will be rotated. args : SimpleNamespace Gradient(s). Returns ------- rotated_grads : [SimpleNamespace] Rotated gradient(s). """ if system is None: system = Opts.default axes = ['x', 'y', 'z'] # Cycle through the objects and rotate gradients non-parallel to the given rotation axis. Rotated gradients # assigned to the same axis are then added together. # First create indexes of the objects to be bypassed or rotated i_rotate1 = [] i_rotate2 = [] i_bypass = [] axes.remove(axis) axes_to_rotate = axes if len(axes_to_rotate) != 2: raise ValueError('Incorrect axes specification.') for i in range(len(args)): event = args[i] if (event.type != 'grad' and event.type != 'trap') or event.channel == axis: i_bypass.append(i) else: if event.channel == axes_to_rotate[0]: i_rotate1.append(i) else: if event.channel == axes_to_rotate[1]: i_rotate2.append(i) else: i_bypass.append(i) # Should never happen # Now every gradient to be rotated generates two new gradients: one on the original axis and one on the other from # the axes_to_rotate list rotated1 = [] rotated2 = [] max_mag = 0 # Measure of relevant amplitude for i in range(len(i_rotate1)): g = args[i_rotate1[i]] max_mag = max(max_mag, __get_grad_abs_mag(g)) rotated1.append(scale_grad(grad=g, scale=np.cos(angle))) g = scale_grad(grad=g, scale=np.sin(angle)) g.channel = axes_to_rotate[1] rotated2.append(g) for i in range(len(i_rotate2)): g = args[i_rotate2[i]] max_mag = max(max_mag, __get_grad_abs_mag(g)) rotated2.append(scale_grad(grad=g, scale=np.cos(angle))) g = scale_grad(grad=g, scale=-np.sin(angle)) g.channel = axes_to_rotate[0] rotated1.append(g) # Eliminate zero-amplitude gradients threshold = 1e-6 * max_mag for i in range(len(rotated1) - 1, -1, -1): if __get_grad_abs_mag(rotated1[i]) < threshold: rotated1.pop(i) for i in range(len(rotated2) - 1, -1, -1): if __get_grad_abs_mag(rotated2[i]) < threshold: rotated2.pop(i) # Add gradients on the corresponding axis together g = [] if len(rotated1) != 0: g.append(add_gradients(grads=rotated1, system=system)) if len(rotated2) != 0: g.append(add_gradients(grads=rotated2, system=system)) # Eliminate zero amplitude gradients for i in range(len(g) - 1, -1, -1): if __get_grad_abs_mag(g[i]) < threshold: g.pop(i) # Export bypass = np.take(args, i_bypass) rotated_grads = [*bypass, *g] if trace_enabled(): for grad in rotated_grads: grad.trace = trace() return rotated_grads