def test_Wrotate_crossing_fibers(): # Test 2 - simulate crossing fibers intersecting at 70 degrees. # In this case, diffusion tensor principal eigenvector will be aligned in # the middle of the crossing fibers. Thus, after rotating the kurtosis # tensor, this will be equal to a kurtosis tensor simulate of crossing # fibers both deviating 35 degrees from the x-axis. Moreover, we know that # crossing fibers will be aligned to the x-y plane, because the smaller # diffusion eigenvalue, perpendicular to both crossings fibers, will be # aligned to the z-axis. # Simulate the crossing fiber angles = [(90, 30), (90, 30), (20, 30), (20, 30)] fie = 0.49 frac = [fie*50, (1-fie) * 50, fie*50, (1-fie) * 50] mevals = np.array([[0.00099, 0, 0], [0.00226, 0.00087, 0.00087], [0.00099, 0, 0], [0.00226, 0.00087, 0.00087]]) signal, dt, kt = multi_tensor_dki(gtab_2s, mevals, angles=angles, fractions=frac, snr=None) evals, evecs = decompose_tensor(from_lower_triangular(dt)) kt_rotated = dki.Wrotate(kt, evecs) # Now coordinate system has diffusion tensor diagonal aligned to the x-axis # Simulate the reference kurtosis tensor angles = [(90, 35), (90, 35), (90, -35), (90, -35)] signal, dt, kt_ref = multi_tensor_dki(gtab_2s, mevals, angles=angles, fractions=frac, snr=None) # Compare rotated with the reference assert_array_almost_equal(kt_rotated, kt_ref)
def test_decompose_tensor_nan(): D_fine = np.array([1.7e-3, 0.0, 0.3e-3, 0.0, 0.0, 0.2e-3]) D_alter = np.array([1.6e-3, 0.0, 0.4e-3, 0.0, 0.0, 0.3e-3]) D_nan = np.nan * np.ones(6) lref, vref = decompose_tensor(from_lower_triangular(D_fine)) lfine, vfine = _decompose_tensor_nan(from_lower_triangular(D_fine), from_lower_triangular(D_alter)) assert_array_almost_equal(lfine, np.array([1.7e-3, 0.3e-3, 0.2e-3])) assert_array_almost_equal(vfine, vref) lref, vref = decompose_tensor(from_lower_triangular(D_alter)) lalter, valter = _decompose_tensor_nan(from_lower_triangular(D_nan), from_lower_triangular(D_alter)) assert_array_almost_equal(lalter, np.array([1.6e-3, 0.4e-3, 0.3e-3])) assert_array_almost_equal(valter, vref)
def test_Wrotate_single_fiber(): # Rotate the kurtosis tensor of single fiber simulate to the diffusion # tensor diagonal and check that is equal to the kurtosis tensor of the # same single fiber simulated directly to the x-axis # Define single fiber simulate mevals = np.array([[0.00099, 0, 0], [0.00226, 0.00087, 0.00087]]) fie = 0.49 frac = [fie*100, (1 - fie)*100] # simulate single fiber not aligned to the x-axis theta = random.uniform(0, 180) phi = random.uniform(0, 320) angles = [(theta, phi), (theta, phi)] signal, dt, kt = multi_tensor_dki(gtab_2s, mevals, angles=angles, fractions=frac, snr=None) evals, evecs = decompose_tensor(from_lower_triangular(dt)) kt_rotated = dki.Wrotate(kt, evecs) # Now coordinate system has the DT diagonal aligned to the x-axis # Reference simulation in which DT diagonal is directly aligned to the # x-axis angles = (90, 0), (90, 0) signal, dt_ref, kt_ref = multi_tensor_dki(gtab_2s, mevals, angles=angles, fractions=frac, snr=None) assert_array_almost_equal(kt_rotated, kt_ref)
def _wls_iter(design_matrix, inv_design, sig, min_diffusivity): """ Helper function used by wls_fit_dki - Applies WLS fit of the diffusion kurtosis model to single voxel signals. Parameters ---------- design_matrix : array (g, 22) Design matrix holding the covariants used to solve for the regression coefficients inv_design : array (g, 22) Inverse of the design matrix. sig : array (g, ) Diffusion-weighted signal for a single voxel data. min_diffusivity : float Because negative eigenvalues are not physical and small eigenvalues, much smaller than the diffusion weighting, cause quite a lot of noise in metrics such as fa, diffusivity values smaller than `min_diffusivity` are replaced with `min_diffusivity`. Returns ------- dki_params : array (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 """ A = design_matrix # DKI ordinary linear least square solution log_s = np.log(sig) ols_result = np.dot(inv_design, log_s) # Define weights as diag(yn**2) W = np.diag(np.exp(2 * np.dot(A, ols_result))) # DKI weighted linear least square solution inv_AT_W_A = np.linalg.pinv(np.dot(np.dot(A.T, W), A)) AT_W_LS = np.dot(np.dot(A.T, W), log_s) wls_result = np.dot(inv_AT_W_A, AT_W_LS) # Extracting the diffusion tensor parameters from solution DT_elements = wls_result[:6] evals, evecs = decompose_tensor(from_lower_triangular(DT_elements), min_diffusivity=min_diffusivity) # Extracting kurtosis tensor parameters from solution MD_square = (evals.mean(0))**2 KT_elements = wls_result[6:21] / MD_square # Write output dki_params = np.concatenate((evals, evecs[0], evecs[1], evecs[2], KT_elements), axis=0) return dki_params
def _wls_iter(design_matrix, inv_design, sig, min_diffusivity): """ Helper function used by wls_fit_dki - Applies WLS fit of the diffusion kurtosis model to single voxel signals. Parameters ---------- design_matrix : array (g, 22) Design matrix holding the covariants used to solve for the regression coefficients inv_design : array (g, 22) Inverse of the design matrix. sig : array (g, ) Diffusion-weighted signal for a single voxel data. min_diffusivity : float Because negative eigenvalues are not physical and small eigenvalues, much smaller than the diffusion weighting, cause quite a lot of noise in metrics such as fa, diffusivity values smaller than `min_diffusivity` are replaced with `min_diffusivity`. Returns ------- dki_params : array (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 """ A = design_matrix # DKI ordinary linear least square solution log_s = np.log(sig) ols_result = np.dot(inv_design, log_s) # Define weights as diag(yn**2) W = np.diag(np.exp(2 * np.dot(A, ols_result))) # DKI weighted linear least square solution inv_AT_W_A = np.linalg.pinv(np.dot(np.dot(A.T, W), A)) AT_W_LS = np.dot(np.dot(A.T, W), log_s) wls_result = np.dot(inv_AT_W_A, AT_W_LS) # Extracting the diffusion tensor parameters from solution DT_elements = wls_result[:6] evals, evecs = decompose_tensor(from_lower_triangular(DT_elements), min_diffusivity=min_diffusivity) # Extracting kurtosis tensor parameters from solution MD_square = (evals.mean(0))**2 KT_elements = wls_result[6:21] / MD_square # Write output dki_params = np.concatenate( (evals, evecs[0], evecs[1], evecs[2], KT_elements), axis=0) return dki_params
def setup_module(): """Module-level setup""" global gtab, gtab_2s, mevals, model_params_mv global DWI, FAref, GTF, MDref, FAdti, MDdti _, fbvals, fbvecs = get_fnames('small_64D') bvals, bvecs = read_bvals_bvecs(fbvals, fbvecs) gtab = gradient_table(bvals, bvecs) # FW model requires multishell data bvals_2s = np.concatenate((bvals, bvals * 1.5), axis=0) bvecs_2s = np.concatenate((bvecs, bvecs), axis=0) gtab_2s = gradient_table(bvals_2s, bvecs_2s) # Simulation a typical DT and DW signal for no water contamination S0 = np.array(100) dt = np.array([0.0017, 0, 0.0003, 0, 0, 0.0003]) evals, evecs = decompose_tensor(from_lower_triangular(dt)) S_tissue = single_tensor(gtab_2s, S0=100, evals=evals, evecs=evecs, snr=None) dm = dti.TensorModel(gtab_2s, 'WLS') dtifit = dm.fit(S_tissue) FAdti = dtifit.fa MDdti = dtifit.md dtiparams = dtifit.model_params # Simulation of 8 voxels tested DWI = np.zeros((2, 2, 2, len(gtab_2s.bvals))) FAref = np.zeros((2, 2, 2)) MDref = np.zeros((2, 2, 2)) # Diffusion of tissue and water compartments are constant for all voxel mevals = np.array([[0.0017, 0.0003, 0.0003], [0.003, 0.003, 0.003]]) # volume fractions GTF = np.array([[[0.06, 0.71], [0.33, 0.91]], [[0., 0.], [0., 0.]]]) # S0 multivoxel S0m = 100 * np.ones((2, 2, 2)) # model_params ground truth (to be fill) model_params_mv = np.zeros((2, 2, 2, 13)) for i in range(2): for j in range(2): gtf = GTF[0, i, j] S, p = multi_tensor(gtab_2s, mevals, S0=100, angles=[(90, 0), (90, 0)], fractions=[(1 - gtf) * 100, gtf * 100], snr=None) DWI[0, i, j] = S FAref[0, i, j] = FAdti MDref[0, i, j] = MDdti R = all_tensor_evecs(p[0]) R = R.reshape((9)) model_params_mv[0, i, j] = \ np.concatenate(([0.0017, 0.0003, 0.0003], R, [gtf]), axis=0)
def setup_module(): """Module-level setup""" global gtab, gtab_2s, mevals, model_params_mv global DWI, FAref, GTF, MDref, FAdti, MDdti _, fbvals, fbvecs = get_fnames('small_64D') bvals, bvecs = read_bvals_bvecs(fbvals, fbvecs) gtab = gradient_table(bvals, bvecs) # FW model requires multishell data bvals_2s = np.concatenate((bvals, bvals * 1.5), axis=0) bvecs_2s = np.concatenate((bvecs, bvecs), axis=0) gtab_2s = gradient_table(bvals_2s, bvecs_2s) # Simulation a typical DT and DW signal for no water contamination S0 = np.array(100) dt = np.array([0.0017, 0, 0.0003, 0, 0, 0.0003]) evals, evecs = decompose_tensor(from_lower_triangular(dt)) S_tissue = single_tensor(gtab_2s, S0=100, evals=evals, evecs=evecs, snr=None) dm = dti.TensorModel(gtab_2s, 'WLS') dtifit = dm.fit(S_tissue) FAdti = dtifit.fa MDdti = dtifit.md dtiparams = dtifit.model_params # Simulation of 8 voxels tested DWI = np.zeros((2, 2, 2, len(gtab_2s.bvals))) FAref = np.zeros((2, 2, 2)) MDref = np.zeros((2, 2, 2)) # Diffusion of tissue and water compartments are constant for all voxel mevals = np.array([[0.0017, 0.0003, 0.0003], [0.003, 0.003, 0.003]]) # volume fractions GTF = np.array([[[0.06, 0.71], [0.33, 0.91]], [[0., 0.], [0., 0.]]]) # S0 multivoxel S0m = 100 * np.ones((2, 2, 2)) # model_params ground truth (to be fill) model_params_mv = np.zeros((2, 2, 2, 13)) for i in range(2): for j in range(2): gtf = GTF[0, i, j] S, p = multi_tensor(gtab_2s, mevals, S0=100, angles=[(90, 0), (90, 0)], fractions=[(1-gtf) * 100, gtf*100], snr=None) DWI[0, i, j] = S FAref[0, i, j] = FAdti MDref[0, i, j] = MDdti R = all_tensor_evecs(p[0]) R = R.reshape((9)) model_params_mv[0, i, j] = \ np.concatenate(([0.0017, 0.0003, 0.0003], R, [gtf]), axis=0)
def test_single_voxel_DKI_stats(): # tests if AK and RK are equal to expected values for a single fiber # simulate randomly oriented ADi = 0.00099 ADe = 0.00226 RDi = 0 RDe = 0.00087 # Reference values AD = fie * ADi + (1 - fie) * ADe AK = 3 * fie * (1 - fie) * ((ADi - ADe) / AD)**2 RD = fie * RDi + (1 - fie) * RDe RK = 3 * fie * (1 - fie) * ((RDi - RDe) / RD)**2 ref_vals = np.array([AD, AK, RD, RK]) # simulate fiber randomly oriented theta = random.uniform(0, 180) phi = random.uniform(0, 320) angles = [(theta, phi), (theta, phi)] mevals = np.array([[ADi, RDi, RDi], [ADe, RDe, RDe]]) frac = [fie * 100, (1 - fie) * 100] signal, dt, kt = multi_tensor_dki(gtab_2s, mevals, S0=100, angles=angles, fractions=frac, snr=None) evals, evecs = decompose_tensor(from_lower_triangular(dt)) dki_par = np.concatenate((evals, evecs[0], evecs[1], evecs[2], kt), axis=0) # Estimates using dki functions ADe1 = dti.axial_diffusivity(evals) RDe1 = dti.radial_diffusivity(evals) AKe1 = axial_kurtosis(dki_par) RKe1 = radial_kurtosis(dki_par) e1_vals = np.array([ADe1, AKe1, RDe1, RKe1]) assert_array_almost_equal(e1_vals, ref_vals) # Estimates using the kurtosis class object dkiM = dki.DiffusionKurtosisModel(gtab_2s) dkiF = dkiM.fit(signal) e2_vals = np.array([dkiF.ad, dkiF.ak(), dkiF.rd, dkiF.rk()]) assert_array_almost_equal(e2_vals, ref_vals) # test MK (note this test correspond to the MK singularity L2==L3) MK_as = dkiF.mk() sph = Sphere(xyz=gtab.bvecs[gtab.bvals > 0]) MK_nm = np.mean(dkiF.akc(sph)) assert_array_almost_equal(MK_as, MK_nm, decimal=1)
def _compartments_eigenvalues(cdt): """ Helper function that computes the eigenvalues of a tissue sub compartment given its individual diffusion tensor Parameters ---------- cdt : ndarray (..., 6) Diffusion tensors elements of the tissue compartment stored in lower triangular order. Returns ------- eval : ndarry (..., 3) Eigenvalues of the tissue compartment """ evals, evecs = decompose_tensor(from_lower_triangular(cdt)) return evals
def test_single_voxel_DKI_stats(): # tests if AK and RK are equal to expected values for a single fiber # simulate randomly oriented ADi = 0.00099 ADe = 0.00226 RDi = 0 RDe = 0.00087 # Reference values AD = fie * ADi + (1 - fie) * ADe AK = 3 * fie * (1 - fie) * ((ADi-ADe) / AD) ** 2 RD = fie * RDi + (1 - fie) * RDe RK = 3 * fie * (1 - fie) * ((RDi-RDe) / RD) ** 2 ref_vals = np.array([AD, AK, RD, RK]) # simulate fiber randomly oriented theta = random.uniform(0, 180) phi = random.uniform(0, 320) angles = [(theta, phi), (theta, phi)] mevals = np.array([[ADi, RDi, RDi], [ADe, RDe, RDe]]) frac = [fie * 100, (1 - fie) * 100] signal, dt, kt = multi_tensor_dki(gtab_2s, mevals, S0=100, angles=angles, fractions=frac, snr=None) evals, evecs = decompose_tensor(from_lower_triangular(dt)) dki_par = np.concatenate((evals, evecs[0], evecs[1], evecs[2], kt), axis=0) # Estimates using dki functions ADe1 = dti.axial_diffusivity(evals) RDe1 = dti.radial_diffusivity(evals) AKe1 = axial_kurtosis(dki_par) RKe1 = radial_kurtosis(dki_par) e1_vals = np.array([ADe1, AKe1, RDe1, RKe1]) assert_array_almost_equal(e1_vals, ref_vals) # Estimates using the kurtosis class object dkiM = dki.DiffusionKurtosisModel(gtab_2s) dkiF = dkiM.fit(signal) e2_vals = np.array([dkiF.ad, dkiF.ak(), dkiF.rd, dkiF.rk()]) assert_array_almost_equal(e2_vals, ref_vals) # test MK (note this test correspond to the MK singularity L2==L3) MK_as = dkiF.mk() sph = Sphere(xyz=gtab.bvecs[gtab.bvals > 0]) MK_nm = np.mean(dkiF.akc(sph)) assert_array_almost_equal(MK_as, MK_nm, decimal=1)
def reorient_tensor_image(tensor_image, warp_file, mask_img, prefix, output_fname): cmds = [] to_remove = [] reoriented_tensor_fname = prefix + "reoriented_tensor.nii" reorient_cmd = "ReorientTensorImage 3 %s %s %s" % (tensor_image, reoriented_tensor_fname, warp_file) LOGGER.info(reorient_cmd) os.system(reorient_cmd) cmds.append(reorient_cmd) to_remove.append(reoriented_tensor_fname) # Load the reoriented tensor and get the principal directions out reoriented_dt_img = nb.load(reoriented_tensor_fname) reoriented_tensor_data = reoriented_dt_img.get_data().squeeze() mask_data = mask_img.get_data() > 0 output_data = np.zeros(mask_img.shape + (3,)) reoriented_tensors = reoriented_tensor_data[mask_data] reoriented_vectors = np.zeros((reoriented_tensors.shape[0], 3)) def tensor_from_vec(vec): """[dxx, dxy, dyy, dxz, dyz, dzz].""" return np.array([ [vec[0], vec[1], vec[3]], [vec[1], vec[2], vec[4]], [vec[3], vec[4], vec[5]] ]) for nrow, row in enumerate(reoriented_tensors): row_tensor = tensor_from_vec(row) evals, evecs = decompose_tensor(row_tensor) reoriented_vectors[nrow] = evecs[:, 0] output_data[mask_data] = normalized_vector(reoriented_vectors) vector_data = get_vector_nii(output_data, mask_img.affine, mask_img.header) vector_data.to_filename(output_fname) os.remove(reoriented_tensor_fname) os.remove(tensor_image) return output_fname, reorient_cmd
def calculate_scalars(tensor_data, mask): ''' Calculate the scalar images from the tensor returns: FA, MD, TR, AX, RAD ''' mask = np.asarray(mask, dtype=np.bool) shape = mask.shape data = dti.from_lower_triangular(tensor_data[mask]) w, v = dti.decompose_tensor(data) w = np.squeeze(w) v = np.squeeze(v) md = np.zeros(shape) md[mask] = dti.mean_diffusivity(w, axis=-1) fa = np.zeros(shape) fa[mask] = dti.fractional_anisotropy(w, axis=-1) tr = np.zeros(shape) tr[mask] = dti.trace(w, axis=-1) ax = np.zeros(shape) ax[mask] = dti.axial_diffusivity(w, axis=-1) rad = np.zeros(shape) rad[mask] = dti.radial_diffusivity(w, axis=-1) return fa, md, tr, ax, rad
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 wls_iter(design_matrix, sig, S0, Diso=3e-3, mdreg=2.7e-3, min_signal=1.0e-6, piterations=3): """ Applies weighted 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. Default: minimal signal in the data provided to `fit`. piterations : inter, optional Number of iterations used to refine the precision of f. Default is set to 3 corresponding to a precision of 0.01. 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 """ W = design_matrix # DTI ordinary linear least square solution log_s = np.log(np.maximum(sig, min_signal)) # Define weights S2 = np.diag(sig**2) # DTI weighted linear least square solution WTS2 = np.dot(W.T, S2) inv_WT_S2_W = np.linalg.pinv(np.dot(WTS2, W)) invWTS2W_WTS2 = np.dot(inv_WT_S2_W, WTS2) params = np.dot(invWTS2W_WTS2, log_s) md = (params[0] + params[2] + params[5]) / 3 # Process voxel if it has significant signal from tissue if md < mdreg and np.mean(sig) > min_signal and S0 > min_signal: # General free-water signal contribution fwsig = np.exp(np.dot(design_matrix, np.array([Diso, 0, Diso, 0, 0, Diso, 0]))) df = 1 # initialize precision flow = 0 # lower f evaluated fhig = 1 # higher f evaluated ns = 9 # initial number of samples per iteration for p in range(piterations): df = df * 0.1 fs = np.linspace(flow+df, fhig-df, num=ns) # sampling f SFW = np.array([fwsig, ]*ns) # repeat contributions for all values FS, SI = np.meshgrid(fs, sig) SA = SI - FS*S0*SFW.T # SA < 0 means that the signal components from the free water # component is larger than the total fiber. This cases are present # for inapropriate large volume fractions (given the current S0 # value estimated). To overcome this issue negative SA are replaced # by data's min positive signal. SA[SA <= 0] = min_signal y = np.log(SA / (1-FS)) all_new_params = np.dot(invWTS2W_WTS2, y) # Select params for lower F2 SIpred = (1-FS)*np.exp(np.dot(W, all_new_params)) + FS*S0*SFW.T F2 = np.sum(np.square(SI - SIpred), axis=0) Mind = np.argmin(F2) params = all_new_params[:, Mind] f = fs[Mind] # Updated f flow = f - df # refining precision fhig = f + df ns = 19 evals, evecs = decompose_tensor(from_lower_triangular(params)) fw_params = np.concatenate((evals, evecs[0], evecs[1], evecs[2], np.array([f])), axis=0) else: fw_params = np.zeros(13) if md > mdreg: fw_params[12] = 1.0 return fw_params
from dipy.core.gradients import gradient_table from dipy.data import get_data fimg, fbvals, fbvecs = get_data('small_64D') bvals, bvecs = read_bvals_bvecs(fbvals, fbvecs) gtab = gradient_table(bvals, bvecs) # FW model requires multishell data bvals_2s = np.concatenate((bvals, bvals * 1.5), axis=0) bvecs_2s = np.concatenate((bvecs, bvecs), axis=0) gtab_2s = gradient_table(bvals_2s, bvecs_2s) # Simulation a typical DT and DW signal for no water contamination S0 = np.array(100) dt = np.array([0.0017, 0, 0.0003, 0, 0, 0.0003]) evals, evecs = decompose_tensor(from_lower_triangular(dt)) S_tissue = single_tensor(gtab_2s, S0=100, evals=evals, evecs=evecs, snr=None) dm = dti.TensorModel(gtab_2s, 'WLS') dtifit = dm.fit(S_tissue) FAdti = dtifit.fa MDdti = dtifit.md dtiparams = dtifit.model_params # Simulation of 8 voxels tested DWI = np.zeros((2, 2, 2, len(gtab_2s.bvals))) FAref = np.zeros((2, 2, 2)) MDref = np.zeros((2, 2, 2)) # Diffusion of tissue and water compartments are constant for all voxel mevals = np.array([[0.0017, 0.0003, 0.0003], [0.003, 0.003, 0.003]]) # volume fractions
def sfm_design_matrix(gtab, sphere, response, mode='signal'): """ Construct the SFM design matrix Parameters ---------- gtab : GradientTable or Sphere Sets the rows of the matrix, if the mode is 'signal', this should be a GradientTable. If mode is 'odf' this should be a Sphere sphere : Sphere Sets the columns of the matrix response : list of 3 elements The eigenvalues of a tensor which will serve as a kernel function. mode : str {'signal' | 'odf'}, optional Choose the (default) 'signal' for a design matrix containing predicted signal in the measurements defined by the gradient table for putative fascicles oriented along the vertices of the sphere. Otherwise, choose 'odf' for an odf convolution matrix, with values of the odf calculated from a tensor with the provided response eigenvalues, evaluated at the b-vectors in the gradient table, for the tensors with prinicipal diffusion directions along the vertices of the sphere. Returns ------- mat : ndarray A design matrix that can be used for one of the following operations: when the 'signal' mode is used, each column contains the putative signal in each of the bvectors of the `gtab` if a fascicle is oriented in the direction encoded by the sphere vertex corresponding to this column. This is used for deconvolution with a measured DWI signal. If the 'odf' mode is chosen, each column instead contains the values of the tensor ODF for a tensor with a principal diffusion direction corresponding to this vertex. This is used to generate odfs from the fits of the SFM for the purpose of tracking. Examples -------- >>> import dipy.data as dpd >>> data, gtab = dpd.dsi_voxels() >>> sphere = dpd.get_sphere() >>> from dipy.reconst.sfm import sfm_design_matrix A canonical tensor approximating corpus-callosum voxels [Rokem2014]_: >>> tensor_matrix=sfm_design_matrix(gtab, sphere, [0.0015, 0.0005, 0.0005]) A 'stick' function ([Behrens2007]_): >>> stick_matrix = sfm_design_matrix(gtab, sphere, [0.001, 0, 0]) Notes ----- .. [Rokem2014] Ariel Rokem, Jason D. Yeatman, Franco Pestilli, Kendrick N. Kay, Aviv Mezer, Stefan van der Walt, Brian A. Wandell (2014). Evaluating the accuracy of diffusion MRI models in white matter. http://arxiv.org/abs/1411.0721 .. [Behrens2007] Behrens TEJ, Berg HJ, Jbabdi S, Rushworth MFS, Woolrich MW (2007): Probabilistic diffusion tractography with multiple fibre orientations: What can we gain? Neuroimage 34:144-55. """ # Each column of the matrix is the signal in each measurement, as # predicted by a "canonical", symmetrical tensor rotated towards this # vertex of the sphere: canonical_tensor = np.diag(response) if mode == 'signal': mat_gtab = grad.gradient_table(gtab.bvals[~gtab.b0s_mask], gtab.bvecs[~gtab.b0s_mask]) # Preallocate: mat = np.empty((np.sum(~gtab.b0s_mask), sphere.vertices.shape[0])) elif mode == 'odf': mat = np.empty((gtab.x.shape[0], sphere.vertices.shape[0])) # Calculate column-wise: for ii, this_dir in enumerate(sphere.vertices): # Rotate the canonical tensor towards this vertex and calculate the # signal you would have gotten in the direction rot_matrix = geo.vec2vec_rotmat(np.array([1, 0, 0]), this_dir) this_tensor = np.dot(rot_matrix, canonical_tensor) evals, evecs = dti.decompose_tensor(this_tensor) if mode == 'signal': sig = sims.single_tensor(mat_gtab, evals=response, evecs=evecs) mat[:, ii] = sig - np.mean(sig) elif mode == 'odf': # Stick function if response[1] == 0 or response[2] == 0: jj = sphere.find_closest(evecs[0]) mat[jj, ii] = 1 else: odf = sims.single_tensor_odf(gtab.vertices, evals=response, evecs=evecs) mat[:, ii] = odf return mat
from dipy.core.gradients import gradient_table from dipy.data import get_data fimg, fbvals, fbvecs = get_data('small_64D') bvals, bvecs = read_bvals_bvecs(fbvals, fbvecs) gtab = gradient_table(bvals, bvecs) # FW model requires multishell data bvals_2s = np.concatenate((bvals, bvals * 1.5), axis=0) bvecs_2s = np.concatenate((bvecs, bvecs), axis=0) gtab_2s = gradient_table(bvals_2s, bvecs_2s) # Simulation a typical DT and DW signal for no water contamination S0 = np.array(100) dt = np.array([0.0017, 0, 0.0003, 0, 0, 0.0003]) evals, evecs = decompose_tensor(from_lower_triangular(dt)) S_tissue = single_tensor(gtab_2s, S0=100, evals=evals, evecs=evecs, snr=None) dm = dti.TensorModel(gtab_2s, 'WLS') dtifit = dm.fit(S_tissue) FAdti = dtifit.fa MDdti = dtifit.md dtiparams = dtifit.model_params # Simulation of 8 voxels tested DWI = np.zeros((2, 2, 2, len(gtab_2s.bvals))) FAref = np.zeros((2, 2, 2)) MDref = np.zeros((2, 2, 2)) # Diffusion of tissue and water compartments are constant for all voxel mevals = np.array([[0.0017, 0.0003, 0.0003], [0.003, 0.003, 0.003]]) # volume fractions GTF = np.array([[[0.06, 0.71], [0.33, 0.91]], [[0., 0.], [0., 0.]]])
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 exp_transform_from_qform(qform): """Takes a q form and returns evals and evecs""" return (dti.decompose_tensor(dti.from_lower_triangular(qform), min_diffusivity=MIN_POSITIVE_EIGENVALUE))
bvecs_2s = np.concatenate((bvecs, bvecs), axis=0) gtab_2s = gradient_table(bvals_2s, bvecs_2s) # Simulation 1. signals of two crossing fibers are simulated mevals_cross = np.array([[0.00099, 0, 0], [0.00226, 0.00087, 0.00087], [0.00099, 0, 0], [0.00226, 0.00087, 0.00087]]) angles_cross = [(80, 10), (80, 10), (20, 30), (20, 30)] fie = 0.49 frac_cross = [fie*50, (1-fie) * 50, fie*50, (1-fie) * 50] # Noise free simulates signal_cross, dt_cross, kt_cross = multi_tensor_dki(gtab_2s, mevals_cross, S0=100, angles=angles_cross, fractions=frac_cross, snr=None) evals_cross, evecs_cross = decompose_tensor(from_lower_triangular(dt_cross)) crossing_ref = np.concatenate((evals_cross, evecs_cross[0], evecs_cross[1], evecs_cross[2], kt_cross), axis=0) # Simulation 2. Spherical kurtosis tensor.- for white matter, this can be a # biological implaussible scenario, however this simulation is usefull for # testing the estimation of directional apparent kurtosis and the mean # kurtosis, since its directional and mean kurtosis ground truth are a constant # which can be easly mathematicaly calculated. Di = 0.00099 De = 0.00226 mevals_sph = np.array([[Di, Di, Di], [De, De, De]]) frac_sph = [50, 50] signal_sph, dt_sph, kt_sph = multi_tensor_dki(gtab_2s, mevals_sph, S0=100, fractions=frac_sph, snr=None)
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=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)) # 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_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, 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_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