Source code for mrinufft.io.cfl

"""utility functions for Complex-Float data file.

This allow compatibility with BART toolbox and others.

References
----------
BART Toolbox: https://bart-doc.readthedocs.io/en/latest/data.html
"""

from pathlib import Path
import os
import mmap
import warnings
import numpy as np


def _readcfl(cfl_file, hdr_file=None):
    """Read a pair of .cfl/.hdr file to get a complex numpy array.

    Adapted from the BART python cfl library.

    Parameters
    ----------
    name : str
        Name of the file to read.

    Returns
    -------
    array : array_like
        Complex array read from the file.
    """
    basename = Path(cfl_file).with_suffix("")
    if hdr_file is None:
        hdr_file = basename.with_suffix(".hdr")
    cfl_file = basename.with_suffix(".cfl")

    # get dims from .hdr
    with open(hdr_file) as h:
        h.readline()  # skip
        line = h.readline()
    dims = [int(i) for i in line.split()]

    # remove singleton dimensions from the end
    n = np.prod(dims)
    dims_prod = np.cumprod(dims)
    dims = dims[: np.searchsorted(dims_prod, n) + 1]

    # load data and reshape into dims
    with open(cfl_file, "rb") as d:
        a = np.fromfile(d, dtype=np.complex64, count=n)
    return a.reshape(dims, order="F")


def _writecfl(array, cfl_file, hdr_file=None):
    """Write a pair of .cfl/.hdr file representing a complex array.

    Adapted from the BART python cfl library.

    Parameters
    ----------
    name : str
        Name of the file to write.
    array : array_like
        Array to write to file.

    """
    basename = Path(cfl_file).with_suffix("")
    if hdr_file is None:
        hdr_file = basename.with_suffix(".hdr")
    cfl_file = basename.with_suffix(".cfl")

    with open(hdr_file, "w") as h:
        h.write("# Dimensions\n")
        h.write("".join(f"{i} " for i in array.shape))
        h.write("\n")

    size = np.prod(array.shape) * np.dtype(np.complex64).itemsize

    with open(cfl_file, "w+b") as d:
        os.ftruncate(d.fileno(), size)
        mm = mmap.mmap(d.fileno(), size, flags=mmap.MAP_SHARED, prot=mmap.PROT_WRITE)
        array = array.astype(np.complex64, copy=False)
        mm.write(np.ascontiguousarray(array.T))
        mm.close()


[docs] def traj2cfl(traj, shape, basename): """ Export a trajectory defined in MRI-nufft to a BART compatible format. The trajectory will be normalized to -(FOV-1)/2 +(FOV-1)/2, and reshape to BART format. Parameters ---------- traj: array_like trajectory array, shape (N_shot, N_points, 2 or 3) shape: tuple volume shape (FOV) """ traj_ = traj * (np.array(shape) - 1) if traj.shape[-1] == 2: traj_3d = np.zeros(traj_.shape[:-1] + (3,), dtype=traj_.dtype) traj_3d[..., :2] = traj_ traj_ = traj_3d else: traj_ = traj_.astype(np.complex64, copy=False) traj_ = traj_[None, None, ...] _writecfl(traj_.T, basename)
[docs] def cfl2traj(basename, shape=None): """Convert a trajectory BART file to a numpy array compatible with MRI-nufft. Parameters ---------- filename: str Base filename for the trajectory shape: optional Shape of the Image domain. Returns ------- np.ndarray MRI-NUFFT compatible trajectory of shape (n_shot, n_samples, dim) """ traj_raw = _readcfl(basename) # Convert to float array and take only the real part traj = np.ascontiguousarray(traj_raw.T.view("(2,)float32")[..., 0]) if np.all(traj[..., -1] == 0): warnings.warn("2D Trajectory Detected") traj = traj[..., :-1] if shape is None: maxs = [np.max(traj[..., i]) for i in range(traj.shape[-1])] mins = [np.min(traj[..., i]) for i in range(traj.shape[-1])] shape = np.array(maxs) - np.array(mins) warnings.warn(f"Estimated shape {shape}") else: shape = np.asarray(shape) - 1 traj /= np.asarray(shape) return np.squeeze(traj)