def main(): parser = _build_arg_parser() args = parser.parse_args() assert_inputs_exist(parser, args.input_sh) assert_outputs_exist(parser, args, args.output_name) input_basis = args.sh_basis output_basis = 'descoteaux07' if input_basis == 'tournier07' else 'tournier07' sph_harm_basis_ori = sph_harm_lookup.get(input_basis) sph_harm_basis_des = sph_harm_lookup.get(output_basis) sphere = get_sphere('repulsion724').subdivide(1) img = nib.load(args.input_sh) data = img.get_data() sh_order = find_order_from_nb_coeff(data) b_ori, m_ori, n_ori = sph_harm_basis_ori(sh_order, sphere.theta, sphere.phi) b_des, m_des, n_des = sph_harm_basis_des(sh_order, sphere.theta, sphere.phi) l_des = -n_des * (n_des + 1) inv_b_des = smooth_pinv(b_des, 0 * l_des) indices = np.argwhere(np.any(data, axis=3)) for i, ind in enumerate(indices): ind = tuple(ind) sf_1 = np.dot(data[ind], b_ori.T) data[ind] = np.dot(sf_1, inv_b_des.T) img = nib.Nifti1Image(data, img.affine, img.header) nib.save(nib.Nifti1Image(data, img.affine, img.header), args.output_name)
def resample_dwi(self, directions=None, sh_order=8, smooth=0.006): """ Resamples a diffusion signal according to a set of directions using spherical harmonics. Parameters ----------- directions : `dipy.core.sphere.Sphere` object, optional Directions the diffusion signal will be resampled to. Directions are assumed to be on the whole sphere, not the hemisphere like bvecs. If omitted, 100 directions evenly distributed on the sphere will be used. sh_order : int, optional SH order. Default: 8 smooth : float, optional Lambda-regularization in the SH fit. Default: 0.006. """ data_sh = self.get_spherical_harmonics_coefficients(self.dwi, self.bvals, self.bvecs, sh_order=sh_order, smooth=smooth) sphere = get_sphere('repulsion100') if directions is not None: sphere = Sphere(xyz=directions) sph_harm_basis = sph_harm_lookup.get("tournier07") Ba, m, n = sph_harm_basis(sh_order, sphere.theta, sphere.phi) data_resampled = np.dot(data_sh, Ba.T) return data_resampled
def print_peaks(sh_signal, mask=None): if has_fury: data_small = sh_signal[:, :, 50:51] ren = window.Renderer() sh_order = order_from_ncoef(data_small.shape[-1]) theta = default_sphere.theta phi = default_sphere.phi sh_params = SIGNAL_PARAMETERS['processing_params']['sh_params'] basis_type = sh_params['basis_type'] sph_harm_basis = sph_harm_lookup.get(basis_type) sampling_matrix, m, n = sph_harm_basis(sh_order, theta, phi) odfs = np.dot(data_small, sampling_matrix.T) odfs = np.clip(odfs, 0, np.max(odfs, -1)[..., None]) odfs_actor = actor.odf_slicer(odfs, sphere=default_sphere, colormap='plasma', scale=0.4) odfs_actor.display(z=0) ren.add(odfs_actor) print('Saving illustration as csa_odfs.png') window.record(ren, n_frames=1, out_path='csa_odfs.png', size=(600, 600)) window.show(ren)
def get_b_matrix(order, sphere, basis_type, return_all=False): sph_harm_basis = sph_harm_lookup.get(basis_type) if sph_harm_basis is None: raise ValueError("Invalid basis name.") b_matrix, m, n = sph_harm_basis(order, sphere.theta, sphere.phi) if return_all: return b_matrix, m, n return b_matrix
def sf_to_sh_invB(sphere, sh_order=8, basis_type=None, smooth=0.0): sph_harm_basis = sph_harm_lookup.get(basis_type) if sph_harm_basis is None: raise ValueError("Invalid basis name.") B, m, n = sph_harm_basis(sh_order, sphere.theta, sphere.phi) L = -n * (n + 1) invB = smooth_pinv(B, np.sqrt(smooth) * L) return invB.T
def resample_dwi(dwi, bvals, bvecs, directions=None, sh_order=8, smooth=0.006, mean_centering=True): """ Resamples a diffusion signal according to a set of directions using spherical harmonics. Parameters ----------- dwi : `nibabel.NiftiImage` object Diffusion signal as weighted images (4D). bvals : ndarray shape (N,) B-values used with each direction. bvecs : ndarray shape (N, 3) Directions of the diffusion signal. Directions are assumed to be only on the hemisphere. directions : `dipy.core.sphere.Sphere` object, optional Directions the diffusion signal will be resampled to. Directions are assumed to be on the whole sphere, not the hemisphere like bvecs. If omitted, 100 directions evenly distributed on the sphere will be used. sh_order : int, optional SH order. Default: 8 smooth : float, optional Lambda-regularization in the SH fit. Default: 0.006. mean_centering : bool If True, signal will have zero mean in each direction for all nonzero voxels Returns ------- ndarray Diffusion weights resampled according to `sphere`. """ data_sh = get_spherical_harmonics_coefficients(dwi, bvals, bvecs, sh_order=sh_order, smooth=smooth, mean_centering=False) sphere = get_sphere('repulsion100') # sphere = get_sphere('repulsion724') if directions is not None: sphere = Sphere(xyz=bvecs[1:]) sph_harm_basis = sph_harm_lookup.get('mrtrix') Ba, m, n = sph_harm_basis(sh_order, sphere.theta, sphere.phi) data_resampled = np.dot(data_sh, Ba.T) if mean_centering: # Normalization in each direction (zero mean) idx = data_resampled.sum(axis=-1).nonzero() means = data_resampled[idx].mean(axis=0) data_resampled[idx] -= means return data_resampled
def resample_raw_dwi_from_sh(dwi_image: nib.Nifti1Image, gradient_table: GradientTable, sh_basis: str = 'descoteaux07', sphere: Sphere = None, sh_order: int = 8, smooth: float = 0.006): """Resample a diffusion signal according to a set of directions using spherical harmonics. Parameters ---------- dwi_image : nib.Nifti1Image object Diffusion signal as weighted images (4D). gradient_table : GradientTable Dipy object that contains all bvals and bvecs. sh_basis: str Either 'tournier07' or 'descoteaux07'. Default: descoteaux07. sphere : dipy.core.sphere.Sphere, optional Directions the diffusion signal will be resampled to. Directions are assumed to be on the whole sphere, not the hemisphere like bvecs. If omitted, 100 directions evenly distributed on the sphere will be used (Dipy's "repulsion100"). sh_order : int, optional SH order to fit, by default 8. smooth : float, optional Lambda-regularization coefficient in the SH fit, by default 0.006. Returns ------- resampled_dwi : np.ndarray (4D) Resampled "raw" diffusion signal. """ validate_sh_basis_choice(sh_basis) # Get the "real" SH fit # sphere = None, so it computes the sh coefficients based on the bvecs. data_sh = compute_sh_coefficients(dwi_image, gradient_table, sh_order, basis_type=sh_basis, smooth=smooth) # Get new directions if sphere is None: sphere = get_sphere("repulsion100") sh_basis = sph_harm_lookup.get(sh_basis) # Resample data # B.T contains the new sampling scheme and B*data_sh projects to the sphere. # B : 2-D array; real harmonics sampled at (\theta, \phi) # m : array; degree of the sampled harmonics. # l : array; order of the sampled harmonics. B, m, l = sh_basis(sh_order, sphere.theta, sphere.phi) data_resampled = np.dot(data_sh, B.T) return data_resampled
def get_spherical_harmonics_coefficients(dwi, bvals, bvecs, sh_order=8, smooth=0.006, first=False, mean_centering=True): """ Compute coefficients of the spherical harmonics basis. Parameters ----------- dwi : `nibabel.NiftiImage` object Diffusion signal as weighted images (4D). bvals : ndarray shape (N,) B-values used with each direction. bvecs : ndarray shape (N, 3) Directions of the diffusion signal. Directions are assumed to be only on the hemisphere. sh_order : int, optional SH order. Default: 8 smooth : float, optional Lambda-regularization in the SH fit. Default: 0.006. mean_centering : bool If True, signal will have zero mean in each direction for all nonzero voxels Returns ------- sh_coeffs : ndarray of shape (X, Y, Z, #coeffs) Spherical harmonics coefficients at every voxel. The actual number of coeffs depends on `sh_order`. """ bvals = np.asarray(bvals) bvecs = np.asarray(bvecs) dwi_weights = dwi.get_data().astype("float32") # Exract the averaged b0. b0_idx = bvals == 0 b0 = dwi_weights[..., b0_idx].mean(axis=3) # Extract diffusion weights and normalize by the b0. bvecs = bvecs[np.logical_not(b0_idx)] weights = dwi_weights[..., np.logical_not(b0_idx)] weights = normalize_dwi(weights, b0) # Assuming all directions are on the hemisphere. raw_sphere = HemiSphere(xyz=bvecs) # Fit SH to signal sph_harm_basis = sph_harm_lookup.get('mrtrix') Ba, m, n = sph_harm_basis(sh_order, raw_sphere.theta, raw_sphere.phi) L = -n * (n + 1) invB = smooth_pinv(Ba, np.sqrt(smooth) * L) data_sh = np.dot(weights, invB.T) if mean_centering: # Normalization in each direction (zero mean) idx = data_sh.sum(axis=-1).nonzero() means = data_sh[idx].mean(axis=0) data_sh[idx] -= means return data_sh
def get_spherical_harmonics_coefficients(dwi, bvals, bvecs, sh_order=8, smooth=0.006, first=False): """ Compute coefficients of the spherical harmonics basis. Parameters ----------- dwi : `nibabel.NiftiImage` object Diffusion signal as weighted images (4D). bvals : ndarray shape (N,) B-values used with each direction. bvecs : ndarray shape (N, 3) Directions of the diffusion signal. Directions are assumed to be only on the hemisphere. sh_order : int, optional SH order. Default: 8 smooth : float, optional Lambda-regularization in the SH fit. Default: 0.006. Returns ------- sh_coeffs : ndarray of shape (X, Y, Z, #coeffs) Spherical harmonics coefficients at every voxel. The actual number of coeffs depends on `sh_order`. """ bvals = np.asarray(bvals) bvecs = np.asarray(bvecs) dwi_weights = dwi.get_data().astype("float32") # Exract the averaged b0. b0_idx = bvals == 0 b0 = dwi_weights[..., b0_idx].mean(axis=3) # Extract diffusion weights and normalize by the b0. bvecs = bvecs[np.logical_not(b0_idx)] weights = dwi_weights[..., np.logical_not(b0_idx)] weights = normalize_dwi(weights, b0) # Assuming all directions are on the hemisphere. raw_sphere = HemiSphere(xyz=bvecs) # Fit SH to signal sph_harm_basis = sph_harm_lookup.get('mrtrix') Ba, m, n = sph_harm_basis(sh_order, raw_sphere.theta, raw_sphere.phi) L = -n * (n + 1) invB = smooth_pinv(Ba, np.sqrt(smooth) * L) data_sh = np.dot(weights, invB.T) return data_sh
def get_spherical_harmonics_coefficients(self, dwi_weights, bvals, bvecs, sh_order=8, smooth=0.006): """ Compute coefficients of the spherical harmonics basis. Parameters ----------- dwi_weights : `nibabel.NiftiImage` object Diffusion signal as weighted images (4D). bvals : ndarray shape (N,) B-values used with each direction. bvecs : ndarray shape (N, 3) Directions of the diffusion signal. Directions are assumed to be only on the hemisphere. sh_order : int, optional SH order. Default: 8 smooth : float, optional Lambda-regularization in the SH fit. Default: 0.006. Returns ------- sh_coeffs : ndarray of shape (X, Y, Z, #coeffs) Spherical harmonics coefficients at every voxel. The actual number of coeffs depends on `sh_order`. """ # Exract the averaged b0. b0_idx = bvals == 0 b0 = dwi_weights[..., b0_idx].mean(axis=3) + 1e-10 # Extract diffusion weights and normalize by the b0. bvecs = bvecs[np.logical_not(b0_idx)] weights = dwi_weights[..., np.logical_not(b0_idx)] weights = self.normalize_dwi(weights, b0) # Assuming all directions are on the hemisphere. raw_sphere = HemiSphere(xyz=bvecs) # Fit SH to signal sph_harm_basis = sph_harm_lookup.get("tournier07") Ba, m, n = sph_harm_basis(sh_order, raw_sphere.theta, raw_sphere.phi) L = -n * (n + 1) invB = smooth_pinv(Ba, np.sqrt(smooth) * L) data_sh = np.dot(weights, invB.T) return data_sh
def resample_dwi(dwi, bvals, bvecs, directions=None, sh_order=8, smooth=0.006): """ Resamples a diffusion signal according to a set of directions using spherical harmonics. Parameters ----------- dwi : `nibabel.NiftiImage` object Diffusion signal as weighted images (4D). bvals : ndarray shape (N,) B-values used with each direction. bvecs : ndarray shape (N, 3) Directions of the diffusion signal. Directions are assumed to be only on the hemisphere. directions : `dipy.core.sphere.Sphere` object, optional Directions the diffusion signal will be resampled to. Directions are assumed to be on the whole sphere, not the hemisphere like bvecs. If omitted, 100 directions evenly distributed on the sphere will be used. sh_order : int, optional SH order. Default: 8 smooth : float, optional Lambda-regularization in the SH fit. Default: 0.006. Returns ------- ndarray Diffusion weights resampled according to `sphere`. """ data_sh = get_spherical_harmonics_coefficients(dwi, bvals, bvecs, sh_order=sh_order, smooth=smooth) # sphere = get_sphere('repulsion100') # # sphere = get_sphere('repulsion724') # if directions is not None: # # sphere = Sphere(xyz=bvecs[1:]) sphere = Sphere(xyz=directions) sph_harm_basis = sph_harm_lookup.get('tournier07') Ba, m, n = sph_harm_basis(sh_order, sphere.theta, sphere.phi) data_resampled = np.dot(data_sh, Ba.T) return data_resampled
def resample_dwi(dwi, bvals, bvecs, directions=None, sh_order=8, smooth=0.006): """ Resamples a diffusion signal according to a set of directions using spherical harmonics. Parameters ----------- dwi : `nibabel.NiftiImage` object Diffusion signal as weighted images (4D). bvals : ndarray shape (N,) B-values used with each direction. bvecs : ndarray shape (N, 3) Directions of the diffusion signal. Directions are assumed to be only on the hemisphere. directions : `dipy.core.sphere.Sphere` object, optional Directions the diffusion signal will be resampled to. Directions are assumed to be on the whole sphere, not the hemisphere like bvecs. If omitted, 100 directions evenly distributed on the sphere will be used. sh_order : int, optional SH order. Default: 8 smooth : float, optional Lambda-regularization in the SH fit. Default: 0.006. Returns ------- ndarray Diffusion weights resampled according to `sphere`. """ data_sh = get_spherical_harmonics_coefficients(dwi, bvals, bvecs, sh_order=sh_order, smooth=smooth) sphere = get_sphere('repulsion100') # sphere = get_sphere('repulsion724') if directions is not None: sphere = Sphere(xyz=bvecs[1:]) sph_harm_basis = sph_harm_lookup.get('mrtrix') Ba, m, n = sph_harm_basis(sh_order, sphere.theta, sphere.phi) data_resampled = np.dot(data_sh, Ba.T) return data_resampled
def peaks_from_model(model, data, sphere, relative_peak_threshold, min_separation_angle, mask=None, return_odf=False, return_sh=True, gfa_thr=0, normalize_peaks=False, sh_order=8, sh_basis_type=None): """Fits the model to data and computes peaks and metrics Parameters ---------- model : a model instance `model` will be used to fit the data. sphere : Sphere The Sphere providing discrete directions for evaluation. relative_peak_threshold : float Only return peaks greater than ``relative_peak_threshold * m`` where m is the largest peak. min_separation_angle : float in [0, 90] The minimum distance between directions. If two peaks are too close only the larger of the two is returned. mask : array, optional If `mask` is provided, voxels that are False in `mask` are skipped and no peaks are returned. return_odf : bool If True, the odfs are returned. return_sh : bool If True, the odf as spherical harmonics coefficients is returned gfa_thr : float Voxels with gfa less than `gfa_thr` are skipped, no peaks are returned. normalize_peaks : bool If true, all peak values are calculated relative to `max(odf)`. sh_order : int, optional Maximum SH order in the SH fit. For `sh_order`, there will be ``(sh_order + 1) * (sh_order + 2) / 2`` SH coefficients (default 8). sh_basis_type : {None, 'mrtrix', 'fibernav'} ``None`` for the default dipy basis which is the fibernav basis, ``mrtrix`` for the MRtrix basis, and ``fibernav`` for the FiberNavigator basis Returns ------- pam : PeaksAndMetrics an object with ``gfa``, ``peak_values``, ``peak_indices``, ``odf``, ``shm_coeffs`` as attributes """ data_flat = data.reshape((-1, data.shape[-1])) size = len(data_flat) if mask is None: mask = np.ones(size, dtype='bool') else: mask = mask.ravel() if len(mask) != size: raise ValueError("mask is not the same size as data") npeaks = 5 sh_smooth=0 gfa_array = np.zeros(size) qa_array = np.zeros((size, npeaks)) peak_values = np.zeros((size, npeaks)) peak_indices = np.zeros((size, npeaks), dtype='int') peak_indices.fill(-1) if return_sh: #import here to avoid circular imports from dipy.reconst.shm import sph_harm_lookup, smooth_pinv sph_harm_basis = sph_harm_lookup.get(sh_basis_type) if sph_harm_basis is None: raise ValueError("Invalid basis name.") B, m, n = sph_harm_basis(sh_order, sphere.theta, sphere.phi) L = -n * (n + 1) invB = smooth_pinv(B, np.sqrt(sh_smooth) * L) n_shm_coeff = (sh_order + 2) * (sh_order + 1) / 2 shm_coeff = np.zeros((size, n_shm_coeff)) invB = invB.T #sh = np.dot(sf, invB.T) if return_odf: odf_array = np.zeros((size, len(sphere.vertices))) global_max = -np.inf for i, sig in enumerate(data_flat): if not mask[i]: continue odf = model.fit(sig).odf(sphere) if return_sh: shm_coeff[i] = np.dot(odf, invB) if return_odf: odf_array[i] = odf gfa_array[i] = gfa(odf) if gfa_array[i] < gfa_thr: global_max = max(global_max, odf.max()) continue # Get peaks of odf _, pk, ind = peak_directions(odf, sphere, relative_peak_threshold, min_separation_angle) # Calculate peak metrics global_max = max(global_max, pk[0]) n = min(npeaks, len(pk)) qa_array[i, :n] = pk[:n] - odf.min() if normalize_peaks: peak_values[i, :n] = pk[:n] / pk[0] else: peak_values[i, :n] = pk[:n] peak_indices[i, :n] = ind[:n] shape = data.shape[:-1] gfa_array = gfa_array.reshape(shape) qa_array = qa_array.reshape(shape + (npeaks,)) / global_max peak_values = peak_values.reshape(shape + (npeaks,)) peak_indices = peak_indices.reshape(shape + (npeaks,)) pam = PeaksAndMetrics() pam.peak_values = peak_values pam.peak_indices = peak_indices pam.gfa = gfa_array pam.qa = qa_array if return_sh: pam.shm_coeff = shm_coeff.reshape(shape + (n_shm_coeff,)) pam.invB = invB else: pam.shm_coeff = None pam.invB = None if return_odf: pam.odf = odf_array.reshape(shape + odf_array.shape[-1:]) else: pam.odf = None return pam
def main(): params = readArgs() # read in from the command line read_args = params.collect_args() params.check_args(read_args) # get img obj dwi_img = nib.load(params.dwi_) mask_img = nib.load(params.mask_) from dipy.io import read_bvals_bvecs bvals, bvecs = read_bvals_bvecs(params.bval_, params.bvec_) # need to create the gradient table yo from dipy.core.gradients import gradient_table gtab = gradient_table(bvals, bvecs, b0_threshold=25) # get the data from image objects dwi_data = dwi_img.get_data() mask_data = mask_img.get_data() # and get affine img_affine = dwi_img.affine from dipy.data import get_sphere sphere = get_sphere('repulsion724') from dipy.segment.mask import applymask dwi_data = applymask(dwi_data, mask_data) printfl('dwi_data.shape (%d, %d, %d, %d)' % dwi_data.shape) printfl('\nYour bvecs look like this:{0}'.format(bvecs)) printfl('\nYour bvals look like this:{0}\n'.format(bvals)) from dipy.reconst.shm import anisotropic_power, sph_harm_lookup, smooth_pinv, normalize_data from dipy.core.sphere import HemiSphere smooth = 0.0 normed_data = normalize_data(dwi_data, gtab.b0s_mask) normed_data = normed_data[..., np.where(1 - gtab.b0s_mask)[0]] from dipy.core.gradients import gradient_table_from_bvals_bvecs gtab2 = gradient_table_from_bvals_bvecs( gtab.bvals[np.where(1 - gtab.b0s_mask)[0]], gtab.bvecs[np.where(1 - gtab.b0s_mask)[0]]) signal_native_pts = HemiSphere(xyz=gtab2.bvecs) sph_harm_basis = sph_harm_lookup.get(None) Ba, m, n = sph_harm_basis(params.sh_order_, signal_native_pts.theta, signal_native_pts.phi) L = -n * (n + 1) invB = smooth_pinv(Ba, np.sqrt(smooth) * L) # fit SH basis to DWI signal normed_data_sh = np.dot(normed_data, invB.T) # power map call printfl("fitting power map") pow_map = anisotropic_power(normed_data_sh, norm_factor=0.00001, power=2, non_negative=True) pow_map_img = nib.Nifti1Image(pow_map.astype(np.float32), img_affine) # make output name out_name = ''.join( [params.output_, '_powMap_sh', str(params.sh_order_), '.nii.gz']) printfl("writing power map to: {}".format(out_name)) nib.save(pow_map_img, out_name)
def peaks_from_model(model, data, sphere, relative_peak_threshold, min_separation_angle, mask=None, return_odf=False, return_sh=True, gfa_thr=0, normalize_peaks=False, sh_order=8, sh_basis_type=None): """Fits the model to data and computes peaks and metrics Parameters ---------- model : a model instance `model` will be used to fit the data. sphere : Sphere The Sphere providing discrete directions for evaluation. relative_peak_threshold : float Only return peaks greater than ``relative_peak_threshold * m`` where m is the largest peak. min_separation_angle : float in [0, 90] The minimum distance between directions. If two peaks are too close only the larger of the two is returned. mask : array, optional If `mask` is provided, voxels that are False in `mask` are skipped and no peaks are returned. return_odf : bool If True, the odfs are returned. return_sh : bool If True, the odf as spherical harmonics coefficients is returned gfa_thr : float Voxels with gfa less than `gfa_thr` are skipped, no peaks are returned. normalize_peaks : bool If true, all peak values are calculated relative to `max(odf)`. sh_order : int, optional Maximum SH order in the SH fit. For `sh_order`, there will be ``(sh_order + 1) * (sh_order + 2) / 2`` SH coefficients (default 8). sh_basis_type : {None, 'mrtrix', 'fibernav'} ``None`` for the default dipy basis which is the fibernav basis, ``mrtrix`` for the MRtrix basis, and ``fibernav`` for the FiberNavigator basis Returns ------- pam : PeaksAndMetrics an object with ``gfa``, ``peak_values``, ``peak_indices``, ``odf``, ``shm_coeffs`` as attributes """ data_flat = data.reshape((-1, data.shape[-1])) size = len(data_flat) if mask is None: mask = np.ones(size, dtype='bool') else: mask = mask.ravel() if len(mask) != size: raise ValueError("mask is not the same size as data") npeaks = 5 sh_smooth = 0 gfa_array = np.zeros(size) qa_array = np.zeros((size, npeaks)) peak_values = np.zeros((size, npeaks)) peak_indices = np.zeros((size, npeaks), dtype='int') peak_indices.fill(-1) if return_sh: #import here to avoid circular imports from dipy.reconst.shm import sph_harm_lookup, smooth_pinv sph_harm_basis = sph_harm_lookup.get(sh_basis_type) if sph_harm_basis is None: raise ValueError("Invalid basis name.") B, m, n = sph_harm_basis(sh_order, sphere.theta, sphere.phi) L = -n * (n + 1) invB = smooth_pinv(B, np.sqrt(sh_smooth) * L) n_shm_coeff = (sh_order + 2) * (sh_order + 1) / 2 shm_coeff = np.zeros((size, n_shm_coeff)) invB = invB.T #sh = np.dot(sf, invB.T) if return_odf: odf_array = np.zeros((size, len(sphere.vertices))) global_max = -np.inf for i, sig in enumerate(data_flat): if not mask[i]: continue odf = model.fit(sig).odf(sphere) if return_sh: shm_coeff[i] = np.dot(odf, invB) if return_odf: odf_array[i] = odf gfa_array[i] = gfa(odf) if gfa_array[i] < gfa_thr: global_max = max(global_max, odf.max()) continue # Get peaks of odf _, pk, ind = peak_directions(odf, sphere, relative_peak_threshold, min_separation_angle) # Calculate peak metrics global_max = max(global_max, pk[0]) n = min(npeaks, len(pk)) qa_array[i, :n] = pk[:n] - odf.min() if normalize_peaks: peak_values[i, :n] = pk[:n] / pk[0] else: peak_values[i, :n] = pk[:n] peak_indices[i, :n] = ind[:n] shape = data.shape[:-1] gfa_array = gfa_array.reshape(shape) qa_array = qa_array.reshape(shape + (npeaks, )) / global_max peak_values = peak_values.reshape(shape + (npeaks, )) peak_indices = peak_indices.reshape(shape + (npeaks, )) pam = PeaksAndMetrics() pam.peak_values = peak_values pam.peak_indices = peak_indices pam.gfa = gfa_array pam.qa = qa_array if return_sh: pam.shm_coeff = shm_coeff.reshape(shape + (n_shm_coeff, )) pam.invB = invB else: pam.shm_coeff = None pam.invB = None if return_odf: pam.odf = odf_array.reshape(shape + odf_array.shape[-1:]) else: pam.odf = None return pam
def show_odfs_and_fa(fa, pam, mask, affine, sphere, ftmp='odf.mmap', basis_type=None, norm_odfs=True, scale_odfs=0.5): renderer = window.Renderer() renderer.background((1, 1, 1)) slice_actor = actor.slicer(fa) #, value_range) odf_shape = fa.shape + (sphere.vertices.shape[0],) odfs = np.memmap(ftmp, dtype=np.float32, mode='w+', shape=odf_shape) sph_harm_basis = sph_harm_lookup.get(basis_type) if sph_harm_basis is None: raise ValueError("Invalid basis name.") B, m, n = sph_harm_basis(8, sphere.theta, sphere.phi) odfs[:] = np.dot(pam.shm_coeff.astype('f4'), B.T.astype('f4')) odf_slicer = actor.odf_slicer(odfs, mask=mask, sphere=sphere, scale=scale_odfs, norm=norm_odfs, colormap='magma') renderer.add(odf_slicer) renderer.add(slice_actor) show_m = window.ShowManager(renderer, size=(2000, 1000)) show_m.initialize() """ We'll start by creating the panel and adding it to the ``ShowManager`` """ label_position = ui.TextBlock2D(text='Position:') label_value = ui.TextBlock2D(text='Value:') result_position = ui.TextBlock2D(text='') result_value = ui.TextBlock2D(text='') line_slider_z = ui.LineSlider2D(min_value=0, max_value=shape[2] - 1, initial_value=shape[2] / 2, text_template="{value:.0f}", length=140) def change_slice_z(i_ren, obj, slider): z = int(np.round(slider.value)) slice_actor.display(z=z) odf_slicer.display(z=z) show_m.render() line_slider_z.add_callback(line_slider_z.slider_disk, "LeftButtonReleaseEvent", change_slice_z) panel_picking = ui.Panel2D(center=(200, 120), size=(250, 225), color=(0, 0, 0), opacity=0.75, align="left") # panel_picking.add_element(label_position, 'relative', (0.1, 0.55)) # panel_picking.add_element(label_value, 'relative', (0.1, 0.25)) # panel_picking.add_element(result_position, 'relative', (0.45, 0.55)) # panel_picking.add_element(result_value, 'relative', (0.45, 0.25)) panel_picking.add_element(line_slider_z, 'relative', (0.5, 0.9)) show_m.ren.add(panel_picking) def left_click_callback(obj, ev): """Get the value of the clicked voxel and show it in the panel.""" event_pos = show_m.iren.GetEventPosition() obj.picker.Pick(event_pos[0], event_pos[1], 0, show_m.ren) i, j, k = obj.picker.GetPointIJK() print(i,j,k) result_position.message = '({}, {}, {})'.format(str(i), str(j), str(k)) result_value.message = '%.3f' % fa[i, j, k] slice_actor.SetInterpolate(True) slice_actor.AddObserver('LeftButtonPressEvent', left_click_callback, 1.0) show_m.start() odfs._mmap.close() del odfs os.remove(ftmp)
def show_odfs_and_fa(fa, pam, mask, affine, sphere, ftmp='odf.mmap', basis_type=None, norm_odfs=True, scale_odfs=0.5): renderer = window.Renderer() renderer.background((1, 1, 1)) slice_actor = actor.slicer(fa) #, value_range) odf_shape = fa.shape + (sphere.vertices.shape[0], ) odfs = np.memmap(ftmp, dtype=np.float32, mode='w+', shape=odf_shape) sph_harm_basis = sph_harm_lookup.get(basis_type) if sph_harm_basis is None: raise ValueError("Invalid basis name.") B, m, n = sph_harm_basis(8, sphere.theta, sphere.phi) odfs[:] = np.dot(pam.shm_coeff.astype('f4'), B.T.astype('f4')) odf_slicer = actor.odf_slicer(odfs, mask=mask, sphere=sphere, scale=scale_odfs, norm=norm_odfs, colormap='magma') renderer.add(odf_slicer) renderer.add(slice_actor) show_m = window.ShowManager(renderer, size=(2000, 1000)) show_m.initialize() """ We'll start by creating the panel and adding it to the ``ShowManager`` """ label_position = ui.TextBlock2D(text='Position:') label_value = ui.TextBlock2D(text='Value:') result_position = ui.TextBlock2D(text='') result_value = ui.TextBlock2D(text='') line_slider_z = ui.LineSlider2D(min_value=0, max_value=shape[2] - 1, initial_value=shape[2] / 2, text_template="{value:.0f}", length=140) def change_slice_z(i_ren, obj, slider): z = int(np.round(slider.value)) slice_actor.display(z=z) odf_slicer.display(z=z) show_m.render() line_slider_z.add_callback(line_slider_z.slider_disk, "LeftButtonReleaseEvent", change_slice_z) panel_picking = ui.Panel2D(center=(200, 120), size=(250, 225), color=(0, 0, 0), opacity=0.75, align="left") # panel_picking.add_element(label_position, 'relative', (0.1, 0.55)) # panel_picking.add_element(label_value, 'relative', (0.1, 0.25)) # panel_picking.add_element(result_position, 'relative', (0.45, 0.55)) # panel_picking.add_element(result_value, 'relative', (0.45, 0.25)) panel_picking.add_element(line_slider_z, 'relative', (0.5, 0.9)) show_m.ren.add(panel_picking) def left_click_callback(obj, ev): """Get the value of the clicked voxel and show it in the panel.""" event_pos = show_m.iren.GetEventPosition() obj.picker.Pick(event_pos[0], event_pos[1], 0, show_m.ren) i, j, k = obj.picker.GetPointIJK() print(i, j, k) result_position.message = '({}, {}, {})'.format(str(i), str(j), str(k)) result_value.message = '%.3f' % fa[i, j, k] slice_actor.SetInterpolate(True) slice_actor.AddObserver('LeftButtonPressEvent', left_click_callback, 1.0) show_m.start() odfs._mmap.close() del odfs os.remove(ftmp)
def peaks_from_model(model, data, sphere, relative_peak_threshold, min_separation_angle, mask=None, return_odf=False, return_sh=True, gfa_thr=0, normalize_peaks=False, sh_order=8, sh_basis_type=None, ravel_peaks=False, npeaks=5, parallel=False, nbr_process=None): """Fits the model to data and computes peaks and metrics Parameters ---------- model : a model instance `model` will be used to fit the data. sphere : Sphere The Sphere providing discrete directions for evaluation. relative_peak_threshold : float Only return peaks greater than ``relative_peak_threshold * m`` where m is the largest peak. min_separation_angle : float in [0, 90] The minimum distance between directions. If two peaks are too close only the larger of the two is returned. mask : array, optional If `mask` is provided, voxels that are False in `mask` are skipped and no peaks are returned. return_odf : bool If True, the odfs are returned. return_sh : bool If True, the odf as spherical harmonics coefficients is returned gfa_thr : float Voxels with gfa less than `gfa_thr` are skipped, no peaks are returned. normalize_peaks : bool If true, all peak values are calculated relative to `max(odf)`. sh_order : int, optional Maximum SH order in the SH fit. For `sh_order`, there will be ``(sh_order + 1) * (sh_order + 2) / 2`` SH coefficients (default 8). sh_basis_type : {None, 'mrtrix', 'fibernav'} ``None`` for the default dipy basis which is the fibernav basis, ``mrtrix`` for the MRtrix basis, and ``fibernav`` for the FiberNavigator basis ravel_peaks : bool If True, the peaks are returned as [x1, y1, z1, ..., xn, yn, zn] instead of Nx3. Set this flag to True if you want to visualize the peaks in the fibernavigator or in mrtrix. npeaks : int Maximum number of peaks found (default 5 peaks). parallel: bool If True, use multiprocessing to compute peaks and metric (default False). nbr_process: int If `parallel == True`, the number of subprocess to use (default multiprocessing.cpu_count()). Returns ------- pam : PeaksAndMetrics An object with ``gfa``, ``peak_directions``, ``peak_values``, ``peak_indices``, ``odf``, ``shm_coeffs`` as attributes """ if parallel: return __peaks_from_model_parallel(model, data, sphere, relative_peak_threshold, min_separation_angle, mask, return_odf, return_sh, gfa_thr, normalize_peaks, sh_order, sh_basis_type, ravel_peaks, npeaks, nbr_process) shape = data.shape[:-1] if mask is None: mask = np.ones(shape, dtype='bool') else: if mask.shape != shape: raise ValueError("Mask is not the same shape as data.") sh_smooth = 0 gfa_array = np.zeros(shape) qa_array = np.zeros((shape + (npeaks,))) peak_dirs = np.zeros((shape + (npeaks, 3))) peak_values = np.zeros((shape + (npeaks,))) peak_indices = np.zeros((shape + (npeaks,)), dtype='int') peak_indices.fill(-1) if return_sh: # import here to avoid circular imports from dipy.reconst.shm import sph_harm_lookup, smooth_pinv sph_harm_basis = sph_harm_lookup.get(sh_basis_type) if sph_harm_basis is None: raise ValueError("Invalid basis name.") B, m, n = sph_harm_basis(sh_order, sphere.theta, sphere.phi) L = -n * (n + 1) invB = smooth_pinv(B, np.sqrt(sh_smooth) * L) n_shm_coeff = (sh_order + 2) * (sh_order + 1) / 2 shm_coeff = np.zeros((shape + (n_shm_coeff,))) invB = invB.T if return_odf: odf_array = np.zeros((shape + (len(sphere.vertices),))) global_max = -np.inf for idx in ndindex(shape): if not mask[idx]: continue odf = model.fit(data[idx]).odf(sphere) if return_sh: shm_coeff[idx] = np.dot(odf, invB) if return_odf: odf_array[idx] = odf gfa_array[idx] = gfa(odf) if gfa_array[idx] < gfa_thr: global_max = max(global_max, odf.max()) continue # Get peaks of odf direction, pk, ind = peak_directions( odf, sphere, relative_peak_threshold, min_separation_angle) # Calculate peak metrics global_max = max(global_max, pk[0]) n = min(npeaks, len(pk)) qa_array[idx][:n] = pk[:n] - odf.min() peak_dirs[idx][:n] = direction[:n] peak_indices[idx][:n] = ind[:n] peak_values[idx][:n] = pk[:n] if normalize_peaks: peak_values[idx][:n] /= pk[0] peak_dirs[idx] *= peak_values[idx][:, None] #gfa_array = gfa_array qa_array /= global_max #peak_values = peak_values #peak_indices = peak_indices # The fibernavigator only supports float32. Since this form is mainly # for external visualisation, we enforce float32. if ravel_peaks: peak_dirs = peak_dirs.reshape(shape + (3 * npeaks,)).astype('float32') pam = PeaksAndMetrics() pam.peak_dirs = peak_dirs pam.peak_values = peak_values pam.peak_indices = peak_indices pam.gfa = gfa_array pam.qa = qa_array if return_sh: pam.shm_coeff = shm_coeff pam.invB = invB else: pam.shm_coeff = None pam.invB = None if return_odf: pam.odf = odf_array else: pam.odf = None return pam