"""Variable flip angle train generation routines."""
__all__ = ["piecewise_fa", "sinusoidal_fa"]
import math
import numpy as np
import torch
[docs]def piecewise_fa(
fa_ref=[1.5556, 70.0, 0.7778], length=[350, 230], recovery=300, head=None
):
"""
Design a multi-segment linear flip angle train.
The resulting train, similar to the one introduced by Gomez et al. [1],
consists of ``len(fa_max)`` linear sections. The ``n``-th sections starts at
``fa_ref[n] [deg]``, ends at ``fa_ref[n+1] [deg]`` and consists of ``length[n]`` pulses.
The train is followed by a constant flip angle section of length ``recovery``.
Parameters
----------
fa_ref : Iterable[float], optional
Starting flip angle in ``[deg]`` per linear segment.
The default is ``(1.5556, 70., 0.7778) [deg]``.
length : Iterable[int], optional
Linear segment length.
The default is ``(350, 200)`` pulses.
recovery : int, optional
Constant flip angle recovery train length.
The default is ``300``.
head : Header, optional
Pre-existing acquisition header.
If provided, interpolate FA train to ``ncontrast = head.traj.shape[0]``
and include it in the ``head.FA`` field.
The default is ``None``.
Returns
-------
torch.Tensor | deepmr.Header
If ``head`` is not provided, returns piecewise linear flip angle train in ``[deg]``
as a ``torch.Tensor``. If ``head`` is provided, insert flip angle train
(linearly interpolated to ``ncontrast = head.traj.shape[0]``) in ``head.FA`` field.
References
----------
[1] Gómez, P.A., Cencini, M., Golbabaee, M. et al.
Rapid three-dimensional multiparametric MRI with quantitative transient-state imaging.
Sci Rep 10, 13769 (2020). https://doi.org/10.1038/s41598-020-70789-2
"""
# generate segments
rf_schedule = [
np.linspace(fa_ref[n], fa_ref[n + 1], length[n], dtype=np.float32)
for n in range(len(fa_ref - 1))
]
rf_schedule = np.concatenate(rf_schedule)
# add recovery
if recovery > 0:
rf_schedule = np.concatenate((rf_schedule, np.ones(recovery, dtype=np.float32)))
if head is not None:
if head.traj is not None:
ncontrasts = head.traj.shape[0]
rf_schedule = np.interp(
np.linspace(0, 1, ncontrasts),
np.linspace(0, 1, len(rf_schedule)),
rf_schedule,
)
head.FA = torch.as_tensor(rf_schedule)
return head
return torch.as_tensor(rf_schedule)
[docs]def sinusoidal_fa(
fa_max=(35.0, 43.0, 70.0, 45.0, 27.0),
length=200,
spacing=10,
recovery=0,
offset=5.0,
head=None,
):
"""
Design a multi-segment sinusoidal flip angle train.
The resulting train, similar to the one introduced by Jiang et al. [1],
consists of ``len(fa_max)`` sinusoidal section (positive wave),
each of length ``length` separed by constant flip angle sections
of length ``spacing``. The ``n``-th sinusoidal section peaks at
``fa_max[n] [deg]``. The train is followed by a constant flip angle
section of length ``recovery``. The overall schedule minimum flip angle
is determined by the ``offset`` parameter (in ``[deg]``).
Parameters
----------
fa_max : Iterable[float], optional
Maximum flip angle in ``[deg]`` per sinusoidal segment.
The default is ``(35., 43., 70., 45., 27.) [deg]``.
length : int, optional
Sinusoidal segment length.
The default is ``200`` pulses.
spacing : int, optional
Zero degrees flip angle pulses in between segments.
The default is ``10``.
recovery : int, optional
Constant flip angle recovery train length.
The default is ``0``.
offset : float, optional
Minimum flip angle in ``[deg]``. The default is ``5. [deg]``.
head : Header, optional
Pre-existing acquisition header.
If provided, interpolate FA train to ``ncontrast = head.traj.shape[0]``
and include it in the ``head.FA`` field.
The default is ``None``.
Returns
-------
torch.Tensor | deepmr.Header
If ``head`` is not provided, returns sinusoidal flip angle train in ``[deg]``
as a ``torch.Tensor``. If ``head`` is provided, insert flip angle train
(linearly interpolated to ``ncontrast = head.traj.shape[0]``) in ``head.FA`` field.
Examples
--------
>>> import deepmr
A ``5`` sections flip angle train with ``10``-pulses long separation
and no recovery (e.g., for 2D MR Fingerprinting) can be generated as:
>>> fa_train = deepmr.sinusoidal_fa((35., 43., 70., 45., 27.), 200, 10)
A final ``100`` constant flip angle segment (e.g., for 3D MR Fingerprinting)
can be added via the ``recovery`` argument:
>>> fa_train = deepmr.sinusoidal_fa((35., 43., 70., 45., 27.), 200, 10, recovery=100)
References
----------
[1] Jiang, Y., Ma, D., Seiberlich, N., Gulani, V. and Griswold, M.A. (2015),
MR fingerprinting using fast imaging with steady state precession (FISP) with spiral readout.
Magn. Reson. Med., 74: 1621-1631. https://doi.org/10.1002/mrm.25559
"""
# get maximum flip angle
max_fa = np.array(fa_max, dtype=np.float32) - offset
n_segments = len(max_fa)
# build schedule
n = np.arange(length, dtype=np.float32) + 1
rest = np.zeros(spacing, dtype=np.float32)
rf_schedule = np.concatenate((np.sin(n * np.pi / length) * max_fa[0], rest))
for i in range(1, n_segments):
segment = np.concatenate((np.sin(n * math.pi / length) * max_fa[i], rest))
rf_schedule = np.concatenate((rf_schedule, segment))
# add recovery
if recovery > 0:
rf_schedule = np.concatenate(
(rf_schedule, np.zeros(recovery, dtype=np.float32))
)
# add back offset
rf_schedule += offset
if head is not None:
if head.traj is not None:
ncontrasts = head.traj.shape[0]
rf_schedule = np.interp(
np.linspace(0, 1, ncontrasts),
np.linspace(0, 1, len(rf_schedule)),
rf_schedule,
)
head.FA = torch.as_tensor(rf_schedule)
return head
return torch.as_tensor(rf_schedule)