Source code for scilpy.reconst.fiber_coherence

# -*- coding: utf-8 -*-
import itertools
import numpy as np


NB_FLIPS = 4
ANGLE_TH = np.pi / 6.

# directions to the 26 neighbors.
# Preparing once rather than in compute_fiber_coherence, possibly called many
# times.
ALL_NEIGHBORS = np.indices((3, 3, 3))
ALL_NEIGHBORS = ALL_NEIGHBORS.T.reshape((27, 3)) - 1
ALL_NEIGHBORS = np.delete(ALL_NEIGHBORS, 13, axis=0)


[docs] def compute_coherence_table_for_transforms(directions, values): """ Compute fiber coherence indexes for all possible axes permutations/flips (ex, originating from a flip in the gradient table). The mathematics are presented in : [1] Schilling et al. A fiber coherence index for quality control of B-table orientation in diffusion MRI scans. Magn Reson Imaging. 2019 May;58:82-89. doi: 10.1016/j.mri.2019.01.018. Parameters ---------- directions: ndarray (x, y, z, 3) Principal fiber orientation for each voxel. values: ndarray (x, y, z) Anisotropy measure for each voxel (e.g. FA map). Returns ------- coherence: list Fiber coherence value for each permutation/flip. transforms: list Transform representing each permutation/flip, in the same order as `coherence` list. """ # Generate transforms for 24 possible permutation/flips of # gradient directions. (Reminder. We want to verify if there was possibly # an error in the gradient table). permutations = list(itertools.permutations([0, 1, 2])) transforms = np.zeros((len(permutations)*NB_FLIPS, 3, 3)) for i in range(len(permutations)): transforms[i*NB_FLIPS, np.arange(3), permutations[i]] = 1 for ii in range(3): flip = np.eye(3) flip[ii, ii] = -1 transforms[ii+i*NB_FLIPS+1] = transforms[i*NB_FLIPS].dot(flip) # Compute the coherence for each one. coherence = [] for t in transforms: index = compute_fiber_coherence(directions.dot(t), values) coherence.append(index) return coherence, list(transforms)
[docs] def compute_fiber_coherence(peaks, values): """ Compute the fiber coherence for `peaks` and `values`. Parameters ---------- peaks: ndarray (x, y, z, 3) Principal fiber orientation for each voxel. values: ndarray (x, y, z) Anisotropy measure for each voxel (e.g. FA map). Returns ------- coherence: float Fiber coherence value. """ # Normalizing peaks norm_peaks = np.zeros_like(peaks) norms = np.linalg.norm(peaks, axis=-1) norm_peaks[norms > 0] = peaks[norms > 0] / norms[norms > 0][..., None] coherence = 0.0 for di in ALL_NEIGHBORS: tx, ty, tz = di.astype(int) slice_x = slice(1 + tx, peaks.shape[0] - 1 + tx) slice_y = slice(1 + ty, peaks.shape[1] - 1 + ty) slice_z = slice(1 + tz, peaks.shape[2] - 1 + tz) di_norm = di / np.linalg.norm(di) # Spatial coherence between the peak at each voxel and the direction to # the neighbor di. # Ex: if the peak is aligned in x and current di is aligned in x, # returns True (with angle < 30 ; cos angle > 30) cos_angles = np.abs(norm_peaks.dot(di_norm)) I_u = cos_angles > np.cos(ANGLE_TH) # Doing the same thing with v; results in the same image but translated # from one voxel. (With 1 voxel padding around the border). I_v = np.zeros_like(I_u) I_v[1:-1, 1:-1, 1:-1] = I_u[slice_x, slice_y, slice_z] # Where both conditions are met: I_uv = np.logical_and(I_u, I_v) u = np.nonzero(I_uv) # v = the same voxels as u, but with the neighborhood difference. v = tuple(np.array(u) + di.astype(int).reshape(3, 1)) # Summing the FA of those voxels coherence += np.sum(values[u]) + np.sum(values[v]) return coherence