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