diff --git a/mriqc/qc/diffusion.py b/mriqc/qc/diffusion.py index 11743d1c5..431d240fa 100644 --- a/mriqc/qc/diffusion.py +++ b/mriqc/qc/diffusion.py @@ -22,9 +22,6 @@ # https://www.nipreps.org/community/licensing/ # -import numpy as np - - """ Image quality metrics for diffusion MRI data ============================================ @@ -35,6 +32,13 @@ from dipy.core.gradients import GradientTable from dipy.reconst.dti import TensorModel from dipy.denoise.noise_estimate import piesno +from dipy.core.gradients import unique_bvals_magnitude +from dipy.core.gradients import round_bvals +from dipy.segment.mask import segment_from_cfa +from dipy.segment.mask import bounding_box + +def noise_func(img, gtab): + pass def noise_b0(data, gtab, mask=None): """ @@ -83,7 +87,7 @@ def cc_snr(data, gtab, bmag=None, mask=None): gtab : GradientTable class instance or tuple bmag : int - From dipy.core.gradients: + From dipy.core.gradients: The order of magnitude that the bvalues have to differ to be considered an unique b-value. B-values are also rounded up to this order of magnitude. Default: derive this value from the @@ -122,7 +126,7 @@ def cc_snr(data, gtab, bmag=None, mask=None): b0_data = data[..., gtab.b0s_mask] std_signal = np.std(b0_data[mask_cc_part], axis=-1) - + # Per-shell calculation rounded_bvals = round_bvals(gtab.bvals, bmag) bvals = unique_bvals_magnitude(gtab.bvals, bmag) @@ -139,7 +143,7 @@ def cc_snr(data, gtab, bmag=None, mask=None): bval_data = data[..., rounded_bvals == bval] bval_bvecs = gtab.bvecs[rounded_bvals == bval] - + axis_X = np.argmin(np.sum((bval_bvecs-np.array([1, 0, 0]))**2, axis=-1)) axis_Y = np.argmin(np.sum((bval_bvecs-np.array([0, 1, 0]))**2, axis=-1)) axis_Z = np.argmin(np.sum((bval_bvecs-np.array([0, 0, 1]))**2, axis=-1)) @@ -162,83 +166,99 @@ def get_spike_mask(data, z_threshold=3, grouping_vals=None, bmag=None): """ Return binary mask of spike/no spike -def noise_func(img, gtab): - pass - - - -def noise_b0(data, gtab, mask=None): - """ - Estimate noise in raw dMRI based on b0 variance. - Parameters ---------- - """ - if mask is None: - mask = np.ones(data.shape[:3], dtype=bool) - b0 = data[..., ~gtab.b0s_mask] - return np.percentile(np.var(b0[mask], -1), (25, 50, 75)) - + data : numpy array + Data to be thresholded + z_threshold : :obj:`float` + Number of standard deviations above the mean to use as spike threshold + grouping_vals : numpy array + Values by which to group data for thresholding (bvals or full mask) + bmag : int + From dipy.core.gradients: + The order of magnitude that the bvalues have to differ to be + considered an unique b-value. B-values are also rounded up to + this order of magnitude. Default: derive this value from the + maximal b-value provided: $bmag=log_{10}(max(bvals)) - 1$. -def noise_piesno(data, n_channels=4): + Returns + --------- + numpy array """ - Estimate noise in raw dMRI data using the PIESNO [1]_ algorithm. + if grouping_vals is None: + threshold = (z_threshold*np.std(data)) + np.mean(data) + spike_mask = data > threshold + return spike_mask - Parameters - ---------- + threshold_mask = np.zeros(data.shape) - Returns - ------- + rounded_grouping_vals = round_bvals(grouping_vals, bmag) + gvals = unique_bvals_magnitude(grouping_vals, bmag) + if grouping_vals.shape == data.shape: + for gval in gvals: + gval_data = data[rounded_grouping_vals == gval] + gval_threshold = (z_threshold*np.std(gval_data)) + np.mean(gval_data) + threshold_mask[rounded_grouping_vals == gval] = gval_threshold*np.ones(gval_data.shape) + else: + for gval in gvals: + gval_data = data[..., rounded_grouping_vals == gval] + gval_threshold = (z_threshold*np.std(gval_data)) + np.mean(gval_data) + threshold_mask[..., rounded_grouping_vals == gval] = gval_threshold*np.ones(gval_data.shape) - Notes - ----- + spike_mask = data > threshold_mask - .. [1] Koay C.G., E. Ozarslan, C. Pierpaoli. Probabilistic Identification - and Estimation of Noise (PIESNO): A self-consistent approach and - its applications in MRI. JMR, 199(1):94-103, 2009. - """ - sigma, mask = piesno(data, N=n_channels, return_mask=True) - return sigma, mask + return spike_mask -def cc_snr(data, gtab): +def get_slice_spike_percentage(data, z_threshold=3, slice_threshold=.05): """ - Calculate worse-/best-case signal-to-noise ratio in the corpus callosum + Return percentage of slices spiking along each dimension Parameters ---------- - data : ndarray - - gtab : GradientTable class instance or tuple + data : numpy array + Data to be thresholded + z_threshold : :obj:`float` + Number of standard deviations above the mean to use as spike threshold + slice_threshold : :obj:`float` + Percentage of slice elements that need to be above spike threshold for slice to be considered spiking + Returns + --------- + array """ - if isinstance(gtab, GradientTable): - pass + spike_mask = get_spike_mask(data, z_threshold) - # XXX Per-shell calculation - tenmodel = TensorModel(gtab) - tensorfit = tenmodel.fit(data, mask=mask) + ndim = data.ndim + slice_spike_percentage = np.zeros(ndim) - from dipy.segment.mask import segment_from_cfa - from dipy.segment.mask import bounding_box + for ii in range(ndim): + slice_spike_percentage[ii] = np.mean(np.mean(spike_mask, ii) > slice_threshold) - threshold = (0.6, 1, 0, 0.1, 0, 0.1) - CC_box = np.zeros_like(data[..., 0]) + return slice_spike_percentage - mins, maxs = bounding_box(mask) - mins = np.array(mins) - maxs = np.array(maxs) - diff = (maxs - mins) // 4 - bounds_min = mins + diff - bounds_max = maxs - diff - CC_box[bounds_min[0]:bounds_max[0], - bounds_min[1]:bounds_max[1], - bounds_min[2]:bounds_max[2]] = 1 +def get_global_spike_percentage(data, z_threshold=3): + """ + Return percentage of array elements spiking - mask_cc_part, cfa = segment_from_cfa(tensorfit, CC_box, threshold, - return_cfa=True) + Parameters + ---------- + data : numpy array + Data to be thresholded + z_threshold : :obj:`float` + Number of standard deviations above the mean to use as spike threshold + + Returns + --------- + float + """ + spike_mask = get_spike_mask(data, z_threshold) + global_spike_percentage = np.mean(np.ravel(spike_mask)) + + return global_spike_percentage - mean_signal = np.mean(data[mask_cc_part], axis=0) +def noise_func_for_shelled_data(shelled_data, gtab): + pass \ No newline at end of file diff --git a/mriqc/qc/tests/test_diffusion.py b/mriqc/qc/tests/test_diffusion.py index 1e5d32359..6d6d1d45e 100644 --- a/mriqc/qc/tests/test_diffusion.py +++ b/mriqc/qc/tests/test_diffusion.py @@ -20,9 +20,7 @@ # # https://www.nipreps.org/community/licensing/ # - import pytest -import os.path as op import numpy as np import nibabel as nib from dipy.core.gradients import gradient_table @@ -30,14 +28,6 @@ from dipy.core.gradients import unique_bvals_magnitude, round_bvals import os.path as op from ..diffusion import noise_func, get_spike_mask, get_slice_spike_percentage, get_global_spike_percentage -from ..diffusion import noise_b0, noise_piesno - -import numpy as np - - - -import numpy as np -from mriqc.mriqc.qc import get_spike_mask, get_slice_spike_percentage, get_global_spike_percentage class DiffusionData(object): @@ -70,14 +60,6 @@ def shelled_data(self): def ddata(): return DiffusionData() -def test_get_spike_mask(ddata): - img, gtab = ddata.get_fdata() - spike_mask = get_spike_mask(img, 2) - - assert np.min(np.ravel(spike_mask)) == 0 - assert np.max(np.ravel(spike_mask)) == 1 - assert spike_mask.shape == img.shape - def test_noise_function(ddata): img, gtab = ddata.get_fdata() @@ -110,24 +92,6 @@ def test_get_global_spike_percentage(ddata): assert global_spike_percentage <= 1 -def test_get_global_spike_percentage(ddata): - img, gtab = ddata.get_fdata() - global_spike_percentage = get_global_spike_percentage(img, 2) - - assert global_spike_percentage >= 0 - assert global_spike_percentage <= 1 - - def test_with_shelled_data(ddata): shelled_data, gtab = ddata.shelled_data() - noise_func_for_shelled_data(shelled_data, gtab) - - -def test_noise_b0(ddata): - data, gtab = ddata.get_data() - noise_b0(data, gtab) - - -def test_noise_piesno(ddata): - data, gtab = ddata.get_data() - noise_piesno(data) + noise_func_for_shelled_data(shelled_data, gtab) \ No newline at end of file