def make_fake_signal(): v, e, f = create_half_unit_sphere(4) vecs_xy = v[np.flatnonzero(v[:, 2] == 0)] evals = np.array([1.8, 0.2, 0.2]) * 10 ** -3 * 1.5 evecs_moveing = np.empty((len(vecs_xy), 3, 3)) evecs_moveing[:, :, 0] = vecs_xy evecs_moveing[:, :, 1] = [0, 0, 1] evecs_moveing[:, :, 2] = np.cross(evecs_moveing[:, :, 0], evecs_moveing[:, :, 1]) assert ((evecs_moveing * evecs_moveing).sum(1) - 1 < 0.001).all() assert ((evecs_moveing * evecs_moveing).sum(2) - 1 < 0.001).all() gtab = np.empty((len(v) + 1, 3)) bval = np.empty(len(v) + 1) bval[0] = 0 bval[1:] = 2000 gtab[0] = [0, 0, 0] gtab[1:] = v bvec = gtab.T B = design_matrix(bvec, bval) tensor_moveing = np.empty_like(evecs_moveing) for ii in xrange(len(vecs_xy)): tensor_moveing[ii] = np.dot(evecs_moveing[ii] * evals, evecs_moveing[ii].T) D_moveing = lower_triangular(tensor_moveing, 1) tensor_fixed = np.diag(evals) D_fixed = lower_triangular(tensor_fixed, 1) sig = 0.45 * np.exp(np.dot(D_moveing, B.T)) + 0.55 * np.exp(np.dot(B, D_fixed)) assert sig.max() <= 1 assert sig.min() > 0 return v, e, vecs_xy, bval, bvec, sig
def make_fake_signal(): v, e, f = create_half_unit_sphere(4) vecs_xy = v[np.flatnonzero(v[:, 2] == 0)] evals = np.array([1.8, .2, .2]) * 10**-3 * 1.5 evecs_moveing = np.empty((len(vecs_xy), 3, 3)) evecs_moveing[:, :, 0] = vecs_xy evecs_moveing[:, :, 1] = [0, 0, 1] evecs_moveing[:, :, 2] = np.cross(evecs_moveing[:, :, 0], evecs_moveing[:, :, 1]) assert ((evecs_moveing * evecs_moveing).sum(1) - 1 < .001).all() assert ((evecs_moveing * evecs_moveing).sum(2) - 1 < .001).all() gtab = np.empty((len(v) + 1, 3)) bval = np.empty(len(v) + 1) bval[0] = 0 bval[1:] = 2000 gtab[0] = [0, 0, 0] gtab[1:] = v bvec = gtab.T B = design_matrix(bvec, bval) tensor_moveing = np.empty_like(evecs_moveing) for ii in xrange(len(vecs_xy)): tensor_moveing[ii] = np.dot(evecs_moveing[ii] * evals, evecs_moveing[ii].T) D_moveing = lower_triangular(tensor_moveing, 1) tensor_fixed = np.diag(evals) D_fixed = lower_triangular(tensor_fixed, 1) sig = .45 * np.exp(np.dot(D_moveing, B.T)) + .55 * np.exp( np.dot(B, D_fixed)) assert sig.max() <= 1 assert sig.min() > 0 return v, e, vecs_xy, bval, bvec, sig
def test_lower_triangular(): tensor = np.arange(9).reshape((3, 3)) D = lower_triangular(tensor) assert_array_equal(D, [0, 3, 4, 6, 7, 8]) D = lower_triangular(tensor, 1) assert_array_equal(D, [0, 3, 4, 6, 7, 8, 0]) assert_raises(ValueError, lower_triangular, np.zeros((2, 3))) shape = (4, 5, 6) many_tensors = np.empty(shape + (3, 3)) many_tensors[:] = tensor result = np.empty(shape + (6,)) result[:] = [0, 3, 4, 6, 7, 8] D = lower_triangular(many_tensors) assert_array_equal(D, result) D = lower_triangular(many_tensors, 1) result = np.empty(shape + (7,)) result[:] = [0, 3, 4, 6, 7, 8, 0] assert_array_equal(D, result)
def dki_prediction(dki_params, gtab, S0=150): """ Predict a signal given diffusion kurtosis imaging parameters. Parameters ---------- dki_params : ndarray (x, y, z, 27) or (n, 27) All parameters estimated from the diffusion kurtosis model. Parameters are ordered as follow: 1) Three diffusion tensor's eingenvalues 2) Three lines of the eigenvector matrix each containing the first, second and third coordinates of the eigenvector 3) Fifteen elements of the kurtosis tensor gtab : a GradientTable class instance The gradient table for this prediction S0 : float or ndarray (optional) The non diffusion-weighted signal in every voxel, or across all voxels. Default: 150 Returns -------- S : (..., N) ndarray Simulated signal based on the DKI model: .. math:: S=S_{0}e^{-bD+\frac{1}{6}b^{2}D^{2}K} """ evals, evecs, kt = split_dki_param(dki_params) # Define DKI design matrix according to given gtab A = design_matrix(gtab) # Flat parameters and initialize pred_sig fevals = evals.reshape((-1, evals.shape[-1])) fevecs = evecs.reshape((-1, ) + evecs.shape[-2:]) fkt = kt.reshape((-1, kt.shape[-1])) pred_sig = np.zeros((len(fevals), len(gtab.bvals))) # lopping for all voxels for v in range(len(pred_sig)): DT = np.dot(np.dot(fevecs[v], np.diag(fevals[v])), fevecs[v].T) dt = lower_triangular(DT) MD = (dt[0] + dt[2] + dt[5]) / 3 X = np.concatenate((dt, fkt[v] * MD * MD, np.array([np.log(S0)])), axis=0) pred_sig[v] = np.exp(np.dot(A, X)) # Reshape data according to the shape of dki_params pred_sig = pred_sig.reshape(dki_params.shape[:-1] + (pred_sig.shape[-1], )) return pred_sig
def dki_prediction(dki_params, gtab, S0=150): """ Predict a signal given diffusion kurtosis imaging parameters. Parameters ---------- dki_params : ndarray (x, y, z, 27) or (n, 27) All parameters estimated from the diffusion kurtosis model. Parameters are ordered as follow: 1) Three diffusion tensor's eingenvalues 2) Three lines of the eigenvector matrix each containing the first, second and third coordinates of the eigenvector 3) Fifteen elements of the kurtosis tensor gtab : a GradientTable class instance The gradient table for this prediction S0 : float or ndarray (optional) The non diffusion-weighted signal in every voxel, or across all voxels. Default: 150 Returns -------- S : (..., N) ndarray Simulated signal based on the DKI model: .. math:: S=S_{0}e^{-bD+\frac{1}{6}b^{2}D^{2}K} """ evals, evecs, kt = split_dki_param(dki_params) # Define DKI design matrix according to given gtab A = design_matrix(gtab) # Flat parameters and initialize pred_sig fevals = evals.reshape((-1, evals.shape[-1])) fevecs = evecs.reshape((-1,) + evecs.shape[-2:]) fkt = kt.reshape((-1, kt.shape[-1])) pred_sig = np.zeros((len(fevals), len(gtab.bvals))) # lopping for all voxels for v in range(len(pred_sig)): DT = np.dot(np.dot(fevecs[v], np.diag(fevals[v])), fevecs[v].T) dt = lower_triangular(DT) MD = (dt[0] + dt[2] + dt[5]) / 3 X = np.concatenate((dt, fkt[v]*MD*MD, np.array([np.log(S0)])), axis=0) pred_sig[v] = np.exp(np.dot(A, X)) # Reshape data according to the shape of dki_params pred_sig = pred_sig.reshape(dki_params.shape[:-1] + (pred_sig.shape[-1],)) return pred_sig
def test_eig_from_lo_tri(): psphere = get_sphere('symmetric362') bvecs = np.concatenate(([[0, 0, 0]], psphere.vertices)) bvals = np.zeros(len(bvecs)) + 1000 bvals[0] = 0 gtab = grad.gradient_table(bvals, bvecs) mevals = np.array(([0.0015, 0.0003, 0.0001], [0.0015, 0.0003, 0.0003])) mevecs = [np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]), np.array([[0, 0, 1], [0, 1, 0], [1, 0, 0]])] S = np.array([[single_tensor(gtab, 100, mevals[0], mevecs[0], snr=None), single_tensor(gtab, 100, mevals[0], mevecs[0], snr=None)]]) dm = dti.TensorModel(gtab, 'LS') dmfit = dm.fit(S) lo_tri = lower_triangular(dmfit.quadratic_form) assert_array_almost_equal(dti.eig_from_lo_tri(lo_tri), dmfit.model_params)
def diffusion_components(dki_params, sphere='repulsion100', awf=None, mask=None): """ Extracts the restricted and hindered diffusion tensors of well aligned fibers from diffusion kurtosis imaging parameters [1]_. Parameters ---------- dki_params : ndarray (x, y, z, 27) or (n, 27) All parameters estimated from the diffusion kurtosis model. Parameters are ordered as follows: 1) Three diffusion tensor's eigenvalues 2) Three lines of the eigenvector matrix each containing the first, second and third coordinates of the eigenvector 3) Fifteen elements of the kurtosis tensor sphere : Sphere class instance, optional The sphere providing sample directions to sample the restricted and hindered cellular diffusion tensors. For more details see Fieremans et al., 2011. awf : ndarray (optional) Array containing values of the axonal water fraction that has the shape dki_params.shape[:-1]. If not given this will be automatically computed using :func:`axonal_water_fraction`" with function's default precision. mask : ndarray (optional) A boolean array used to mark the coordinates in the data that should be analyzed that has the shape dki_params.shape[:-1] Returns ------- edt : ndarray (x, y, z, 6) or (n, 6) Parameters of the hindered diffusion tensor. idt : ndarray (x, y, z, 6) or (n, 6) Parameters of the restricted diffusion tensor. Notes ----- In the original article of DKI microstructural model [1]_, the hindered and restricted tensors were definde as the intra-cellular and extra-cellular diffusion compartments respectively. References ---------- .. [1] Fieremans E, Jensen JH, Helpern JA, 2011. White matter characterization with diffusional kurtosis imaging. Neuroimage 58(1):177-88. doi: 10.1016/j.neuroimage.2011.06.006 """ shape = dki_params.shape[:-1] # load gradient directions if not isinstance(sphere, dps.Sphere): sphere = get_sphere(sphere) # select voxels where to apply the single fiber model if mask is None: mask = np.ones(shape, dtype='bool') else: if mask.shape != shape: raise ValueError("Mask is not the same shape as dki_params.") else: mask = np.array(mask, dtype=bool, copy=False) # check or compute awf values if awf is None: awf = axonal_water_fraction(dki_params, sphere=sphere, mask=mask) else: if awf.shape != shape: raise ValueError("awf array is not the same shape as dki_params.") # Initialize hindered and restricted diffusion tensors edt_all = np.zeros(shape + (6, )) idt_all = np.zeros(shape + (6, )) # Generate matrix that converts apparent diffusion coefficients to tensors B = np.zeros((sphere.x.size, 6)) B[:, 0] = sphere.x * sphere.x # Bxx B[:, 1] = sphere.x * sphere.y * 2. # Bxy B[:, 2] = sphere.y * sphere.y # Byy B[:, 3] = sphere.x * sphere.z * 2. # Bxz B[:, 4] = sphere.y * sphere.z * 2. # Byz B[:, 5] = sphere.z * sphere.z # Bzz pinvB = np.linalg.pinv(B) # Compute hindered and restricted diffusion tensors for all voxels evals, evecs, kt = split_dki_param(dki_params) dt = lower_triangular(vec_val_vect(evecs, evals)) md = mean_diffusivity(evals) index = ndindex(mask.shape) for idx in index: if not mask[idx]: continue # sample apparent diffusion and kurtosis values di = directional_diffusion(dt[idx], sphere.vertices) ki = directional_kurtosis(dt[idx], md[idx], kt[idx], sphere.vertices, adc=di, min_kurtosis=0) edi = di * (1 + np.sqrt(ki * awf[idx] / (3.0 - 3.0 * awf[idx]))) edt = np.dot(pinvB, edi) edt_all[idx] = edt # We only move on if there is an axonal water fraction. # Otherwise, remaining params are already zero, so move on if awf[idx] == 0: continue # Convert apparent diffusion and kurtosis values to apparent diffusion # values of the hindered and restricted diffusion idi = di * (1 - np.sqrt(ki * (1.0 - awf[idx]) / (3.0 * awf[idx]))) # generate hindered and restricted diffusion tensors idt = np.dot(pinvB, idi) idt_all[idx] = idt return edt_all, idt_all
def nls_iter_bounds(design_matrix, sig, S0, Diso=3e-3, min_signal=1.0e-6, bounds=None, jac=True): """ Applies non-linear least-squares fit with constraints of the water free elimination model to single voxel signals. Parameters ---------- design_matrix : array (g, 7) Design matrix holding the covariants used to solve for the regression coefficients. sig : array (g, ) Diffusion-weighted signal for a single voxel data. S0 : float Non diffusion weighted signal (i.e. signal for b-value=0). Diso : float, optional Value of the free water isotropic diffusion. Default is set to 3e-3 $mm^{2}.s^{-1}$. Please ajust this value if you are assuming different units of diffusion. min_signal : float The minimum signal value. Needs to be a strictly positive number. bounds : 2-tuple of arrays with 14 elements, optional Lower and upper bounds on fwdti model variables and the log of non-diffusion signal S0. Use np.inf with an appropriate sign to disable bounds on all or some variables. When bounds is set to None the following default variable bounds is used: ([0., -Diso, 0., -Diso, -Diso, 0., 0., np.exp(-10.)], [Diso, Diso, Diso, Diso, Diso, Diso, 1., np.exp(10.)]) jac : bool Use the Jacobian? Default: False Returns ------- All parameters estimated from the free water tensor model. Parameters are ordered as follows: 1) Three diffusion tensor's eigenvalues 2) Three lines of the eigenvector matrix each containing the first, second and third coordinates of the eigenvector 3) The volume fraction of the free water compartment. References ---------- .. [1] Henriques, R.N., Rokem, A., Garyfallidis, E., St-Jean, S., Peterson, E.T., Correia, M.M., 2017. Re: Optimization of a free water elimination two-compartmental model for diffusion tensor imaging. ReScience """ # Initial guess params = wls_iter(design_matrix, sig, S0, min_signal=min_signal, Diso=Diso) # Set bounds if bounds is None: bounds = ([0., -Diso, 0., -Diso, -Diso, 0., -10., 0], [Diso, Diso, Diso, Diso, Diso, Diso, 10., 1]) else: # In the helper subfunctions it was easier to have log(S0) first than # the water volume. Therefore, we have to reorder the boundaries if # specified by the user S0low = np.log(bounds[0][7]) S0hig = np.log(bounds[1][7]) bounds[0][7] = bounds[0][6] bounds[1][7] = bounds[1][6] bounds[0][6] = S0low bounds[1][6] = S0hig # Process voxel if it has significant signal from tissue if np.mean(sig) > min_signal and S0 > min_signal: # converting evals and evecs to diffusion tensor elements evals = params[:3] evecs = params[3:12].reshape((3, 3)) dt = lower_triangular(vec_val_vect(evecs, evals)) f = params[12] # Use the Levenberg-Marquardt algorithm wrapped in opt.leastsq start_params = np.concatenate((dt, [-np.log(S0), f]), axis=0) lb = np.array(bounds[0]) ub = np.array(bounds[1]) start_params[start_params < lb] = lb[start_params < lb] start_params[start_params > ub] = ub[start_params > ub] if jac: out = opt.least_squares(_nls_err_func, start_params[:8], args=(design_matrix, sig, Diso, False, False), jac=_nls_jacobian_func, bounds=bounds) else: out = opt.least_squares(_nls_err_func, start_params[:8], args=(design_matrix, sig, Diso, False, False), bounds=bounds) this_tensor = out.x # The parameters are the evals and the evecs: evals, evecs = decompose_tensor(from_lower_triangular(this_tensor[:6])) params = np.concatenate((evals, evecs[0], evecs[1], evecs[2], np.array([this_tensor[7]])), axis=0) return params
def nls_fit_fwdki(design_matrix, design_matrix_dki, data, S0, params=None, Diso=3e-3, f_transform=True, mdreg=2.7e-3): """ Fit the water elimination DKI model using the non-linear least-squares. Parameters ---------- design_matrix : array (g, 22) Design matrix holding the covariants used to solve for the regression coefficients. data : ndarray ([X, Y, Z, ...], g) Data or response variables holding the data. Note that the last dimension should contain the data. It makes no copies of data. S0 : ndarray ([X, Y, Z]) A first guess of the non-diffusion signal S0. params : ndarray ([X, Y, Z, ...], 28), optional A first model parameters guess (3 eigenvalues, 3 coordinates of 3 eigenvalues, 15 elements of the kurtosis tensor and the volume fraction of the free water compartment). If the initial params are not given, for the diffusion and kurtosis tensor parameters, its initial guess is obtain from the standard DKI model, while for the free water fraction its value is estimated using the fwDTI model. Default: None Diso : float, optional Value of the free water isotropic diffusion. Default is set to 3e-3 $mm^{2}.s^{-1}$. Please ajust this value if you are assuming different units of diffusion. f_transform : bool, optional If true, the water volume fractions is converted during the convergence procedure to ft = arcsin(2*f - 1) + pi/2, insuring f estimates between 0 and 1. Default: True mdreg : float, optimal DTI's mean diffusivity regularization threshold. If standard DTI diffusion tensor's mean diffusivity is almost near the free water diffusion value, the diffusion signal is assumed to be only free water diffusion (i.e. volume fraction will be set to 1 and tissue's diffusion parameters are set to zero). Default md_reg is 2.7e-3 $mm^{2}.s^{-1}$ (corresponding to 90% of the free water diffusion value). Returns ------- fw_params : ndarray (x, y, z, 28) Matrix containing in the dimention the free water model parameters in the following order: 1) Three diffusion tensor's eigenvalues 2) Three lines of the eigenvector matrix each containing the first, second and third coordinates of the eigenvector 3) Fifteen elements of the kurtosis tensor 4) The volume fraction of the free water compartment S0 : ndarray (x, y, z) The models estimate of the non diffusion-weighted signal S0. """ # preparing data and initializing parameters data = np.asarray(data) data_flat = np.reshape(data, (-1, data.shape[-1])) S0out = S0.copy() S0out = S0out.ravel() # Computing WLS DTI solution for MD regularization dtiparams = dti.wls_fit_tensor(design_matrix, data_flat) md = dti.mean_diffusivity(dtiparams[..., :3]) cond = md > mdreg # removal condition data_cond = data_flat[~cond, :] # Initializing fw_params according to selected initial guess if np.any(params) is None: params_out = np.zeros((len(data_flat), 28)) dkiparams = dki.wls_fit_dki(design_matrix_dki, data_flat) fweparams, sd = fwdti.wls_fit_tensor(design_matrix, data_flat, S0=S0, Diso=Diso, mdreg=2.7e-3) params_out[:, 0:27] = dkiparams params_out[:, 27] = fweparams[:, 12] else: params_out = params.copy() params_out = np.reshape(params_out, (-1, params_out.shape[-1])) params_cond = params_out[~cond, :] S0_cond = S0out[~cond] for vox in range(data_cond.shape[0]): if np.all(data_cond[vox] == 0): raise ValueError("The data in this voxel contains only zeros") params = params_cond[vox] # converting evals and evecs to diffusion tensor elements evals = params[:3] evecs = params[3:12].reshape((3, 3)) dt = lower_triangular(vec_val_vect(evecs, evals)) kt = params[..., 12:27] s0 = S0_cond[vox] MD = evals.mean() # f transformation if requested if f_transform: f = np.arcsin(2 * params[27] - 1) + np.pi / 2 else: f = params[27] # Use the Levenberg-Marquardt algorithm wrapped in opt.leastsq start_params = np.concatenate((dt, kt * MD * MD, [np.log(s0), f]), axis=0) this_tensor, status = opt.leastsq(_nls_err_func, start_params, args=(design_matrix_dki, data_cond[vox], Diso, f_transform)) # Invert f transformation if this was requested if f_transform: this_tensor[22] = 0.5 * (1 + np.sin(this_tensor[22] - np.pi / 2)) # The parameters are the evals and the evecs: evals, evecs = decompose_tensor(from_lower_triangular(this_tensor[:6])) MD = evals.mean() params_cond[vox, :3] = evals params_cond[vox, 3:12] = evecs.ravel() params_cond[vox, 12:27] = this_tensor[6:21] / (MD**2) params_cond[vox, 27] = this_tensor[22] S0_cond[vox] = np.exp(-this_tensor[21]) params_out[~cond, :] = params_cond params_out[cond, 27] = 1 # Only free water params_out = np.reshape(params_out, (data.shape[:-1]) + (28, )) S0out[~cond] = S0_cond S0out[cond] = \ np.mean(data_flat[cond, :] / \ np.exp(np.dot(design_matrix[..., :6], np.array([Diso, 0, Diso, 0, 0, Diso]))), -1) # Only free water S0out = S0out.reshape(data.shape[:-1]) return params_out, S0out
def nls_iter(design_matrix, sig, S0, Diso=3e-3, mdreg=2.7e-3, min_signal=1.0e-6, cholesky=False, f_transform=True, jac=True): """ Applies non linear least squares fit of the water free elimination model to single voxel signals. Parameters ---------- design_matrix : array (g, 7) Design matrix holding the covariants used to solve for the regression coefficients. sig : array (g, ) Diffusion-weighted signal for a single voxel data. S0 : float Non diffusion weighted signal (i.e. signal for b-value=0). Diso : float, optional Value of the free water isotropic diffusion. Default is set to 3e-3 $mm^{2}.s^{-1}$. Please ajust this value if you are assuming different units of diffusion. mdreg : float, optimal DTI's mean diffusivity regularization threshold. If standard DTI diffusion tensor's mean diffusivity is almost near the free water diffusion value, the diffusion signal is assumed to be only free water diffusion (i.e. volume fraction will be set to 1 and tissue's diffusion parameters are set to zero). Default md_reg is 2.7e-3 $mm^{2}.s^{-1}$ (corresponding to 90% of the free water diffusion value). min_signal : float The minimum signal value. Needs to be a strictly positive number. cholesky : bool, optional If true it uses cholesky decomposition to insure that diffusion tensor is positive define. Default: False f_transform : bool, optional If true, the water volume fractions is converted during the convergence procedure to ft = arcsin(2*f - 1) + pi/2, insuring f estimates between 0 and 1. Default: True jac : bool Use the Jacobian? Default: False Returns ------- All parameters estimated from the free water tensor model. Parameters are ordered as follows: 1) Three diffusion tensor's eigenvalues 2) Three lines of the eigenvector matrix each containing the first, second and third coordinates of the eigenvector 3) The volume fraction of the free water compartment. """ # Initial guess params = wls_iter(design_matrix, sig, S0, min_signal=min_signal, Diso=Diso, mdreg=mdreg) # Process voxel if it has significant signal from tissue if params[12] < 0.99 and np.mean(sig) > min_signal and S0 > min_signal: # converting evals and evecs to diffusion tensor elements evals = params[:3] evecs = params[3:12].reshape((3, 3)) dt = lower_triangular(vec_val_vect(evecs, evals)) # Cholesky decomposition if requested if cholesky: dt = lower_triangular_to_cholesky(dt) # f transformation if requested if f_transform: f = np.arcsin(2*params[12] - 1) + np.pi/2 else: f = params[12] # Use the Levenberg-Marquardt algorithm wrapped in opt.leastsq start_params = np.concatenate((dt, [-np.log(S0), f]), axis=0) if jac: this_tensor, status = opt.leastsq(_nls_err_func, start_params[:8], args=(design_matrix, sig, Diso, cholesky, f_transform), Dfun=_nls_jacobian_func) else: this_tensor, status = opt.leastsq(_nls_err_func, start_params[:8], args=(design_matrix, sig, Diso, cholesky, f_transform)) # Invert the cholesky decomposition if this was requested if cholesky: this_tensor[:6] = cholesky_to_lower_triangular(this_tensor[:6]) # Invert f transformation if this was requested if f_transform: this_tensor[7] = 0.5 * (1 + np.sin(this_tensor[7] - np.pi/2)) # The parameters are the evals and the evecs: evals, evecs = decompose_tensor(from_lower_triangular(this_tensor[:6])) params = np.concatenate((evals, evecs[0], evecs[1], evecs[2], np.array([this_tensor[7]])), axis=0) return params
def run(self, input_files, bvalues_files, bvectors_files, mask_files, b0_threshold=50.0, save_metrics=[], out_dir='', out_dt_tensor='dti_tensors.nii.gz', out_fa='fa.nii.gz', out_ga='ga.nii.gz', out_rgb='rgb.nii.gz', out_md='md.nii.gz', out_ad='ad.nii.gz', out_rd='rd.nii.gz', out_mode='mode.nii.gz', out_evec='evecs.nii.gz', out_eval='evals.nii.gz', out_dk_tensor="dki_tensors.nii.gz", out_mk="mk.nii.gz", out_ak="ak.nii.gz", out_rk="rk.nii.gz"): """ Workflow for Diffusion Kurtosis reconstruction and for computing DKI metrics. Performs a DKI reconstruction on the files by 'globing' ``input_files`` and saves the DKI metrics in a directory specified by ``out_dir``. Parameters ---------- input_files : string Path to the input volumes. This path may contain wildcards to process multiple inputs at once. bvalues_files : string Path to the bvalues files. This path may contain wildcards to use multiple bvalues files at once. bvectors_files : string Path to the bvalues files. This path may contain wildcards to use multiple bvalues files at once. mask_files : string Path to the input masks. This path may contain wildcards to use multiple masks at once. (default: No mask used) b0_threshold : float, optional Threshold used to find b0 volumes. save_metrics : variable string, optional List of metrics to save. Possible values: fa, ga, rgb, md, ad, rd, mode, tensor, evec, eval out_dir : string, optional Output directory. (default current directory) out_dt_tensor : string, optional Name of the tensors volume to be saved. out_dk_tensor : string, optional Name of the tensors volume to be saved. out_fa : string, optional Name of the fractional anisotropy volume to be saved. out_ga : string, optional Name of the geodesic anisotropy volume to be saved. out_rgb : string, optional Name of the color fa volume to be saved. out_md : string, optional Name of the mean diffusivity volume to be saved. out_ad : string, optional Name of the axial diffusivity volume to be saved. out_rd : string, optional Name of the radial diffusivity volume to be saved. out_mode : string, optional Name of the mode volume to be saved. out_evec : string, optional Name of the eigenvectors volume to be saved. out_eval : string, optional Name of the eigenvalues to be saved. out_mk : string, optional Name of the mean kurtosis to be saved. out_ak : string, optional Name of the axial kurtosis to be saved. out_rk : string, optional Name of the radial kurtosis to be saved. References ---------- .. [1] Tabesh, A., Jensen, J.H., Ardekani, B.A., Helpern, J.A., 2011. Estimation of tensors and tensor-derived measures in diffusional kurtosis imaging. Magn Reson Med. 65(3), 823-836 .. [2] Jensen, Jens H., Joseph A. Helpern, Anita Ramani, Hanzhang Lu, and Kyle Kaczynski. 2005. Diffusional Kurtosis Imaging: The Quantification of Non-Gaussian Water Diffusion by Means of Magnetic Resonance Imaging. MRM 53 (6):1432-40. """ io_it = self.get_io_iterator() for (dwi, bval, bvec, mask, otensor, ofa, oga, orgb, omd, oad, orad, omode, oevecs, oevals, odk_tensor, omk, oak, ork) in io_it: logging.info('Computing DKI metrics for {0}'.format(dwi)) data, affine = load_nifti(dwi) if mask is not None: mask = load_nifti_data(mask).astype(bool) dkfit, _ = self.get_fitted_tensor(data, mask, bval, bvec, b0_threshold) if not save_metrics: save_metrics = [ 'mk', 'rk', 'ak', 'fa', 'md', 'rd', 'ad', 'ga', 'rgb', 'mode', 'evec', 'eval', 'dt_tensor', 'dk_tensor' ] evals, evecs, kt = split_dki_param(dkfit.model_params) FA = fractional_anisotropy(evals) FA[np.isnan(FA)] = 0 FA = np.clip(FA, 0, 1) if 'dt_tensor' in save_metrics: tensor_vals = lower_triangular(dkfit.quadratic_form) correct_order = [0, 1, 3, 2, 4, 5] tensor_vals_reordered = tensor_vals[..., correct_order] save_nifti(otensor, tensor_vals_reordered.astype(np.float32), affine) if 'dk_tensor' in save_metrics: save_nifti(odk_tensor, dkfit.kt.astype(np.float32), affine) if 'fa' in save_metrics: save_nifti(ofa, FA.astype(np.float32), affine) if 'ga' in save_metrics: GA = geodesic_anisotropy(dkfit.evals) save_nifti(oga, GA.astype(np.float32), affine) if 'rgb' in save_metrics: RGB = color_fa(FA, dkfit.evecs) save_nifti(orgb, np.array(255 * RGB, 'uint8'), affine) if 'md' in save_metrics: MD = mean_diffusivity(dkfit.evals) save_nifti(omd, MD.astype(np.float32), affine) if 'ad' in save_metrics: AD = axial_diffusivity(dkfit.evals) save_nifti(oad, AD.astype(np.float32), affine) if 'rd' in save_metrics: RD = radial_diffusivity(dkfit.evals) save_nifti(orad, RD.astype(np.float32), affine) if 'mode' in save_metrics: MODE = get_mode(dkfit.quadratic_form) save_nifti(omode, MODE.astype(np.float32), affine) if 'evec' in save_metrics: save_nifti(oevecs, dkfit.evecs.astype(np.float32), affine) if 'eval' in save_metrics: save_nifti(oevals, dkfit.evals.astype(np.float32), affine) if 'mk' in save_metrics: save_nifti(omk, dkfit.mk().astype(np.float32), affine) if 'ak' in save_metrics: save_nifti(oak, dkfit.ak().astype(np.float32), affine) if 'rk' in save_metrics: save_nifti(ork, dkfit.rk().astype(np.float32), affine) logging.info('DKI metrics saved in {0}'.format( os.path.dirname(oevals)))
def run(self, input_files, bvalues, bvectors, mask_files, b0_threshold=0.0, save_metrics=[], out_dir='', out_tensor='tensors.nii.gz', out_fa='fa.nii.gz', out_ga='ga.nii.gz', out_rgb='rgb.nii.gz', out_md='md.nii.gz', out_ad='ad.nii.gz', out_rd='rd.nii.gz', out_mode='mode.nii.gz', out_evec='evecs.nii.gz', out_eval='evals.nii.gz'): """ Workflow for tensor reconstruction and for computing DTI metrics. Performs a tensor reconstruction on the files by 'globing' ``input_files`` and saves the DTI metrics in a directory specified by ``out_dir``. Parameters ---------- input_files : string Path to the input volumes. This path may contain wildcards to process multiple inputs at once. bvalues : string Path to the bvalues files. This path may contain wildcards to use multiple bvalues files at once. bvectors : string Path to the bvalues files. This path may contain wildcards to use multiple bvalues files at once. mask_files : string Path to the input masks. This path may contain wildcards to use multiple masks at once. (default: No mask used) b0_threshold : float, optional Threshold used to find b=0 directions (default 0.0) save_metrics : variable string, optional List of metrics to save. Possible values: fa, ga, rgb, md, ad, rd, mode, tensor, evec, eval (default [] (all)) out_dir : string, optional Output directory (default input file directory) out_tensor : string, optional Name of the tensors volume to be saved (default 'tensors.nii.gz') out_fa : string, optional Name of the fractional anisotropy volume to be saved (default 'fa.nii.gz') out_ga : string, optional Name of the geodesic anisotropy volume to be saved (default 'ga.nii.gz') out_rgb : string, optional Name of the color fa volume to be saved (default 'rgb.nii.gz') out_md : string, optional Name of the mean diffusivity volume to be saved (default 'md.nii.gz') out_ad : string, optional Name of the axial diffusivity volume to be saved (default 'ad.nii.gz') out_rd : string, optional Name of the radial diffusivity volume to be saved (default 'rd.nii.gz') out_mode : string, optional Name of the mode volume to be saved (default 'mode.nii.gz') out_evec : string, optional Name of the eigenvectors volume to be saved (default 'evecs.nii.gz') out_eval : string, optional Name of the eigenvalues to be saved (default 'evals.nii.gz') """ io_it = self.get_io_iterator() for dwi, bval, bvec, mask, otensor, ofa, oga, orgb, omd, oad, orad, \ omode, oevecs, oevals in io_it: logging.info('Computing DTI metrics for {0}'.format(dwi)) img = nib.load(dwi) data = img.get_data() affine = img.get_affine() if mask is None: mask = None else: mask = nib.load(mask).get_data().astype(np.bool) tenfit, _ = self.get_fitted_tensor(data, mask, bval, bvec, b0_threshold) if not save_metrics: save_metrics = [ 'fa', 'md', 'rd', 'ad', 'ga', 'rgb', 'mode', 'evec', 'eval', 'tensor' ] FA = fractional_anisotropy(tenfit.evals) FA[np.isnan(FA)] = 0 FA = np.clip(FA, 0, 1) if 'tensor' in save_metrics: tensor_vals = lower_triangular(tenfit.quadratic_form) correct_order = [0, 1, 3, 2, 4, 5] tensor_vals_reordered = tensor_vals[..., correct_order] fiber_tensors = nib.Nifti1Image( tensor_vals_reordered.astype(np.float32), affine) nib.save(fiber_tensors, otensor) if 'fa' in save_metrics: fa_img = nib.Nifti1Image(FA.astype(np.float32), affine) nib.save(fa_img, ofa) if 'ga' in save_metrics: GA = geodesic_anisotropy(tenfit.evals) ga_img = nib.Nifti1Image(GA.astype(np.float32), affine) nib.save(ga_img, oga) if 'rgb' in save_metrics: RGB = color_fa(FA, tenfit.evecs) rgb_img = nib.Nifti1Image(np.array(255 * RGB, 'uint8'), affine) nib.save(rgb_img, orgb) if 'md' in save_metrics: MD = mean_diffusivity(tenfit.evals) md_img = nib.Nifti1Image(MD.astype(np.float32), affine) nib.save(md_img, omd) if 'ad' in save_metrics: AD = axial_diffusivity(tenfit.evals) ad_img = nib.Nifti1Image(AD.astype(np.float32), affine) nib.save(ad_img, oad) if 'rd' in save_metrics: RD = radial_diffusivity(tenfit.evals) rd_img = nib.Nifti1Image(RD.astype(np.float32), affine) nib.save(rd_img, orad) if 'mode' in save_metrics: MODE = get_mode(tenfit.quadratic_form) mode_img = nib.Nifti1Image(MODE.astype(np.float32), affine) nib.save(mode_img, omode) if 'evec' in save_metrics: evecs_img = nib.Nifti1Image(tenfit.evecs.astype(np.float32), affine) nib.save(evecs_img, oevecs) if 'eval' in save_metrics: evals_img = nib.Nifti1Image(tenfit.evals.astype(np.float32), affine) nib.save(evals_img, oevals) logging.info('DTI metrics saved in {0}'.format( os.path.dirname(oevals)))
def fwdti_prediction(params, gtab, S0=1, Diso=3.0e-3): r""" Signal prediction given the free water DTI model parameters. Parameters ---------- params : (..., 13) ndarray Model parameters. The last dimension should have the 12 tensor parameters (3 eigenvalues, followed by the 3 corresponding eigenvectors) and the volume fraction of the free water compartment. gtab : a GradientTable class instance The gradient table for this prediction S0 : float or ndarray The non diffusion-weighted signal in every voxel, or across all voxels. Default: 1 Diso : float, optional Value of the free water isotropic diffusion. Default is set to 3e-3 $mm^{2}.s^{-1}$. Please adjust this value if you are assuming different units of diffusion. Returns -------- S : (..., N) ndarray Simulated signal based on the free water DTI model Notes ----- The predicted signal is given by: $S(\theta, b) = S_0 * [(1-f) * e^{-b ADC} + f * e^{-b D_{iso}]$, where $ADC = \theta Q \theta^T$, $\theta$ is a unit vector pointing at any direction on the sphere for which a signal is to be predicted, $b$ is the b value provided in the GradientTable input for that direction, $Q$ is the quadratic form of the tensor determined by the input parameters, $f$ is the free water diffusion compartment, $D_{iso}$ is the free water diffusivity which is equal to $3 * 10^{-3} mm^{2}s^{-1} [1]_. References ---------- .. [1] Hoy, A.R., Koay, C.G., Kecskemeti, S.R., Alexander, A.L., 2014. Optimization of a free water elimination two-compartmental model for diffusion tensor imaging. NeuroImage 103, 323-333. doi: 10.1016/j.neuroimage.2014.09.053 """ evals = params[..., :3] evecs = params[..., 3:-1].reshape(params.shape[:-1] + (3, 3)) f = params[..., 12] qform = vec_val_vect(evecs, evals) lower_dt = lower_triangular(qform, S0) lower_diso = lower_dt.copy() lower_diso[..., 0] = lower_diso[..., 2] = lower_diso[..., 5] = Diso lower_diso[..., 1] = lower_diso[..., 3] = lower_diso[..., 4] = 0 D = design_matrix(gtab) pred_sig = np.zeros(f.shape + (gtab.bvals.shape[0], )) mask = _positive_evals(evals[..., 0], evals[..., 1], evals[..., 2]) index = ndindex(f.shape) for v in index: if mask[v]: pred_sig[v] = (1 - f[v]) * np.exp(np.dot(lower_dt[v], D.T)) + \ f[v] * np.exp(np.dot(lower_diso[v], D.T)) return pred_sig
def run(self, input_files, bvalues_files, bvectors_files, mask_files, b0_threshold=50, bvecs_tol=0.01, save_metrics=[], out_dir='', out_tensor='tensors.nii.gz', out_fa='fa.nii.gz', out_ga='ga.nii.gz', out_rgb='rgb.nii.gz', out_md='md.nii.gz', out_ad='ad.nii.gz', out_rd='rd.nii.gz', out_mode='mode.nii.gz', out_evec='evecs.nii.gz', out_eval='evals.nii.gz'): """ Workflow for tensor reconstruction and for computing DTI metrics. using Weighted Least-Squares. Performs a tensor reconstruction on the files by 'globing' ``input_files`` and saves the DTI metrics in a directory specified by ``out_dir``. Parameters ---------- input_files : string Path to the input volumes. This path may contain wildcards to process multiple inputs at once. bvalues_files : string Path to the bvalues files. This path may contain wildcards to use multiple bvalues files at once. bvectors_files : string Path to the bvectors files. This path may contain wildcards to use multiple bvectors files at once. mask_files : string Path to the input masks. This path may contain wildcards to use multiple masks at once. (default: No mask used) b0_threshold : float, optional Threshold used to find b=0 directions (default 0.0) bvecs_tol : float, optional Threshold used to check that norm(bvec) = 1 +/- bvecs_tol b-vectors are unit vectors (default 0.01) save_metrics : variable string, optional List of metrics to save. Possible values: fa, ga, rgb, md, ad, rd, mode, tensor, evec, eval (default [] (all)) out_dir : string, optional Output directory (default input file directory) out_tensor : string, optional Name of the tensors volume to be saved (default 'tensors.nii.gz') out_fa : string, optional Name of the fractional anisotropy volume to be saved (default 'fa.nii.gz') out_ga : string, optional Name of the geodesic anisotropy volume to be saved (default 'ga.nii.gz') out_rgb : string, optional Name of the color fa volume to be saved (default 'rgb.nii.gz') out_md : string, optional Name of the mean diffusivity volume to be saved (default 'md.nii.gz') out_ad : string, optional Name of the axial diffusivity volume to be saved (default 'ad.nii.gz') out_rd : string, optional Name of the radial diffusivity volume to be saved (default 'rd.nii.gz') out_mode : string, optional Name of the mode volume to be saved (default 'mode.nii.gz') out_evec : string, optional Name of the eigenvectors volume to be saved (default 'evecs.nii.gz') out_eval : string, optional Name of the eigenvalues to be saved (default 'evals.nii.gz') References ---------- .. [1] Basser, P.J., Mattiello, J., LeBihan, D., 1994. Estimation of the effective self-diffusion tensor from the NMR spin echo. J Magn Reson B 103, 247-254. .. [2] Basser, P., Pierpaoli, C., 1996. Microstructural and physiological features of tissues elucidated by quantitative diffusion-tensor MRI. Journal of Magnetic Resonance 111, 209-219. .. [3] Lin-Ching C., Jones D.K., Pierpaoli, C. 2005. RESTORE: Robust estimation of tensors by outlier rejection. MRM 53: 1088-1095 .. [4] hung, SW., Lu, Y., Henry, R.G., 2006. Comparison of bootstrap approaches for estimation of uncertainties of DTI parameters. NeuroImage 33, 531-541. """ io_it = self.get_io_iterator() for dwi, bval, bvec, mask, otensor, ofa, oga, orgb, omd, oad, orad, \ omode, oevecs, oevals in io_it: logging.info('Computing DTI metrics for {0}'.format(dwi)) data, affine = load_nifti(dwi) if mask is not None: mask = nib.load(mask).get_data().astype(np.bool) tenfit, _ = self.get_fitted_tensor(data, mask, bval, bvec, b0_threshold, bvecs_tol) if not save_metrics: save_metrics = ['fa', 'md', 'rd', 'ad', 'ga', 'rgb', 'mode', 'evec', 'eval', 'tensor'] FA = fractional_anisotropy(tenfit.evals) FA[np.isnan(FA)] = 0 FA = np.clip(FA, 0, 1) if 'tensor' in save_metrics: tensor_vals = lower_triangular(tenfit.quadratic_form) correct_order = [0, 1, 3, 2, 4, 5] tensor_vals_reordered = tensor_vals[..., correct_order] save_nifti(otensor, tensor_vals_reordered.astype(np.float32), affine) if 'fa' in save_metrics: save_nifti(ofa, FA.astype(np.float32), affine) if 'ga' in save_metrics: GA = geodesic_anisotropy(tenfit.evals) save_nifti(oga, GA.astype(np.float32), affine) if 'rgb' in save_metrics: RGB = color_fa(FA, tenfit.evecs) save_nifti(orgb, np.array(255 * RGB, 'uint8'), affine) if 'md' in save_metrics: MD = mean_diffusivity(tenfit.evals) save_nifti(omd, MD.astype(np.float32), affine) if 'ad' in save_metrics: AD = axial_diffusivity(tenfit.evals) save_nifti(oad, AD.astype(np.float32), affine) if 'rd' in save_metrics: RD = radial_diffusivity(tenfit.evals) save_nifti(orad, RD.astype(np.float32), affine) if 'mode' in save_metrics: MODE = get_mode(tenfit.quadratic_form) save_nifti(omode, MODE.astype(np.float32), affine) if 'evec' in save_metrics: save_nifti(oevecs, tenfit.evecs.astype(np.float32), affine) if 'eval' in save_metrics: save_nifti(oevals, tenfit.evals.astype(np.float32), affine) dname_ = os.path.dirname(oevals) if dname_ == '': logging.info('DTI metrics saved in current directory') else: logging.info( 'DTI metrics saved in {0}'.format(dname_))
def run(self, input_files, bvalues_files, bvectors_files, mask_files, b0_threshold=50.0, save_metrics=[], out_dir='', out_dt_tensor='dti_tensors.nii.gz', out_fa='fa.nii.gz', out_ga='ga.nii.gz', out_rgb='rgb.nii.gz', out_md='md.nii.gz', out_ad='ad.nii.gz', out_rd='rd.nii.gz', out_mode='mode.nii.gz', out_evec='evecs.nii.gz', out_eval='evals.nii.gz', out_dk_tensor="dki_tensors.nii.gz", out_mk="mk.nii.gz", out_ak="ak.nii.gz", out_rk="rk.nii.gz"): """ Workflow for Diffusion Kurtosis reconstruction and for computing DKI metrics. Performs a DKI reconstruction on the files by 'globing' ``input_files`` and saves the DKI metrics in a directory specified by ``out_dir``. Parameters ---------- input_files : string Path to the input volumes. This path may contain wildcards to process multiple inputs at once. bvalues_files : string Path to the bvalues files. This path may contain wildcards to use multiple bvalues files at once. bvectors_files : string Path to the bvalues files. This path may contain wildcards to use multiple bvalues files at once. mask_files : string Path to the input masks. This path may contain wildcards to use multiple masks at once. (default: No mask used) b0_threshold : float, optional Threshold used to find b=0 directions (default 0.0) save_metrics : variable string, optional List of metrics to save. Possible values: fa, ga, rgb, md, ad, rd, mode, tensor, evec, eval (default [] (all)) out_dir : string, optional Output directory (default input file directory) out_dt_tensor : string, optional Name of the tensors volume to be saved (default: 'dti_tensors.nii.gz') out_dk_tensor : string, optional Name of the tensors volume to be saved (default 'dki_tensors.nii.gz') out_fa : string, optional Name of the fractional anisotropy volume to be saved (default 'fa.nii.gz') out_ga : string, optional Name of the geodesic anisotropy volume to be saved (default 'ga.nii.gz') out_rgb : string, optional Name of the color fa volume to be saved (default 'rgb.nii.gz') out_md : string, optional Name of the mean diffusivity volume to be saved (default 'md.nii.gz') out_ad : string, optional Name of the axial diffusivity volume to be saved (default 'ad.nii.gz') out_rd : string, optional Name of the radial diffusivity volume to be saved (default 'rd.nii.gz') out_mode : string, optional Name of the mode volume to be saved (default 'mode.nii.gz') out_evec : string, optional Name of the eigenvectors volume to be saved (default 'evecs.nii.gz') out_eval : string, optional Name of the eigenvalues to be saved (default 'evals.nii.gz') out_mk : string, optional Name of the mean kurtosis to be saved (default: 'mk.nii.gz') out_ak : string, optional Name of the axial kurtosis to be saved (default: 'ak.nii.gz') out_rk : string, optional Name of the radial kurtosis to be saved (default: 'rk.nii.gz') References ---------- .. [1] Tabesh, A., Jensen, J.H., Ardekani, B.A., Helpern, J.A., 2011. Estimation of tensors and tensor-derived measures in diffusional kurtosis imaging. Magn Reson Med. 65(3), 823-836 .. [2] Jensen, Jens H., Joseph A. Helpern, Anita Ramani, Hanzhang Lu, and Kyle Kaczynski. 2005. Diffusional Kurtosis Imaging: The Quantification of Non-Gaussian Water Diffusion by Means of Magnetic Resonance Imaging. MRM 53 (6):1432-40. """ io_it = self.get_io_iterator() for (dwi, bval, bvec, mask, otensor, ofa, oga, orgb, omd, oad, orad, omode, oevecs, oevals, odk_tensor, omk, oak, ork) in io_it: logging.info('Computing DKI metrics for {0}'.format(dwi)) data, affine = load_nifti(dwi) if mask is not None: mask = nib.load(mask).get_data().astype(np.bool) dkfit, _ = self.get_fitted_tensor(data, mask, bval, bvec, b0_threshold) if not save_metrics: save_metrics = ['mk', 'rk', 'ak', 'fa', 'md', 'rd', 'ad', 'ga', 'rgb', 'mode', 'evec', 'eval', 'dt_tensor', 'dk_tensor'] evals, evecs, kt = split_dki_param(dkfit.model_params) FA = fractional_anisotropy(evals) FA[np.isnan(FA)] = 0 FA = np.clip(FA, 0, 1) if 'dt_tensor' in save_metrics: tensor_vals = lower_triangular(dkfit.quadratic_form) correct_order = [0, 1, 3, 2, 4, 5] tensor_vals_reordered = tensor_vals[..., correct_order] save_nifti(otensor, tensor_vals_reordered.astype(np.float32), affine) if 'dk_tensor' in save_metrics: save_nifti(odk_tensor, dkfit.kt.astype(np.float32), affine) if 'fa' in save_metrics: save_nifti(ofa, FA.astype(np.float32), affine) if 'ga' in save_metrics: GA = geodesic_anisotropy(dkfit.evals) save_nifti(oga, GA.astype(np.float32), affine) if 'rgb' in save_metrics: RGB = color_fa(FA, dkfit.evecs) save_nifti(orgb, np.array(255 * RGB, 'uint8'), affine) if 'md' in save_metrics: MD = mean_diffusivity(dkfit.evals) save_nifti(omd, MD.astype(np.float32), affine) if 'ad' in save_metrics: AD = axial_diffusivity(dkfit.evals) save_nifti(oad, AD.astype(np.float32), affine) if 'rd' in save_metrics: RD = radial_diffusivity(dkfit.evals) save_nifti(orad, RD.astype(np.float32), affine) if 'mode' in save_metrics: MODE = get_mode(dkfit.quadratic_form) save_nifti(omode, MODE.astype(np.float32), affine) if 'evec' in save_metrics: save_nifti(oevecs, dkfit.evecs.astype(np.float32), affine) if 'eval' in save_metrics: save_nifti(oevals, dkfit.evals.astype(np.float32), affine) if 'mk' in save_metrics: save_nifti(omk, dkfit.mk().astype(np.float32), affine) if 'ak' in save_metrics: save_nifti(oak, dkfit.ak().astype(np.float32), affine) if 'rk' in save_metrics: save_nifti(ork, dkfit.rk().astype(np.float32), affine) logging.info('DKI metrics saved in {0}'. format(os.path.dirname(oevals)))
import utils if __name__ == "__main__": # loading image and gradient table bvals, bvecs = read_bvals_bvecs("tp3_data\\bvals2000", "tp3_data\\bvecs2000") gtab = gradient_table(bvals, bvecs) img = utils.load_nifti("dwi2000.nii.gz") ##### utilisation d'un mask pour le calcul du tenseur tenmodel = dti.TensorModel(gtab) brain_mask = nib.load("tp3_data\\_mask.nii.gz").get_data() tenfit = tenmodel.fit(img.get_data(), mask=brain_mask) ##### sauvegarde des orientations principales peaks_fiberNav = np.zeros((112, 112, 60, 15), dtype='float32') peaks_fiberNav[:, :, :, 0:3] = tenfit.evecs[..., 0].astype(np.float32) nib.save(nib.Nifti1Image(peaks_fiberNav, img.get_affine()), "tp3_data\\_peaks") ##### sauvegarde des tenseurs from dipy.reconst.dti import lower_triangular tensor_vals = lower_triangular(tenfit.quadratic_form) correct_order = [0, 1, 3, 2, 4, 5] tensor_vals_reordered = tensor_vals[..., correct_order] fiber_tensors = nib.Nifti1Image(tensor_vals_reordered.astype(np.float32), img.get_affine()) nib.save(fiber_tensors, "tp3_data\\_tensor")
def fit_dti_dipy(input_dwi, input_bval, input_bvec, output_dir, fit_type='', mask='', bmax='', mask_tensor='F', bids_fmt=False, bids_id=''): if not os.path.exists(output_dir): os.makedirs(output_dir) img = nib.load(input_dwi) axis_orient = nib.aff2axcodes(img.affine) ras_img = nib.as_closest_canonical(img) data = ras_img.get_data() bvals, bvecs = read_bvals_bvecs(input_bval, input_bvec) bvecs = reorient_vectors(bvecs, axis_orient[0] + axis_orient[1] + axis_orient[2], 'RAS', axis=1) if mask != '': mask_img = nib.as_closest_canonical(nib.load(mask)) mask_data = mask_img.get_data() if bmax != "": jj = np.where(bvals >= bmax) bvals = np.delete(bvals, jj) bvecs = np.delete(bvecs, jj, 0) data = np.delete(data, jj, axis=3) values = np.array(bvals) ii = np.where(values == bvals.min())[0] b0_average = np.mean(data[:, :, :, ii], axis=3) gtab = gradient_table(bvals, bvecs) if fit_type == 'RESTORE': sigma = estimate_sigma(data) #calculate the average sigma from the b0's sigma = np.mean(sigma[ii]) dti_model = dti.TensorModel(gtab, fit_method='RESTORE', sigma=sigma) if mask != '': dti_fit = dti_model.fit(data, mask_data) else: dti_fit = dti_model.fit(data) elif fit_type != 'RESTORE' and fit_type != '': dti_model = dti.TensorModel(gtab, fit_method=fit_type) if mask != '': dti_fit = dti_model.fit(data, mask_data) else: dti_fit = dti_model.fit(data) else: dti_model = dti.TensorModel(gtab) if mask != '': dti_fit = dti_model.fit(data, mask_data) else: dti_fit = dti_model.fit(data) estimate_data = dti_fit.predict(gtab, S0=b0_average) residuals = np.absolute(data - estimate_data) tensor = dti.lower_triangular(dti_fit.quadratic_form.astype(np.float32)) evecs = dti_fit.evecs.astype(np.float32) evals = dti_fit.evals.astype(np.float32) if not os.path.exists(output_dir): os.makedirs(output_dir) output_imgs = [] #Define output imgs if bids_fmt: output_tensor_nifti = output_dir + '/' + bids_id + '_model-DTI_parameter-TENSOR.nii.gz' output_tensor_fsl = output_dir + '/' + bids_id + '_model-DTI_parameter-FSL_TENSOR.nii.gz' output_tensor_mrtrix = output_dir + '/' + bids_id + '_model-DTI_parameter-MRTRIX_TENSOR.nii.gz' output_V1 = output_dir + '/' + bids_id + '_model-DTI_parameter-V1.nii.gz' output_V2 = output_dir + '/' + bids_id + '_model-DTI_parameter-V2.nii.gz' output_V3 = output_dir + '/' + bids_id + '_model-DTI_parameter-V3.nii.gz' output_FSL_V1 = output_dir + '/' + bids_id + '_model-DTI_parameter-FSL_V1.nii.gz' output_FSL_V2 = output_dir + '/' + bids_id + '_model-DTI_parameter-FSL_V2.nii.gz' output_FSL_V3 = output_dir + '/' + bids_id + '_model-DTI_parameter-FSL_V3.nii.gz' output_L1 = output_dir + '/' + bids_id + '_model-DTI_parameter-L1.nii.gz' output_L2 = output_dir + '/' + bids_id + '_model-DTI_parameter-L2.nii.gz' output_L3 = output_dir + '/' + bids_id + '_model-DTI_parameter-L3.nii.gz' output_fa = output_dir + '/' + bids_id + '_model-DTI_parameter-FA.nii.gz' output_md = output_dir + '/' + bids_id + '_model-DTI_parameter-MD.nii.gz' output_rd = output_dir + '/' + bids_id + '_model-DTI_parameter-RD.nii.gz' output_ad = output_dir + '/' + bids_id + '_model-DTI_parameter-AD.nii.gz' output_tr = output_dir + '/' + bids_id + '_model-DTI_parameter-TRACE.nii.gz' output_ga = output_dir + '/' + bids_id + '_model-DTI_parameter-GA.nii.gz' output_color_fa = output_dir + '/' + bids_id + '_model-DTI_parameter-COLOR_FA.nii.gz' output_PL = output_dir + '/' + bids_id + '_model-DTI_parameter-PLANARITY.nii.gz' output_SP = output_dir + '/' + bids_id + '_model-DTI_parameter-SPHERICITY.nii.gz' output_MO = output_dir + '/' + bids_id + '_model-DTI_parameter-MODE.nii.gz' output_res = output_dir + '/' + bids_id + '_model-DTI_parameter-RESIDUALS.nii.gz' else: output_tensor_fsl = output_dir + '/dti_FSL_TENSOR.nii.gz' output_tensor_nifti = output_dir + '/dti_TENSOR.nii.gz' output_tensor_mrtrix = output_dir + '/dti_MRTRIX_TENSOR.nii.gz' output_V1 = output_dir + '/dti_V1.nii.gz' output_V2 = output_dir + '/dti_V2.nii.gz' output_V3 = output_dir + '/dti_V3.nii.gz' output_FSL_V1 = output_dir + '/dti_FSL_V1.nii.gz' output_FSL_V2 = output_dir + '/dti_FSL_V2.nii.gz' output_FSL_V3 = output_dir + '/dti_FSL_V3.nii.gz' output_L1 = output_dir + '/dti_L1.nii.gz' output_L2 = output_dir + '/dti_L2.nii.gz' output_L3 = output_dir + '/dti_L3.nii.gz' output_fa = output_dir + '/dti_FA.nii.gz' output_md = output_dir + '/dti_MD.nii.gz' output_rd = output_dir + '/dti_RD.nii.gz' output_ad = output_dir + '/dti_AD.nii.gz' output_tr = output_dir + '/dti_TRACE.nii.gz' output_ga = output_dir + '/dti_GA.nii.gz' output_color_fa = output_dir + '/dti_COLOR_FA.nii.gz' output_PL = output_dir + '/dti_PLANARITY.nii.gz' output_SP = output_dir + '/dti_SPHERICITY.nii.gz' output_MO = output_dir + '/dti_MODE.nii.gz' output_res = output_dir + '/dti_RESIDUALS.nii.gz' tensor_img = nifti1_symmat(tensor, ras_img.affine, ras_img.header) tensor_img.header.set_intent = 'NIFTI_INTENT_SYMMATRIX' tensor_img.to_filename(output_tensor_nifti) tensor_fsl = np.empty(tensor.shape) tensor_fsl[:, :, :, 0] = tensor[:, :, :, 0] tensor_fsl[:, :, :, 1] = tensor[:, :, :, 1] tensor_fsl[:, :, :, 2] = tensor[:, :, :, 3] tensor_fsl[:, :, :, 3] = tensor[:, :, :, 2] tensor_fsl[:, :, :, 4] = tensor[:, :, :, 4] tensor_fsl[:, :, :, 5] = tensor[:, :, :, 5] save_nifti(output_tensor_fsl, tensor_fsl, ras_img.affine, ras_img.header) tensor_mrtrix = np.empty(tensor.shape) tensor_mrtrix[:, :, :, 0] = tensor[:, :, :, 0] tensor_mrtrix[:, :, :, 1] = tensor[:, :, :, 2] tensor_mrtrix[:, :, :, 2] = tensor[:, :, :, 5] tensor_mrtrix[:, :, :, 3] = tensor[:, :, :, 1] tensor_mrtrix[:, :, :, 4] = tensor[:, :, :, 3] tensor_mrtrix[:, :, :, 5] = tensor[:, :, :, 4] save_nifti(output_tensor_mrtrix, tensor_mrtrix, ras_img.affine, ras_img.header) fa = dti_fit.fa color_fa = dti_fit.color_fa md = dti_fit.md rd = dti_fit.rd ad = dti_fit.ad ga = dti_fit.ga trace = dti_fit.trace dti_mode = dti_fit.mode dti_planarity = dti_fit.planarity dti_sphericity = dti_fit.sphericity #Remove any nan fa[np.isnan(fa)] = 0 color_fa[np.isnan(color_fa)] = 0 md[np.isnan(md)] = 0 rd[np.isnan(rd)] = 0 ad[np.isnan(ad)] = 0 ga[np.isnan(ga)] = 0 trace[np.isnan(trace)] = 0 dti_mode[np.isnan(dti_mode)] = 0 dti_planarity[np.isnan(dti_planarity)] = 0 dti_sphericity[np.isnan(dti_sphericity)] = 0 save_nifti(output_V1, evecs[:, :, :, :, 0], ras_img.affine, ras_img.header) save_nifti(output_V2, evecs[:, :, :, :, 1], ras_img.affine, ras_img.header) save_nifti(output_V3, evecs[:, :, :, :, 2], ras_img.affine, ras_img.header) save_nifti(output_L1, evals[:, :, :, 0], ras_img.affine, ras_img.header) save_nifti(output_L2, evals[:, :, :, 1], ras_img.affine, ras_img.header) save_nifti(output_L3, evals[:, :, :, 2], ras_img.affine, ras_img.header) save_nifti(output_fa, fa, ras_img.affine, ras_img.header) save_nifti(output_color_fa, color_fa, ras_img.affine, ras_img.header) save_nifti(output_md, md, ras_img.affine, ras_img.header) save_nifti(output_ad, ad, ras_img.affine, ras_img.header) save_nifti(output_rd, rd, ras_img.affine, ras_img.header) save_nifti(output_ga, ga, ras_img.affine, ras_img.header) save_nifti(output_tr, trace, ras_img.affine, ras_img.header) save_nifti(output_PL, dti_planarity, ras_img.affine, ras_img.header) save_nifti(output_SP, dti_sphericity, ras_img.affine, ras_img.header) save_nifti(output_MO, dti_mode, ras_img.affine, ras_img.header) save_nifti(output_res, residuals, ras_img.affine, ras_img.header) #Reorient back to the original output_imgs.append(output_tensor_nifti) output_imgs.append(output_tensor_fsl) output_imgs.append(output_tensor_mrtrix) output_imgs.append(output_V1) output_imgs.append(output_V2) output_imgs.append(output_V3) output_imgs.append(output_L1) output_imgs.append(output_L2) output_imgs.append(output_L3) output_imgs.append(output_fa) output_imgs.append(output_md) output_imgs.append(output_rd) output_imgs.append(output_ad) output_imgs.append(output_ga) output_imgs.append(output_color_fa) output_imgs.append(output_PL) output_imgs.append(output_SP) output_imgs.append(output_MO) output_imgs.append(output_res) #Change orientation back to the original orientation orig_ornt = nib.io_orientation(ras_img.affine) targ_ornt = nib.io_orientation(img.affine) transform = nib.orientations.ornt_transform(orig_ornt, targ_ornt) affine_xfm = nib.orientations.inv_ornt_aff(transform, ras_img.shape) trans_mat = affine_xfm[0:3, 0:3] for img_path in output_imgs: orig_img = nib.load(img_path) reoriented = orig_img.as_reoriented(transform) reoriented.to_filename(img_path) #Correct FSL tensor for orientation dirs = [] dirs.append(np.array([[1], [0], [0]])) dirs.append(np.array([[1], [1], [0]])) dirs.append(np.array([[1], [0], [1]])) dirs.append(np.array([[0], [1], [0]])) dirs.append(np.array([[0], [1], [1]])) dirs.append(np.array([[0], [0], [1]])) tensor_fsl = nib.load(output_tensor_fsl) corr_fsl_tensor = np.empty(tensor_fsl.get_data().shape) for i in range(0, len(dirs)): rot_dir = np.matmul(trans_mat, dirs[i]) sign = 1.0 if np.sum(rot_dir) == 0.0: sign = -1.0 if (np.absolute(rot_dir) == np.array([[1], [0], [0]])).all(): tensor_ind = 0 elif (np.absolute(rot_dir) == np.array([[1], [1], [0]])).all(): tensor_ind = 1 elif (np.absolute(rot_dir) == np.array([[1], [0], [1]])).all(): tensor_ind = 2 elif (np.absolute(rot_dir) == np.array([[0], [1], [0]])).all(): tensor_ind = 3 elif (np.absolute(rot_dir) == np.array([[0], [1], [1]])).all(): tensor_ind = 4 elif (np.absolute(rot_dir) == np.array([[0], [0], [1]])).all(): tensor_ind = 5 corr_fsl_tensor[:, :, :, i] = sign * tensor_fsl.get_data()[:, :, :, tensor_ind] save_nifti(output_tensor_fsl, corr_fsl_tensor, tensor_fsl.affine, tensor_fsl.header) #Now correct the eigenvectors #Determine the order to rearrange vec_order = np.transpose(targ_ornt[:, 0]).astype(int) sign_order = np.transpose(targ_ornt[:, 1]).astype(int) fsl_v1 = nib.load(output_V1) corr_fsl_v1 = fsl_v1.get_data()[:, :, :, vec_order] for i in range(0, 2): corr_fsl_v1[:, :, :, i] = sign_order[i] * corr_fsl_v1[:, :, :, i] save_nifti(output_FSL_V1, corr_fsl_v1, fsl_v1.affine, fsl_v1.header) fsl_v2 = nib.load(output_V2) corr_fsl_v2 = fsl_v2.get_data()[:, :, :, vec_order] for i in range(0, 2): corr_fsl_v2[:, :, :, i] = sign_order[i] * corr_fsl_v2[:, :, :, i] save_nifti(output_FSL_V2, corr_fsl_v2, fsl_v2.affine, fsl_v2.header) fsl_v3 = nib.load(output_V3) corr_fsl_v3 = fsl_v3.get_data()[:, :, :, vec_order] for i in range(0, 2): corr_fsl_v3[:, :, :, i] = sign_order[i] * corr_fsl_v3[:, :, :, i] save_nifti(output_FSL_V3, corr_fsl_v3, fsl_v3.affine, fsl_v3.header)
def nls_fit_fwdki(design_matrix, design_matrix_dki, data, S0, params=None, Diso=3e-3, f_transform=True, mdreg=2.7e-3): """ Fit the water elimination DKI model using the non-linear least-squares. Parameters ---------- design_matrix : array (g, 22) Design matrix holding the covariants used to solve for the regression coefficients. data : ndarray ([X, Y, Z, ...], g) Data or response variables holding the data. Note that the last dimension should contain the data. It makes no copies of data. S0 : ndarray ([X, Y, Z]) A first guess of the non-diffusion signal S0. params : ndarray ([X, Y, Z, ...], 28), optional A first model parameters guess (3 eigenvalues, 3 coordinates of 3 eigenvalues, 15 elements of the kurtosis tensor and the volume fraction of the free water compartment). If the initial params are not given, for the diffusion and kurtosis tensor parameters, its initial guess is obtain from the standard DKI model, while for the free water fraction its value is estimated using the fwDTI model. Default: None Diso : float, optional Value of the free water isotropic diffusion. Default is set to 3e-3 $mm^{2}.s^{-1}$. Please ajust this value if you are assuming different units of diffusion. f_transform : bool, optional If true, the water volume fractions is converted during the convergence procedure to ft = arcsin(2*f - 1) + pi/2, insuring f estimates between 0 and 1. Default: True mdreg : float, optimal DTI's mean diffusivity regularization threshold. If standard DTI diffusion tensor's mean diffusivity is almost near the free water diffusion value, the diffusion signal is assumed to be only free water diffusion (i.e. volume fraction will be set to 1 and tissue's diffusion parameters are set to zero). Default md_reg is 2.7e-3 $mm^{2}.s^{-1}$ (corresponding to 90% of the free water diffusion value). Returns ------- fw_params : ndarray (x, y, z, 28) Matrix containing in the dimention the free water model parameters in the following order: 1) Three diffusion tensor's eigenvalues 2) Three lines of the eigenvector matrix each containing the first, second and third coordinates of the eigenvector 3) Fifteen elements of the kurtosis tensor 4) The volume fraction of the free water compartment S0 : ndarray (x, y, z) The models estimate of the non diffusion-weighted signal S0. """ # preparing data and initializing parameters data = np.asarray(data) data_flat = np.reshape(data, (-1, data.shape[-1])) S0out = S0.copy() S0out = S0out.ravel() # Computing WLS DTI solution for MD regularization dtiparams = dti.wls_fit_tensor(design_matrix, data_flat) md = dti.mean_diffusivity(dtiparams[..., :3]) cond = md > mdreg # removal condition data_cond = data_flat[~cond, :] # Initializing fw_params according to selected initial guess if np.any(params) is None: params_out = np.zeros((len(data_flat), 28)) dkiparams = dki.wls_fit_dki(design_matrix_dki, data_flat) fweparams, sd = fwdti.wls_fit_tensor(design_matrix, data_flat, S0=S0, Diso=Diso, mdreg=2.7e-3) params_out[:, 0:27] = dkiparams params_out[:, 27] = fweparams[:, 12] else: params_out = params.copy() params_out = np.reshape(params_out, (-1, params_out.shape[-1])) params_cond = params_out[~cond, :] S0_cond = S0out[~cond] for vox in range(data_cond.shape[0]): if np.all(data_cond[vox] == 0): raise ValueError("The data in this voxel contains only zeros") params = params_cond[vox] # converting evals and evecs to diffusion tensor elements evals = params[:3] evecs = params[3:12].reshape((3, 3)) dt = lower_triangular(vec_val_vect(evecs, evals)) kt = params[..., 12:27] s0 = S0_cond[vox] MD = evals.mean() # f transformation if requested if f_transform: f = np.arcsin(2*params[27] - 1) + np.pi/2 else: f = params[27] # Use the Levenberg-Marquardt algorithm wrapped in opt.leastsq start_params = np.concatenate((dt, kt*MD*MD, [np.log(s0), f]), axis=0) this_tensor, status = opt.leastsq(_nls_err_func, start_params, args=(design_matrix_dki, data_cond[vox], Diso, f_transform)) # Invert f transformation if this was requested if f_transform: this_tensor[22] = 0.5 * (1 + np.sin(this_tensor[22] - np.pi/2)) # The parameters are the evals and the evecs: evals, evecs = decompose_tensor(from_lower_triangular(this_tensor[:6])) MD = evals.mean() params_cond[vox, :3] = evals params_cond[vox, 3:12] = evecs.ravel() params_cond[vox, 12:27] = this_tensor[6:21] / (MD ** 2) params_cond[vox, 27] = this_tensor[22] S0_cond[vox] = np.exp(-this_tensor[21]) params_out[~cond, :] = params_cond params_out[cond, 27] = 1 # Only free water params_out = np.reshape(params_out, (data.shape[:-1]) + (28,)) S0out[~cond] = S0_cond S0out[cond] = \ np.mean(data_flat[cond, :] / \ np.exp(np.dot(design_matrix[..., :6], np.array([Diso, 0, Diso, 0, 0, Diso]))), -1) # Only free water S0out = S0out.reshape(data.shape[:-1]) return params_out, S0out
def main(): parser = buildArgsParser() args = parser.parse_args() # Load data img = nib.load(args.input) data = img.get_data() affine = img.get_affine() # Setting suffix savename if args.savename is None: filename = "" else: filename = args.savename + "_" if os.path.exists(filename + 'fa.nii.gz'): if not args.overwrite: raise ValueError("File " + filename + "fa.nii.gz" + " already exists. Use -f option to overwrite.") print (filename + "fa.nii.gz", " already exists and will be overwritten.") if args.mask is not None: mask = nib.load(args.mask).get_data() else: print("No mask specified. Computing mask with median_otsu.") data, mask = median_otsu(data) mask_img = nib.Nifti1Image(mask.astype(np.float32), affine) nib.save(mask_img, filename + 'mask.nii.gz') # Get tensors print('Tensor estimation...') b_vals, b_vecs = read_bvals_bvecs(args.bvals, args.bvecs) gtab = gradient_table_from_bvals_bvecs(b_vals, b_vecs) tenmodel = TensorModel(gtab) tenfit = tenmodel.fit(data, mask) # FA print('Computing FA...') FA = fractional_anisotropy(tenfit.evals) FA[np.isnan(FA)] = 0 # RGB print('Computing RGB...') FA = np.clip(FA, 0, 1) RGB = color_fa(FA, tenfit.evecs) if args.all : print('Computing Diffusivities...') # diffusivities MD = mean_diffusivity(tenfit.evals) AD = axial_diffusivity(tenfit.evals) RD = radial_diffusivity(tenfit.evals) print('Computing Mode...') MODE = mode(tenfit.quadratic_form) print('Saving tensor coefficients and metrics...') # Get the Tensor values and format them for visualisation in the Fibernavigator. tensor_vals = lower_triangular(tenfit.quadratic_form) correct_order = [0, 1, 3, 2, 4, 5] tensor_vals_reordered = tensor_vals[..., correct_order] fiber_tensors = nib.Nifti1Image(tensor_vals_reordered.astype(np.float32), affine) nib.save(fiber_tensors, filename + 'tensors.nii.gz') # Save - for some reason this is not read properly by the FiberNav md_img = nib.Nifti1Image(MD.astype(np.float32), affine) nib.save(md_img, filename + 'md.nii.gz') ad_img = nib.Nifti1Image(AD.astype(np.float32), affine) nib.save(ad_img, filename + 'ad.nii.gz') rd_img = nib.Nifti1Image(RD.astype(np.float32), affine) nib.save(rd_img, filename + 'rd.nii.gz') mode_img = nib.Nifti1Image(MODE.astype(np.float32), affine) nib.save(mode_img, filename + 'mode.nii.gz') fa_img = nib.Nifti1Image(FA.astype(np.float32), affine) nib.save(fa_img, filename + 'fa.nii.gz') rgb_img = nib.Nifti1Image(np.array(255 * RGB, 'uint8'), affine) nib.save(rgb_img, filename + 'rgb.nii.gz')
def run(self, input_files, bvalues, bvectors, mask_files, b0_threshold=0.0, save_metrics=[], out_dir='', out_tensor='tensors.nii.gz', out_fa='fa.nii.gz', out_ga='ga.nii.gz', out_rgb='rgb.nii.gz', out_md='md.nii.gz', out_ad='ad.nii.gz', out_rd='rd.nii.gz', out_mode='mode.nii.gz', out_evec='evecs.nii.gz', out_eval='evals.nii.gz'): """ Workflow for tensor reconstruction and for computing DTI metrics. Performs a tensor reconstruction on the files by 'globing' ``input_files`` and saves the DTI metrics in a directory specified by ``out_dir``. Parameters ---------- input_files : string Path to the input volumes. This path may contain wildcards to process multiple inputs at once. bvalues : string Path to the bvalues files. This path may contain wildcards to use multiple bvalues files at once. bvectors : string Path to the bvalues files. This path may contain wildcards to use multiple bvalues files at once. mask_files : string Path to the input masks. This path may contain wildcards to use multiple masks at once. (default: No mask used) b0_threshold : float, optional Threshold used to find b=0 directions (default 0.0) save_metrics : variable string, optional List of metrics to save. Possible values: fa, ga, rgb, md, ad, rd, mode, tensor, evec, eval (default [] (all)) out_dir : string, optional Output directory (default input file directory) out_tensor : string, optional Name of the tensors volume to be saved (default 'tensors.nii.gz') out_fa : string, optional Name of the fractional anisotropy volume to be saved (default 'fa.nii.gz') out_ga : string, optional Name of the geodesic anisotropy volume to be saved (default 'ga.nii.gz') out_rgb : string, optional Name of the color fa volume to be saved (default 'rgb.nii.gz') out_md : string, optional Name of the mean diffusivity volume to be saved (default 'md.nii.gz') out_ad : string, optional Name of the axial diffusivity volume to be saved (default 'ad.nii.gz') out_rd : string, optional Name of the radial diffusivity volume to be saved (default 'rd.nii.gz') out_mode : string, optional Name of the mode volume to be saved (default 'mode.nii.gz') out_evec : string, optional Name of the eigenvectors volume to be saved (default 'evecs.nii.gz') out_eval : string, optional Name of the eigenvalues to be saved (default 'evals.nii.gz') """ io_it = self.get_io_iterator() for dwi, bval, bvec, mask, otensor, ofa, oga, orgb, omd, oad, orad, \ omode, oevecs, oevals in io_it: logging.info('Computing DTI metrics for {0}'.format(dwi)) img = nib.load(dwi) data = img.get_data() affine = img.get_affine() if mask is None: mask = None else: mask = nib.load(mask).get_data().astype(np.bool) tenfit, _ = self.get_fitted_tensor(data, mask, bval, bvec, b0_threshold) if not save_metrics: save_metrics = ['fa', 'md', 'rd', 'ad', 'ga', 'rgb', 'mode', 'evec', 'eval', 'tensor'] FA = fractional_anisotropy(tenfit.evals) FA[np.isnan(FA)] = 0 FA = np.clip(FA, 0, 1) if 'tensor' in save_metrics: tensor_vals = lower_triangular(tenfit.quadratic_form) correct_order = [0, 1, 3, 2, 4, 5] tensor_vals_reordered = tensor_vals[..., correct_order] fiber_tensors = nib.Nifti1Image(tensor_vals_reordered.astype( np.float32), affine) nib.save(fiber_tensors, otensor) if 'fa' in save_metrics: fa_img = nib.Nifti1Image(FA.astype(np.float32), affine) nib.save(fa_img, ofa) if 'ga' in save_metrics: GA = geodesic_anisotropy(tenfit.evals) ga_img = nib.Nifti1Image(GA.astype(np.float32), affine) nib.save(ga_img, oga) if 'rgb' in save_metrics: RGB = color_fa(FA, tenfit.evecs) rgb_img = nib.Nifti1Image(np.array(255 * RGB, 'uint8'), affine) nib.save(rgb_img, orgb) if 'md' in save_metrics: MD = mean_diffusivity(tenfit.evals) md_img = nib.Nifti1Image(MD.astype(np.float32), affine) nib.save(md_img, omd) if 'ad' in save_metrics: AD = axial_diffusivity(tenfit.evals) ad_img = nib.Nifti1Image(AD.astype(np.float32), affine) nib.save(ad_img, oad) if 'rd' in save_metrics: RD = radial_diffusivity(tenfit.evals) rd_img = nib.Nifti1Image(RD.astype(np.float32), affine) nib.save(rd_img, orad) if 'mode' in save_metrics: MODE = get_mode(tenfit.quadratic_form) mode_img = nib.Nifti1Image(MODE.astype(np.float32), affine) nib.save(mode_img, omode) if 'evec' in save_metrics: evecs_img = nib.Nifti1Image(tenfit.evecs.astype(np.float32), affine) nib.save(evecs_img, oevecs) if 'eval' in save_metrics: evals_img = nib.Nifti1Image(tenfit.evals.astype(np.float32), affine) nib.save(evals_img, oevals) logging.info('DTI metrics saved in {0}'. format(os.path.dirname(oevals)))
def main(): parser = _build_args_parser() args = parser.parse_args() if not args.not_all: args.fa = args.fa or 'fa.nii.gz' args.ga = args.ga or 'ga.nii.gz' args.rgb = args.rgb or 'rgb.nii.gz' args.md = args.md or 'md.nii.gz' args.ad = args.ad or 'ad.nii.gz' args.rd = args.rd or 'rd.nii.gz' args.mode = args.mode or 'mode.nii.gz' args.norm = args.norm or 'tensor_norm.nii.gz' args.tensor = args.tensor or 'tensor.nii.gz' args.evecs = args.evecs or 'tensor_evecs.nii.gz' args.evals = args.evals or 'tensor_evals.nii.gz' args.residual = args.residual or 'dti_residual.nii.gz' args.p_i_signal =\ args.p_i_signal or 'physically_implausible_signals_mask.nii.gz' args.pulsation = args.pulsation or 'pulsation_and_misalignment.nii.gz' outputs = [args.fa, args.ga, args.rgb, args.md, args.ad, args.rd, args.mode, args.norm, args.tensor, args.evecs, args.evals, args.residual, args.p_i_signal, args.pulsation] if args.not_all and not any(outputs): parser.error('When using --not_all, you need to specify at least ' + 'one metric to output.') assert_inputs_exist( parser, [args.input, args.bvals, args.bvecs], [args.mask]) assert_outputs_exists(parser, args, outputs) img = nib.load(args.input) data = img.get_data() affine = img.get_affine() if args.mask is None: mask = None else: mask = nib.load(args.mask).get_data().astype(np.bool) # Validate bvals and bvecs logging.info('Tensor estimation with the %s method...', args.method) bvals, bvecs = read_bvals_bvecs(args.bvals, args.bvecs) if not is_normalized_bvecs(bvecs): logging.warning('Your b-vectors do not seem normalized...') bvecs = normalize_bvecs(bvecs) check_b0_threshold(args, bvals.min()) gtab = gradient_table(bvals, bvecs, b0_threshold=bvals.min()) # Get tensors if args.method == 'restore': sigma = ne.estimate_sigma(data) tenmodel = TensorModel(gtab, fit_method=args.method, sigma=sigma, min_signal=_get_min_nonzero_signal(data)) else: tenmodel = TensorModel(gtab, fit_method=args.method, min_signal=_get_min_nonzero_signal(data)) tenfit = tenmodel.fit(data, mask) FA = fractional_anisotropy(tenfit.evals) FA[np.isnan(FA)] = 0 FA = np.clip(FA, 0, 1) if args.tensor: # Get the Tensor values and format them for visualisation # in the Fibernavigator. tensor_vals = lower_triangular(tenfit.quadratic_form) correct_order = [0, 1, 3, 2, 4, 5] tensor_vals_reordered = tensor_vals[..., correct_order] fiber_tensors = nib.Nifti1Image( tensor_vals_reordered.astype(np.float32), affine) nib.save(fiber_tensors, args.tensor) if args.fa: fa_img = nib.Nifti1Image(FA.astype(np.float32), affine) nib.save(fa_img, args.fa) if args.ga: GA = geodesic_anisotropy(tenfit.evals) GA[np.isnan(GA)] = 0 ga_img = nib.Nifti1Image(GA.astype(np.float32), affine) nib.save(ga_img, args.ga) if args.rgb: RGB = color_fa(FA, tenfit.evecs) rgb_img = nib.Nifti1Image(np.array(255 * RGB, 'uint8'), affine) nib.save(rgb_img, args.rgb) if args.md: MD = mean_diffusivity(tenfit.evals) md_img = nib.Nifti1Image(MD.astype(np.float32), affine) nib.save(md_img, args.md) if args.ad: AD = axial_diffusivity(tenfit.evals) ad_img = nib.Nifti1Image(AD.astype(np.float32), affine) nib.save(ad_img, args.ad) if args.rd: RD = radial_diffusivity(tenfit.evals) rd_img = nib.Nifti1Image(RD.astype(np.float32), affine) nib.save(rd_img, args.rd) if args.mode: # Compute tensor mode inter_mode = dipy_mode(tenfit.quadratic_form) # Since the mode computation can generate NANs when not masked, # we need to remove them. non_nan_indices = np.isfinite(inter_mode) mode = np.zeros(inter_mode.shape) mode[non_nan_indices] = inter_mode[non_nan_indices] mode_img = nib.Nifti1Image(mode.astype(np.float32), affine) nib.save(mode_img, args.mode) if args.norm: NORM = norm(tenfit.quadratic_form) norm_img = nib.Nifti1Image(NORM.astype(np.float32), affine) nib.save(norm_img, args.norm) if args.evecs: evecs = tenfit.evecs.astype(np.float32) evecs_img = nib.Nifti1Image(evecs, affine) nib.save(evecs_img, args.evecs) # save individual e-vectors also e1_img = nib.Nifti1Image(evecs[..., 0], affine) e2_img = nib.Nifti1Image(evecs[..., 1], affine) e3_img = nib.Nifti1Image(evecs[..., 2], affine) nib.save(e1_img, add_filename_suffix(args.evecs, '_v1')) nib.save(e2_img, add_filename_suffix(args.evecs, '_v2')) nib.save(e3_img, add_filename_suffix(args.evecs, '_v3')) if args.evals: evals = tenfit.evals.astype(np.float32) evals_img = nib.Nifti1Image(evals, affine) nib.save(evals_img, args.evals) # save individual e-values also e1_img = nib.Nifti1Image(evals[..., 0], affine) e2_img = nib.Nifti1Image(evals[..., 1], affine) e3_img = nib.Nifti1Image(evals[..., 2], affine) nib.save(e1_img, add_filename_suffix(args.evals, '_e1')) nib.save(e2_img, add_filename_suffix(args.evals, '_e2')) nib.save(e3_img, add_filename_suffix(args.evals, '_e3')) if args.p_i_signal: S0 = np.mean(data[..., gtab.b0s_mask], axis=-1, keepdims=True) DWI = data[..., ~gtab.b0s_mask] pis_mask = np.max(S0 < DWI, axis=-1) if args.mask is not None: pis_mask *= mask pis_img = nib.Nifti1Image(pis_mask.astype(np.int16), affine) nib.save(pis_img, args.p_i_signal) if args.pulsation: STD = np.std(data[..., ~gtab.b0s_mask], axis=-1) if args.mask is not None: STD *= mask std_img = nib.Nifti1Image(STD.astype(np.float32), affine) nib.save(std_img, add_filename_suffix(args.pulsation, '_std_dwi')) if np.sum(gtab.b0s_mask) <= 1: logger.info('Not enough b=0 images to output standard ' 'deviation map') else: if len(np.where(gtab.b0s_mask)) == 2: logger.info('Only two b=0 images. Be careful with the ' 'interpretation of this std map') STD = np.std(data[..., gtab.b0s_mask], axis=-1) if args.mask is not None: STD *= mask std_img = nib.Nifti1Image(STD.astype(np.float32), affine) nib.save(std_img, add_filename_suffix(args.pulsation, '_std_b0')) if args.residual: if args.mask is None: logger.info("Outlier detection will not be performed, since no " "mask was provided.") S0 = np.mean(data[..., gtab.b0s_mask], axis=-1) data_p = tenfit.predict(gtab, S0) R = np.mean(np.abs(data_p[..., ~gtab.b0s_mask] - data[..., ~gtab.b0s_mask]), axis=-1) if args.mask is not None: R *= mask R_img = nib.Nifti1Image(R.astype(np.float32), affine) nib.save(R_img, args.residual) R_k = np.zeros(data.shape[-1]) # mean residual per DWI std = np.zeros(data.shape[-1]) # std residual per DWI q1 = np.zeros(data.shape[-1]) # first quartile q3 = np.zeros(data.shape[-1]) # third quartile iqr = np.zeros(data.shape[-1]) # interquartile for i in range(data.shape[-1]): x = np.abs(data_p[..., i] - data[..., i])[mask] R_k[i] = np.mean(x) std[i] = np.std(x) q3[i], q1[i] = np.percentile(x, [75, 25]) iqr[i] = q3[i] - q1[i] # Outliers are observations that fall below Q1 - 1.5(IQR) or # above Q3 + 1.5(IQR) We check if a volume is an outlier only if # we have a mask, else we are biased. if args.mask is not None and R_k[i] < (q1[i] - 1.5 * iqr[i]) \ or R_k[i] > (q3[i] + 1.5 * iqr[i]): logger.warning('WARNING: Diffusion-Weighted Image i=%s is an ' 'outlier', i) residual_basename, _ = split_name_with_nii(args.residual) res_stats_basename = residual_basename + ".npy" np.save(add_filename_suffix( res_stats_basename, "_mean_residuals"), R_k) np.save(add_filename_suffix(res_stats_basename, "_q1_residuals"), q1) np.save(add_filename_suffix(res_stats_basename, "_q3_residuals"), q3) np.save(add_filename_suffix(res_stats_basename, "_iqr_residuals"), iqr) np.save(add_filename_suffix(res_stats_basename, "_std_residuals"), std) # To do: I would like to have an error bar with q1 and q3. # Now, q1 acts as a std dwi = np.arange(R_k[~gtab.b0s_mask].shape[0]) plt.bar(dwi, R_k[~gtab.b0s_mask], 0.75, color='y', yerr=q1[~gtab.b0s_mask]) plt.xlabel('DW image') plt.ylabel('Mean residuals +- q1') plt.title('Residuals') plt.savefig(residual_basename + '_residuals_stats.png')
def run(self, input_files, bvalues_files, bvectors_files, mask_files, b0_threshold=0.0, bvecs_tol=0.01, save_metrics=[], out_dir='', out_tensor='tensors.nii.gz', out_fa='fa.nii.gz', out_ga='ga.nii.gz', out_rgb='rgb.nii.gz', out_md='md.nii.gz', out_ad='ad.nii.gz', out_rd='rd.nii.gz', out_mode='mode.nii.gz', out_evec='evecs.nii.gz', out_eval='evals.nii.gz'): """ Workflow for tensor reconstruction and for computing DTI metrics. using Weighted Least-Squares. Performs a tensor reconstruction on the files by 'globing' ``input_files`` and saves the DTI metrics in a directory specified by ``out_dir``. Parameters ---------- input_files : string Path to the input volumes. This path may contain wildcards to process multiple inputs at once. bvalues_files : string Path to the bvalues files. This path may contain wildcards to use multiple bvalues files at once. bvectors_files : string Path to the bvectors files. This path may contain wildcards to use multiple bvectors files at once. mask_files : string Path to the input masks. This path may contain wildcards to use multiple masks at once. (default: No mask used) b0_threshold : float, optional Threshold used to find b=0 directions (default 0.0) bvecs_tol : float, optional Threshold used to check that norm(bvec) = 1 +/- bvecs_tol b-vectors are unit vectors (default 0.01) save_metrics : variable string, optional List of metrics to save. Possible values: fa, ga, rgb, md, ad, rd, mode, tensor, evec, eval (default [] (all)) out_dir : string, optional Output directory (default input file directory) out_tensor : string, optional Name of the tensors volume to be saved (default 'tensors.nii.gz') out_fa : string, optional Name of the fractional anisotropy volume to be saved (default 'fa.nii.gz') out_ga : string, optional Name of the geodesic anisotropy volume to be saved (default 'ga.nii.gz') out_rgb : string, optional Name of the color fa volume to be saved (default 'rgb.nii.gz') out_md : string, optional Name of the mean diffusivity volume to be saved (default 'md.nii.gz') out_ad : string, optional Name of the axial diffusivity volume to be saved (default 'ad.nii.gz') out_rd : string, optional Name of the radial diffusivity volume to be saved (default 'rd.nii.gz') out_mode : string, optional Name of the mode volume to be saved (default 'mode.nii.gz') out_evec : string, optional Name of the eigenvectors volume to be saved (default 'evecs.nii.gz') out_eval : string, optional Name of the eigenvalues to be saved (default 'evals.nii.gz') References ---------- .. [1] Basser, P.J., Mattiello, J., LeBihan, D., 1994. Estimation of the effective self-diffusion tensor from the NMR spin echo. J Magn Reson B 103, 247-254. .. [2] Basser, P., Pierpaoli, C., 1996. Microstructural and physiological features of tissues elucidated by quantitative diffusion-tensor MRI. Journal of Magnetic Resonance 111, 209-219. .. [3] Lin-Ching C., Jones D.K., Pierpaoli, C. 2005. RESTORE: Robust estimation of tensors by outlier rejection. MRM 53: 1088-1095 .. [4] hung, SW., Lu, Y., Henry, R.G., 2006. Comparison of bootstrap approaches for estimation of uncertainties of DTI parameters. NeuroImage 33, 531-541. """ io_it = self.get_io_iterator() for dwi, bval, bvec, mask, otensor, ofa, oga, orgb, omd, oad, orad, \ omode, oevecs, oevals in io_it: logging.info('Computing DTI metrics for {0}'.format(dwi)) img = nib.load(dwi) data = img.get_data() affine = img.affine if mask is not None: mask = nib.load(mask).get_data().astype(np.bool) tenfit, _ = self.get_fitted_tensor(data, mask, bval, bvec, b0_threshold, bvecs_tol) if not save_metrics: save_metrics = [ 'fa', 'md', 'rd', 'ad', 'ga', 'rgb', 'mode', 'evec', 'eval', 'tensor' ] FA = fractional_anisotropy(tenfit.evals) FA[np.isnan(FA)] = 0 FA = np.clip(FA, 0, 1) if 'tensor' in save_metrics: tensor_vals = lower_triangular(tenfit.quadratic_form) correct_order = [0, 1, 3, 2, 4, 5] tensor_vals_reordered = tensor_vals[..., correct_order] fiber_tensors = nib.Nifti1Image( tensor_vals_reordered.astype(np.float32), affine) nib.save(fiber_tensors, otensor) if 'fa' in save_metrics: fa_img = nib.Nifti1Image(FA.astype(np.float32), affine) nib.save(fa_img, ofa) if 'ga' in save_metrics: GA = geodesic_anisotropy(tenfit.evals) ga_img = nib.Nifti1Image(GA.astype(np.float32), affine) nib.save(ga_img, oga) if 'rgb' in save_metrics: RGB = color_fa(FA, tenfit.evecs) rgb_img = nib.Nifti1Image(np.array(255 * RGB, 'uint8'), affine) nib.save(rgb_img, orgb) if 'md' in save_metrics: MD = mean_diffusivity(tenfit.evals) md_img = nib.Nifti1Image(MD.astype(np.float32), affine) nib.save(md_img, omd) if 'ad' in save_metrics: AD = axial_diffusivity(tenfit.evals) ad_img = nib.Nifti1Image(AD.astype(np.float32), affine) nib.save(ad_img, oad) if 'rd' in save_metrics: RD = radial_diffusivity(tenfit.evals) rd_img = nib.Nifti1Image(RD.astype(np.float32), affine) nib.save(rd_img, orad) if 'mode' in save_metrics: MODE = get_mode(tenfit.quadratic_form) mode_img = nib.Nifti1Image(MODE.astype(np.float32), affine) nib.save(mode_img, omode) if 'evec' in save_metrics: evecs_img = nib.Nifti1Image(tenfit.evecs.astype(np.float32), affine) nib.save(evecs_img, oevecs) if 'eval' in save_metrics: evals_img = nib.Nifti1Image(tenfit.evals.astype(np.float32), affine) nib.save(evals_img, oevals) dname_ = os.path.dirname(oevals) if dname_ == '': logging.info('DTI metrics saved in current directory') else: logging.info('DTI metrics saved in {0}'.format(dname_))
def apparent_kurtosis_coef(dki_params, sphere, min_diffusivity=0, min_kurtosis=-1): r""" Calculate the apparent kurtosis coefficient (AKC) in each direction of a sphere. Parameters ---------- dki_params : ndarray (x, y, z, 27) or (n, 27) All parameters estimated from the diffusion kurtosis model. Parameters are ordered as follow: 1) Three diffusion tensor's eingenvalues 2) Three lines of the eigenvector matrix each containing the first, second and third coordinates of the eigenvectors respectively 3) Fifteen elements of the kurtosis tensor sphere : a Sphere class instance The AKC will be calculated for each of the vertices in the sphere min_diffusivity : float (optional) Because negative eigenvalues are not physical and small eigenvalues cause quite a lot of noise in diffusion based metrics, diffusivity values smaller than `min_diffusivity` are replaced with `min_diffusivity`. defaut = 0 min_kurtosis : float (optional) Because high amplitude negative values of kurtosis are not physicaly and biologicaly pluasible, and these causes huge artefacts in kurtosis based measures, directional kurtosis values than `min_kurtosis` are replaced with `min_kurtosis`. defaut = -1 Returns -------- AKC : ndarray (x, y, z, g) or (n, g) Apparent kurtosis coefficient (AKC) for all g directions of a sphere. Notes ----- For each sphere direction with coordinates $(n_{1}, n_{2}, n_{3})$, the calculation of AKC is done using formula: .. math :: AKC(n)=\frac{MD^{2}}{ADC(n)^{2}}\sum_{i=1}^{3}\sum_{j=1}^{3} \sum_{k=1}^{3}\sum_{l=1}^{3}n_{i}n_{j}n_{k}n_{l}W_{ijkl} where $W_{ijkl}$ are the elements of the kurtosis tensor, MD the mean diffusivity and ADC the apparent diffusion coefficent computed as: .. math :: ADC(n)=\sum_{i=1}^{3}\sum_{j=1}^{3}n_{i}n_{j}D_{ij} where $D_{ij}$ are the elements of the diffusion tensor. """ # Flat parameters outshape = dki_params.shape[:-1] dki_params = dki_params.reshape((-1, dki_params.shape[-1])) # Split data evals, evecs, kt = split_dki_param(dki_params) # Compute MD MD = mean_diffusivity(evals) # Initialize AKC matrix V = sphere.vertices AKC = np.zeros((len(kt), len(V))) # loop over all voxels for vox in range(len(kt)): R = evecs[vox] dt = lower_triangular(np.dot(np.dot(R, np.diag(evals[vox])), R.T)) AKC[vox] = _directional_kurtosis(dt, MD[vox], kt[vox], V, min_diffusivity=min_diffusivity, min_kurtosis=min_kurtosis) # reshape data according to input data AKC = AKC.reshape((outshape + (len(V), ))) return AKC
def _run_interface(self, runtime): # Load the 4D image files img = nb.load(self.inputs.in_file) data = img.get_data() affine = img.get_affine() if self.inputs.lower_triangular_input: try: dti_params = dti.eig_from_lo_tri(data) except: dti_params = dti.tensor_eig_from_lo_tri(data) else: data = np.asarray(data) data_flat = data.reshape((-1, data.shape[-1])) dti_params = np.empty((len(data_flat), 4, 3)) for ii in range(len(data_flat)): tensor = from_upper_triangular(data_flat[ii]) evals, evecs = dti.decompose_tensor(tensor) dti_params[ii, 0] = evals dti_params[ii, 1:] = evecs dti_params.shape = data.shape[:-1] + (12, ) evals = dti_params[..., :3] evecs = dti_params[..., 3:] evecs = evecs.reshape(np.shape(evecs)[:3] + (3, 3)) # Estimate electrical conductivity evals = abs(self.inputs.eigenvalue_scaling_factor * evals) if self.inputs.volume_normalized_mapping: # Calculate the cube root of the product of the three eigenvalues (for # normalization) denominator = np.power( (evals[..., 0] * evals[..., 1] * evals[..., 2]), (1 / 3)) # Calculate conductivity and normalize the eigenvalues evals = self.inputs.sigma_white_matter * evals / denominator evals[denominator < 0.0001] = self.inputs.sigma_white_matter # Threshold outliers that show unusually high conductivity if self.inputs.use_outlier_correction: evals[evals > 0.4] = 0.4 conductivity_quadratic = np.array(vec_val_vect(evecs, evals)) if self.inputs.lower_triangular_output: conductivity_data = dti.lower_triangular(conductivity_quadratic) else: conductivity_data = upper_triangular(conductivity_quadratic) # Write as a 4D Nifti tensor image with the original affine img = nb.Nifti1Image(conductivity_data, affine=affine) out_file = op.abspath(self._gen_outfilename()) nb.save(img, out_file) IFLOGGER.info( 'Conductivity tensor image saved as {i}'.format(i=out_file)) return runtime
def run(self, input_files, bvalues_files, bvectors_files, mask_files, b0_threshold=50, bvecs_tol=0.01, save_metrics=[], out_dir='', out_tensor='tensors.nii.gz', out_fa='fa.nii.gz', out_ga='ga.nii.gz', out_rgb='rgb.nii.gz', out_md='md.nii.gz', out_ad='ad.nii.gz', out_rd='rd.nii.gz', out_mode='mode.nii.gz', out_evec='evecs.nii.gz', out_eval='evals.nii.gz', nifti_tensor=True): """ Workflow for tensor reconstruction and for computing DTI metrics. using Weighted Least-Squares. Performs a tensor reconstruction on the files by 'globing' ``input_files`` and saves the DTI metrics in a directory specified by ``out_dir``. Parameters ---------- input_files : string Path to the input volumes. This path may contain wildcards to process multiple inputs at once. bvalues_files : string Path to the bvalues files. This path may contain wildcards to use multiple bvalues files at once. bvectors_files : string Path to the bvectors files. This path may contain wildcards to use multiple bvectors files at once. mask_files : string Path to the input masks. This path may contain wildcards to use multiple masks at once. b0_threshold : float, optional Threshold used to find b0 volumes. bvecs_tol : float, optional Threshold used to check that norm(bvec) = 1 +/- bvecs_tol b-vectors are unit vectors. save_metrics : variable string, optional List of metrics to save. Possible values: fa, ga, rgb, md, ad, rd, mode, tensor, evec, eval out_dir : string, optional Output directory. (default current directory) out_tensor : string, optional Name of the tensors volume to be saved. Per default, this will be saved following the nifti standard: with the tensor elements as Dxx, Dxy, Dyy, Dxz, Dyz, Dzz on the last (5th) dimension of the volume (shape: (i, j, k, 1, 6)). If `nifti_tensor` is False, this will be saved in an alternate format that is used by other software (e.g., FSL): a 4-dimensional volume (shape (i, j, k, 6)) with Dxx, Dxy, Dxz, Dyy, Dyz, Dzz on the last dimension. out_fa : string, optional Name of the fractional anisotropy volume to be saved. out_ga : string, optional Name of the geodesic anisotropy volume to be saved. out_rgb : string, optional Name of the color fa volume to be saved. out_md : string, optional Name of the mean diffusivity volume to be saved. out_ad : string, optional Name of the axial diffusivity volume to be saved. out_rd : string, optional Name of the radial diffusivity volume to be saved. out_mode : string, optional Name of the mode volume to be saved. out_evec : string, optional Name of the eigenvectors volume to be saved. out_eval : string, optional Name of the eigenvalues to be saved. nifti_tensor : bool, optional Whether the tensor is saved in the standard Nifti format or in an alternate format that is used by other software (e.g., FSL): a 4-dimensional volume (shape (i, j, k, 6)) with Dxx, Dxy, Dxz, Dyy, Dyz, Dzz on the last dimension. References ---------- .. [1] Basser, P.J., Mattiello, J., LeBihan, D., 1994. Estimation of the effective self-diffusion tensor from the NMR spin echo. J Magn Reson B 103, 247-254. .. [2] Basser, P., Pierpaoli, C., 1996. Microstructural and physiological features of tissues elucidated by quantitative diffusion-tensor MRI. Journal of Magnetic Resonance 111, 209-219. .. [3] Lin-Ching C., Jones D.K., Pierpaoli, C. 2005. RESTORE: Robust estimation of tensors by outlier rejection. MRM 53: 1088-1095 .. [4] hung, SW., Lu, Y., Henry, R.G., 2006. Comparison of bootstrap approaches for estimation of uncertainties of DTI parameters. NeuroImage 33, 531-541. """ io_it = self.get_io_iterator() for dwi, bval, bvec, mask, otensor, ofa, oga, orgb, omd, oad, orad, \ omode, oevecs, oevals in io_it: logging.info('Computing DTI metrics for {0}'.format(dwi)) data, affine = load_nifti(dwi) if mask is not None: mask = load_nifti_data(mask).astype(bool) tenfit, _ = self.get_fitted_tensor(data, mask, bval, bvec, b0_threshold, bvecs_tol) if not save_metrics: save_metrics = [ 'fa', 'md', 'rd', 'ad', 'ga', 'rgb', 'mode', 'evec', 'eval', 'tensor' ] FA = fractional_anisotropy(tenfit.evals) FA[np.isnan(FA)] = 0 FA = np.clip(FA, 0, 1) if 'tensor' in save_metrics: tensor_vals = lower_triangular(tenfit.quadratic_form) if nifti_tensor: ten_img = nifti1_symmat(tensor_vals, affine=affine) else: alt_order = [0, 1, 3, 2, 4, 5] ten_img = nib.Nifti1Image( tensor_vals[..., alt_order].astype(np.float32), affine) nib.save(ten_img, otensor) if 'fa' in save_metrics: save_nifti(ofa, FA.astype(np.float32), affine) if 'ga' in save_metrics: GA = geodesic_anisotropy(tenfit.evals) save_nifti(oga, GA.astype(np.float32), affine) if 'rgb' in save_metrics: RGB = color_fa(FA, tenfit.evecs) save_nifti(orgb, np.array(255 * RGB, 'uint8'), affine) if 'md' in save_metrics: MD = mean_diffusivity(tenfit.evals) save_nifti(omd, MD.astype(np.float32), affine) if 'ad' in save_metrics: AD = axial_diffusivity(tenfit.evals) save_nifti(oad, AD.astype(np.float32), affine) if 'rd' in save_metrics: RD = radial_diffusivity(tenfit.evals) save_nifti(orad, RD.astype(np.float32), affine) if 'mode' in save_metrics: MODE = get_mode(tenfit.quadratic_form) save_nifti(omode, MODE.astype(np.float32), affine) if 'evec' in save_metrics: save_nifti(oevecs, tenfit.evecs.astype(np.float32), affine) if 'eval' in save_metrics: save_nifti(oevals, tenfit.evals.astype(np.float32), affine) dname_ = os.path.dirname(oevals) if dname_ == '': logging.info('DTI metrics saved in current directory') else: logging.info('DTI metrics saved in {0}'.format(dname_))
def apparent_kurtosis_coef(dki_params, sphere, min_diffusivity=0, min_kurtosis=-1): r""" Calculate the apparent kurtosis coefficient (AKC) in each direction of a sphere. Parameters ---------- dki_params : ndarray (x, y, z, 27) or (n, 27) All parameters estimated from the diffusion kurtosis model. Parameters are ordered as follow: 1) Three diffusion tensor's eingenvalues 2) Three lines of the eigenvector matrix each containing the first, second and third coordinates of the eigenvectors respectively 3) Fifteen elements of the kurtosis tensor sphere : a Sphere class instance The AKC will be calculated for each of the vertices in the sphere min_diffusivity : float (optional) Because negative eigenvalues are not physical and small eigenvalues cause quite a lot of noise in diffusion based metrics, diffusivity values smaller than `min_diffusivity` are replaced with `min_diffusivity`. defaut = 0 min_kurtosis : float (optional) Because high amplitude negative values of kurtosis are not physicaly and biologicaly pluasible, and these causes huge artefacts in kurtosis based measures, directional kurtosis values than `min_kurtosis` are replaced with `min_kurtosis`. defaut = -1 Returns -------- AKC : ndarray (x, y, z, g) or (n, g) Apparent kurtosis coefficient (AKC) for all g directions of a sphere. Notes ----- For each sphere direction with coordinates $(n_{1}, n_{2}, n_{3})$, the calculation of AKC is done using formula: .. math :: AKC(n)=\frac{MD^{2}}{ADC(n)^{2}}\sum_{i=1}^{3}\sum_{j=1}^{3} \sum_{k=1}^{3}\sum_{l=1}^{3}n_{i}n_{j}n_{k}n_{l}W_{ijkl} where $W_{ijkl}$ are the elements of the kurtosis tensor, MD the mean diffusivity and ADC the apparent diffusion coefficent computed as: .. math :: ADC(n)=\sum_{i=1}^{3}\sum_{j=1}^{3}n_{i}n_{j}D_{ij} where $D_{ij}$ are the elements of the diffusion tensor. """ # Flat parameters outshape = dki_params.shape[:-1] dki_params = dki_params.reshape((-1, dki_params.shape[-1])) # Split data evals, evecs, kt = split_dki_param(dki_params) # Compute MD MD = mean_diffusivity(evals) # Initialize AKC matrix V = sphere.vertices AKC = np.zeros((len(kt), len(V))) # loop over all voxels for vox in range(len(kt)): R = evecs[vox] dt = lower_triangular(np.dot(np.dot(R, np.diag(evals[vox])), R.T)) AKC[vox] = _directional_kurtosis(dt, MD[vox], kt[vox], V, min_diffusivity=min_diffusivity, min_kurtosis=min_kurtosis) # reshape data according to input data AKC = AKC.reshape((outshape + (len(V),))) return AKC
def main(): parser = _build_args_parser() args = parser.parse_args() if not args.not_all: args.fa = args.fa or 'fa.nii.gz' args.ga = args.ga or 'ga.nii.gz' args.rgb = args.rgb or 'rgb.nii.gz' args.md = args.md or 'md.nii.gz' args.ad = args.ad or 'ad.nii.gz' args.rd = args.rd or 'rd.nii.gz' args.mode = args.mode or 'mode.nii.gz' args.norm = args.norm or 'tensor_norm.nii.gz' args.tensor = args.tensor or 'tensor.nii.gz' args.evecs = args.evecs or 'tensor_evecs.nii.gz' args.evals = args.evals or 'tensor_evals.nii.gz' args.residual = args.residual or 'dti_residual.nii.gz' args.p_i_signal =\ args.p_i_signal or 'physically_implausible_signals_mask.nii.gz' args.pulsation = args.pulsation or 'pulsation_and_misalignment.nii.gz' outputs = [args.fa, args.ga, args.rgb, args.md, args.ad, args.rd, args.mode, args.norm, args.tensor, args.evecs, args.evals, args.residual, args.p_i_signal, args.pulsation] if args.not_all and not any(outputs): parser.error('When using --not_all, you need to specify at least ' + 'one metric to output.') assert_inputs_exist( parser, [args.input, args.bvals, args.bvecs], args.mask) assert_outputs_exist(parser, args, outputs) img = nib.load(args.input) data = img.get_data() affine = img.get_affine() if args.mask is None: mask = None else: mask = nib.load(args.mask).get_data().astype(np.bool) # Validate bvals and bvecs logging.info('Tensor estimation with the %s method...', args.method) bvals, bvecs = read_bvals_bvecs(args.bvals, args.bvecs) if not is_normalized_bvecs(bvecs): logging.warning('Your b-vectors do not seem normalized...') bvecs = normalize_bvecs(bvecs) check_b0_threshold(args, bvals.min()) gtab = gradient_table(bvals, bvecs, b0_threshold=bvals.min()) # Get tensors if args.method == 'restore': sigma = ne.estimate_sigma(data) tenmodel = TensorModel(gtab, fit_method=args.method, sigma=sigma, min_signal=_get_min_nonzero_signal(data)) else: tenmodel = TensorModel(gtab, fit_method=args.method, min_signal=_get_min_nonzero_signal(data)) tenfit = tenmodel.fit(data, mask) FA = fractional_anisotropy(tenfit.evals) FA[np.isnan(FA)] = 0 FA = np.clip(FA, 0, 1) if args.tensor: # Get the Tensor values and format them for visualisation # in the Fibernavigator. tensor_vals = lower_triangular(tenfit.quadratic_form) correct_order = [0, 1, 3, 2, 4, 5] tensor_vals_reordered = tensor_vals[..., correct_order] fiber_tensors = nib.Nifti1Image( tensor_vals_reordered.astype(np.float32), affine) nib.save(fiber_tensors, args.tensor) if args.fa: fa_img = nib.Nifti1Image(FA.astype(np.float32), affine) nib.save(fa_img, args.fa) if args.ga: GA = geodesic_anisotropy(tenfit.evals) GA[np.isnan(GA)] = 0 ga_img = nib.Nifti1Image(GA.astype(np.float32), affine) nib.save(ga_img, args.ga) if args.rgb: RGB = color_fa(FA, tenfit.evecs) rgb_img = nib.Nifti1Image(np.array(255 * RGB, 'uint8'), affine) nib.save(rgb_img, args.rgb) if args.md: MD = mean_diffusivity(tenfit.evals) md_img = nib.Nifti1Image(MD.astype(np.float32), affine) nib.save(md_img, args.md) if args.ad: AD = axial_diffusivity(tenfit.evals) ad_img = nib.Nifti1Image(AD.astype(np.float32), affine) nib.save(ad_img, args.ad) if args.rd: RD = radial_diffusivity(tenfit.evals) rd_img = nib.Nifti1Image(RD.astype(np.float32), affine) nib.save(rd_img, args.rd) if args.mode: # Compute tensor mode inter_mode = dipy_mode(tenfit.quadratic_form) # Since the mode computation can generate NANs when not masked, # we need to remove them. non_nan_indices = np.isfinite(inter_mode) mode = np.zeros(inter_mode.shape) mode[non_nan_indices] = inter_mode[non_nan_indices] mode_img = nib.Nifti1Image(mode.astype(np.float32), affine) nib.save(mode_img, args.mode) if args.norm: NORM = norm(tenfit.quadratic_form) norm_img = nib.Nifti1Image(NORM.astype(np.float32), affine) nib.save(norm_img, args.norm) if args.evecs: evecs = tenfit.evecs.astype(np.float32) evecs_img = nib.Nifti1Image(evecs, affine) nib.save(evecs_img, args.evecs) # save individual e-vectors also e1_img = nib.Nifti1Image(evecs[..., 0], affine) e2_img = nib.Nifti1Image(evecs[..., 1], affine) e3_img = nib.Nifti1Image(evecs[..., 2], affine) nib.save(e1_img, add_filename_suffix(args.evecs, '_v1')) nib.save(e2_img, add_filename_suffix(args.evecs, '_v2')) nib.save(e3_img, add_filename_suffix(args.evecs, '_v3')) if args.evals: evals = tenfit.evals.astype(np.float32) evals_img = nib.Nifti1Image(evals, affine) nib.save(evals_img, args.evals) # save individual e-values also e1_img = nib.Nifti1Image(evals[..., 0], affine) e2_img = nib.Nifti1Image(evals[..., 1], affine) e3_img = nib.Nifti1Image(evals[..., 2], affine) nib.save(e1_img, add_filename_suffix(args.evals, '_e1')) nib.save(e2_img, add_filename_suffix(args.evals, '_e2')) nib.save(e3_img, add_filename_suffix(args.evals, '_e3')) if args.p_i_signal: S0 = np.mean(data[..., gtab.b0s_mask], axis=-1, keepdims=True) DWI = data[..., ~gtab.b0s_mask] pis_mask = np.max(S0 < DWI, axis=-1) if args.mask is not None: pis_mask *= mask pis_img = nib.Nifti1Image(pis_mask.astype(np.int16), affine) nib.save(pis_img, args.p_i_signal) if args.pulsation: STD = np.std(data[..., ~gtab.b0s_mask], axis=-1) if args.mask is not None: STD *= mask std_img = nib.Nifti1Image(STD.astype(np.float32), affine) nib.save(std_img, add_filename_suffix(args.pulsation, '_std_dwi')) if np.sum(gtab.b0s_mask) <= 1: logger.info('Not enough b=0 images to output standard ' 'deviation map') else: if len(np.where(gtab.b0s_mask)) == 2: logger.info('Only two b=0 images. Be careful with the ' 'interpretation of this std map') STD = np.std(data[..., gtab.b0s_mask], axis=-1) if args.mask is not None: STD *= mask std_img = nib.Nifti1Image(STD.astype(np.float32), affine) nib.save(std_img, add_filename_suffix(args.pulsation, '_std_b0')) if args.residual: # Mean residual image S0 = np.mean(data[..., gtab.b0s_mask], axis=-1) data_p = tenfit.predict(gtab, S0) R = np.mean(np.abs(data_p[..., ~gtab.b0s_mask] - data[..., ~gtab.b0s_mask]), axis=-1) if args.mask is not None: R *= mask R_img = nib.Nifti1Image(R.astype(np.float32), affine) nib.save(R_img, args.residual) # Each volume's residual statistics if args.mask is None: logger.info("Outlier detection will not be performed, since no " "mask was provided.") stats = [dict.fromkeys(['label', 'mean', 'iqr', 'cilo', 'cihi', 'whishi', 'whislo', 'fliers', 'q1', 'med', 'q3'], []) for i in range(data.shape[-1])] # stats with format for boxplots # Note that stats will be computed manually and plotted using bxp # but could be computed using stats = cbook.boxplot_stats # or pyplot.boxplot(x) R_k = np.zeros(data.shape[-1]) # mean residual per DWI std = np.zeros(data.shape[-1]) # std residual per DWI q1 = np.zeros(data.shape[-1]) # first quartile per DWI q3 = np.zeros(data.shape[-1]) # third quartile per DWI iqr = np.zeros(data.shape[-1]) # interquartile per DWI percent_outliers = np.zeros(data.shape[-1]) nb_voxels = np.count_nonzero(mask) for k in range(data.shape[-1]): x = np.abs(data_p[..., k] - data[..., k])[mask] R_k[k] = np.mean(x) std[k] = np.std(x) q3[k], q1[k] = np.percentile(x, [75, 25]) iqr[k] = q3[k] - q1[k] stats[k]['med'] = (q1[k] + q3[k]) / 2 stats[k]['mean'] = R_k[k] stats[k]['q1'] = q1[k] stats[k]['q3'] = q3[k] stats[k]['whislo'] = q1[k] - 1.5 * iqr[k] stats[k]['whishi'] = q3[k] + 1.5 * iqr[k] stats[k]['label'] = k # Outliers are observations that fall below Q1 - 1.5(IQR) or # above Q3 + 1.5(IQR) We check if a voxel is an outlier only if # we have a mask, else we are biased. if args.mask is not None: outliers = (x < stats[k]['whislo']) | (x > stats[k]['whishi']) percent_outliers[k] = np.sum(outliers)/nb_voxels*100 # What would be our definition of too many outliers? # Maybe mean(all_means)+-3SD? # Or we let people choose based on the figure. # if percent_outliers[k] > ???? : # logger.warning(' Careful! Diffusion-Weighted Image' # ' i=%s has %s %% outlier voxels', # k, percent_outliers[k]) # Saving all statistics as npy values residual_basename, _ = split_name_with_nii(args.residual) res_stats_basename = residual_basename + ".npy" np.save(add_filename_suffix( res_stats_basename, "_mean_residuals"), R_k) np.save(add_filename_suffix(res_stats_basename, "_q1_residuals"), q1) np.save(add_filename_suffix(res_stats_basename, "_q3_residuals"), q3) np.save(add_filename_suffix(res_stats_basename, "_iqr_residuals"), iqr) np.save(add_filename_suffix(res_stats_basename, "_std_residuals"), std) # Showing results in graph if args.mask is None: fig, axe = plt.subplots(nrows=1, ncols=1, squeeze=False) else: fig, axe = plt.subplots(nrows=1, ncols=2, squeeze=False, figsize=[10, 4.8]) # Default is [6.4, 4.8]. Increasing width to see better. medianprops = dict(linestyle='-', linewidth=2.5, color='firebrick') meanprops = dict(linestyle='-', linewidth=2.5, color='green') axe[0, 0].bxp(stats, showmeans=True, meanline=True, showfliers=False, medianprops=medianprops, meanprops=meanprops) axe[0, 0].set_xlabel('DW image') axe[0, 0].set_ylabel('Residuals per DWI volume. Red is median,\n' 'green is mean. Whiskers are 1.5*interquartile') axe[0, 0].set_title('Residuals') axe[0, 0].set_xticks(range(0, q1.shape[0], 5)) axe[0, 0].set_xticklabels(range(0, q1.shape[0], 5)) if args.mask is not None: axe[0, 1].plot(range(data.shape[-1]), percent_outliers) axe[0, 1].set_xticks(range(0, q1.shape[0], 5)) axe[0, 1].set_xticklabels(range(0, q1.shape[0], 5)) axe[0, 1].set_xlabel('DW image') axe[0, 1].set_ylabel('Percentage of outlier voxels') axe[0, 1].set_title('Outliers') plt.savefig(residual_basename + '_residuals_stats.png')
def fwdti_prediction(params, gtab, S0=1, Diso=3.0e-3): r""" Signal prediction given the free water DTI model parameters. Parameters ---------- params : (..., 13) ndarray Model parameters. The last dimension should have the 12 tensor parameters (3 eigenvalues, followed by the 3 corresponding eigenvectors) and the volume fraction of the free water compartment. gtab : a GradientTable class instance The gradient table for this prediction S0 : float or ndarray The non diffusion-weighted signal in every voxel, or across all voxels. Default: 1 Diso : float, optional Value of the free water isotropic diffusion. Default is set to 3e-3 $mm^{2}.s^{-1}$. Please adjust this value if you are assuming different units of diffusion. Returns -------- S : (..., N) ndarray Simulated signal based on the free water DTI model Notes ----- The predicted signal is given by: $S(\theta, b) = S_0 * [(1-f) * e^{-b ADC} + f * e^{-b D_{iso}]$, where $ADC = \theta Q \theta^T$, $\theta$ is a unit vector pointing at any direction on the sphere for which a signal is to be predicted, $b$ is the b value provided in the GradientTable input for that direction, $Q$ is the quadratic form of the tensor determined by the input parameters, $f$ is the free water diffusion compartment, $D_{iso}$ is the free water diffusivity which is equal to $3 * 10^{-3} mm^{2}s^{-1} [1]_. References ---------- .. [1] Hoy, A.R., Koay, C.G., Kecskemeti, S.R., Alexander, A.L., 2014. Optimization of a free water elimination two-compartmental model for diffusion tensor imaging. NeuroImage 103, 323-333. doi: 10.1016/j.neuroimage.2014.09.053 """ evals = params[..., :3] evecs = params[..., 3:-1].reshape(params.shape[:-1] + (3, 3)) f = params[..., 12] qform = vec_val_vect(evecs, evals) lower_dt = lower_triangular(qform, S0) lower_diso = lower_dt.copy() lower_diso[..., 0] = lower_diso[..., 2] = lower_diso[..., 5] = Diso lower_diso[..., 1] = lower_diso[..., 3] = lower_diso[..., 4] = 0 D = design_matrix(gtab) pred_sig = np.zeros(f.shape + (gtab.bvals.shape[0],)) mask = _positive_evals(evals[..., 0], evals[..., 1], evals[..., 2]) index = ndindex(f.shape) for v in index: if mask[v]: pred_sig[v] = (1 - f[v]) * np.exp(np.dot(lower_dt[v], D.T)) + \ f[v] * np.exp(np.dot(lower_diso[v], D.T)) return pred_sig
def nls_iter_bounds(design_matrix, sig, S0, Diso=3e-3, mdreg=2.7e-3, min_signal=1.0e-6, bounds=None, jac=True): """ Applies non-linear least-squares fit with constraints of the water free elimination model to single voxel signals. Parameters ---------- design_matrix : array (g, 7) Design matrix holding the covariants used to solve for the regression coefficients. sig : array (g, ) Diffusion-weighted signal for a single voxel data. S0 : float Non diffusion weighted signal (i.e. signal for b-value=0). Diso : float, optional Value of the free water isotropic diffusion. Default is set to 3e-3 $mm^{2}.s^{-1}$. Please ajust this value if you are assuming different units of diffusion. mdreg : float, optimal DTI's mean diffusivity regularization threshold. If standard DTI diffusion tensor's mean diffusivity is almost near the free water diffusion value, the diffusion signal is assumed to be only free water diffusion (i.e. volume fraction will be set to 1 and tissue's diffusion parameters are set to zero). Default md_reg is 2.7e-3 $mm^{2}.s^{-1}$ (corresponding to 90% of the free water diffusion value). min_signal : float The minimum signal value. Needs to be a strictly positive number. bounds : 2-tuple of arrays with 14 elements, optional Lower and upper bounds on fwdti model variables and the log of non-diffusion signal S0. Use np.inf with an appropriate sign to disable bounds on all or some variables. When bounds is set to None the following default variable bounds is used: ([0., -Diso, 0., -Diso, -Diso, 0., 0., np.exp(-10.)], [Diso, Diso, Diso, Diso, Diso, Diso, 1., np.exp(10.)]) jac : bool Use the Jacobian? Default: False Returns ------- All parameters estimated from the free water tensor model. Parameters are ordered as follows: 1) Three diffusion tensor's eigenvalues 2) Three lines of the eigenvector matrix each containing the first, second and third coordinates of the eigenvector 3) The volume fraction of the free water compartment. """ # Initial guess params = wls_iter(design_matrix, sig, S0, min_signal=min_signal, Diso=Diso, mdreg=mdreg) # Set bounds if bounds is None: bounds = ([0., -Diso, 0., -Diso, -Diso, 0., -10., 0], [Diso, Diso, Diso, Diso, Diso, Diso, 10., 1]) else: # In the helper subfunctions it was easier to have log(S0) first than # the water volume. Therefore, we have to reorder the boundaries if # specified by the user S0low = np.log(bounds[0][7]) S0hig = np.log(bounds[1][7]) bounds[0][7] = bounds[0][6] bounds[1][7] = bounds[1][6] bounds[0][6] = S0low bounds[1][6] = S0hig # Process voxel if it has significant signal from tissue if params[12] < 0.99 and np.mean(sig) > min_signal: # converting evals and evecs to diffusion tensor elements evals = params[:3] evecs = params[3:12].reshape((3, 3)) dt = lower_triangular(vec_val_vect(evecs, evals)) f = params[12] # Use the Levenberg-Marquardt algorithm wrapped in opt.leastsq start_params = np.concatenate((dt, [-np.log(S0), f]), axis=0) lb = np.array(bounds[0]) ub = np.array(bounds[1]) start_params[start_params < lb] = lb[start_params < lb] start_params[start_params > ub] = ub[start_params > ub] if jac: out = opt.least_squares(_nls_err_func, start_params[:8], args=(design_matrix, sig, Diso, False, False), jac=_nls_jacobian_func, bounds=bounds) else: out = opt.least_squares(_nls_err_func, start_params[:8], args=(design_matrix, sig, Diso, False, False), bounds=bounds) this_tensor = out.x # The parameters are the evals and the evecs: evals, evecs = decompose_tensor(from_lower_triangular(this_tensor[:6])) params = np.concatenate((evals, evecs[0], evecs[1], evecs[2], np.array([this_tensor[7]])), axis=0) return params
def nls_iter(design_matrix, sig, S0, Diso=3e-3, mdreg=2.7e-3, min_signal=1.0e-6, cholesky=False, f_transform=True, jac=False, weighting=None, sigma=None): """ Applies non linear least squares fit of the water free elimination model to single voxel signals. Parameters ---------- design_matrix : array (g, 7) Design matrix holding the covariants used to solve for the regression coefficients. sig : array (g, ) Diffusion-weighted signal for a single voxel data. S0 : float Non diffusion weighted signal (i.e. signal for b-value=0). Diso : float, optional Value of the free water isotropic diffusion. Default is set to 3e-3 $mm^{2}.s^{-1}$. Please ajust this value if you are assuming different units of diffusion. mdreg : float, optimal DTI's mean diffusivity regularization threshold. If standard DTI diffusion tensor's mean diffusivity is almost near the free water diffusion value, the diffusion signal is assumed to be only free water diffusion (i.e. volume fraction will be set to 1 and tissue's diffusion parameters are set to zero). Default md_reg is 2.7e-3 $mm^{2}.s^{-1}$ (corresponding to 90% of the free water diffusion value). min_signal : float The minimum signal value. Needs to be a strictly positive number. cholesky : bool, optional If true it uses cholesky decomposition to insure that diffusion tensor is positive define. Default: False f_transform : bool, optional If true, the water volume fractions is converted during the convergence procedure to ft = arcsin(2*f - 1) + pi/2, insuring f estimates between 0 and 1. Default: True jac : bool Use the Jacobian? Default: False weighting: str, optional the weighting scheme to use in considering the squared-error. Default behavior is to use uniform weighting. Other options: 'sigma' 'gmm' sigma: float, optional If the 'sigma' weighting scheme is used, a value of sigma needs to be provided here. According to [Chang2005]_, a good value to use is 1.5267 * std(background_noise), where background_noise is estimated from some part of the image known to contain no signal (only noise). Returns ------- All parameters estimated from the free water tensor model. Parameters are ordered as follows: 1) Three diffusion tensor's eigenvalues 2) Three lines of the eigenvector matrix each containing the first, second and third coordinates of the eigenvector 3) The volume fraction of the free water compartment. """ # Initial guess params = wls_iter(design_matrix, sig, S0, min_signal=min_signal, Diso=Diso, mdreg=mdreg) # Process voxel if it has significant signal from tissue if params[12] < 0.99 and np.mean(sig) > min_signal and S0 > min_signal: # converting evals and evecs to diffusion tensor elements evals = params[:3] evecs = params[3:12].reshape((3, 3)) dt = lower_triangular(vec_val_vect(evecs, evals)) # Cholesky decomposition if requested if cholesky: dt = lower_triangular_to_cholesky(dt) # f transformation if requested if f_transform: f = np.arcsin(2*params[12] - 1) + np.pi/2 else: f = params[12] # Use the Levenberg-Marquardt algorithm wrapped in opt.leastsq start_params = np.concatenate((dt, [-np.log(S0), f]), axis=0) if jac: this_tensor, status = opt.leastsq(_nls_err_func, start_params[:8], args=(design_matrix, sig, Diso, weighting, sigma, cholesky, f_transform), Dfun=_nls_jacobian_func) else: this_tensor, status = opt.leastsq(_nls_err_func, start_params[:8], args=(design_matrix, sig, Diso, weighting, sigma, cholesky, f_transform)) # Process tissue diffusion tensor if cholesky: this_tensor[:6] = cholesky_to_lower_triangular(this_tensor[:6]) evals, evecs = _decompose_tensor_nan( from_lower_triangular(this_tensor[:6]), from_lower_triangular(start_params[:6])) # Process water volume fraction f f = this_tensor[7] if f_transform: f = 0.5 * (1 + np.sin(f - np.pi/2)) params = np.concatenate((evals, evecs[0], evecs[1], evecs[2], np.array([f])), axis=0) return params
def diffusion_components(dki_params, sphere='repulsion100', awf=None, mask=None): """ Extracts the restricted and hindered diffusion tensors of well aligned fibers from diffusion kurtosis imaging parameters [1]_. Parameters ---------- dki_params : ndarray (x, y, z, 27) or (n, 27) All parameters estimated from the diffusion kurtosis model. Parameters are ordered as follows: 1) Three diffusion tensor's eigenvalues 2) Three lines of the eigenvector matrix each containing the first, second and third coordinates of the eigenvector 3) Fifteen elements of the kurtosis tensor sphere : Sphere class instance, optional The sphere providing sample directions to sample the restricted and hindered cellular diffusion tensors. For more details see Fieremans et al., 2011. awf : ndarray (optional) Array containing values of the axonal water fraction that has the shape dki_params.shape[:-1]. If not given this will be automatically computed using :func:`axonal_water_fraction`" with function's default precision. mask : ndarray (optional) A boolean array used to mark the coordinates in the data that should be analyzed that has the shape dki_params.shape[:-1] Returns -------- edt : ndarray (x, y, z, 6) or (n, 6) Parameters of the hindered diffusion tensor. idt : ndarray (x, y, z, 6) or (n, 6) Parameters of the restricted diffusion tensor. Note ---- In the original article of DKI microstructural model [1]_, the hindered and restricted tensors were definde as the intra-cellular and extra-cellular diffusion compartments respectively. References ---------- .. [1] Fieremans E, Jensen JH, Helpern JA, 2011. White matter characterization with diffusional kurtosis imaging. Neuroimage 58(1):177-88. doi: 10.1016/j.neuroimage.2011.06.006 """ shape = dki_params.shape[:-1] # load gradient directions if not isinstance(sphere, dps.Sphere): sphere = get_sphere(sphere) # select voxels where to apply the single fiber model if mask is None: mask = np.ones(shape, dtype='bool') else: if mask.shape != shape: raise ValueError("Mask is not the same shape as dki_params.") else: mask = np.array(mask, dtype=bool, copy=False) # check or compute awf values if awf is None: awf = axonal_water_fraction(dki_params, sphere=sphere, mask=mask) else: if awf.shape != shape: raise ValueError("awf array is not the same shape as dki_params.") # Initialize hindered and restricted diffusion tensors edt_all = np.zeros(shape + (6,)) idt_all = np.zeros(shape + (6,)) # Generate matrix that converts apparant diffusion coefficients to tensors B = np.zeros((sphere.x.size, 6)) B[:, 0] = sphere.x * sphere.x # Bxx B[:, 1] = sphere.x * sphere.y * 2. # Bxy B[:, 2] = sphere.y * sphere.y # Byy B[:, 3] = sphere.x * sphere.z * 2. # Bxz B[:, 4] = sphere.y * sphere.z * 2. # Byz B[:, 5] = sphere.z * sphere.z # Bzz pinvB = np.linalg.pinv(B) # Compute hindered and restricted diffusion tensors for all voxels evals, evecs, kt = split_dki_param(dki_params) dt = lower_triangular(vec_val_vect(evecs, evals)) md = mean_diffusivity(evals) index = ndindex(mask.shape) for idx in index: if not mask[idx]: continue # sample apparent diffusion and kurtosis values di = directional_diffusion(dt[idx], sphere.vertices) ki = directional_kurtosis(dt[idx], md[idx], kt[idx], sphere.vertices, adc=di, min_kurtosis=0) edi = di * (1 + np.sqrt(ki * awf[idx] / (3.0 - 3.0 * awf[idx]))) edt = np.dot(pinvB, edi) edt_all[idx] = edt # We only move on if there is an axonal water fraction. # Otherwise, remaining params are already zero, so move on if awf[idx] == 0: continue # Convert apparent diffusion and kurtosis values to apparent diffusion # values of the hindered and restricted diffusion idi = di * (1 - np.sqrt(ki * (1.0 - awf[idx]) / (3.0 * awf[idx]))) # generate hindered and restricted diffusion tensors idt = np.dot(pinvB, idi) idt_all[idx] = idt return edt_all, idt_all
def nls_iter(design_matrix, sig, S0, Diso=3e-3, mdreg=2.7e-3, min_signal=1.0e-6, cholesky=False, f_transform=True, jac=True): """ Applies non linear least squares fit of the water free elimination model to single voxel signals. Parameters ---------- design_matrix : array (g, 7) Design matrix holding the covariants used to solve for the regression coefficients. sig : array (g, ) Diffusion-weighted signal for a single voxel data. S0 : float Non diffusion weighted signal (i.e. signal for b-value=0). Diso : float, optional Value of the free water isotropic diffusion. Default is set to 3e-3 $mm^{2}.s^{-1}$. Please ajust this value if you are assuming different units of diffusion. min_signal : float The minimum signal value. Needs to be a strictly positive number. cholesky : bool, optional If true it uses cholesky decomposition to insure that diffusion tensor is positive define. Default: False f_transform : bool, optional If true, the water volume fractions is converted during the convergence procedure to ft = arcsin(2*f - 1) + pi/2, insuring f estimates between 0 and 1. Default: True jac : bool Use the Jacobian? Default: False Returns ------- All parameters estimated from the free water tensor model. Parameters are ordered as follows: 1) Three diffusion tensor's eigenvalues 2) Three lines of the eigenvector matrix each containing the first, second and third coordinates of the eigenvector 3) The volume fraction of the free water compartment. """ # Initial guess params = wls_iter(design_matrix, sig, S0, min_signal=min_signal, Diso=Diso) # Process voxel if it has significant signal from tissue if np.mean(sig) > min_signal and S0 > min_signal: # converting evals and evecs to diffusion tensor elements evals = params[:3] evecs = params[3:12].reshape((3, 3)) dt = lower_triangular(vec_val_vect(evecs, evals)) # Cholesky decomposition if requested if cholesky: dt = lower_triangular_to_cholesky(dt) # f transformation if requested if f_transform: f = np.arcsin(2*params[12] - 1) + np.pi/2 else: f = params[12] # Use the Levenberg-Marquardt algorithm wrapped in opt.leastsq start_params = np.concatenate((dt, [-np.log(S0), f]), axis=0) if jac: this_tensor, status = opt.leastsq(_nls_err_func, start_params[:8], args=(design_matrix, sig, Diso, cholesky, f_transform), Dfun=_nls_jacobian_func) else: this_tensor, status = opt.leastsq(_nls_err_func, start_params[:8], args=(design_matrix, sig, Diso, cholesky, f_transform)) # Invert the cholesky decomposition if this was requested if cholesky: this_tensor[:6] = cholesky_to_lower_triangular(this_tensor[:6]) # Invert f transformation if this was requested if f_transform: this_tensor[7] = 0.5 * (1 + np.sin(this_tensor[7] - np.pi/2)) # The parameters are the evals and the evecs: evals, evecs = decompose_tensor(from_lower_triangular(this_tensor[:6])) params = np.concatenate((evals, evecs[0], evecs[1], evecs[2], np.array([this_tensor[7]])), axis=0) return params