Source code for gigablochs.rf

import numpy as np
import scipy.signal as sig

from gigablochs import bloch, utils

[docs] def sinc_pulse(flip_angle, duration, bandwidth, dt, phase_angle=0, window='hann'): """ Generate a sinc pulse with a given flip angle and duration. Parameters ---------- flip_angle : float Desired flip angle in degrees. duration : float Pulse duration in seconds. bandwidth : float Pulse bandwidth in Hz. dt : float Time step in seconds. phase_angle : float, optional Desired phase angle in degrees. Default is 0. window : str, optional Window function to apply to the sinc pulse. Default is Hann. Returns ------- ndarray Sinc pulse waveform. Notes ----- The flip angle of an RF pulse is given by the integral of the pulse B1 field: .. math:: \\theta = \\gamma \\int_0^T B_1 dt where :math:`\\theta` is the flip angle in rads, :math:`\\gamma` is the gyromagnetic ratio in rads/s/T, :math:`T` is the pulse duration is s, and :math:`B_1` is the pulse amplitude in T. The sinc pulse is normalized to achieve the desired flip angle. """ theta, alpha = np.deg2rad(phase_angle), np.deg2rad(flip_angle) time_bandwidth_product = duration * bandwidth x = np.linspace(-time_bandwidth_product / 2, time_bandwidth_product / 2, round(duration / dt), endpoint=False) pulse = sig.get_window(window, len(x)) * np.sinc(x) B1 = np.exp(1j * theta) * alpha / (np.trapz(pulse, dx=dt) * bloch.GAMMA) return B1 * pulse # Tesla
[docs] def adiabaticity(pulse_am, pulse_fm, dt): """ Compute the adiabaticity of an RF pulse. Parameters ---------- pulse_am : ndarray Amplitude modulation waveform in Tesla. pulse_fm : ndarray Frequency modulation waveform in Hz, relative to the Larmor frequency. dt : float Time step in seconds. Returns ------- ndarray Adiabaticity waveform. Notes ----- The adiabaticity of an RF pulse is given by: .. math:: K = \\frac{\\left | \\gamma B_{\\mathrm{effective}} \\right |}{\\left | \\dv{\\varphi}{t} \\right |} = \\frac{\\gamma\\sqrt{A^2(t) + \\left (\\frac{f(t)}{\\gammabar} \\right )^2}}{\\left| \\dv{}{t}\\left ( \\arctan(\\frac{\\gammabar A(t)}{f(t)}) \\right ) \\right|} where :math:`A(t)` and :math:`f(t)` are the amplitude and frequency modulation waveforms, respectively. The adiabaticity is a measure of the ability of the pulse to drive the magnetization to follow the instantaneous effective magnetic field in the rotating frame. When the adiabaticity is much greater than 1, for all time, the pulse is considered adiabatic. """ Bz_eff = pulse_fm / bloch.GAMMA_BAR return bloch.GAMMA * np.sqrt(pulse_am**2 + Bz_eff**2) / np.abs(np.gradient(np.arctan2(pulse_am, Bz_eff), dt))
[docs] def adiabatic_pulse(flip_angle, duration, bandwidth, stretch, dt, amplitude=1e-5, type='sech'): """ Generate an adiabatic pulse with a given flip angle and duration. Parameters ---------- flip_angle : float Desired flip angle in degrees. Not yet implemented. duration : float Pulse duration in seconds. bandwidth : float Pulse bandwidth in Hz. dt : float Time step in seconds. amplitude : float, optional Pulse amplitude in Tesla. Default is 10 µT. This may need to be adjusted along with the bandwidth. type : str, optional Type of adiabatic pulse. Default is hyperbolic secant. Returns ------- pulse_am : ndarray Adiabatic pulse amplitude modulated waveform in Tesla. pulse_fm : ndarray Adiabatic pulse frequency modulated waveform in Hz. Notes ----- The adiabatic pulse is generated by modulating the amplitude and frequency of the pulse waveform. The amplitude and frequency modulations are designed to achieve adiabaticity, i.e., to drive the magnetization to follow the instantaneous effective magnetic field in the rotating frame. """ # TODO: implement flip angle for adiabadic pulse # pulse_am[-1] / pulse_fm[-1] = ratio # TODO: implement phase for adiabadic pulse, to bring magnetization # to any target position in the Bloch sphere # TODO: implement spatially selective RF pulses and compare TimeSLIP pencil beam # pulse to single vessel selective PCASL (use Gx and Gy and increment RF phase # accordingly for both labelling plane and vessel location) ratio = np.tan(np.deg2rad(flip_angle)) / bloch.GAMMA_BAR time_bandwidth_product = duration * bandwidth x = np.linspace(-time_bandwidth_product / 2, time_bandwidth_product / 2, round(duration / dt), endpoint=False) / stretch if type == 'sech': pulse_am = amplitude * np.cosh(x) ** -1 pulse_fm = -bandwidth * np.tanh(x) / 2 else: message = f'Unsupported adiabatic pulse type: {type}' raise ValueError(message) return pulse_am, pulse_fm
[docs] def extend(pulse, duration, dt, axis=0): """ Extend the pulse waveform to a given duration. Parameters ---------- pulse : ndarray Pulse waveform. duration : float Desired pulse duration in seconds. dt : float Time step in seconds. axis : int, optional Axis along which to extend the waveform. Default is 0. Returns ------- ndarray Extended pulse waveform. Notes ----- The pulse waveform is extended to the desired duration by appending zeros to the end of the waveform. """ shap = list(np.shape(pulse)) num_zero = round(duration / dt) - shap[axis] shap[axis] = num_zero zero = np.broadcast_to(utils.expand_dims_to(np.zeros(num_zero), pulse, dimodifier=-1), shap) return np.append(pulse, zero, axis=axis)