def __init__(self, gtab, response, sh_order, lambda_=1, tau=0.1): super(NumberSmallfODF, self).__init__() m, n = sph_harm_ind_list(sh_order) # x, y, z = gtab.gradients[~gtab.b0s_mask].T # r, theta, phi = cart2sphere(x, y, z) # self.B_dwi = real_sph_harm(m, n, theta[:, None], phi[:, None]) self.B_dwi = shm.get_B_matrix(gtab, sh_order) self.sphere = get_sphere('symmetric362') r, theta, phi = cart2sphere(self.sphere.x, self.sphere.y, self.sphere.z) self.B_reg = real_sph_harm(m, n, theta[:, None], phi[:, None]) S_r = shm.estimate_response(gtab, response[0:3], response[3]) r_sh = np.linalg.lstsq(self.B_dwi, S_r[~gtab.b0s_mask], rcond=-1)[0] n_response = n m_response = m r_rh = sh_to_rh(r_sh, m_response, n_response) R = forward_sdeconv_mat(r_rh, n) # scale lambda_ to account for differences in the number of # SH coefficients and number of mapped directions # This is exactly what is done in [4]_ lambda_ = (lambda_ * R.shape[0] * r_rh[0] / (np.sqrt(self.B_reg.shape[0]) * np.sqrt(362.))) self.B_reg = self.B_reg * lambda_ self.B_reg = nn.Parameter(torch.FloatTensor(self.B_reg), requires_grad=False) self.tau = tau
def get_deconv_matrix(gtab, response, sh_order): """ Compute the deconvolution of the response of a single fiber Attributes: gtab (dipy GradientTable): the gradient of the dwi in wich the response is represented. response (float[4]): first 3 elements: eigenvalues, 4th: mean b0 value sh_order (int): the sh_order of the DWI used for the deconvolution Returns: R: r_rh: B_dwi (np.array(nb_sh_coeff, nb_dwi_directions)): matrix to fit DWi->SH """ m, n = sph_harm_ind_list(sh_order) # x, y, z = gtab.gradients[~gtab.b0s_mask].T # r, theta, phi = cart2sphere(x, y, z) # # for the gradient sphere # B_dwi = real_sph_harm(m, n, theta[:, None], phi[:, None]) B_dwi, _ = get_B_matrix(gtab, sh_order) S_r = estimate_response(gtab, response[0:3], response[3]) r_sh = np.linalg.lstsq(B_dwi, S_r[~gtab.b0s_mask], rcond=-1)[0] n_response = n m_response = m r_rh = sh_to_rh(r_sh, m_response, n_response) R = forward_sdeconv_mat(r_rh, n) # X = R.diagonal() * B_dwi return R, r_rh, B_dwi
def __init__(self, gtab, response, reg_sphere=None, sh_order=8, lambda_=1, tau=0.1, convergence=50): r""" Constrained Spherical Deconvolution (CSD) [1]_. Spherical deconvolution computes a fiber orientation distribution (FOD), also called fiber ODF (fODF) [2]_, as opposed to a diffusion ODF as the QballModel or the CsaOdfModel. This results in a sharper angular profile with better angular resolution that is the best object to be used for later deterministic and probabilistic tractography [3]_. A sharp fODF is obtained because a single fiber *response* function is injected as *a priori* knowledge. The response function is often data-driven and is thus provided as input to the ConstrainedSphericalDeconvModel. It will be used as deconvolution kernel, as described in [1]_. Parameters ---------- gtab : GradientTable response : tuple or AxSymShResponse object A tuple with two elements. The first is the eigen-values as an (3,) ndarray and the second is the signal value for the response function without diffusion weighting. This is to be able to generate a single fiber synthetic signal. The response function will be used as deconvolution kernel ([1]_) reg_sphere : Sphere (optional) sphere used to build the regularization B matrix. Default: 'symmetric362'. sh_order : int (optional) maximal spherical harmonics order. Default: 8 lambda_ : float (optional) weight given to the constrained-positivity regularization part of the deconvolution equation (see [1]_). Default: 1 tau : float (optional) threshold controlling the amplitude below which the corresponding fODF is assumed to be zero. Ideally, tau should be set to zero. However, to improve the stability of the algorithm, tau is set to tau*100 % of the mean fODF amplitude (here, 10% by default) (see [1]_). Default: 0.1 convergence : int Maximum number of iterations to allow the deconvolution to converge. References ---------- .. [1] Tournier, J.D., et al. NeuroImage 2007. Robust determination of the fibre orientation distribution in diffusion MRI: Non-negativity constrained super-resolved spherical deconvolution .. [2] Descoteaux, M., et al. IEEE TMI 2009. Deterministic and Probabilistic Tractography Based on Complex Fibre Orientation Distributions .. [3] Côté, M-A., et al. Medical Image Analysis 2013. Tractometer: Towards validation of tractography pipelines .. [4] Tournier, J.D, et al. Imaging Systems and Technology 2012. MRtrix: Diffusion Tractography in Crossing Fiber Regions """ # Initialize the parent class: SphHarmModel.__init__(self, gtab) m, n = sph_harm_ind_list(sh_order) self.m, self.n = m, n self._where_b0s = lazy_index(gtab.b0s_mask) self._where_dwi = lazy_index(~gtab.b0s_mask) no_params = ((sh_order + 1) * (sh_order + 2)) / 2 if no_params > np.sum(~gtab.b0s_mask): msg = "Number of parameters required for the fit are more " msg += "than the actual data points" warnings.warn(msg, UserWarning) x, y, z = gtab.gradients[self._where_dwi].T r, theta, phi = cart2sphere(x, y, z) # for the gradient sphere self.B_dwi = real_sph_harm(m, n, theta[:, None], phi[:, None]) # for the sphere used in the regularization positivity constraint if reg_sphere is None: self.sphere = small_sphere else: self.sphere = reg_sphere r, theta, phi = cart2sphere(self.sphere.x, self.sphere.y, self.sphere.z) self.B_reg = real_sph_harm(m, n, theta[:, None], phi[:, None]) if response is None: response = (np.array([0.0015, 0.0003, 0.0003]), 1) self.response = response if isinstance(response, AxSymShResponse): r_sh = response.dwi_response self.response_scaling = response.S0 n_response = response.n m_response = response.m else: self.S_r = estimate_response(gtab, self.response[0], self.response[1]) r_sh = np.linalg.lstsq(self.B_dwi, self.S_r[self._where_dwi], rcond=-1)[0] n_response = n m_response = m self.response_scaling = response[1] r_rh = sh_to_rh(r_sh, m_response, n_response) self.R = forward_sdeconv_mat(r_rh, n) # scale lambda_ to account for differences in the number of # SH coefficients and number of mapped directions # This is exactly what is done in [4]_ lambda_ = (lambda_ * self.R.shape[0] * r_rh[0] / (np.sqrt(self.B_reg.shape[0]) * np.sqrt(362.))) self.B_reg *= lambda_ self.sh_order = sh_order self.tau = tau self.convergence = convergence self._X = X = self.R.diagonal() * self.B_dwi self._P = np.dot(X.T, X)
def __init__(self, gtab, response, reg_sphere=None, sh_order=8, lambda_=1, tau=0.1): r""" Constrained Spherical Deconvolution (CSD) [1]_. Spherical deconvolution computes a fiber orientation distribution (FOD), also called fiber ODF (fODF) [2]_, as opposed to a diffusion ODF as the QballModel or the CsaOdfModel. This results in a sharper angular profile with better angular resolution that is the best object to be used for later deterministic and probabilistic tractography [3]_. A sharp fODF is obtained because a single fiber *response* function is injected as *a priori* knowledge. The response function is often data-driven and is thus provided as input to the ConstrainedSphericalDeconvModel. It will be used as deconvolution kernel, as described in [1]_. Parameters ---------- gtab : GradientTable response : tuple or AxSymShResponse object A tuple with two elements. The first is the eigen-values as an (3,) ndarray and the second is the signal value for the response function without diffusion weighting. This is to be able to generate a single fiber synthetic signal. The response function will be used as deconvolution kernel ([1]_) reg_sphere : Sphere (optional) sphere used to build the regularization B matrix. Default: 'symmetric362'. sh_order : int (optional) maximal spherical harmonics order. Default: 8 lambda_ : float (optional) weight given to the constrained-positivity regularization part of the deconvolution equation (see [1]_). Default: 1 tau : float (optional) threshold controlling the amplitude below which the corresponding fODF is assumed to be zero. Ideally, tau should be set to zero. However, to improve the stability of the algorithm, tau is set to tau*100 % of the mean fODF amplitude (here, 10% by default) (see [1]_). Default: 0.1 References ---------- .. [1] Tournier, J.D., et al. NeuroImage 2007. Robust determination of the fibre orientation distribution in diffusion MRI: Non-negativity constrained super-resolved spherical deconvolution .. [2] Descoteaux, M., et al. IEEE TMI 2009. Deterministic and Probabilistic Tractography Based on Complex Fibre Orientation Distributions .. [3] C\^ot\'e, M-A., et al. Medical Image Analysis 2013. Tractometer: Towards validation of tractography pipelines .. [4] Tournier, J.D, et al. Imaging Systems and Technology 2012. MRtrix: Diffusion Tractography in Crossing Fiber Regions """ # Initialize the parent class: SphHarmModel.__init__(self, gtab) m, n = sph_harm_ind_list(sh_order) self.m, self.n = m, n self._where_b0s = lazy_index(gtab.b0s_mask) self._where_dwi = lazy_index(~gtab.b0s_mask) no_params = ((sh_order + 1) * (sh_order + 2)) / 2 if no_params > np.sum(~gtab.b0s_mask): msg = "Number of parameters required for the fit are more " msg += "than the actual data points" warnings.warn(msg, UserWarning) x, y, z = gtab.gradients[self._where_dwi].T r, theta, phi = cart2sphere(x, y, z) # for the gradient sphere self.B_dwi = real_sph_harm(m, n, theta[:, None], phi[:, None]) # for the sphere used in the regularization positivity constraint if reg_sphere is None: self.sphere = small_sphere else: self.sphere = reg_sphere r, theta, phi = cart2sphere( self.sphere.x, self.sphere.y, self.sphere.z ) self.B_reg = real_sph_harm(m, n, theta[:, None], phi[:, None]) if response is None: response = (np.array([0.0015, 0.0003, 0.0003]), 1) self.response = response if isinstance(response, AxSymShResponse): r_sh = response.dwi_response self.response_scaling = response.S0 n_response = response.n m_response = response.m else: self.S_r = estimate_response(gtab, self.response[0], self.response[1]) r_sh = np.linalg.lstsq(self.B_dwi, self.S_r[self._where_dwi])[0] n_response = n m_response = m self.response_scaling = response[1] r_rh = sh_to_rh(r_sh, m_response, n_response) self.R = forward_sdeconv_mat(r_rh, n) # scale lambda_ to account for differences in the number of # SH coefficients and number of mapped directions # This is exactly what is done in [4]_ lambda_ = (lambda_ * self.R.shape[0] * r_rh[0] / (np.sqrt(self.B_reg.shape[0]) * np.sqrt(362.))) self.B_reg *= lambda_ self.sh_order = sh_order self.tau = tau self._X = X = self.R.diagonal() * self.B_dwi self._P = np.dot(X.T, X)
def csd_predict(sh_coeff, gtab, response=None, S0=1, R=None): """ Compute a signal prediction given spherical harmonic coefficients and (optionally) a response function for the provided GradientTable class instance Parameters ---------- sh_coeff : ndarray Spherical harmonic coefficients gtab : GradientTable class instance response : tuple A tuple with two elements. The first is the eigen-values as an (3,) ndarray and the second is the signal value for the response function without diffusion weighting. Default: (np.array([0.0015, 0.0003, 0.0003]), 1) S0 : ndarray or float The non diffusion-weighted signal value. R : ndarray Optionally, provide an R matrix. If not provided, calculated from the gtab, response function, etc. Returns ------- pred_sig : ndarray The signal predicted from the provided SH coefficients for a measurement with the provided GradientTable. The last dimension of the resulting array is the same as the number of bvals/bvecs in the GradientTable. The first dimensions have shape: `sh_coeff.shape[:-1]`. """ n_coeff = sh_coeff.shape[-1] sh_order = order_from_ncoef(n_coeff) x, y, z = gtab.gradients[~gtab.b0s_mask].T r, theta, phi = cart2sphere(x, y, z) SH_basis, m, n = real_sym_sh_basis(sh_order, theta, phi) if R is None: # for the gradient sphere B_dwi = real_sph_harm(m, n, theta[:, None], phi[:, None]) if response is None: response = (np.array([0.0015, 0.0003, 0.0003]), 1) else: response = response S_r = estimate_response(gtab, response[0], response[1]) r_sh = np.linalg.lstsq(B_dwi, S_r[~gtab.b0s_mask])[0] r_rh = sh_to_rh(r_sh, m, n) R = forward_sdeconv_mat(r_rh, n) predict_matrix = np.dot(SH_basis, R) if np.iterable(S0): # If it's an array, we need to give it one more dimension: S0 = S0[..., None] # This is the key operation: convolve and multiply by S0: pre_pred_sig = S0 * np.dot(predict_matrix, sh_coeff) # Now put everything in its right place: pred_sig = np.zeros(pre_pred_sig.shape[:-1] + (gtab.bvals.shape[0],)) pred_sig[..., ~gtab.b0s_mask] = pre_pred_sig pred_sig[..., gtab.b0s_mask] = S0 return pred_sig