Source code for mrinufft.trajectories.display3D

"""Utils for displaying 3D trajectories."""

from mrinufft import get_operator, get_density
from mrinufft.trajectories.utils import (
    convert_trajectory_to_gradients,
    convert_gradients_to_slew_rates,
    KMAX,
    DEFAULT_RASTER_TIME,
)
from mrinufft.density.utils import flat_traj
import numpy as np


[docs] def get_gridded_trajectory( trajectory: np.ndarray, shape: tuple, grid_type: str = "density", osf: int = 1, backend: str = "gpunufft", traj_params: dict = None, turbo_factor: int = 176, elliptical_samp: bool = True, threshold: float = 1e-3, ): """ Compute various trajectory characteristics onto a grid. This function helps in gridding a k-space sampling trajectory to a desired shape, allowing for easier viewing of the trajectory. The gridding process can be carried out to reflect the sampling density, sampling time, inversion time, k-space holes, gradient strengths, or slew rates. Please check `grid_type` parameter to know the benefits of each type of gridding. During the gridding process, the values corresponding to various samples within the same voxel get averaged. Parameters ---------- trajectory : ndarray The input array of shape (N, M, D), where N is the number of shots and M is the number of samples per shot and D is the dimension of the trajectory (usually 3) shape : tuple The desired shape of the gridded trajectory. grid_type : str, optional The type of gridded trajectory to compute. Default is "density". It can be one of the following: "density" : Get the sampling density in closest number of samples per voxel. Helps understand suboptimal sampling, by showcasing regions with strong oversampling. "time" : Showcases when the k-space data is acquired in time. This is helpful to view and understand off-resonance effects. Generally, lower off-resonance effects occur when the sampling trajectory has smoother k-space sampling time over the k-space. "inversion" : Relative inversion time at the sampling location. Needs `turbo_factor` to be set. This is useful for analyzing the exact inversion time when the k-space is acquired, for sequences like MP(2)RAGE. "holes": Show the k-space missing coverage, or holes, within a ellipsoid of the k-space. "gradients": Show the gradient strengths of the k-space trajectory. "slew": Show the slew rate of the k-space trajectory. osf : int, optional The oversampling factor for the gridded trajectory. Default is 1. backend : str, optional The backend to use for gridding. Default is "gpunufft". Note that "gpunufft" is anyway used to get the `pipe` density internally. traj_params : dict, optional The trajectory parameters. Default is None. This is only needed when `grid_type` is "gradients" or "slew". The parameters needed include `img_size` (tuple), `FOV` (tuple in `m`), and `gamma` (float in kHz/T) of the sequence. Generally these values are stored in the header of the trajectory file. turbo_factor : int, optional The turbo factor when sampling is with inversion. Default is 176, which is the default turbo factor for MPRAGE acquisitions at 1mm whole brain acquisitions. elliptical_samp : bool, optional Whether the k-space corners should be expected to be covered or ignored when `grid_type` is "holes", i.e. the trajectory is an ellipsoid or a cuboic and whether corners should be considered as potential holes. Ignoring them with `True` corresponds to trajectories with spherical/elliptical sampling. Default is `True`. threshold: float, optional default 1e-3 The threshold for the k-space holes in number of samples per voxel This value is set heuristically to visualize the k-space hole. Returns ------- ndarray The gridded trajectory of shape `shape`. """ samples = trajectory.reshape(-1, trajectory.shape[-1]) dcomp = get_density("pipe")(trajectory, shape) grid_op = get_operator(backend)( trajectory, [sh * osf for sh in shape], density=dcomp, upsampfac=1 ) gridded_ones = grid_op.raw_op.adj_op(np.ones(samples.shape[0]), None, True) if grid_type == "density": return np.abs(gridded_ones).squeeze() elif grid_type == "time": data = grid_op.raw_op.adj_op( np.tile(np.linspace(1, 10, trajectory.shape[1]), (trajectory.shape[0],)), None, True, ) elif grid_type == "inversion": data = grid_op.raw_op.adj_op( np.repeat( np.linspace(1, 10, turbo_factor), samples.shape[0] // turbo_factor + 1, )[: samples.shape[0]], None, True, ) elif grid_type == "holes": data = np.abs(gridded_ones).squeeze() < threshold if elliptical_samp: # If the trajectory uses elliptical sampling, ignore the k-space holes # outside the ellipsoid. data[ np.linalg.norm( np.meshgrid( *[np.linspace(-1, 1, sh) for sh in shape], indexing="ij" ), axis=0, ) > 1 ] = 0 elif grid_type in ["gradients", "slew"]: gradients, initial_position = convert_trajectory_to_gradients( trajectory, norm_factor=KMAX, resolution=np.asarray(traj_params["FOV"]) / np.asarray(traj_params["img_size"]), raster_time=DEFAULT_RASTER_TIME, gamma=traj_params["gamma"], ) if grid_type == "gradients": data = np.hstack( [gradients, np.zeros((gradients.shape[0], 1, gradients.shape[2]))] ) else: slews, _ = convert_gradients_to_slew_rates(gradients, DEFAULT_RASTER_TIME) data = np.hstack([slews, np.zeros((slews.shape[0], 2, slews.shape[2]))]) data = grid_op.raw_op.adj_op( np.linalg.norm(data, axis=-1).flatten(), None, True ) return np.squeeze(np.abs(data))