def precompute_rotation_matrices(lmax, ndirs): """Precompute the rotation matrices to rotate the high-resolution kernels (500 directions/shell). Parameters ---------- lmax : int Maximum SH order to use for the rotation phase (default : 12) ndirs : int Number of directions on the half of the sphere representing the possible orientations of the response functions (default : 32761) """ if not isdir(dipy_home): makedirs(dipy_home) filename = pjoin( dipy_home, 'AMICO_aux_matrices_lmax=%d_ndirs=%d.pickle' % (lmax, ndirs)) if isfile(filename): return directions = load_directions(ndirs) LOG('\n-> Precomputing rotation matrices for l_max=%d:' % lmax) AUX = {} AUX['lmax'] = lmax AUX['ndirs'] = ndirs # matrix to fit the SH coefficients _, theta, phi = cart2sphere(grad[:, 0], grad[:, 1], grad[:, 2]) tmp, _, _ = real_sym_sh_basis(lmax, theta, phi) AUX['fit'] = np.dot(np.linalg.pinv(np.dot(tmp.T, tmp)), tmp.T) # matrices to rotate the functions in SH space AUX['Ylm_rot'] = np.zeros(ndirs, dtype=np.object) for i in range(ndirs): _, theta, phi = cart2sphere(directions[i][0], directions[i][1], directions[i][2]) tmp, _, _ = real_sym_sh_basis(lmax, theta, phi) AUX['Ylm_rot'][i] = tmp.reshape(-1) # auxiliary data to perform rotations AUX['const'] = np.zeros(AUX['fit'].shape[0], dtype=np.float64) AUX['idx_m0'] = np.zeros(AUX['fit'].shape[0], dtype=np.int32) i = 0 for l in range(0, AUX['lmax'] + 1, 2): const = np.sqrt(4.0 * np.pi / (2.0 * l + 1.0)) idx_m0 = (l * l + l + 2.0) / 2.0 - 1 for m in range(-l, l + 1): AUX['const'][i] = const AUX['idx_m0'][i] = idx_m0 i += 1 with open(filename, 'wb+') as fid: pickle.dump(AUX, fid, protocol=2) LOG(' [ DONE ]')
def __init__(self, data, model, sphere, sh_order=None, tol=1e-2): if sh_order is None: if hasattr(model, "sh_order"): sh_order = model.sh_order else: sh_order = default_SH self.where_dwi = shm.lazy_index(~model.gtab.b0s_mask) if not isinstance(self.where_dwi, slice): msg = ("For optimal bootstrap tracking consider reordering the " "diffusion volumes so that all the b0 volumes are at the " "beginning") warn(msg) x, y, z = model.gtab.gradients[self.where_dwi].T r, theta, phi = cart2sphere(x, y, z) b_range = (r.max() - r.min()) / r.min() if b_range > tol: raise ValueError("BootOdfGen only supports single shell data") B, m, n = shm.real_sym_sh_basis(sh_order, theta, phi) H = shm.hat(B) R = shm.lcr_matrix(H) self.data = np.asarray(data, "float64") self.model = model self.sphere = sphere self.H = H self.R = R
def aux_structures_resample( scheme, lmax = 12 ) : """Compute the auxiliary data structures to resample the kernels to the original acquisition scheme. Parameters ---------- scheme : Scheme class Acquisition scheme of the acquired signal lmax : int Maximum SH order to use for the rotation phase (default : 12) Returns ------- idx_OUT : numpy array Indices of the samples belonging to each shell Ylm_OUT : numpy array Operator to transform each shell from Spherical harmonics to original signal space """ nSH = (lmax+1)*(lmax+2)/2 idx_OUT = np.zeros( scheme.dwi_count, dtype=np.int32 ) Ylm_OUT = np.zeros( (scheme.dwi_count,nSH*len(scheme.shells)), dtype=np.float32 ) # matrix from SH to real space idx = 0 for s in xrange( len(scheme.shells) ) : nS = len( scheme.shells[s]['idx'] ) idx_OUT[ idx:idx+nS ] = scheme.shells[s]['idx'] _, theta, phi = cart2sphere( scheme.shells[s]['grad'][:,0], scheme.shells[s]['grad'][:,1], scheme.shells[s]['grad'][:,2] ) tmp, _, _ = real_sym_sh_basis( lmax, theta, phi ) Ylm_OUT[ idx:idx+nS, nSH*s:nSH*(s+1) ] = tmp idx += nS return ( idx_OUT, Ylm_OUT )
def __init__(self, *args, sh_order=6, smooth=0.006, **kwargs): ModelHARDI.__init__(self, *args, **kwargs) c = self.constvars x, y, z = self.data.gtab.gradients[self.data.gtab.bvals > 0].T r, theta, phi = cart2sphere(x, y, z) B, m, n = real_sym_sh_basis(sh_order, theta[:, None], phi[:, None]) L = -n * (n + 1) legendre0 = lpn(sh_order, 0)[0] F = legendre0[n] self.sh_order = sh_order self.B = B self.m = m self.n = n self._fit_matrix_fw = (F * L) / (8 * np.pi) c['Y'] = np.ascontiguousarray(self.B) c['l_shm'] = c['Y'].shape[1] c['M'] = np.zeros(self._fit_matrix_fw.shape) c['M'][1:] = 1.0 / self._fit_matrix_fw[1:] assert (c['M'].size == c['l_shm']) c['YM'] = np.einsum('lk,k->lk', c['Y'], c['M']) logging.info("HARDI SHM setup ({l_labels} labels, {l_shm} shm; " \ "img: {imagedims}; lambda={lbd:.3g}) ready.".format( lbd=c['lbd'], l_labels=c['l_labels'], l_shm=c['l_shm'], imagedims="x".join(map(str,c['imagedims']))))
def aux_structures_resample(scheme, lmax=12): """Compute the auxiliary data structures to resample the kernels to the original acquisition scheme. Parameters ---------- scheme : Scheme class Acquisition scheme of the acquired signal lmax : int Maximum SH order to use for the rotation phase (default : 12) Returns ------- idx_OUT : numpy array Indices of the samples belonging to each shell Ylm_OUT : numpy array Operator to transform each shell from Spherical harmonics to original signal space """ nSH = (lmax + 1) * (lmax + 2) // 2 idx_OUT = np.zeros(scheme.dwi_count, dtype=np.int32) Ylm_OUT = np.zeros((scheme.dwi_count, nSH * len(scheme.shells)), dtype=np.float32) # matrix from SH to real space idx = 0 for s in range(len(scheme.shells)): nS = len(scheme.shells[s]['idx']) idx_OUT[idx:idx + nS] = scheme.shells[s]['idx'] _, theta, phi = cart2sphere(scheme.shells[s]['grad'][:, 0], scheme.shells[s]['grad'][:, 1], scheme.shells[s]['grad'][:, 2]) tmp, _, _ = real_sym_sh_basis(lmax, theta, phi) Ylm_OUT[idx:idx + nS, nSH * s:nSH * (s + 1)] = tmp idx += nS return (idx_OUT, Ylm_OUT)
def test_real_sym_sh_basis(): new_order = [0, 5, 4, 3, 2, 1, 14, 13, 12, 11, 10, 9, 8, 7, 6] sphere = hemi_icosahedron.subdivide(2) with warnings.catch_warnings(): warnings.filterwarnings("ignore") basis, m, n = real_sym_sh_mrtrix(4, sphere.theta, sphere.phi) expected = basis[:, new_order] expected *= np.where(m == 0, 1., np.sqrt(2)) with warnings.catch_warnings(record=True) as w: descoteaux07_basis, m, n = real_sym_sh_basis(4, sphere.theta, sphere.phi) npt.assert_equal(len(w), 2) npt.assert_(issubclass(w[0].category, DeprecationWarning)) npt.assert_( "dipy.reconst.shm.real_sym_sh_basis is deprecated, Please use " "dipy.reconst.shm.real_sh_descoteaux instead" in str(w[0].message)) npt.assert_(issubclass(w[1].category, PendingDeprecationWarning)) npt.assert_(descoteaux07_legacy_msg in str(w[1].message)) assert_array_almost_equal(descoteaux07_basis, expected)
def test_default_lambda_csdmodel(): """We check that the default value of lambda is the expected value with the symmetric362 sphere. This value has empirically been found to work well and changes to this default value should be discussed with the dipy team. """ expected_lambda = {4: 27.5230088, 8: 82.5713865, 16: 216.0843135} expected_warnings = {4: 0, 8: 0, 16: 1} sphere = default_sphere # Create gradient table _, fbvals, fbvecs = get_fnames('small_64D') bvals, bvecs = read_bvals_bvecs(fbvals, fbvecs) gtab = gradient_table(bvals, bvecs) # Some response function response = (np.array([0.0015, 0.0003, 0.0003]), 100) for sh_order, expected, e_warn in zip(expected_lambda.keys(), expected_lambda.values(), expected_warnings.values()): with warnings.catch_warnings(record=True) as w: model_full = ConstrainedSphericalDeconvModel(gtab, response, sh_order=sh_order, reg_sphere=sphere) npt.assert_equal(len(w), e_warn) if e_warn: npt.assert_(issubclass(w[0].category, UserWarning)) npt.assert_("Number of parameters required " in str(w[0]. message)) B_reg, _, _ = real_sym_sh_basis(sh_order, sphere.theta, sphere.phi) npt.assert_array_almost_equal(model_full.B_reg, expected * B_reg)
def precompute_rotation_matrices(lmax=12): """Precompute the rotation matrices to rotate the high-resolution kernels (500 directions/shell). Parameters ---------- lmax : int Maximum SH order to use for the rotation phase (default : 12) """ if not isdir(dipy_home): makedirs(dipy_home) filename = pjoin(dipy_home, 'AMICO_aux_matrices_lmax=%d.pickle' % lmax) if isfile(filename): return print('\n-> Precomputing rotation matrices for l_max=%d:' % lmax) AUX = {} AUX['lmax'] = lmax # matrix to fit the SH coefficients _, theta, phi = cart2sphere(grad[:, 0], grad[:, 1], grad[:, 2]) tmp, _, _ = real_sym_sh_basis(lmax, theta, phi) AUX['fit'] = np.dot(np.linalg.pinv(np.dot(tmp.T, tmp)), tmp.T) # matrices to rotate the functions in SH space AUX['Ylm_rot'] = np.zeros((181, 181), dtype=np.object) for ox in range(181): for oy in range(181): tmp, _, _ = real_sym_sh_basis(lmax, ox / 180.0 * np.pi, oy / 180.0 * np.pi) AUX['Ylm_rot'][ox, oy] = tmp.reshape(-1) # auxiliary data to perform rotations AUX['const'] = np.zeros(AUX['fit'].shape[0], dtype=np.float64) AUX['idx_m0'] = np.zeros(AUX['fit'].shape[0], dtype=np.int32) i = 0 for l in range(0, AUX['lmax'] + 1, 2): const = np.sqrt(4.0 * np.pi / (2.0 * l + 1.0)) idx_m0 = (l * l + l + 2.0) / 2.0 - 1 for m in range(-l, l + 1): AUX['const'][i] = const AUX['idx_m0'][i] = idx_m0 i += 1 with open(filename, 'wb+') as fid: pickle.dump(AUX, fid, protocol=2) print(' [ DONE ]')
def precompute_rotation_matrices( lmax = 12 ) : """Precompute the rotation matrices to rotate the high-resolution kernels (500 directions/shell). Parameters ---------- lmax : int Maximum SH order to use for the rotation phase (default : 12) """ if not isdir(dipy_home) : makedirs(dipy_home) filename = pjoin( dipy_home, 'AMICO_aux_matrices_lmax=%d.pickle'%lmax ) if isfile( filename ) : return print '\n-> Precomputing rotation matrices for l_max=%d:' % lmax AUX = {} AUX['lmax'] = lmax # matrix to fit the SH coefficients _, theta, phi = cart2sphere( grad[:,0], grad[:,1], grad[:,2] ) tmp, _, _ = real_sym_sh_basis( lmax, theta, phi ) AUX['fit'] = np.dot( np.linalg.pinv( np.dot(tmp.T,tmp) ), tmp.T ) # matrices to rotate the functions in SH space AUX['Ylm_rot'] = np.zeros( (181,181), dtype=np.object ) for ox in xrange(181) : for oy in xrange(181) : tmp, _, _ = real_sym_sh_basis( lmax, ox/180.0*np.pi, oy/180.0*np.pi ) AUX['Ylm_rot'][ox,oy] = tmp.reshape(-1) # auxiliary data to perform rotations AUX['const'] = np.zeros( AUX['fit'].shape[0], dtype=np.float64 ) AUX['idx_m0'] = np.zeros( AUX['fit'].shape[0], dtype=np.int32 ) i = 0 for l in xrange(0,AUX['lmax']+1,2) : const = np.sqrt(4.0*np.pi/(2.0*l+1.0)) idx_m0 = (l*l + l + 2.0)/2.0 - 1 for m in xrange(-l,l+1) : AUX['const'][i] = const AUX['idx_m0'][i] = idx_m0 i += 1 with open( filename, 'wb+' ) as fid : cPickle.dump( AUX, fid, protocol=2 ) print ' [ DONE ]'
def test_sh_to_sf_matrix(): sphere = Sphere(xyz=hemi_icosahedron.vertices) B1, invB1 = sh_to_sf_matrix(sphere) B2, m, n = real_sym_sh_basis(4, sphere.theta, sphere.phi) invB2 = smooth_pinv(B2, L=np.zeros_like(n)) B3 = sh_to_sf_matrix(sphere, return_inv=False) assert_array_almost_equal(B1, B2.T) assert_array_almost_equal(invB1, invB2.T) assert_array_almost_equal(B3, B1) assert_raises(ValueError, sh_to_sf_matrix, sphere, basis_type="")
def test_real_sym_sh_basis(): # This test should do for now # The mrtrix basis should be the same as re-ordering and re-scaling the # fibernav basis new_order = [0, 5, 4, 3, 2, 1, 14, 13, 12, 11, 10, 9, 8, 7, 6] sphere = hemi_icosahedron.subdivide(2) basis, m, n = real_sym_sh_mrtrix(4, sphere.theta, sphere.phi) expected = basis[:, new_order] expected *= np.where(m == 0, 1., np.sqrt(2)) fibernav_basis, m, n = real_sym_sh_basis(4, sphere.theta, sphere.phi) assert_array_almost_equal(fibernav_basis, expected)
def test_real_sym_sh_basis(): # This test should do for now # The tournier07 basis should be the same as re-ordering and re-scaling the # descoteaux07 basis new_order = [0, 5, 4, 3, 2, 1, 14, 13, 12, 11, 10, 9, 8, 7, 6] sphere = hemi_icosahedron.subdivide(2) basis, m, n = real_sym_sh_mrtrix(4, sphere.theta, sphere.phi) expected = basis[:, new_order] expected *= np.where(m == 0, 1., np.sqrt(2)) descoteaux07_basis, m, n = real_sym_sh_basis(4, sphere.theta, sphere.phi) assert_array_almost_equal(descoteaux07_basis, expected)
def test_bdg_residual(): """This tests the bootstrapping residual. """ hsph_updated = HemiSphere.from_sphere(unit_icosahedron).subdivide(2) vertices = hsph_updated.vertices bvecs = vertices bvals = np.ones(len(vertices)) * 1000 bvecs = np.insert(bvecs, 0, np.array([0, 0, 0]), axis=0) bvals = np.insert(bvals, 0, 0) gtab = gradient_table(bvals, bvecs) r, theta, phi = cart2sphere(*vertices.T) B, m, n = shm.real_sym_sh_basis(6, theta, phi) shm_coeff = np.random.random(B.shape[1]) # sphere_func is sampled of the spherical function for each point of # the sphere sphere_func = np.dot(shm_coeff, B.T) voxel = np.concatenate((np.zeros(1), sphere_func)) data = np.tile(voxel, (3, 3, 3, 1)) csd_model = ConstrainedSphericalDeconvModel(gtab, response, sh_order=6) boot_pmf_gen = BootPmfGen(data, model=csd_model, sphere=hsph_updated, sh_order=6) # Two boot samples should be the same odf1 = boot_pmf_gen.get_pmf(np.array([1.5, 1.5, 1.5])) odf2 = boot_pmf_gen.get_pmf(np.array([1.5, 1.5, 1.5])) npt.assert_array_almost_equal(odf1, odf2) # A boot sample with less sh coeffs should have residuals, thus the two # should be different boot_pmf_gen2 = BootPmfGen(data, model=csd_model, sphere=hsph_updated, sh_order=4) odf1 = boot_pmf_gen2.get_pmf(np.array([1.5, 1.5, 1.5])) odf2 = boot_pmf_gen2.get_pmf(np.array([1.5, 1.5, 1.5])) npt.assert_(np.any(odf1 != odf2)) # test with a gtab with two shells and assert you get an error bvals[-1] = 2000 gtab = gradient_table(bvals, bvecs) csd_model = ConstrainedSphericalDeconvModel(gtab, response, sh_order=6) npt.assert_raises(ValueError, BootPmfGen, data, csd_model, hsph_updated, 6)
def test_bdg_residual(): """This tests the bootstrapping residual. """ hsph_updated = HemiSphere.from_sphere(unit_icosahedron).subdivide(2) vertices = hsph_updated.vertices bvecs = vertices bvals = np.ones(len(vertices)) * 1000 bvecs = np.insert(bvecs, 0, np.array([0, 0, 0]), axis=0) bvals = np.insert(bvals, 0, 0) gtab = gradient_table(bvals, bvecs) r, theta, phi = cart2sphere(*vertices.T) B, m, n = shm.real_sym_sh_basis(6, theta, phi) shm_coeff = np.random.random(B.shape[1]) # sphere_func is sampled of the spherical function for each point of # the sphere sphere_func = np.dot(shm_coeff, B.T) voxel = np.concatenate((np.zeros(1), sphere_func)) data = np.tile(voxel, (3, 3, 3, 1)) csd_model = ConstrainedSphericalDeconvModel(gtab, None, sh_order=6) boot_pmf_gen = BootPmfGen(data, model=csd_model, sphere=hsph_updated, sh_order=6) # Two boot samples should be the same odf1 = boot_pmf_gen.get_pmf(np.array([1.5, 1.5, 1.5])) odf2 = boot_pmf_gen.get_pmf(np.array([1.5, 1.5, 1.5])) npt.assert_array_almost_equal(odf1, odf2) # A boot sample with less sh coeffs should have residuals, thus the two # should be different boot_pmf_gen2 = BootPmfGen(data, model=csd_model, sphere=hsph_updated, sh_order=4) odf1 = boot_pmf_gen2.get_pmf(np.array([1.5, 1.5, 1.5])) odf2 = boot_pmf_gen2.get_pmf(np.array([1.5, 1.5, 1.5])) npt.assert_(np.any(odf1 != odf2)) # test with a gtab with two shells and assert you get an error bvals[-1] = 2000 gtab = gradient_table(bvals, bvecs) csd_model = ConstrainedSphericalDeconvModel(gtab, None, sh_order=6) npt.assert_raises(ValueError, BootPmfGen, data, csd_model, hsph_updated, 6)
def predict(self, sh_coeff, gtab=None, S0=1): """Compute a signal prediction given spherical harmonic coefficients and (optionally) a response function for the provided GradientTable class instance. Parameters ---------- sh_coeff : ndarray The spherical harmonic representation of the FOD from which to make the signal prediction. gtab : GradientTable The gradients for which the signal will be predicted. Use the model's gradient table by default. S0 : ndarray or float The non diffusion-weighted signal value. Returns ------- pred_sig : ndarray The predicted signal. """ if gtab is None or gtab is self.gtab: SH_basis = self.B_dwi gtab = self.gtab else: x, y, z = gtab.gradients[~gtab.b0s_mask].T r, theta, phi = cart2sphere(x, y, z) SH_basis, m, n = real_sym_sh_basis(self.sh_order, theta, phi) # Because R is diagonal, the matrix multiply is written as a multiply predict_matrix = SH_basis * self.R.diagonal() S0 = np.asarray(S0)[..., None] scaling = S0 / self.response_scaling # This is the key operation: convolve and multiply by S0: pre_pred_sig = scaling * 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
def test_default_lambda_csdmodel(): """We check that the default value of lambda is the expected value with the symmetric362 sphere. This value has empirically been found to work well and changes to this default value should be discussed with the dipy team. """ sphere = default_sphere # Create gradient table _, fbvals, fbvecs = get_fnames('small_64D') bvals, bvecs = read_bvals_bvecs(fbvals, fbvecs) gtab = gradient_table(bvals, bvecs) # Some response function response = (np.array([0.0015, 0.0003, 0.0003]), 100) for sh_order, expected in expected_lambda.items(): model_full = ConstrainedSphericalDeconvModel(gtab, response, sh_order=sh_order, reg_sphere=sphere) B_reg, _, _ = real_sym_sh_basis(sh_order, sphere.theta, sphere.phi) npt.assert_array_almost_equal(model_full.B_reg, expected * B_reg)
def test_default_lambda_csdmodel(): """We check that the default value of lambda is the expected value with the symmetric362 sphere. This value has empirically been found to work well and changes to this default value should be discusses with the dipy team. """ sphere = get_sphere("symmetric362") # Create gradient table _, fbvals, fbvecs = get_data("small_64D") bvals = np.load(fbvals) bvecs = np.load(fbvecs) gtab = gradient_table(bvals, bvecs) # Some response function response = (np.array([0.0015, 0.0003, 0.0003]), 100) for sh_order, expected in expected_lambda.items(): model_full = ConstrainedSphericalDeconvModel(gtab, response, sh_order=sh_order, reg_sphere=sphere) B_reg, _, _ = real_sym_sh_basis(sh_order, sphere.theta, sphere.phi) npt.assert_array_almost_equal(model_full.B_reg, expected * B_reg)
def get_B_matrix(gtab=None, sh_order=8, theta=None, phi=None, smooth=0.006): """Function that return matrices for Spherical Harmonics fitting Attributes: gtab (dipy GradientTable): the gradient of the dwi to be fitted sh_order (int): Order of the Spherical Harmonics theta: custom gradient angle used instead of gtab in combinaison w/ phi phi: same as theta smooth (float): the smoothing parameter for the laplacian Returns: B (np.array(nb_dwi_directions, nb_sh_coeff)): matrix to fit SH->DWI invB (np.array(nb_sh_coeff, nb_dwi_directions)): matrix to fit DWI->SH """ if theta is None or phi is None: x, y, z = gtab.gradients[~gtab.b0s_mask].T r, theta, phi = cart2sphere(x, y, z) B, m, n = real_sym_sh_basis(sh_order, theta[:, None], phi[:, None]) # B = real_sph_harm(m, n, theta[:, None], phi[:, None]) L = -n * (n + 1) invB = smooth_pinv(B, np.sqrt(smooth) * L) return B, invB
#Workaround: don't rotate bvec bvecRotated = bvecs[index] #print bvecs[index] #print bvecRotated if bvals[index] < 100: attenuatedBrainData = segmentedBrainData else: #Convert bvecs to angles x = bvecRotated[0] y = bvecRotated[1] z = bvecRotated[2] r, theta, phi = shm.cart2sphere(x, y, z) #Make design matrix B, m, n = shm.real_sym_sh_basis(order, theta, phi) #Get attenuated data print('Attenuating volume ' + str(index)) if bvals[index] < 1500: attenuatedBrainData = pl.attenuateImageSphericalHarmonics( segmentedBrainData, B, coefficientsb1000, bvals[index], 1000) elif bvals[index] > 1500 and bvals[index] < 2500: attenuatedBrainData = pl.attenuateImageSphericalHarmonics( segmentedBrainData, B, coefficientsb2000, bvals[index], 2000) attenuatedBrainNii = nib.Nifti1Image(attenuatedBrainData, segmentedBrain.get_affine(), segmentedBrain.get_header())
def __init__(self, gtab, response, reg_sphere=default_sphere, sh_order=8, iso=2): r""" Multi-Shell Multi-Tissue Constrained Spherical Deconvolution (MSMT-CSD) [1]_. This method extends the CSD model proposed in [2]_ by the estimation of multiple response functions as a function of multiple b-values and multiple tissue types. Spherical deconvolution computes a fiber orientation distribution (FOD), also called fiber ODF (fODF) [2]_. The fODF is derived from different tissue types and thus overcomes the overestimation of WM in GM and CSF areas. The response function is based on the different tissue types and is provided as input to the MultiShellDeconvModel. It will be used as deconvolution kernel, as described in [2]_. Parameters ---------- gtab : GradientTable response : ndarray or MultiShellResponse object Pre-computed multi-shell fiber response function in the form of a MultiShellResponse object, or simple response function as a ndarray. The later must be of shape (3, len(bvals)-1, 4), because it will be converted into a MultiShellResponse object via the `multi_shell_fiber_response` method (important note: the function `unique_bvals_tolerance` is used here to select unique bvalues from gtab as input). Each column (3,) has two elements. The first is the eigen-values as a (3,) ndarray and the second is the signal value for the response function without diffusion weighting (S0). Note that in order to use more than three compartments, one must create a MultiShellResponse object on the side. reg_sphere : Sphere (optional) sphere used to build the regularization B matrix. Default: 'symmetric362'. sh_order : int (optional) maximal spherical harmonics order. Default: 8 iso: int (optional) Number of tissue compartments for running the MSMT-CSD. Minimum number of compartments required is 2. Default: 2 References ---------- .. [1] Jeurissen, B., et al. NeuroImage 2014. Multi-tissue constrained spherical deconvolution for improved analysis of multi-shell diffusion MRI data .. [2] Tournier, J.D., et al. NeuroImage 2007. Robust determination of the fibre orientation distribution in diffusion MRI: Non-negativity constrained super-resolved spherical deconvolution .. [3] Tournier, J.D, et al. Imaging Systems and Technology 2012. MRtrix: Diffusion Tractography in Crossing Fiber Regions """ if not iso >= 2: msg = ("Multi-tissue CSD requires at least 2 tissue compartments") raise ValueError(msg) super(MultiShellDeconvModel, self).__init__(gtab) if not isinstance(response, MultiShellResponse): bvals = unique_bvals_tolerance(gtab.bvals, tol=20) if iso > 2: msg = """Too many compartments for this kind of response input. It must be two tissue compartments.""" raise ValueError(msg) if response.shape != (3, len(bvals) - 1, 4): msg = """Response must be of shape (3, len(bvals)-1, 4) or be a MultiShellResponse object.""" raise ValueError(msg) response = multi_shell_fiber_response(sh_order, bvals=bvals, wm_rf=response[0], gm_rf=response[1], csf_rf=response[2]) B, m, n = multi_tissue_basis(gtab, sh_order, iso) delta = _basic_delta(response.iso, response.m, response.n, 0., 0.) self.delta = delta multiplier_matrix = _inflate_response(response, gtab, n, delta) r, theta, phi = geo.cart2sphere(*reg_sphere.vertices.T) odf_reg, _, _ = shm.real_sym_sh_basis(sh_order, theta, phi) reg = np.zeros([i + iso for i in odf_reg.shape]) reg[:iso, :iso] = np.eye(iso) reg[iso:, iso:] = odf_reg X = B * multiplier_matrix self.fitter = QpFitter(X, reg) self.sh_order = sh_order self._X = X self.sphere = reg_sphere self.gtab = gtab self.B_dwi = B self.m = m self.n = n self.response = response
# Setup direction getter args print('Bootstrap direction getter') #boot_dg = BootDirectionGetter.from_data(data, model, max_angle=60., sphere=small_sphere) b0s_mask = gtab.b0s_mask dwi_mask = ~b0s_mask # get fit_matrix from model fit_matrix = model._fit_matrix delta_b, delta_q = fit_matrix # setup sampling matrix sphere = small_sphere theta = sphere.theta phi = sphere.phi sampling_matrix, _, _ = shm.real_sym_sh_basis(sh_order, theta, phi) ## from BootPmfGen __init__ # setup H and R matrices # TODO: figure out how to get H, R matrices from direction getter object x, y, z = model.gtab.gradients[dwi_mask].T r, theta, phi = shm.cart2sphere(x, y, z) B, _, _ = shm.real_sym_sh_basis(sh_order, theta, phi) H = shm.hat(B) R = shm.lcr_matrix(H) # create floating point copy of data dataf = np.asarray(data, dtype=float) print('streamline gen') global_chunk_size = args.chunk_size * args.ngpus
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
def __init__(self, gtab, response, reg_sphere=default_sphere, iso=2): r""" Multi-Shell Multi-Tissue Constrained Spherical Deconvolution (MSMT-CSD) [1]_. This method extends the CSD model proposed in [2]_ by the estimation of multiple response functions as a function of multiple b-values and multiple tissue types. Spherical deconvolution computes a fiber orientation distribution (FOD), also called fiber ODF (fODF) [2]_. The fODF is derived from different tissue types and thus overcomes the overestimation of WM in GM and CSF areas. The response function is based on the different tissue types and is provided as input to the MultiShellDeconvModel. It will be used as deconvolution kernel, as described in [2]_. 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'. iso: int (optional) Number of tissue compartments for running the MSMT-CSD. Minimum number of compartments required is 2. Default: 2 References ---------- .. [1] Jeurissen, B., et al. NeuroImage 2014. Multi-tissue constrained spherical deconvolution for improved analysis of multi-shell diffusion MRI data .. [2] Tournier, J.D., et al. NeuroImage 2007. Robust determination of the fibre orientation distribution in diffusion MRI: Non-negativity constrained super-resolved spherical deconvolution .. [3] Tournier, J.D, et al. Imaging Systems and Technology 2012. MRtrix: Diffusion Tractography in Crossing Fiber Regions """ if not iso >= 2: msg = ("Multi-tissue CSD requires at least 2 tissue compartments") raise ValueError(msg) sh_order = response.sh_order super(MultiShellDeconvModel, self).__init__(gtab) B, m, n = multi_tissue_basis(gtab, sh_order, iso) delta = _basic_delta(response.iso, response.m, response.n, 0., 0.) self.delta = delta multiplier_matrix = _inflate_response(response, gtab, n, delta) r, theta, phi = geo.cart2sphere(*reg_sphere.vertices.T) odf_reg, _, _ = shm.real_sym_sh_basis(sh_order, theta, phi) reg = np.zeros([i + iso for i in odf_reg.shape]) reg[:iso, :iso] = np.eye(iso) reg[iso:, iso:] = odf_reg X = B * multiplier_matrix self.fitter = QpFitter(X, reg) self.sh_order = sh_order self._X = X self.sphere = reg_sphere self.response = response self.B_dwi = B self.m = m self.n = n
# Normalise data dataNormalised = shm.normalize_data(data, gtab.b0s_mask) # dataNormalisedNii = nib.Nifti1Image(dataNormalised, img.get_affine(), # img.get_header()) # dataNormalisedNii.to_filename('dataNormalised.nii.gz') # Convert bvecs to angles where_dwis = 1 - gtab.b0s_mask x = gtab.gradients[where_dwis == True, 0] y = gtab.gradients[where_dwis == True, 1] z = gtab.gradients[where_dwis == True, 2] r, theta, phi = shm.cart2sphere(x, y, z) # Make design matrix B, m, n = shm.real_sym_sh_basis(order, theta[:, None], phi[:, None]) Binverse = shm.pinv(B) # Make matrix to hold coefficients dataSize = data.shape coefficientArray = np.zeros( (dataSize[0], dataSize[1], dataSize[2], len(B[1, :]))) # Get coefficients for i in range(0, dataSize[0]): for j in range(0, dataSize[1]): for k in range(0, dataSize[2]): if maskData[i, j, k] != 0: dataColumn = dataNormalised[i, j, k, where_dwis == True] coefficientArray[i, j, k] = np.dot(Binverse, dataColumn)