1""" Classes and functions for fitting tensors without free water 2contamination """ 3 4import warnings 5 6import numpy as np 7 8import scipy.optimize as opt 9 10from dipy.reconst.base import ReconstModel 11 12from dipy.reconst.dti import (TensorFit, design_matrix, decompose_tensor, 13 _decompose_tensor_nan, from_lower_triangular, 14 lower_triangular) 15from dipy.reconst.dki import _positive_evals 16 17from dipy.reconst.vec_val_sum import vec_val_vect 18from dipy.core.ndindex import ndindex 19from dipy.core.gradients import check_multi_b 20from dipy.reconst.multi_voxel import multi_voxel_fit 21 22 23def fwdti_prediction(params, gtab, S0=1, Diso=3.0e-3): 24 r""" Signal prediction given the free water DTI model parameters. 25 26 Parameters 27 ---------- 28 params : (..., 13) ndarray 29 Model parameters. The last dimension should have the 12 tensor 30 parameters (3 eigenvalues, followed by the 3 corresponding 31 eigenvectors) and the volume fraction of the free water compartment. 32 gtab : a GradientTable class instance 33 The gradient table for this prediction 34 S0 : float or ndarray 35 The non diffusion-weighted signal in every voxel, or across all 36 voxels. Default: 1 37 Diso : float, optional 38 Value of the free water isotropic diffusion. Default is set to 3e-3 39 $mm^{2}.s^{-1}$. Please adjust this value if you are assuming different 40 units of diffusion. 41 42 Returns 43 -------- 44 S : (..., N) ndarray 45 Simulated signal based on the free water DTI model 46 47 Notes 48 ----- 49 The predicted signal is given by: 50 $S(\theta, b) = S_0 * [(1-f) * e^{-b ADC} + f * e^{-b D_{iso}]$, where 51 $ADC = \theta Q \theta^T$, $\theta$ is a unit vector pointing at any 52 direction on the sphere for which a signal is to be predicted, $b$ is the b 53 value provided in the GradientTable input for that direction, $Q$ is the 54 quadratic form of the tensor determined by the input parameters, $f$ is the 55 free water diffusion compartment, $D_{iso}$ is the free water diffusivity 56 which is equal to $3 * 10^{-3} mm^{2}s^{-1} [1]_. 57 58 References 59 ---------- 60 .. [1] Henriques, R.N., Rokem, A., Garyfallidis, E., St-Jean, S., 61 Peterson E.T., Correia, M.M., 2017. [Re] Optimization of a free 62 water elimination two-compartment model for diffusion tensor 63 imaging. ReScience volume 3, issue 1, article number 2 64 """ 65 evals = params[..., :3] 66 evecs = params[..., 3:-1].reshape(params.shape[:-1] + (3, 3)) 67 f = params[..., 12] 68 qform = vec_val_vect(evecs, evals) 69 lower_dt = lower_triangular(qform, S0) 70 lower_diso = lower_dt.copy() 71 lower_diso[..., 0] = lower_diso[..., 2] = lower_diso[..., 5] = Diso 72 lower_diso[..., 1] = lower_diso[..., 3] = lower_diso[..., 4] = 0 73 D = design_matrix(gtab) 74 75 pred_sig = np.zeros(f.shape + (gtab.bvals.shape[0],)) 76 mask = _positive_evals(evals[..., 0], evals[..., 1], evals[..., 2]) 77 index = ndindex(f.shape) 78 for v in index: 79 if mask[v]: 80 pred_sig[v] = (1 - f[v]) * np.exp(np.dot(lower_dt[v], D.T)) + \ 81 f[v] * np.exp(np.dot(lower_diso[v], D.T)) 82 83 return pred_sig 84 85 86class FreeWaterTensorModel(ReconstModel): 87 """ Class for the Free Water Elimination Diffusion Tensor Model """ 88 def __init__(self, gtab, fit_method="NLS", *args, **kwargs): 89 """ Free Water Diffusion Tensor Model [1]_. 90 91 Parameters 92 ---------- 93 gtab : GradientTable class instance 94 fit_method : str or callable 95 str can be one of the following: 96 97 'WLS' for weighted linear least square fit according to [1]_ 98 :func:`fwdti.wls_iter` 99 'NLS' for non-linear least square fit according to [1]_ 100 :func:`fwdti.nls_iter` 101 102 callable has to have the signature: 103 fit_method(design_matrix, data, *args, **kwargs) 104 args, kwargs : arguments and key-word arguments passed to the 105 fit_method. See fwdti.wls_iter, fwdti.nls_iter for 106 details 107 108 References 109 ---------- 110 .. [1] Henriques, R.N., Rokem, A., Garyfallidis, E., St-Jean, S., 111 Peterson E.T., Correia, M.M., 2017. [Re] Optimization of a free 112 water elimination two-compartment model for diffusion tensor 113 imaging. ReScience volume 3, issue 1, article number 2 114 """ 115 ReconstModel.__init__(self, gtab) 116 117 if not callable(fit_method): 118 try: 119 fit_method = common_fit_methods[fit_method] 120 except KeyError: 121 e_s = '"' + str(fit_method) + '" is not a known fit ' 122 e_s += 'method, the fit method should either be a ' 123 e_s += 'function or one of the common fit methods' 124 raise ValueError(e_s) 125 self.fit_method = fit_method 126 self.design_matrix = design_matrix(self.gtab) 127 self.args = args 128 self.kwargs = kwargs 129 130 # Check if at least three b-values are given 131 enough_b = check_multi_b(self.gtab, 3, non_zero=False) 132 if not enough_b: 133 mes = "fwDTI requires at least 3 b-values (which can include b=0)" 134 raise ValueError(mes) 135 136 @multi_voxel_fit 137 def fit(self, data, mask=None): 138 """ Fit method of the free water elimination DTI model class 139 140 Parameters 141 ---------- 142 data : array 143 The measured signal from one voxel. 144 mask : array 145 A boolean array used to mark the coordinates in the data that 146 should be analyzed that has the shape data.shape[:-1] 147 """ 148 S0 = np.mean(data[self.gtab.b0s_mask]) 149 fwdti_params = self.fit_method(self.design_matrix, data, S0, 150 *self.args, **self.kwargs) 151 152 return FreeWaterTensorFit(self, fwdti_params) 153 154 def predict(self, fwdti_params, S0=1): 155 """ Predict a signal for this TensorModel class instance given 156 parameters. 157 158 Parameters 159 ---------- 160 fwdti_params : (..., 13) ndarray 161 The last dimension should have 13 parameters: the 12 tensor 162 parameters (3 eigenvalues, followed by the 3 corresponding 163 eigenvectors) and the free water volume fraction. 164 S0 : float or ndarray 165 The non diffusion-weighted signal in every voxel, or across all 166 voxels. Default: 1 167 168 Returns 169 -------- 170 S : (..., N) ndarray 171 Simulated signal based on the free water DTI model 172 """ 173 return fwdti_prediction(fwdti_params, self.gtab, S0=S0) 174 175 176class FreeWaterTensorFit(TensorFit): 177 """ Class for fitting the Free Water Tensor Model """ 178 def __init__(self, model, model_params): 179 """ Initialize a FreeWaterTensorFit class instance. 180 Since the free water tensor model is an extension of DTI, class 181 instance is defined as subclass of the TensorFit from dti.py 182 183 Parameters 184 ---------- 185 model : FreeWaterTensorModel Class instance 186 Class instance containing the free water tensor model for the fit 187 model_params : ndarray (x, y, z, 13) or (n, 13) 188 All parameters estimated from the free water tensor model. 189 Parameters are ordered as follows: 190 1) Three diffusion tensor's eigenvalues 191 2) Three lines of the eigenvector matrix each containing the 192 first, second and third coordinates of the eigenvector 193 3) The volume fraction of the free water compartment 194 195 References 196 ---------- 197 .. [1] Henriques, R.N., Rokem, A., Garyfallidis, E., St-Jean, S., 198 Peterson E.T., Correia, M.M., 2017. [Re] Optimization of a free 199 water elimination two-compartment model for diffusion tensor 200 imaging. ReScience volume 3, issue 1, article number 2 201 """ 202 TensorFit.__init__(self, model, model_params) 203 204 @property 205 def f(self): 206 """ Returns the free water diffusion volume fraction f """ 207 return self.model_params[..., 12] 208 209 def predict(self, gtab, S0=1): 210 r""" Given a free water tensor model fit, predict the signal on the 211 vertices of a gradient table 212 213 Parameters 214 ---------- 215 gtab : a GradientTable class instance 216 The gradient table for this prediction 217 218 S0 : float array 219 The mean non-diffusion weighted signal in each voxel. Default: 1 in 220 all voxels. 221 222 Returns 223 -------- 224 S : (..., N) ndarray 225 Simulated signal based on the free water DTI model 226 """ 227 return fwdti_prediction(self.model_params, gtab, S0=S0) 228 229 230def wls_iter(design_matrix, sig, S0, Diso=3e-3, mdreg=2.7e-3, 231 min_signal=1.0e-6, piterations=3): 232 """ Applies weighted linear least squares fit of the water free elimination 233 model to single voxel signals. 234 235 Parameters 236 ---------- 237 design_matrix : array (g, 7) 238 Design matrix holding the covariants used to solve for the regression 239 coefficients. 240 sig : array (g, ) 241 Diffusion-weighted signal for a single voxel data. 242 S0 : float 243 Non diffusion weighted signal (i.e. signal for b-value=0). 244 Diso : float, optional 245 Value of the free water isotropic diffusion. Default is set to 3e-3 246 $mm^{2}.s^{-1}$. Please adjust this value if you are assuming different 247 units of diffusion. 248 mdreg : float, optimal 249 DTI's mean diffusivity regularization threshold. If standard DTI 250 diffusion tensor's mean diffusivity is almost near the free water 251 diffusion value, the diffusion signal is assumed to be only free water 252 diffusion (i.e. volume fraction will be set to 1 and tissue's diffusion 253 parameters are set to zero). Default md_reg is 2.7e-3 $mm^{2}.s^{-1}$ 254 (corresponding to 90% of the free water diffusion value). 255 min_signal : float 256 The minimum signal value. Needs to be a strictly positive 257 number. Default: minimal signal in the data provided to `fit`. 258 piterations : inter, optional 259 Number of iterations used to refine the precision of f. Default is set 260 to 3 corresponding to a precision of 0.01. 261 262 Returns 263 ------- 264 All parameters estimated from the free water tensor model. 265 Parameters are ordered as follows: 266 1) Three diffusion tensor's eigenvalues 267 2) Three lines of the eigenvector matrix each containing the 268 first, second and third coordinates of the eigenvector 269 3) The volume fraction of the free water compartment 270 """ 271 W = design_matrix 272 273 # DTI ordinary linear least square solution 274 log_s = np.log(np.maximum(sig, min_signal)) 275 276 # Define weights 277 S2 = np.diag(sig**2) 278 279 # DTI weighted linear least square solution 280 WTS2 = np.dot(W.T, S2) 281 inv_WT_S2_W = np.linalg.pinv(np.dot(WTS2, W)) 282 invWTS2W_WTS2 = np.dot(inv_WT_S2_W, WTS2) 283 params = np.dot(invWTS2W_WTS2, log_s) 284 285 md = (params[0] + params[2] + params[5]) / 3 286 # Process voxel if it has significant signal from tissue 287 if md < mdreg and np.mean(sig) > min_signal and S0 > min_signal: 288 # General free-water signal contribution 289 fwsig = np.exp(np.dot(design_matrix, 290 np.array([Diso, 0, Diso, 0, 0, Diso, 0]))) 291 292 df = 1 # initialize precision 293 flow = 0 # lower f evaluated 294 fhig = 1 # higher f evaluated 295 ns = 9 # initial number of samples per iteration 296 for p in range(piterations): 297 df = df * 0.1 298 fs = np.linspace(flow+df, fhig-df, num=ns) # sampling f 299 SFW = np.array([fwsig, ]*ns) # repeat contributions for all values 300 FS, SI = np.meshgrid(fs, sig) 301 SA = SI - FS*S0*SFW.T 302 # SA < 0 means that the signal components from the free water 303 # component is larger than the total fiber. This cases are present 304 # for inappropriate large volume fractions (given the current S0 305 # value estimated). To overcome this issue negative SA are replaced 306 # by data's min positive signal. 307 SA[SA <= 0] = min_signal 308 y = np.log(SA / (1-FS)) 309 all_new_params = np.dot(invWTS2W_WTS2, y) 310 # Select params for lower F2 311 SIpred = (1-FS)*np.exp(np.dot(W, all_new_params)) + FS*S0*SFW.T 312 F2 = np.sum(np.square(SI - SIpred), axis=0) 313 Mind = np.argmin(F2) 314 params = all_new_params[:, Mind] 315 f = fs[Mind] # Updated f 316 flow = f - df # refining precision 317 fhig = f + df 318 ns = 19 319 320 evals, evecs = decompose_tensor(from_lower_triangular(params)) 321 fw_params = np.concatenate((evals, evecs[0], evecs[1], evecs[2], 322 np.array([f])), axis=0) 323 else: 324 fw_params = np.zeros(13) 325 if md > mdreg: 326 fw_params[12] = 1.0 327 328 return fw_params 329 330 331def wls_fit_tensor(gtab, data, Diso=3e-3, mask=None, min_signal=1.0e-6, 332 piterations=3, mdreg=2.7e-3): 333 r""" Computes weighted least squares (WLS) fit to calculate self-diffusion 334 tensor using a linear regression model [1]_. 335 336 Parameters 337 ---------- 338 gtab : a GradientTable class instance 339 The gradient table containing diffusion acquisition parameters. 340 data : ndarray ([X, Y, Z, ...], g) 341 Data or response variables holding the data. Note that the last 342 dimension should contain the data. It makes no copies of data. 343 Diso : float, optional 344 Value of the free water isotropic diffusion. Default is set to 3e-3 345 $mm^{2}.s^{-1}$. Please adjust this value if you are assuming different 346 units of diffusion. 347 mask : array, optional 348 A boolean array used to mark the coordinates in the data that should 349 be analyzed that has the shape data.shape[:-1] 350 min_signal : float 351 The minimum signal value. Needs to be a strictly positive 352 number. Default: 1.0e-6. 353 piterations : inter, optional 354 Number of iterations used to refine the precision of f. Default is set 355 to 3 corresponding to a precision of 0.01. 356 mdreg : float, optimal 357 DTI's mean diffusivity regularization threshold. If standard DTI 358 diffusion tensor's mean diffusivity is almost near the free water 359 diffusion value, the diffusion signal is assumed to be only free water 360 diffusion (i.e. volume fraction will be set to 1 and tissue's diffusion 361 parameters are set to zero). Default md_reg is 2.7e-3 $mm^{2}.s^{-1}$ 362 (corresponding to 90% of the free water diffusion value). 363 364 Returns 365 ------- 366 fw_params : ndarray (x, y, z, 13) 367 Matrix containing in the last dimension the free water model parameters 368 in the following order: 369 1) Three diffusion tensor's eigenvalues 370 2) Three lines of the eigenvector matrix each containing the 371 first, second and third coordinates of the eigenvector 372 3) The volume fraction of the free water compartment. 373 374 References 375 ---------- 376 .. [1] Henriques, R.N., Rokem, A., Garyfallidis, E., St-Jean, S., 377 Peterson E.T., Correia, M.M., 2017. [Re] Optimization of a free 378 water elimination two-compartment model for diffusion tensor 379 imaging. ReScience volume 3, issue 1, article number 2 380 """ 381 fw_params = np.zeros(data.shape[:-1] + (13,)) 382 W = design_matrix(gtab) 383 384 # Prepare mask 385 if mask is None: 386 mask = np.ones(data.shape[:-1], dtype=bool) 387 else: 388 if mask.shape != data.shape[:-1]: 389 raise ValueError("Mask is not the same shape as data.") 390 mask = np.array(mask, dtype=bool, copy=False) 391 392 # Prepare S0 393 S0 = np.mean(data[:, :, :, gtab.b0s_mask], axis=-1) 394 395 index = ndindex(mask.shape) 396 for v in index: 397 if mask[v]: 398 params = wls_iter(W, data[v], S0[v], min_signal=min_signal, 399 Diso=3e-3, piterations=piterations, mdreg=mdreg) 400 fw_params[v] = params 401 402 return fw_params 403 404 405def _nls_err_func(tensor_elements, design_matrix, data, Diso=3e-3, 406 weighting=None, sigma=None, cholesky=False, 407 f_transform=False): 408 """ Error function for the non-linear least-squares fit of the tensor water 409 elimination model. 410 411 Parameters 412 ---------- 413 tensor_elements : array (8, ) 414 The six independent elements of the diffusion tensor followed by 415 -log(S0) and the volume fraction f of the water elimination 416 compartment. Note that if cholesky is set to true, tensor elements are 417 assumed to be written as Cholesky's decomposition elements. If 418 f_transform is true, volume fraction f has to be converted to 419 ft = arcsin(2*f - 1) + pi/2 420 design_matrix : array 421 The design matrix 422 data : array 423 The voxel signal in all gradient directions 424 Diso : float, optional 425 Value of the free water isotropic diffusion. Default is set to 3e-3 426 $mm^{2}.s^{-1}$. Please adjust this value if you are assuming different 427 units of diffusion. 428 weighting : str (optional). 429 Whether to use the Geman-McClure weighting criterion (see [1]_ 430 for details) 431 sigma : float or float array (optional) 432 If 'sigma' weighting is used, we will weight the error function 433 according to the background noise estimated either in aggregate over 434 all directions (when a float is provided), or to an estimate of the 435 noise in each diffusion-weighting direction (if an array is 436 provided). If 'gmm', the Geman-Mclure M-estimator is used for 437 weighting. 438 cholesky : bool, optional 439 If true, the diffusion tensor elements were decomposed using Cholesky 440 decomposition. See fwdti.nls_fit_tensor 441 Default: False 442 f_transform : bool, optional 443 If true, the water volume fraction was converted to 444 ft = arcsin(2*f - 1) + pi/2, insuring f estimates between 0 and 1. 445 See fwdti.nls_fit_tensor 446 Default: True 447 """ 448 tensor = np.copy(tensor_elements) 449 if cholesky: 450 tensor[:6] = cholesky_to_lower_triangular(tensor[:6]) 451 452 if f_transform: 453 f = 0.5 * (1 + np.sin(tensor[7] - np.pi/2)) 454 else: 455 f = tensor[7] 456 457 # This is the predicted signal given the params: 458 y = (1-f) * np.exp(np.dot(design_matrix, tensor[:7])) + \ 459 f * np.exp(np.dot(design_matrix, 460 np.array([Diso, 0, Diso, 0, 0, Diso, tensor[6]]))) 461 462 # Compute the residuals 463 residuals = data - y 464 465 # If we don't want to weight the residuals, we are basically done: 466 if weighting is None: 467 # And we return the SSE: 468 return residuals 469 se = residuals ** 2 470 # If the user provided a sigma (e.g 1.5267 * std(background_noise), as 471 # suggested by Chang et al.) we will use it: 472 if weighting == 'sigma': 473 if sigma is None: 474 e_s = "Must provide sigma value as input to use this weighting" 475 e_s += " method" 476 raise ValueError(e_s) 477 w = 1/(sigma**2) 478 479 elif weighting == 'gmm': 480 # We use the Geman-McClure M-estimator to compute the weights on the 481 # residuals: 482 C = 1.4826 * np.median(np.abs(residuals - np.median(residuals))) 483 with warnings.catch_warnings(): 484 warnings.simplefilter("ignore") 485 w = 1/(se + C**2) 486 # The weights are normalized to the mean weight (see p. 1089): 487 w = w/np.mean(w) 488 489 # Return the weighted residuals: 490 with warnings.catch_warnings(): 491 warnings.simplefilter("ignore") 492 return np.sqrt(w * se) 493 494 495def _nls_jacobian_func(tensor_elements, design_matrix, data, Diso=3e-3, 496 weighting=None, sigma=None, cholesky=False, 497 f_transform=False): 498 """The Jacobian is the first derivative of the least squares error 499 function. 500 501 Parameters 502 ---------- 503 tensor_elements : array (8, ) 504 The six independent elements of the diffusion tensor followed by 505 -log(S0) and the volume fraction f of the water elimination 506 compartment. Note that if f_transform is true, volume fraction f is 507 converted to ft = arcsin(2*f - 1) + pi/2 508 design_matrix : array 509 The design matrix 510 Diso : float, optional 511 Value of the free water isotropic diffusion. Default is set to 3e-3 512 $mm^{2}.s^{-1}$. Please adjust this value if you are assuming different 513 units of diffusion. 514 f_transform : bool, optional 515 If true, the water volume fraction was converted to 516 ft = arcsin(2*f - 1) + pi/2, insuring f estimates between 0 and 1. 517 See fwdti.nls_fit_tensor 518 Default: True 519 """ 520 tensor = np.copy(tensor_elements) 521 if f_transform: 522 f = 0.5 * (1 + np.sin(tensor[7] - np.pi/2)) 523 else: 524 f = tensor[7] 525 526 t = np.exp(np.dot(design_matrix, tensor[:7])) 527 s = np.exp(np.dot(design_matrix, 528 np.array([Diso, 0, Diso, 0, 0, Diso, tensor[6]]))) 529 T = (f-1.0) * t[:, None] * design_matrix 530 S = np.zeros(design_matrix.shape) 531 S[:, 6] = f * s 532 533 if f_transform: 534 df = (t-s) * (0.5*np.cos(tensor[7]-np.pi/2)) 535 else: 536 df = (t-s) 537 return np.concatenate((T - S, df[:, None]), axis=1) 538 539 540def nls_iter(design_matrix, sig, S0, Diso=3e-3, mdreg=2.7e-3, 541 min_signal=1.0e-6, cholesky=False, f_transform=True, jac=False, 542 weighting=None, sigma=None): 543 """ Applies non linear least squares fit of the water free elimination 544 model to single voxel signals. 545 546 Parameters 547 ---------- 548 design_matrix : array (g, 7) 549 Design matrix holding the covariants used to solve for the regression 550 coefficients. 551 sig : array (g, ) 552 Diffusion-weighted signal for a single voxel data. 553 S0 : float 554 Non diffusion weighted signal (i.e. signal for b-value=0). 555 Diso : float, optional 556 Value of the free water isotropic diffusion. Default is set to 3e-3 557 $mm^{2}.s^{-1}$. Please adjust this value if you are assuming different 558 units of diffusion. 559 mdreg : float, optimal 560 DTI's mean diffusivity regularization threshold. If standard DTI 561 diffusion tensor's mean diffusivity is almost near the free water 562 diffusion value, the diffusion signal is assumed to be only free water 563 diffusion (i.e. volume fraction will be set to 1 and tissue's diffusion 564 parameters are set to zero). Default md_reg is 2.7e-3 $mm^{2}.s^{-1}$ 565 (corresponding to 90% of the free water diffusion value). 566 min_signal : float 567 The minimum signal value. Needs to be a strictly positive 568 number. 569 cholesky : bool, optional 570 If true it uses Cholesky decomposition to insure that diffusion tensor 571 is positive define. 572 Default: False 573 f_transform : bool, optional 574 If true, the water volume fractions is converted during the convergence 575 procedure to ft = arcsin(2*f - 1) + pi/2, insuring f estimates between 576 0 and 1. 577 Default: True 578 jac : bool 579 Use the Jacobian? Default: False 580 weighting: str, optional 581 the weighting scheme to use in considering the 582 squared-error. Default behavior is to use uniform weighting. Other 583 options: 'sigma' 'gmm' 584 sigma: float, optional 585 If the 'sigma' weighting scheme is used, a value of sigma needs to be 586 provided here. According to [Chang2005]_, a good value to use is 587 1.5267 * std(background_noise), where background_noise is estimated 588 from some part of the image known to contain no signal (only noise). 589 590 Returns 591 ------- 592 All parameters estimated from the free water tensor model. 593 Parameters are ordered as follows: 594 1) Three diffusion tensor's eigenvalues 595 2) Three lines of the eigenvector matrix each containing the 596 first, second and third coordinates of the eigenvector 597 3) The volume fraction of the free water compartment. 598 """ 599 # Initial guess 600 params = wls_iter(design_matrix, sig, S0, 601 min_signal=min_signal, Diso=Diso, mdreg=mdreg) 602 603 # Process voxel if it has significant signal from tissue 604 if params[12] < 0.99 and np.mean(sig) > min_signal and S0 > min_signal: 605 # converting evals and evecs to diffusion tensor elements 606 evals = params[:3] 607 evecs = params[3:12].reshape((3, 3)) 608 dt = lower_triangular(vec_val_vect(evecs, evals)) 609 610 # Cholesky decomposition if requested 611 if cholesky: 612 dt = lower_triangular_to_cholesky(dt) 613 614 # f transformation if requested 615 if f_transform: 616 f = np.arcsin(2*params[12] - 1) + np.pi/2 617 else: 618 f = params[12] 619 620 # Use the Levenberg-Marquardt algorithm wrapped in opt.leastsq 621 start_params = np.concatenate((dt, [-np.log(S0), f]), axis=0) 622 if jac: 623 this_tensor, status = opt.leastsq(_nls_err_func, start_params[:8], 624 args=(design_matrix, sig, Diso, 625 weighting, sigma, cholesky, 626 f_transform), 627 Dfun=_nls_jacobian_func) 628 else: 629 this_tensor, status = opt.leastsq(_nls_err_func, start_params[:8], 630 args=(design_matrix, sig, Diso, 631 weighting, sigma, cholesky, 632 f_transform)) 633 634 # Process tissue diffusion tensor 635 if cholesky: 636 this_tensor[:6] = cholesky_to_lower_triangular(this_tensor[:6]) 637 638 evals, evecs = _decompose_tensor_nan( 639 from_lower_triangular(this_tensor[:6]), 640 from_lower_triangular(start_params[:6])) 641 642 # Process water volume fraction f 643 f = this_tensor[7] 644 if f_transform: 645 f = 0.5 * (1 + np.sin(f - np.pi/2)) 646 647 params = np.concatenate((evals, evecs[0], evecs[1], evecs[2], 648 np.array([f])), axis=0) 649 return params 650 651 652def nls_fit_tensor(gtab, data, mask=None, Diso=3e-3, mdreg=2.7e-3, 653 min_signal=1.0e-6, f_transform=True, cholesky=False, 654 jac=False, weighting=None, sigma=None): 655 """ 656 Fit the water elimination tensor model using the non-linear least-squares. 657 658 Parameters 659 ---------- 660 gtab : a GradientTable class instance 661 The gradient table containing diffusion acquisition parameters. 662 data : ndarray ([X, Y, Z, ...], g) 663 Data or response variables holding the data. Note that the last 664 dimension should contain the data. It makes no copies of data. 665 mask : array, optional 666 A boolean array used to mark the coordinates in the data that should 667 be analyzed that has the shape data.shape[:-1] 668 Diso : float, optional 669 Value of the free water isotropic diffusion. Default is set to 3e-3 670 $mm^{2}.s^{-1}$. Please adjust this value if you are assuming different 671 units of diffusion. 672 mdreg : float, optimal 673 DTI's mean diffusivity regularization threshold. If standard DTI 674 diffusion tensor's mean diffusivity is almost near the free water 675 diffusion value, the diffusion signal is assumed to be only free water 676 diffusion (i.e. volume fraction will be set to 1 and tissue's diffusion 677 parameters are set to zero). Default md_reg is 2.7e-3 $mm^{2}.s^{-1}$ 678 (corresponding to 90% of the free water diffusion value). 679 min_signal : float 680 The minimum signal value. Needs to be a strictly positive 681 number. Default: 1.0e-6. 682 f_transform : bool, optional 683 If true, the water volume fractions is converted during the convergence 684 procedure to ft = arcsin(2*f - 1) + pi/2, insuring f estimates between 685 0 and 1. 686 Default: True 687 cholesky : bool, optional 688 If true it uses Cholesky decomposition to insure that diffusion tensor 689 is positive define. 690 Default: False 691 jac : bool 692 Use the Jacobian? Default: False 693 weighting: str, optional 694 the weighting scheme to use in considering the 695 squared-error. Default behavior is to use uniform weighting. Other 696 options: 'sigma' 'gmm' 697 sigma: float, optional 698 If the 'sigma' weighting scheme is used, a value of sigma needs to be 699 provided here. According to [Chang2005]_, a good value to use is 700 1.5267 * std(background_noise), where background_noise is estimated 701 from some part of the image known to contain no signal (only noise). 702 703 Returns 704 ------- 705 fw_params : ndarray (x, y, z, 13) 706 Matrix containing in the dimension the free water model parameters in 707 the following order: 708 1) Three diffusion tensor's eigenvalues 709 2) Three lines of the eigenvector matrix each containing the 710 first, second and third coordinates of the eigenvector 711 3) The volume fraction of the free water compartment 712 """ 713 fw_params = np.zeros(data.shape[:-1] + (13,)) 714 W = design_matrix(gtab) 715 716 # Prepare mask 717 if mask is None: 718 mask = np.ones(data.shape[:-1], dtype=bool) 719 else: 720 if mask.shape != data.shape[:-1]: 721 raise ValueError("Mask is not the same shape as data.") 722 mask = np.array(mask, dtype=bool, copy=False) 723 724 # Prepare S0 725 S0 = np.mean(data[:, :, :, gtab.b0s_mask], axis=-1) 726 727 index = ndindex(mask.shape) 728 for v in index: 729 if mask[v]: 730 params = nls_iter(W, data[v], S0[v], Diso=Diso, mdreg=mdreg, 731 min_signal=min_signal, f_transform=f_transform, 732 cholesky=cholesky, jac=jac, weighting=weighting, 733 sigma=sigma) 734 fw_params[v] = params 735 736 return fw_params 737 738 739def lower_triangular_to_cholesky(tensor_elements): 740 """ Performs Cholesky decomposition of the diffusion tensor 741 742 Parameters 743 ---------- 744 tensor_elements : array (6,) 745 Array containing the six elements of diffusion tensor's lower 746 triangular. 747 748 Returns 749 ------- 750 cholesky_elements : array (6,) 751 Array containing the six Cholesky's decomposition elements 752 (R0, R1, R2, R3, R4, R5) [1]_. 753 754 References 755 ---------- 756 .. [1] Koay, C.G., Carew, J.D., Alexander, A.L., Basser, P.J., 757 Meyerand, M.E., 2006. Investigation of anomalous estimates of 758 tensor-derived quantities in diffusion tensor imaging. Magnetic 759 Resonance in Medicine, 55(4), 930-936. doi:10.1002/mrm.20832 760 """ 761 R0 = np.sqrt(tensor_elements[0]) 762 R3 = tensor_elements[1] / R0 763 R1 = np.sqrt(tensor_elements[2] - R3**2) 764 R5 = tensor_elements[3] / R0 765 R4 = (tensor_elements[4] - R3*R5) / R1 766 R2 = np.sqrt(tensor_elements[5] - R4**2 - R5**2) 767 768 return np.array([R0, R1, R2, R3, R4, R5]) 769 770 771def cholesky_to_lower_triangular(R): 772 """ Convert Cholesky decompostion elements to the diffusion tensor elements 773 774 Parameters 775 ---------- 776 R : array (6,) 777 Array containing the six Cholesky's decomposition elements 778 (R0, R1, R2, R3, R4, R5) [1]_. 779 780 Returns 781 ------- 782 tensor_elements : array (6,) 783 Array containing the six elements of diffusion tensor's lower 784 triangular. 785 786 References 787 ---------- 788 .. [1] Koay, C.G., Carew, J.D., Alexander, A.L., Basser, P.J., 789 Meyerand, M.E., 2006. Investigation of anomalous estimates of 790 tensor-derived quantities in diffusion tensor imaging. Magnetic 791 Resonance in Medicine, 55(4), 930-936. doi:10.1002/mrm.20832 792 """ 793 Dxx = R[0]**2 794 Dxy = R[0]*R[3] 795 Dyy = R[1]**2 + R[3]**2 796 Dxz = R[0]*R[5] 797 Dyz = R[1]*R[4] + R[3]*R[5] 798 Dzz = R[2]**2 + R[4]**2 + R[5]**2 799 return np.array([Dxx, Dxy, Dyy, Dxz, Dyz, Dzz]) 800 801 802common_fit_methods = {'WLLS': wls_iter, 803 'WLS': wls_iter, 804 'NLLS': nls_iter, 805 'NLS': nls_iter, 806 } 807