def test_vec_val_vect(): for shape0 in ((10, ), (100, ), (10, 12), (12, 10, 5)): for shape1 in ((3, 3), (4, 3), (3, 4)): shape = shape0 + shape1 evecs, evals = make_vecs_vals(shape) res1 = np.einsum('...ij,...j,...kj->...ik', evecs, evals, evecs) assert_almost_equal(res1, vec_val_vect(evecs, evals))
def test_vec_val_vect_dumber(): for shape0 in ((10,), (100,)): for shape1 in ((3, 3), (4, 3), (3, 4)): shape = shape0 + shape1 evecs, evals = make_vecs_vals(shape) res1 = dumb_sum(evecs, evals) assert_almost_equal(res1, vec_val_vect(evecs, evals))
def test_vec_val_vect(): for shape0 in ((10,), (100,), (10, 12), (12, 10, 5)): for shape1 in ((3, 3), (4, 3), (3, 4)): shape = shape0 + shape1 evecs, evals = make_vecs_vals(shape) res1 = np.einsum('...ij,...j,...kj->...ik', evecs, evals, evecs) assert_almost_equal(res1, vec_val_vect(evecs, evals))
def test_vec_val_vect_dumber(): for shape0 in ((10, ), (100, )): for shape1 in ((3, 3), (4, 3), (3, 4)): shape = shape0 + shape1 evecs, evals = make_vecs_vals(shape) res1 = dumb_sum(evecs, evals) assert_almost_equal(res1, vec_val_vect(evecs, evals))
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 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(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 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 log_transform_to_qform(evals, evecs): """Takes evals and evecs and returns a q-form that can be sent into free water calc""" evals_copy = evals.copy() evals_copy[evals_copy <= 0] = MIN_POSITIVE_EIGENVALUE qform = vec_val_vect(evecs, evals) return (qform)
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 _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 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_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
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 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 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