Source code for monalisa.io._siemens_read_meta_data

"""Port of src/rawDataReader/siemens/dhSiemensReadMetaData.m."""

import numpy as np

from monalisa._mat import squeeze
from monalisa.fourier._idf import idf
from monalisa.io._check_metadata_interactive import check_metadata_interactive
from monalisa.io._correct_metadata_interactive import correct_metadata_interactive
from monalisa.io._mri_acquisition_param import MriAcquisitionParam
from monalisa.io._twix import read_twix

# Header fields probed for the field of view, in MATLAB's order (the last found wins).
_FOV_FIELDS = (
    ("Meas", "ReadFoV"), ("Meas", "FOV"),
    ("Protocol", "ReadFoV"), ("Protocol", "PeFOV"), ("Protocol", "PhaseFoV"),
    ("Config", "ReadFoV"), ("Config", "PhaseFoV"), ("Config", "PeFOV"), ("Config", "RoFOV"),
    ("Dicom", "dPhaseFOV"), ("Dicom", "dReadoutFOV"),
)


def _fov(hdr):
    FoV = np.array([[-1.0, -1.0, -1.0]])
    for sec, key in _FOV_FIELDS:
        val = hdr.get(sec, {}).get(key, None) if hasattr(hdr.get(sec, {}), "get") else None
        if isinstance(val, (int, float)) and not isinstance(val, bool):
            FoV = np.array([[float(val) * 2]])
    return FoV


def _movstd_discard(x, k):
    """MATLAB ``movstd(x, k, 'Endpoints', 'discard')`` for a row vector."""
    w = np.lib.stride_tricks.sliding_window_view(np.ravel(x), k)
    return np.std(w, axis=1, ddof=1).astype(x.dtype)[None, :]


[docs] def siemens_read_meta_data(obj, nargout=1): """Acquisition parameters of a Siemens file. The number of non steady-state shots ``nShot_off`` is estimated from the self-navigation signal. When ``obj.autoFlag`` is false, or the FoV is not in the header, the parameters are shown for correction (correct_metadata_interactive, then check_metadata_interactive with the self-navigation plot); with ``autoFlag=True`` set the fields of the returned object in the code instead. Returns the parameters, or ``(parameters, reconFoV)`` for ``nargout=2``. """ myTwix = read_twix(obj.argFile) img = myTwix.image N, nShot, nLine, nEcho = img.NCol, img.NSeg, img.NLin, img.NEco nSeg = nLine / nShot FoV = _fov(myTwix.hdr) node = MriAcquisitionParam(None) node.N = np.float64(N) node.nLine = np.float64(nLine) node.nShot = np.float64(nShot) node.nSeg = np.float64(nSeg) node.nCh = np.float64(1) node.nEcho = np.float64(nEcho) node.FoV = FoV node.timestamp = img.timestamp reconFoV = FoV node.nShot_off = np.float64(0) node.selfNav_flag = True node.roosk_flag = False interactive = (not obj.autoFlag) or np.array_equal(np.ravel(reconFoV), [-1, -1, -1]) if interactive: node, reconFoV = correct_metadata_interactive(node, reconFoV) if nEcho != 1: raise NotImplementedError("siemens_read_meta_data: nEcho > 1, case not implemented yet") y_raw = np.transpose(img.unsorted(), (1, 0, 2)) node.nCh = np.float64(y_raw.shape[0]) y_raw = np.reshape(y_raw, (int(node.nCh), int(node.N), int(node.nSeg), int(node.nShot)), order="F") mySI = squeeze(y_raw[:, :, 0:1, :]) mySI = idf(mySI, 1, None, 2) mySI = squeeze(np.sqrt(np.sum(np.abs(mySI) ** 2, axis=0, keepdims=True))) mySI = mySI - np.min(mySI) mySI = mySI / np.max(mySI) x_SI = np.tile(np.arange(1, mySI.shape[0] + 1, dtype=np.float64)[:, None], (1, mySI.shape[1])) prod = (x_SI * mySI).astype(mySI.dtype) # double .* single -> single s_mean = np.mean(prod, axis=0, keepdims=True) s_center_mass = np.sum(prod, axis=0, keepdims=True) / np.sum(mySI, axis=0, keepdims=True) running_std = _movstd_discard(s_mean, 5) threshold = np.percentile(running_std, 15, method="hazen") node.nShot_off = np.float64(np.flatnonzero(np.ravel(running_std) < threshold)[0] + 1) if interactive: node, reconFoV = check_metadata_interactive(mySI, s_mean, s_center_mass, node, reconFoV) if nargout >= 2: return node, reconFoV return node