def get_streamvals(streamlines, affine, value_array, voxel_array = None): """With a set of streamlines and a value matrix, iterates through the set of streamlines in order to determine what is the average and maximum values found in that array expected to work with FA, MD, etc (must be properly registered and in same voxel space)""" #vals = np.ndarray(shape=(3, 0), dtype=int) #locations = np.ndarray(shape=(3, 0), dtype=int) if voxel_array is None: stream_voxels = [] lin_T, offset = _mapping_to_voxel(affine) for sl, _ in enumerate(streamlines): # Convert streamline to voxel coordinates entire = _to_voxel_coordinates(streamlines[sl], lin_T, offset) stream_voxels_temp = array_to_tuplelist(entire) stream_voxels = stream_voxels + stream_voxels_temp stream_voxels_uniq = catch_unique(stream_voxels) #to ensure that voxels arent counted multiple times. Remove if voxel weight is considered a positive voxel_array = np.array(stream_voxels_uniq) i,j,k = voxel_array.T if np.size(value_array) == 2: value_array = value_array[0] vals = value_array[i,j,k] meanvals = np.mean(vals) maxvals = np.max(vals) return meanvals, maxvals, stream_voxels_uniq
def fa_extraction( groupsl, fa_vol, N, voxel_size=None, affine=None, ): # input: groupsl, streamlines connecting ith and jth ROIs, groupsl[i,j] # fa_vol, fa values in the image domain # N, # of ROIs lin_T, offset = _mapping_to_voxel(affine, voxel_size) fa_group = defaultdict(list) for i in range(1, N): for j in range(i + 1, N): tmp_streamlines = groupsl[i, j] tmp_streamlines = list(tmp_streamlines) fa_streamlines = [] for sl in tmp_streamlines: slpoints = _to_voxel_coordinates(sl, lin_T, offset) #get FA for each streamlines ii, jj, kk = slpoints.T fa_value = fa_vol[ii, jj, kk] fa_streamlines.append(fa_value) fa_group[i, j].append(fa_streamlines) return fa_group
def streamline_zerolab_endpoints(streamlines,label_volume,voxel_size=None,affine=None): # input: streamlines, input streamlines # anaimg, input image endpointimg = np.zeros(label_volume.shape, dtype='int') # get the ending points of streamlines streamlines = list(streamlines) endpoints = [sl[0::len(sl)-1] for sl in streamlines] # Map the streamlines coordinates to voxel coordinates lin_T, offset = _mapping_to_voxel(affine, voxel_size) endpoints = _to_voxel_coordinates(endpoints, lin_T, offset) #process the endpoints Nstreamlines = len(endpoints) endlabels = np.zeros((2,Nstreamlines),dtype=int) for i in range(0,Nstreamlines): endpoint = endpoints[i] endlabel = endpoints_processing(endpoint,label_volume) if(endlabel[0] == 0): endpointimg[endpoint[0][0],endpoint[0][1],endpoint[0][2]]=1 if(endlabel[1] == 0): endpointimg[endpoint[1][0],endpoint[1][1],endpoint[1][2]]=1 return endpointimg
def rois_countmatrix_matinput(label_img, N, groupsl, voxel_size=None, affine=None): # input: label_img: image of labels, index from 0 - N # N: # of ROIs # groupsl: streamlines connecting ith and jth ROIs, groupsl[i,j] # voxel_size, affine: some parameters to transfore streamlines to voxel lin_T, offset = _mapping_to_voxel(affine, voxel_size) countm = np.zeros([N + 1, N + 1]) for i in range(0, N - 1): for j in range(i + 1, N): tmp_streamlines = groupsl[i, j] [tmp1, tmp2, Nfibers] = tmp_streamlines.shape for n in range(0, Nfibers): tmp = tmp_streamlines[:, :, n] sl = tmp.transpose( ) #flip the matrix to get the right input for extracting fa; slpoints = _to_voxel_coordinates(sl, lin_T, offset) ii, jj, kk = slpoints.T newlabel_img = label_img[ii, jj, kk] roi_id = list(set(newlabel_img)) Nid = len(roi_id) for aa in range(1, Nid - 1): for bb in range(aa + 1, Nid): countm[roi_id[aa], roi_id[bb]] = countm[roi_id[aa], roi_id[bb]] + 1 return countm
def rois_passingrois_matinput(label_img, N, groupsl, voxel_size=None, affine=None): # input: label_img: image of labels, index from 0 - N # N: # of ROIs # groupsl: streamlines connecting ith and jth ROIs, groupsl[i,j] # voxel_size, affine: some parameters to transfore streamlines to voxel lin_T, offset = _mapping_to_voxel(affine, voxel_size) passroicm = defaultdict(list) for i in range(0, N - 1): for j in range(i + 1, N): tmp_streamlines = groupsl[i, j] [tmp1, tmp2, Nfibers] = tmp_streamlines.shape passroicm_2nodes = [] for n in range(0, Nfibers): tmp = tmp_streamlines[:, :, n] sl = tmp.transpose( ) #flip the matrix to get the right input for extracting fa; slpoints = _to_voxel_coordinates(sl, lin_T, offset) ii, jj, kk = slpoints.T newlabel_img = label_img[ii, jj, kk] passroicm_2nodes.append(newlabel_img) passroicm[i, j].append(passroicm_2nodes) return passroicm
def tworoi_inters_streamlines_matinput(label_img, N, groupsl, roia, roib, voxel_size=None, affine=None): # input: label_img: image of labels, index from 0 - N # N: # of ROIs # groupsl: streamlines connecting ith and jth ROIs, groupsl[i,j] # voxel_size, affine: some parameters to transfore streamlines to voxel lin_T, offset = _mapping_to_voxel(affine, voxel_size) roia_roib_stls = [] for i in range(0, N - 1): for j in range(i + 1, N): tmp_streamlines = groupsl[i, j] [tmp1, tmp2, Nfibers] = tmp_streamlines.shape for n in range(0, Nfibers): tmp = tmp_streamlines[:, :, n] sl = tmp.transpose( ) #flip the matrix to get the right input for extracting fa; slpoints = _to_voxel_coordinates(sl, lin_T, offset) ii, jj, kk = slpoints.T newlabel_img = label_img[ii, jj, kk] if ((roia in newlabel_img) & (roib in newlabel_img)): roia_roib_stls.append(sl) print newlabel_img return roia_roib_stls
def streamline_endpoints(streamlines,label_volume,voxel_size=None,affine=None): # input: streamlines, input streamlines # anaimg, input image endpointimg = np.zeros(label_volume.shape, dtype='int') # get the ending points of streamlines streamlines = list(streamlines) endpoints = [sl[0::len(sl)-1] for sl in streamlines] # Map the streamlines coordinates to voxel coordinates lin_T, offset = _mapping_to_voxel(affine, voxel_size) endpoints = _to_voxel_coordinates(endpoints, lin_T, offset) i, j, k = endpoints.T #processing the i,j,k, because of the offset, some endpoints are mapped #to the outside of the brain dim = label_volume.shape if(np.max(i)>dim[0] and np.max(j)>dim[1] and np.max(k)>dim[2]): raise IndexError('streamline has points that map to outside of the brain voxel') i[np.where(i>dim[0]-1)] = dim[0]-1 j[np.where(j>dim[1]-1)] = dim[1]-1 k[np.where(k>dim[2]-1)] = dim[2]-1 #pdb.set_trace() endpointimg[i, j, k] = 1 return endpointimg
def fa_extraction_use_cellinput(cell_streamlines, cell_id, fa_vol, N, voxel_size=None, affine=None): # input: groupsl, streamlines connecting ith and jth ROIs, groupsl[i,j] # fa_vol, fa values in the image domain # N, # of ROIs lin_T, offset = _mapping_to_voxel(affine, voxel_size) fa_group = defaultdict(list) idx = 0 for i in range(1, N): for j in range(i + 1, N): tmp_streamlines = cell_streamlines[idx] ROI_pair = cell_id[idx] idx = idx + 1 fa_streamlines = [] for sl in tmp_streamlines: slpoints = _to_voxel_coordinates(sl, lin_T, offset) #get FA for each streamlines ii, jj, kk = slpoints.T fa_value = fa_vol[ii, jj, kk] fa_streamlines.append(fa_value) fa_group[i, j].append(fa_streamlines) # if(len(fa_streamlines)>20): # pdb.set_trace() return fa_group
def convert_to_indices(streamline, papaya_aff, aff, img): #print(streamline) topoints = lambda x : np.array([[m["x"], m["y"], m["z"]] for m in x["world_coor"]]) points_orig = topoints(streamline) points_nifti_space = list(utils.move_streamlines([points_orig], aff, input_space=papaya_aff))[0] from dipy.tracking._utils import _to_voxel_coordinates, _mapping_to_voxel lin_T, offset = _mapping_to_voxel(aff, None) idx = _to_voxel_coordinates(points_orig, lin_T, offset) return points_nifti_space, idx
def _run_interface(self, runtime): # Loading the ROI file import nibabel as nib import numpy as np from dipy.tracking import utils img = nib.load(self.inputs.ROI_file) data = img.get_data() affine = img.get_affine() # Getting the FA file img = nib.load(self.inputs.FA_file) FA_data = img.get_data() FA_affine = img.get_affine() # Loading the streamlines from nibabel import trackvis streams, hdr = trackvis.read(self.inputs.trackfile,points_space='rasmm') streamlines = [s[0] for s in streams] streamlines_affine = trackvis.aff_from_hdr(hdr,atleast_v2=True) # Checking for negative values from dipy.tracking._utils import _mapping_to_voxel, _to_voxel_coordinates endpoints = [sl[0::len(sl)-1] for sl in streamlines] lin_T, offset = _mapping_to_voxel(affine, (1.,1.,1.)) inds = np.dot(endpoints, lin_T) inds += offset negative_values = np.where(inds <0)[0] for negative_value in sorted(negative_values, reverse=True): del streamlines[negative_value] # Constructing the streamlines matrix matrix,mapping = utils.connectivity_matrix(streamlines=streamlines,label_volume=data,affine=streamlines_affine,symmetric=True,return_mapping=True,mapping_as_streamlines=True) matrix[matrix < 10] = 0 # Constructing the FA matrix dimensions = matrix.shape FA_matrix = np.empty(shape=dimensions) for i in range(0,dimensions[0]): for j in range(0,dimensions[1]): if matrix[i,j]: dm = utils.density_map(mapping[i,j], FA_data.shape, affine=streamlines_affine) FA_matrix[i,j] = np.mean(FA_data[dm>0]) else: FA_matrix[i,j] = 0 FA_matrix[np.tril_indices(n=len(FA_matrix))] = 0 FA_matrix = FA_matrix.T + FA_matrix - np.diagonal(FA_matrix) from nipype.utils.filemanip import split_filename _, base, _ = split_filename(self.inputs.trackfile) np.savetxt(base + '_FA_matrix.txt',FA_matrix,delimiter='\t') return runtime
def convert_to_indices(streamline, papaya_aff, aff, img): #print(streamline) topoints = lambda x: np.array([[m["x"], m["y"], m["z"]] for m in x["world_coor"]]) points_orig = topoints(streamline) points_nifti_space = list( utils.move_streamlines([points_orig], aff, input_space=papaya_aff))[0] from dipy.tracking._utils import _to_voxel_coordinates, _mapping_to_voxel lin_T, offset = _mapping_to_voxel(aff, None) idx = _to_voxel_coordinates(points_orig, lin_T, offset) return points_nifti_space, idx
def target(streamlines, affine, target_mask, include=True, strict=False): """Filters streamlines based on whether or not they pass through an ROI. Parameters ---------- streamlines : iterable A sequence of streamlines. Each streamline should be a (N, 3) array, where N is the length of the streamline. affine : array (4, 4) The mapping between voxel indices and the point space for seeds. The voxel_to_rasmm matrix, typically from a NIFTI file. target_mask : array-like A mask used as a target. Non-zero values are considered to be within the target region. include : bool, default True If True, streamlines passing through `target_mask` are kept. If False, the streamlines not passing through `target_mask` are kept. Returns ------- streamlines : generator A sequence of streamlines that pass through `target_mask`. Raises ------ ValueError When the points of the streamlines lie outside of the `target_mask`. See Also -------- density_map """ target_mask = np.array(target_mask, dtype=bool, copy=True) lin_T, offset = _mapping_to_voxel(affine) yield # End of initialization for sl in streamlines: try: ind = _to_voxel_coordinates(sl, lin_T, offset) i, j, k = ind.T state = target_mask[i, j, k] except IndexError: raise ValueError("streamlines points are outside of target_mask") if state.any() == include: if strict == 'strict': yield sl[state == include] elif strict == 'longstring': longsl=longstring(state == include,margin=2) yield sl[longsl] else: yield sl
def path_length(streamlines, affine, aoi, fill_value=-1): """Compute the shortest path, along any streamline, between aoi and each voxel. Parameters ---------- streamlines : seq of (N, 3) arrays A sequence of streamlines, path length is given in mm along the curve of the streamline. aoi : array, 3d A mask (binary array) of voxels from which to start computing distance. affine : array (4, 4) The mapping between voxel indices and the point space for seeds. The voxel_to_rasmm matrix, typically from a NIFTI file. fill_value : float The value of voxel in the path length map that are not connected to the aoi. Returns ------- plm : array Same shape as aoi. The minimum distance between every point and aoi along the path of a streamline. """ aoi = np.asarray(aoi, dtype=bool) # path length map plm = np.empty(aoi.shape, dtype=float) plm[:] = np.inf lin_T, offset = _mapping_to_voxel(affine) for sl in streamlines: seg_ind = _to_voxel_coordinates(sl, lin_T, offset) i, j, k = seg_ind.T # Get where streamlines passes through aoi breaks = aoi[i, j, k] # Where streamline passes aoi, dist is zero i, j, k = seg_ind[breaks].T plm[i, j, k] = 0 # If a streamline crosses aoi >1, re-start counting distance for each for seg in _as_segments(sl, breaks): i, j, k = _to_voxel_coordinates(seg[1:], lin_T, offset).T # Get the distance, in mm, between streamline points segment_length = np.sqrt(((seg[1:] - seg[:-1])**2).sum(1)) dist = segment_length.cumsum() # Updates path length map with shorter distances minimum_at(plm, (i, j, k), dist) if fill_value != np.inf: plm = np.where(plm == np.inf, fill_value, plm) return plm
def target_line_based(streamlines, affine, target_mask, include=True): """Filter streamlines based on whether or not they pass through a ROI, using a line-based algorithm. Mostly used as a replacement of `target` for compressed streamlines. This function never returns single-point streamlines, whatever the value of `include`. Parameters ---------- streamlines : iterable A sequence of streamlines. Each streamline should be a (N, 3) array, where N is the length of the streamline. affine : array (4, 4) The mapping between voxel indices and the point space for seeds. The voxel_to_rasmm matrix, typically from a NIFTI file. target_mask : array-like A mask used as a target. Non-zero values are considered to be within the target region. include : bool, default True If True, streamlines passing through `target_mask` are kept. If False, the streamlines not passing through `target_mask` are kept. Returns ------- streamlines : generator A sequence of streamlines that pass through `target_mask`. References ---------- [Bresenham5] Bresenham, Jack Elton. "Algorithm for computer control of a digital plotter", IBM Systems Journal, vol 4, no. 1, 1965. [Houde15] Houde et al. How to avoid biased streamlines-based metrics for streamlines with variable step sizes, ISMRM 2015. See Also -------- dipy.tracking.utils.density_map dipy.tracking.streamline.compress_streamlines """ target_mask = np.array(target_mask, dtype=np.uint8, copy=True) lin_T, offset = _mapping_to_voxel(affine) streamline_index = _streamlines_in_mask(streamlines, target_mask, lin_T, offset) yield # End of initialization for idx in np.where(streamline_index == [0, 1][include])[0]: yield streamlines[idx]
def path_length(streamlines, aoi, affine, fill_value=-1): """ Computes the shortest path, along any streamline, between aoi and each voxel. Parameters ---------- streamlines : seq of (N, 3) arrays A sequence of streamlines, path length is given in mm along the curve of the streamline. aoi : array, 3d A mask (binary array) of voxels from which to start computing distance. affine : array (4, 4) The mapping from voxel indices to streamline points. fill_value : float The value of voxel in the path length map that are not connected to the aoi. Returns ------- plm : array Same shape as aoi. The minimum distance between every point and aoi along the path of a streamline. """ aoi = np.asarray(aoi, dtype=bool) # path length map plm = np.empty(aoi.shape, dtype=float) plm[:] = np.inf lin_T, offset = _mapping_to_voxel(affine, None) for sl in streamlines: seg_ind = _to_voxel_coordinates(sl, lin_T, offset) i, j, k = seg_ind.T # Get where streamlines passes through aoi breaks = aoi[i, j, k] # Where streamline passes aoi, dist is zero i, j, k = seg_ind[breaks].T plm[i, j, k] = 0 # If a streamline crosses aoi >1, re-start counting distance for each for seg in _as_segments(sl, breaks): i, j, k = _to_voxel_coordinates(seg[1:], lin_T, offset).T # Get the distance, in mm, between streamline points segment_length = np.sqrt(((seg[1:] - seg[:-1]) ** 2).sum(1)) dist = segment_length.cumsum() # Updates path length map with shorter distances minimum_at(plm, (i, j, k), dist) if fill_value != np.inf: plm = np.where(plm == np.inf, fill_value, plm) return plm
def target_line_based(streamlines, target_mask, affine=None, include=True): # Copy-Paste from Dipy to get indices target_mask = np.array(target_mask, dtype=np.uint8, copy=True) lin_T, offset = _mapping_to_voxel(affine) streamline_index = _streamlines_in_mask( streamlines, target_mask, lin_T, offset) target_indices = [] target_streamlines = [] for idx in np.where(streamline_index == [0, 1][include])[0]: target_indices.append(idx) target_streamlines.append(streamlines[idx]) return target_streamlines, target_indices
def target_line_based(streamlines, target_mask, affine=None, include=True): """Filters streamlines based on whether or not they pass through a ROI, using a line-based algorithm. Mostly used as a replacement of `target` for compressed streamlines. This function never returns single-point streamlines, whatever the value of `include`. Parameters ---------- streamlines : iterable A sequence of streamlines. Each streamline should be a (N, 3) array, where N is the length of the streamline. target_mask : array-like A mask used as a target. Non-zero values are considered to be within the target region. affine : array (4, 4) The affine transform from voxel indices to streamline points. include : bool, default True If True, streamlines passing through `target_mask` are kept. If False, the streamlines not passing through `target_mask` are kept. Returns ------- streamlines : generator A sequence of streamlines that pass through `target_mask`. References ---------- [Bresenham5] Bresenham, Jack Elton. "Algorithm for computer control of a digital plotter", IBM Systems Journal, vol 4, no. 1, 1965. [Houde15] Houde et al. How to avoid biased streamlines-based metrics for streamlines with variable step sizes, ISMRM 2015. See Also -------- dipy.tracking.utils.density_map dipy.tracking.streamline.compress_streamlines """ target_mask = np.array(target_mask, dtype=np.uint8, copy=True) lin_T, offset = _mapping_to_voxel(affine, voxel_size=None) streamline_index = _streamlines_in_mask( streamlines, target_mask, lin_T, offset) yield # End of initialization for idx in np.where(streamline_index == [0, 1][include])[0]: yield streamlines[idx]
def streamline_pruning(streamlines, label_volume, voxel_size=None, affine=None): """Cut streamlines such that it only connects two regions of interests Parameters ---------- streamlines : sequence A sequence of streamlines. label_volume : ndarray An image volume with an integer data type, where the intensities in the volume map to anatomical structures. voxel_size : This argument is deprecated. affine : array_like (4, 4) The mapping from voxel coordinates to streamline coordinates. """ # Error checking on label_volume kind = label_volume.dtype.kind labels_positive = ((kind == 'u') or ((kind == 'i') and (label_volume.min() >= 0))) valid_label_volume = (labels_positive and label_volume.ndim == 3) if not valid_label_volume: raise ValueError("label_volume must be a 3d integer array with" "non-negative label values") streamlines = list(streamlines) lin_T, offset = _mapping_to_voxel(affine, voxel_size) # for each streamlines, we will check it and cut it. new_streamlines = [] for sl in streamlines: # Map the streamlines coordinates to voxel coordinates sl_volidx = _to_voxel_coordinates(sl, lin_T, offset) sl_labels = label_volume[sl_volidx[:, 0], sl_volidx[:, 1], sl_volidx[:, 2]] temp_streamlines, num_sl = streamline_cut(sl, sl_labels) if (num_sl == 1): curr_sl = temp_streamlines new_streamlines.append(curr_sl) else: for i in range(0, num_sl): curr_sl = temp_streamlines[i] new_streamlines.append(curr_sl) return new_streamlines
def bundle_map(bundle, affine, img_shape, dir_out, cnt_out): lin_T, offset = _mapping_to_voxel(affine) for tract in bundle: inds = _to_voxel_coordinates(tract.streamline, lin_T, offset) i, j, k = inds.T cnt_out[i, j, k] += np.uint16(1) vecs = tract.data_for_points["t"].astype(np.float16) dir_out[i, j, k] += vecs * safe_sign( np.sum(dir_out[i, j, k] * vecs, axis=1, keepdims=True)) dir_out /= (np.expand_dims(cnt_out, -1) + np.float16(10**-6)) dir_out /= (np.linalg.norm(dir_out, axis=-1, keepdims=True) + np.float16(10**-6))
def vol_map(streamlines, affine, vol_vec, vol_dims): from dipy.tracking._utils import (_mapping_to_voxel, _to_voxel_coordinates) """Calculates the mean volume of the streamlines that pass through each voxel. Parameters ---------- streamlines : iterable A sequence of streamlines. affine : array_like (4, 4) The mapping from voxel coordinates to streamline points. The voxel_to_rasmm matrix, typically from a NIFTI file. vol_vec : ndarray, shape (1, N) while N is the number of streamlines The volume (ADD for example) according which the code calculates the new image. vol_dims : 3 ints The shape of the volume to be returned containing the streamlines counts Returns ------- vol_vox : ndarray, shape=vol_dims The mean volume of the streamlines that pass through each voxel. Raises ------ IndexError When the points of the streamlines lie outside of the return volume. Notes ----- A streamline can pass through a voxel even if one of the points of the streamline does not lie in the voxel. For example a step from [0,0,0] to [0,0,2] passes through [0,0,1]. Consider subsegmenting the streamlines when the edges of the voxels are smaller than the steps of the streamlines. """ streamlines = set_number_of_points(streamlines, 50) lin_T, offset = _mapping_to_voxel(affine) counts = np.zeros(vol_dims, 'int') vols_sum = np.zeros(vol_dims, 'float64') for sl, vl in zip(streamlines, vol_vec): inds = _to_voxel_coordinates(sl, lin_T, offset) i, j, k = inds.T # this takes advantage of the fact that numpy's += operator only # acts once even if there are repeats in inds counts[i, j, k] += 1 vols_sum[i, j, k] += vl vox_vol = np.true_divide(vols_sum, counts, out=np.zeros_like(vols_sum), where=counts != 0) return vox_vol
def target(streamlines, target_mask, affine, include=True): """Filters streamlines based on whether or not they pass through an ROI. Parameters ---------- streamlines : iterable A sequence of streamlines. Each streamline should be a (N, 3) array, where N is the length of the streamline. target_mask : array-like A mask used as a target. Non-zero values are considered to be within the target region. affine : array (4, 4) The affine transform from voxel indices to streamline points. include : bool, default True If True, streamlines passing through `target_mask` are kept. If False, the streamlines not passing through `target_mask` are kept. Returns ------- streamlines : generator A sequence of streamlines that pass through `target_mask`. Raises ------ ValueError When the points of the streamlines lie outside of the `target_mask`. See Also -------- density_map """ target_mask = np.array(target_mask, dtype=bool, copy=True) lin_T, offset = _mapping_to_voxel(affine, voxel_size=None) yield # End of initialization for sl in streamlines: try: ind = _to_voxel_coordinates(sl, lin_T, offset) i, j, k = ind.T state = target_mask[i, j, k] except IndexError: raise ValueError("streamlines points are outside of target_mask") if state.any() == include: yield sl
def connectivity_matrix2(streamlines, label_volume, affine, shape, voxel_size=None): endpoints = [sl for sl in streamlines] lin_T, offset = _mapping_to_voxel(affine, voxel_size) # endpoints = _to_voxel_coordinates(streamlines, lin_T, offset) # endpoints = endpoints.astype(int) # streamlines = list(endpoints) # endlabels2 = label_volume[i2, j2, k2] myList = [] indexROI = np.unique(label_volume) indexROI.sort(0) matriz = np.zeros(shape=(len(indexROI), len(indexROI))) from decimal import Decimal print("ROI Number = " + str(len(indexROI))) for ROI in indexROI: ROIimg = (label_volume == ROI) ROIimg = ROIimg.astype(int) for ROI2 in indexROI: # if ((ROI == 1) & (ROI2 == 2)): if (1): if (ROI2 > ROI): ROI2img = (label_volume == ROI2) ROI2img = ROI2img.astype(int) for sl in streamlines: # sl += offset sl_Aux = sl sl = _to_voxel_coordinates(sl, lin_T, offset) i, j, k = sl.T # i2, j2, k2 = endpoints.T labelsROI = ROIimg[i, j, k] labelsROI2 = ROI2img[i, j, k] if ((sum(labelsROI) > 0) & (sum(labelsROI2) > 0)): matriz[ROI, ROI2] = matriz[ROI, ROI2] + 1 # myList.append(sl_Aux) print(ROI) return matriz.astype(int)
def test_create_density_map(): """ Test for create_density_map functionality """ from pynets.dmri import track from dipy.tracking._utils import _mapping_to_voxel base_dir = os.path.abspath( pkg_resources.resource_filename("pynets", "../data/examples")) dir_path = f"{base_dir}/BIDS/sub-25659/ses-1/dwi" dwi_file = f"{base_dir}/BIDS/sub-25659/ses-1/dwi/final_preprocessed_" \ f"dwi.nii.gz" dwi_img = nib.load(dwi_file) # Load output from test_filter_streamlines: dictionary of streamline info streamlines_trk = f"{base_dir}/miscellaneous/streamlines_model-csd_" \ f"nodetype-parc_tracktype-local_traversal-prob_" \ f"minlength-0.trk" streamlines = nib.streamlines.load(streamlines_trk).streamlines # Remove streamlines with negative voxel indices lin_T, offset = _mapping_to_voxel(np.eye(4)) streams_final_filt_final = [] for sl in streamlines: inds = np.dot(sl, lin_T) inds += offset if not inds.min().round(decimals=6) < 0: streams_final_filt_final.append(sl) conn_model = 'csd' node_radius = None curv_thr_list = [40, 30] step_list = [0.1, 0.2, 0.3, 0.4, 0.5] subnet = None roi = None traversal = 'prob' max_length = 0 [dir_path, dm_path ] = track.create_density_map(dwi_img, dir_path, streams_final_filt_final, conn_model, node_radius, curv_thr_list, step_list, subnet, roi, traversal, max_length, '/tmp') assert dir_path is not None assert dm_path is not None
def density_map(streamlines, vol_dims, affine=None): """Counts the number of unique streamlines that pass through each voxel. Parameters ---------- streamlines : iterable A sequence of streamlines. vol_dims : 3 ints The shape of the volume to be returned containing the streamlines counts affine : array_like (4, 4) The mapping from voxel coordinates to streamline points. Returns ------- image_volume : ndarray, shape=vol_dims The number of streamline points in each voxel of volume. Raises ------ IndexError When the points of the streamlines lie outside of the return volume. Notes ----- A streamline can pass through a voxel even if one of the points of the streamline does not lie in the voxel. For example a step from [0,0,0] to [0,0,2] passes through [0,0,1]. Consider subsegmenting the streamlines when the edges of the voxels are smaller than the steps of the streamlines. """ if affine is None: affine = np.eye(4) lin_T, offset = _mapping_to_voxel(affine) counts = np.zeros(vol_dims, 'int') for sl in streamlines: inds = _to_voxel_coordinates(sl, lin_T, offset) i, j, k = inds.T # this takes advantage of the fact that numpy's += operator only # acts once even if there are repeats in inds counts[i, j, k] += 1 return counts
def test_create_density_map(): """ Test for create_density_map functionality """ from pynets.dmri import track from dipy.tracking._utils import _mapping_to_voxel base_dir = str(Path(__file__).parent / "examples") dir_path = f"{base_dir}/BIDS/sub-0025427/ses-1/dwi" dwi_file = f"{base_dir}/BIDS/sub-0025427/ses-1/dwi/final_preprocessed_dwi.nii.gz" dwi_img = nib.load(dwi_file) # Load output from test_filter_streamlines: dictionary of streamline info streamlines_trk = f"{base_dir}/miscellaneous/streamlines_est-csd_nodetype-parc_samples-10000streams_tt-local_dg-prob_ml-0.trk" streamlines = nib.streamlines.load(streamlines_trk).streamlines # Remove streamlines with negative voxel indices lin_T, offset = _mapping_to_voxel(np.eye(4)) streams_final_filt_final = [] for sl in streamlines: inds = np.dot(sl, lin_T) inds += offset if not inds.min().round(decimals=6) < 0: streams_final_filt_final.append(sl) conn_model = 'csd' target_samples = 10000 node_size = None curv_thr_list = [40, 30] step_list = [0.1, 0.2, 0.3, 0.4, 0.5] network = None roi = None directget = 'prob' max_length = 0 [streams, dir_path, dm_path ] = track.create_density_map(dwi_img, dir_path, streams_final_filt_final, conn_model, target_samples, node_size, curv_thr_list, step_list, network, roi, directget, max_length) assert streams is not None assert dir_path is not None assert dm_path is not None
def density_map(streamlines, vol_dims, voxel_size=None, affine=None): """Counts the number of unique streamlines that pass through each voxel. Parameters ---------- streamlines : iterable A sequence of streamlines. vol_dims : 3 ints The shape of the volume to be returned containing the streamlines counts voxel_size : This argument is deprecated. affine : array_like (4, 4) The mapping from voxel coordinates to streamline points. Returns ------- image_volume : ndarray, shape=vol_dims The number of streamline points in each voxel of volume. Raises ------ IndexError When the points of the streamlines lie outside of the return volume. Notes ----- A streamline can pass through a voxel even if one of the points of the streamline does not lie in the voxel. For example a step from [0,0,0] to [0,0,2] passes through [0,0,1]. Consider subsegmenting the streamlines when the edges of the voxels are smaller than the steps of the streamlines. """ lin_T, offset = _mapping_to_voxel(affine, voxel_size) counts = np.zeros(vol_dims, 'int') for sl in streamlines: inds = _to_voxel_coordinates(sl, lin_T, offset) i, j, k = inds.T # this takes advantage of the fact that numpy's += operator only # acts once even if there are repeats in inds counts[i, j, k] += 1 return counts
def rois_connectedvol_matinput(label_img, N, groupsl, voxel_size=None, affine=None): # input: label_img: image of labels, index from 0 - N # N: # of ROIs # groupsl: streamlines connecting ith and jth ROIs, groupsl[i,j] # voxel_size, affine: some parameters to transfore streamlines to voxel ROI_vol = roi_volume_extraction(label_img, N) lin_T, offset = _mapping_to_voxel(affine, voxel_size) connectedvolratio_cm = np.zeros([N, N]) connectedvol_cm = np.zeros([N, N]) [N1, N2] = groupsl.shape for i in range(0, N1 - 1): #starting from 0 for j in range(i + 1, N2): tmp_streamlines = groupsl[i, j] Nfibers = 0 if (len(tmp_streamlines) > 2): [tmp1, tmp2, Nfibers] = tmp_streamlines.shape tmpstartp = [] tmpendp = [] for n in range(0, Nfibers): tmp = tmp_streamlines[:, :, n] sl = tmp.transpose( ) #flip the matrix to get the right input for extracting fa; slpoints = _to_voxel_coordinates(sl, lin_T, offset) ii, jj, kk = slpoints.T newlabel_img = label_img[ii, jj, kk] tmpstartp.append(ii[1] * 1000000 + jj[1] * 1000 + kk[1]) tmpendp.append(ii[-1] * 1000000 + jj[-1] * 1000 + kk[-1]) nstartp_ratio = len(list(set(tmpstartp))) / (ROI_vol[i]) nendp_ratio = len(list(set(tmpendp))) / (ROI_vol[j]) nstartp = len(list(set(tmpstartp))) nendp = len(list(set(tmpendp))) connectedvol_cm[i, j] = (nstartp + nendp) connectedvolratio_cm[i, j] = (nstartp_ratio + nendp_ratio) / 2 return connectedvol_cm, connectedvolratio_cm
def streamline_endpoints(streamlines, label_volume, voxel_size=None, affine=None): # input: streamlines, input streamlines # anaimg, input image endpointimg = np.zeros(label_volume.shape, dtype='int') # get the ending points of streamlines streamlines = list(streamlines) endpoints = [sl[0::len(sl) - 1] for sl in streamlines] # Map the streamlines coordinates to voxel coordinates lin_T, offset = _mapping_to_voxel(affine, voxel_size) endpoints = _to_voxel_coordinates(endpoints, lin_T, offset) i, j, k = endpoints.T endpointimg[i, j, k] = 1 return endpointimg
def rois_connectedvol_cellinput(label_img, N, cell_streamlines, cell_id, voxel_size=None, affine=None): # input: label_img: image of labels, index from 0 - N # N: # of ROIs # groupsl: streamlines connecting ith and jth ROIs, groupsl[i,j] # voxel_size, affine: some parameters to transfore streamlines to voxel ROI_vol = roi_volume_extraction_cellinput(label_img, N) lin_T, offset = _mapping_to_voxel(affine, voxel_size) connectedvolratio_cm = np.zeros([N, N]) connectedvol_cm = np.zeros([N, N]) idx = 0 for i in range(1, N): for j in range(i + 1, N): tmp_streamlines = cell_streamlines[idx] idx = idx + 1 tmpstartp = [] tmpendp = [] for sl in tmp_streamlines: #sl = tmp.transpose() #flip the matrix to get the right input for extracting fa; slpoints = _to_voxel_coordinates(sl, lin_T, offset) ii, jj, kk = slpoints.T newlabel_img = label_img[ii, jj, kk] tmpstartp.append(ii[1] * 1000000 + jj[1] * 1000 + kk[1]) tmpendp.append(ii[-1] * 1000000 + jj[-1] * 1000 + kk[-1]) nstartp_ratio = len(list(set(tmpstartp))) / (ROI_vol[i]) nendp_ratio = len(list(set(tmpendp))) / (ROI_vol[j]) nstartp = len(list(set(tmpstartp))) nendp = len(list(set(tmpendp))) connectedvol_cm[i, j] = (nstartp + nendp) connectedvolratio_cm[i, j] = (nstartp_ratio + nendp_ratio) / 2 return connectedvol_cm, connectedvolratio_cm
def fa_extraction_use_matinput( groupsl, fa_vol, N, voxel_size=None, affine=None, ): # input: groupsl, streamlines connecting ith and jth ROIs, groupsl[i,j] # fa_vol, fa values in the image domain # N, # of ROIs lin_T, offset = _mapping_to_voxel(affine, voxel_size) fa_group = defaultdict(list) [N1, N2] = groupsl.shape for i in range(0, N1): for j in range(i + 1, N2): tmp_streamlines = groupsl[i, j] Nfibers = 0 if (len(tmp_streamlines) > 2): [tmp1, tmp2, Nfibers] = tmp_streamlines.shape fa_streamlines = [] for n in range(0, Nfibers): tmp = tmp_streamlines[:, :, n] sl = tmp.transpose( ) #flip the matrix to get the right input for extracting fa; slpoints = _to_voxel_coordinates(sl, lin_T, offset) #get FA for each streamlines ii, jj, kk = slpoints.T fa_value = fa_vol[ii, jj, kk] fa_streamlines.append(fa_value) fa_group[i, j].append(fa_streamlines) return fa_group
def connectivity_matrix(streamlines, label_volume, voxel_size=None, affine=None, symmetric=True, return_mapping=False, mapping_as_streamlines=False): """Counts the streamlines that start and end at each label pair. Parameters ---------- streamlines : sequence A sequence of streamlines. label_volume : ndarray An image volume with an integer data type, where the intensities in the volume map to anatomical structures. voxel_size : This argument is deprecated. affine : array_like (4, 4) The mapping from voxel coordinates to streamline coordinates. symmetric : bool, True by default Symmetric means we don't distinguish between start and end points. If symmetric is True, ``matrix[i, j] == matrix[j, i]``. return_mapping : bool, False by default If True, a mapping is returned which maps matrix indices to streamlines. mapping_as_streamlines : bool, False by default If True voxel indices map to lists of streamline objects. Otherwise voxel indices map to lists of integers. Returns ------- matrix : ndarray The number of connection between each pair of regions in `label_volume`. mapping : defaultdict(list) ``mapping[i, j]`` returns all the streamlines that connect region `i` to region `j`. If `symmetric` is True mapping will only have one key for each start end pair such that if ``i < j`` mapping will have key ``(i, j)`` but not key ``(j, i)``. """ # Error checking on label_volume kind = label_volume.dtype.kind labels_positive = ((kind == 'u') or ((kind == 'i') and (label_volume.min() >= 0))) valid_label_volume = (labels_positive and label_volume.ndim == 3) if not valid_label_volume: raise ValueError("label_volume must be a 3d integer array with" "non-negative label values") # If streamlines is an iterators if return_mapping and mapping_as_streamlines: streamlines = list(streamlines) # take the first and last point of each streamline endpoints = [sl[0::len(sl)-1] for sl in streamlines] # Map the streamlines coordinates to voxel coordinates lin_T, offset = _mapping_to_voxel(affine, voxel_size) endpoints = _to_voxel_coordinates(endpoints, lin_T, offset) # get labels for label_volume i, j, k = endpoints.T endlabels = label_volume[i, j, k] if symmetric: endlabels.sort(0) mx = label_volume.max() + 1 matrix = ndbincount(endlabels, shape=(mx, mx)) if symmetric: matrix = np.maximum(matrix, matrix.T) if return_mapping: mapping = defaultdict(list) for i, (a, b) in enumerate(endlabels.T): mapping[a, b].append(i) # Replace each list of indices with the streamlines they index if mapping_as_streamlines: for key in mapping: mapping[key] = [streamlines[i] for i in mapping[key]] # Return the mapping matrix and the mapping return matrix, mapping else: return matrix
def connectivity_matrix(streamlines, label_volume, voxel_size=None, affine=None, symmetric=True, return_mapping=False, mapping_as_streamlines=False): """Counts the streamlines that start and end at each label pair. Parameters ---------- streamlines : sequence A sequence of streamlines. label_volume : ndarray An image volume with an integer data type, where the intensities in the volume map to anatomical structures. voxel_size : This argument is deprecated. affine : array_like (4, 4) The mapping from voxel coordinates to streamline coordinates. symmetric : bool, False by default Symmetric means we don't distinguish between start and end points. If symmetric is True, ``matrix[i, j] == matrix[j, i]``. return_mapping : bool, False by default If True, a mapping is returned which maps matrix indices to streamlines. mapping_as_streamlines : bool, False by default If True voxel indices map to lists of streamline objects. Otherwise voxel indices map to lists of integers. Returns ------- matrix : ndarray The number of connection between each pair of regions in `label_volume`. mapping : defaultdict(list) ``mapping[i, j]`` returns all the streamlines that connect region `i` to region `j`. If `symmetric` is True mapping will only have one key for each start end pair such that if ``i < j`` mapping will have key ``(i, j)`` but not key ``(j, i)``. """ # Error checking on label_volume kind = label_volume.dtype.kind labels_positive = ((kind == 'u') or ((kind == 'i') and (label_volume.min() >= 0))) valid_label_volume = (labels_positive and label_volume.ndim == 3) if not valid_label_volume: raise ValueError("label_volume must be a 3d integer array with" "non-negative label values") # If streamlines is an iterators if return_mapping and mapping_as_streamlines: streamlines = list(streamlines) # take the first and last point of each streamline endpoints = [sl[0::len(sl)-1] for sl in streamlines] # Map the streamlines coordinates to voxel coordinates lin_T, offset = _mapping_to_voxel(affine, voxel_size) endpoints = _to_voxel_coordinates(endpoints, lin_T, offset) # get labels for label_volume i, j, k = endpoints.T endlabels = label_volume[i, j, k] if symmetric: endlabels.sort(0) mx = label_volume.max() + 1 matrix = ndbincount(endlabels, shape=(mx, mx)) if symmetric: matrix = np.maximum(matrix, matrix.T) if return_mapping: mapping = defaultdict(list) for i, (a, b) in enumerate(endlabels.T): mapping[a, b].append(i) # Replace each list of indices with the streamlines they index if mapping_as_streamlines: for key in mapping: mapping[key] = [streamlines[i] for i in mapping[key]] # Return the mapping matrix and the mapping return matrix, mapping else: return matrix
def _run_interface(self, runtime): # Loading the ROI file from dipy.tracking import utils import nibabel as nib import numpy as np import os img = nib.load(self.inputs.ROI_file) data = img.get_data() affine = img.get_affine() # Getting ROI volumes if they haven't been generated if not os.path.isfile('/imaging/jb07/CALM/DWI/FA_connectome/Atlas_volumes.csv'): import nibabel as nib import numpy as np import os import pandas as pd import subprocess atlas_file = ROI_file img = nib.load(atlas_file) data = img.get_data() affine = img.get_affine() volumes = pd.DataFrame() atlas_labels = np.unique(data) for atlas_label in atlas_labels: data = nib.load(atlas_file).get_data() data[data != atlas_label] = 0 data[data == atlas_label] = 1 nib.save(nib.Nifti1Image(data, affine), 'temp.nii.gz') volumes.set_value(atlas_label, 'volume', subprocess.check_output(os.environ['FSLDIR'] + '/bin/fslstats temp.nii.gz -V', shell=True).split(' ')[0]) os.remove('temp.nii.gz') volumes.to_csv('/imaging/jb07/CALM/DWI/FA_connectome/Atlas_volumes.csv') ROI_volumes = pd.read_csv('/home/jb07/CALM/DWI/FA_connectome/Atlas_volumes.csv') # Getting the FA file img = nib.load(self.inputs.FA_file) FA_data = img.get_data() FA_affine = img.get_affine() # Loading the streamlines from nibabel import trackvis streams, hdr = trackvis.read(self.inputs.trackfile,points_space='rasmm') streamlines = [s[0] for s in streams] streamlines_affine = trackvis.aff_from_hdr(hdr,atleast_v2=True) # Checking for negative values from dipy.tracking._utils import _mapping_to_voxel, _to_voxel_coordinates endpoints = [sl[0::len(sl)-1] for sl in streamlines] lin_T, offset = _mapping_to_voxel(affine, (1.,1.,1.)) inds = np.dot(endpoints, lin_T) inds += offset negative_values = np.where(inds <0)[0] for negative_value in sorted(negative_values, reverse=True): del streamlines[negative_value] # Constructing the streamlines matrix matrix,mapping = utils.connectivity_matrix(streamlines=streamlines,label_volume=data,affine=streamlines_affine,symmetric=True,return_mapping=True,mapping_as_streamlines=True) matrix[matrix < 10] = 0 # Constructing the FA matrix dimensions = matrix.shape FA_matrix = np.empty(shape=dimensions) density_matrix = np.empty(shape=dimensions) density_corrected_matrix = np.empty(shape=dimensions) for i in range(0,dimensions[0]): for j in range(0,dimensions[1]): if matrix[i,j]: dm = utils.density_map(mapping[i,j], FA_data.shape, affine=streamlines_affine) FA_matrix[i,j] = np.mean(FA_data[dm>0]) if np.sum(dm > 0) > 0: density_matrix[i,j] = np.sum(dm[dm > 0]) density_corrected_matrix[i,j] = np.sum(dm[dm > 0])/np.sum([ROI_volumes.iloc[i].values.astype('int'), ROI_volumes.iloc[j].values.astype('int')]) else: density_matrix[i,j] = 0 density_corrected_matrix[i,j] = 0 else: FA_matrix[i,j] = 0 density_matrix[i,j] = 0 density_corrected_matrix[i,j] = 0 FA_matrix[np.tril_indices(n=len(FA_matrix))] = 0 FA_matrix = FA_matrix.T + FA_matrix - np.diagonal(FA_matrix) density_matrix[np.tril_indices(n=len(density_matrix))] = 0 density_matrix = density_matrix.T + density_matrix - np.diagonal(density_matrix) density_corrected_matrix[np.tril_indices(n=len(density_corrected_matrix))] = 0 density_corrected_matrix = density_corrected_matrix.T + density_corrected_matrix - np.diagonal(density_corrected_matrix) from nipype.utils.filemanip import split_filename _, base, _ = split_filename(self.inputs.trackfile) np.savetxt(base + '_FA_matrix.txt',FA_matrix,delimiter='\t') np.savetxt(base + '_density_matrix.txt',density_matrix,delimiter='\t') np.savetxt(base + '_volume_corrected_density_matrix.txt',density_corrected_matrix,delimiter='\t')
def direct_streamline_norm( streams, fa_path, ap_path, dir_path, track_type, target_samples, conn_model, network, node_size, dens_thresh, ID, roi, min_span_tree, disp_filt, parc, prune, atlas, labels_im_file, uatlas, labels, coords, norm, binary, atlas_mni, basedir_path, curv_thr_list, step_list, directget, min_length, error_margin, t1_aligned_mni ): """ A Function to perform normalization of streamlines tracked in native diffusion space to an MNI-space template. Parameters ---------- streams : str File path to save streamline array sequence in .trk format. fa_path : str File path to FA Nifti1Image. ap_path : str File path to the anisotropic power Nifti1Image. dir_path : str Path to directory containing subject derivative data for a given pynets run. track_type : str Tracking algorithm used (e.g. 'local' or 'particle'). target_samples : int Total number of streamline samples specified to generate streams. conn_model : str Connectivity reconstruction method (e.g. 'csa', 'tensor', 'csd'). network : str Resting-state network based on Yeo-7 and Yeo-17 naming (e.g. 'Default') used to filter nodes in the study of brain subgraphs. node_size : int Spherical centroid node size in the case that coordinate-based centroids are used as ROI's for tracking. dens_thresh : bool Indicates whether a target graph density is to be used as the basis for thresholding. ID : str A subject id or other unique identifier. roi : str File path to binarized/boolean region-of-interest Nifti1Image file. min_span_tree : bool Indicates whether local thresholding from the Minimum Spanning Tree should be used. disp_filt : bool Indicates whether local thresholding using a disparity filter and 'backbone network' should be used. parc : bool Indicates whether to use parcels instead of coordinates as ROI nodes. prune : bool Indicates whether to prune final graph of disconnected nodes/isolates. atlas : str Name of atlas parcellation used. labels_im_file : str File path to atlas parcellation Nifti1Image aligned to dwi space. uatlas : str File path to atlas parcellation Nifti1Image in MNI template space. labels : list List of string labels corresponding to graph nodes. coords : list List of (x, y, z) tuples corresponding to a coordinate atlas used or which represent the center-of-mass of each parcellation node. norm : int Indicates method of normalizing resulting graph. binary : bool Indicates whether to binarize resulting graph edges to form an unweighted graph. atlas_mni : str File path to atlas parcellation Nifti1Image in T1w-warped MNI space. basedir_path : str Path to directory to output direct-streamline normalized temp files and outputs. curv_thr_list : list List of integer curvature thresholds used to perform ensemble tracking. step_list : list List of float step-sizes used to perform ensemble tracking. directget : str The statistical approach to tracking. Options are: det (deterministic), closest (clos), boot (bootstrapped), and prob (probabilistic). min_length : int Minimum fiber length threshold in mm to restrict tracking. t1_aligned_mni : str File path to the T1w Nifti1Image in template MNI space. Returns ------- streams_warp : str File path to normalized streamline array sequence in .trk format. dir_path : str Path to directory containing subject derivative data for a given pynets run. track_type : str Tracking algorithm used (e.g. 'local' or 'particle'). target_samples : int Total number of streamline samples specified to generate streams. conn_model : str Connectivity reconstruction method (e.g. 'csa', 'tensor', 'csd'). network : str Resting-state network based on Yeo-7 and Yeo-17 naming (e.g. 'Default') used to filter nodes in the study of brain subgraphs. node_size : int Spherical centroid node size in the case that coordinate-based centroids are used as ROI's for tracking. dens_thresh : bool Indicates whether a target graph density is to be used as the basis for thresholding. ID : str A subject id or other unique identifier. roi : str File path to binarized/boolean region-of-interest Nifti1Image file. min_span_tree : bool Indicates whether local thresholding from the Minimum Spanning Tree should be used. disp_filt : bool Indicates whether local thresholding using a disparity filter and 'backbone network' should be used. parc : bool Indicates whether to use parcels instead of coordinates as ROI nodes. prune : bool Indicates whether to prune final graph of disconnected nodes/isolates. atlas : str Name of atlas parcellation used. uatlas : str File path to atlas parcellation Nifti1Image in MNI template space. labels : list List of string labels corresponding to graph nodes. coords : list List of (x, y, z) tuples corresponding to a coordinate atlas used or which represent the center-of-mass of each parcellation node. norm : int Indicates method of normalizing resulting graph. binary : bool Indicates whether to binarize resulting graph edges to form an unweighted graph. atlas_mni : str File path to atlas parcellation Nifti1Image in T1w-warped MNI space. directget : str The statistical approach to tracking. Options are: det (deterministic), closest (clos), boot (bootstrapped), and prob (probabilistic). warped_fa : str File path to MNI-space warped FA Nifti1Image. min_length : int Minimum fiber length threshold in mm to restrict tracking. References ---------- .. [1] Greene, C., Cieslak, M., & Grafton, S. T. (2017). Effect of different spatial normalization approaches on tractography and structural brain networks. Network Neuroscience, 1-19. """ import sys import gc from dipy.tracking.streamline import transform_streamlines from pynets.registration import reg_utils as regutils # from pynets.plotting import plot_gen import pkg_resources import yaml import os.path as op from pynets.registration.reg_utils import vdc from nilearn.image import resample_to_img from dipy.io.streamline import load_tractogram from dipy.tracking import utils from dipy.tracking._utils import _mapping_to_voxel from dipy.io.stateful_tractogram import Space, StatefulTractogram, Origin from dipy.io.streamline import save_tractogram # from pynets.core.utils import missing_elements with open( pkg_resources.resource_filename("pynets", "runconfig.yaml"), "r" ) as stream: try: hardcoded_params = yaml.load(stream) run_dsn = hardcoded_params['tracking']["DSN"][0] except FileNotFoundError as e: import sys print(e, "Failed to parse runconfig.yaml") exit(1) stream.close() if run_dsn is True: dsn_dir = f"{basedir_path}/dmri_reg/DSN" if not op.isdir(dsn_dir): os.mkdir(dsn_dir) namer_dir = f"{dir_path}/tractography" if not op.isdir(namer_dir): os.mkdir(namer_dir) atlas_img = nib.load(labels_im_file) # Run SyN and normalize streamlines fa_img = nib.load(fa_path) vox_size = fa_img.header.get_zooms()[0] template_path = pkg_resources.resource_filename( "pynets", f"templates/FA_{int(vox_size)}mm.nii.gz" ) if sys.platform.startswith('win') is False: try: template_img = nib.load(template_path) except indexed_gzip.ZranError as e: print(e, f"\nCannot load FA template. Do you have git-lfs " f"installed?") sys.exit(1) else: try: template_img = nib.load(template_path) except ImportError as e: print(e, f"\nCannot load FA template. Do you have git-lfs " f"installed?") sys.exit(1) uatlas_mni_img = nib.load(atlas_mni) t1_aligned_mni_img = nib.load(t1_aligned_mni) brain_mask = np.asarray(t1_aligned_mni_img.dataobj).astype("bool") streams_mni = "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s" % ( namer_dir, "/streamlines_mni_", "%s" % (network + "_" if network is not None else ""), "%s" % (op.basename(roi).split(".")[0] + "_" if roi is not None else ""), conn_model, "_", target_samples, "%s" % ( "%s%s" % ("_" + str(node_size), "mm_") if ((node_size != "parc") and (node_size is not None)) else "_" ), "curv", str(curv_thr_list).replace(", ", "_"), "step", str(step_list).replace(", ", "_"), "tracktype-", track_type, "_directget-", directget, "_minlength-", min_length, "_tol-", error_margin, ".trk", ) density_mni = "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s" % ( namer_dir, "/density_map_mni_", "%s" % (network + "_" if network is not None else ""), "%s" % (op.basename(roi).split(".")[0] + "_" if roi is not None else ""), conn_model, "_", target_samples, "%s" % ( "%s%s" % ("_" + str(node_size), "mm_") if ((node_size != "parc") and (node_size is not None)) else "_" ), "curv", str(curv_thr_list).replace(", ", "_"), "step", str(step_list).replace(", ", "_"), "tracktype-", track_type, "_directget-", directget, "_minlength-", min_length, "_tol-", error_margin, ".nii.gz", ) # streams_warp_png = '/tmp/dsn.png' # SyN FA->Template [mapping, affine_map, warped_fa] = regutils.wm_syn( template_path, fa_path, t1_aligned_mni, ap_path, dsn_dir ) tractogram = load_tractogram( streams, fa_img, to_origin=Origin.NIFTI, to_space=Space.VOXMM, bbox_valid_check=False, ) fa_img.uncache() streamlines = tractogram.streamlines warped_fa_img = nib.load(warped_fa) warped_fa_affine = warped_fa_img.affine warped_fa_shape = warped_fa_img.shape streams_in_curr_grid = transform_streamlines( streamlines, warped_fa_affine) # Create isocenter mapping where we anchor the origin transformation # affine to the corner of the FOV by scaling x, y, z offsets according # to a multiplicative van der Corput sequence with a base value equal # to the voxel resolution [x_mul, y_mul, z_mul] = [vdc(i, vox_size) for i in range(1, 4)] ref_grid_aff = vox_size * np.eye(4) ref_grid_aff[3][3] = 1 streams_final_filt = [] i = 0 # Test for various types of voxel-grid configurations combs = [(-x_mul, -y_mul, -z_mul), (-x_mul, -y_mul, z_mul), (-x_mul, y_mul, -z_mul), (x_mul, -y_mul, -z_mul), (x_mul, y_mul, z_mul)] while len(streams_final_filt)/len(streams_in_curr_grid) < 0.90: print(f"Warping streamlines to MNI space. Attempt {i}...") print(len(streams_final_filt)/len(streams_in_curr_grid)) adjusted_affine = affine_map.affine.copy() if i > len(combs) - 1: raise ValueError('DSN failed. Header orientation ' 'information may be corrupted. ' 'Is your dataset oblique?') adjusted_affine[0][3] = adjusted_affine[0][3] * combs[i][0] adjusted_affine[1][3] = adjusted_affine[1][3] * combs[i][1] adjusted_affine[2][3] = adjusted_affine[2][3] * combs[i][2] streams_final_filt = regutils.warp_streamlines(adjusted_affine, ref_grid_aff, mapping, warped_fa_img, streams_in_curr_grid, brain_mask) i += 1 # Remove streamlines with negative voxel indices lin_T, offset = _mapping_to_voxel(np.eye(4)) streams_final_filt_final = [] for sl in streams_final_filt: inds = np.dot(sl, lin_T) inds += offset if not inds.min().round(decimals=6) < 0: streams_final_filt_final.append(sl) # Save streamlines stf = StatefulTractogram( streams_final_filt_final, reference=uatlas_mni_img, space=Space.VOXMM, origin=Origin.NIFTI, ) stf.remove_invalid_streamlines() streams_final_filt_final = stf.streamlines save_tractogram(stf, streams_mni, bbox_valid_check=True) warped_fa_img.uncache() # DSN QC plotting # plot_gen.show_template_bundles(streams_final_filt_final, atlas_mni, # streams_warp_png) plot_gen.show_template_bundles(streamlines, # fa_path, streams_warp_png) # Create and save MNI density map nib.save( nib.Nifti1Image( utils.density_map( streams_final_filt_final, affine=np.eye(4), vol_dims=warped_fa_shape), warped_fa_affine, ), density_mni, ) # Map parcellation from native space back to MNI-space and create an # 'uncertainty-union' parcellation with original mni-space uatlas warped_uatlas = affine_map.transform_inverse( mapping.transform( np.asarray(atlas_img.dataobj).astype("int"), interpolation="nearestneighbour", ), interp="nearest", ) atlas_img.uncache() warped_uatlas_img_res_data = np.asarray( resample_to_img( nib.Nifti1Image(warped_uatlas, affine=warped_fa_affine), uatlas_mni_img, interpolation="nearest", clip=False, ).dataobj ) uatlas_mni_data = np.asarray(uatlas_mni_img.dataobj) uatlas_mni_img.uncache() overlap_mask = np.invert( warped_uatlas_img_res_data.astype("bool") * uatlas_mni_data.astype("bool")) os.makedirs(f"{dir_path}/parcellations", exist_ok=True) atlas_mni = f"{dir_path}/parcellations/" \ f"{op.basename(uatlas).split('.nii')[0]}_liberal.nii.gz" nib.save( nib.Nifti1Image( warped_uatlas_img_res_data * overlap_mask.astype("int") + uatlas_mni_data * overlap_mask.astype("int") + np.invert(overlap_mask).astype("int") * warped_uatlas_img_res_data.astype("int"), affine=uatlas_mni_img.affine, ), atlas_mni, ) del ( tractogram, streamlines, warped_uatlas_img_res_data, uatlas_mni_data, overlap_mask, stf, streams_final_filt_final, streams_final_filt, streams_in_curr_grid, brain_mask, ) gc.collect() assert len(coords) == len(labels) else: print( "Skipping Direct Streamline Normalization (DSN). Will proceed to " "define fiber connectivity in native diffusion space...") streams_mni = streams warped_fa = fa_path atlas_mni = labels_im_file return ( streams_mni, dir_path, track_type, target_samples, conn_model, network, node_size, dens_thresh, ID, roi, min_span_tree, disp_filt, parc, prune, atlas, uatlas, labels, coords, norm, binary, atlas_mni, directget, warped_fa, min_length, error_margin )
def streams2graph(atlas_mni, streams, dir_path, track_type, target_samples, conn_model, network, node_size, dens_thresh, ID, roi, min_span_tree, disp_filt, parc, prune, atlas, uatlas, labels, coords, norm, binary, directget, warped_fa, min_length, error_margin): """ Use tracked streamlines as a basis for estimating a structural connectome. Parameters ---------- atlas_mni : str File path to atlas parcellation Nifti1Image in T1w-warped MNI space. streams : str File path to streamline array sequence in .trk format. dir_path : str Path to directory containing subject derivative data for a given pynets run. track_type : str Tracking algorithm used (e.g. 'local' or 'particle'). target_samples : int Total number of streamline samples specified to generate streams. conn_model : str Connectivity reconstruction method (e.g. 'csa', 'tensor', 'csd'). network : str Resting-state network based on Yeo-7 and Yeo-17 naming (e.g. 'Default') used to filter nodes in the study of brain subgraphs. node_size : int Spherical centroid node size in the case that coordinate-based centroids are used as ROI's for tracking. dens_thresh : bool Indicates whether a target graph density is to be used as the basis for thresholding. ID : str A subject id or other unique identifier. roi : str File path to binarized/boolean region-of-interest Nifti1Image file. min_span_tree : bool Indicates whether local thresholding from the Minimum Spanning Tree should be used. disp_filt : bool Indicates whether local thresholding using a disparity filter and 'backbone network' should be used. parc : bool Indicates whether to use parcels instead of coordinates as ROI nodes. prune : bool Indicates whether to prune final graph of disconnected nodes/isolates. atlas : str Name of atlas parcellation used. uatlas : str File path to atlas parcellation Nifti1Image in MNI template space. labels : list List of string labels corresponding to graph nodes. coords : list List of (x, y, z) tuples corresponding to a coordinate atlas used or which represent the center-of-mass of each parcellation node. norm : int Indicates method of normalizing resulting graph. binary : bool Indicates whether to binarize resulting graph edges to form an unweighted graph. directget : str The statistical approach to tracking. Options are: det (deterministic), closest (clos), boot (bootstrapped), and prob (probabilistic). warped_fa : str File path to MNI-space warped FA Nifti1Image. min_length : int Minimum fiber length threshold in mm to restrict tracking. error_margin : int Euclidean margin of error for classifying a streamline as a connection to an ROI. Default is 2 voxels. Returns ------- atlas_mni : str File path to atlas parcellation Nifti1Image in T1w-warped MNI space. streams : str File path to streamline array sequence in .trk format. conn_matrix : array Adjacency matrix stored as an m x n array of nodes and edges. track_type : str Tracking algorithm used (e.g. 'local' or 'particle'). target_samples : int Total number of streamline samples specified to generate streams. dir_path : str Path to directory containing subject derivative data for given run. conn_model : str Connectivity reconstruction method (e.g. 'csa', 'tensor', 'csd'). network : str Resting-state network based on Yeo-7 and Yeo-17 naming (e.g. 'Default') used to filter nodes in the study of brain subgraphs. node_size : int Spherical centroid node size in the case that coordinate-based centroids are used as ROI's for tracking. dens_thresh : bool Indicates whether a target graph density is to be used as the basis for thresholding. ID : str A subject id or other unique identifier. roi : str File path to binarized/boolean region-of-interest Nifti1Image file. min_span_tree : bool Indicates whether local thresholding from the Minimum Spanning Tree should be used. disp_filt : bool Indicates whether local thresholding using a disparity filter and 'backbone network' should be used. parc : bool Indicates whether to use parcels instead of coordinates as ROI nodes. prune : bool Indicates whether to prune final graph of disconnected nodes/isolates. atlas : str Name of atlas parcellation used. uatlas : str File path to atlas parcellation Nifti1Image in MNI template space. labels : list List of string labels corresponding to graph nodes. coords : list List of (x, y, z) tuples corresponding to a coordinate atlas used or which represent the center-of-mass of each parcellation node. norm : int Indicates method of normalizing resulting graph. binary : bool Indicates whether to binarize resulting graph edges to form an unweighted graph. directget : str The statistical approach to tracking. Options are: det (deterministic), closest (clos), boot (bootstrapped), and prob (probabilistic). min_length : int Minimum fiber length threshold in mm to restrict tracking. error_margin : int Euclidean margin of error for classifying a streamline as a connection to an ROI. Default is 2 voxels. References ---------- .. [1] Sporns, O., Tononi, G., & Kötter, R. (2005). The human connectome: A structural description of the human brain. PLoS Computational Biology. https://doi.org/10.1371/journal.pcbi.0010042 .. [2] Sotiropoulos, S. N., & Zalesky, A. (2019). Building connectomes using diffusion MRI: why, how and but. NMR in Biomedicine. https://doi.org/10.1002/nbm.3752 .. [3] Chung, M. K., Hanson, J. L., Adluru, N., Alexander, A. L., Davidson, R. J., & Pollak, S. D. (2017). Integrative Structural Brain Network Analysis in Diffusion Tensor Imaging. Brain Connectivity. https://doi.org/10.1089/brain.2016.0481 """ import gc import time import pkg_resources import sys import yaml from dipy.tracking.streamline import Streamlines, values_from_volume from dipy.tracking._utils import _mapping_to_voxel, _to_voxel_coordinates import networkx as nx from itertools import combinations from collections import defaultdict from pynets.core import utils, nodemaker from pynets.dmri.dmri_utils import generate_sl from dipy.io.streamline import load_tractogram from dipy.io.stateful_tractogram import Space, Origin with open(pkg_resources.resource_filename("pynets", "runconfig.yaml"), "r") as stream: hardcoded_params = yaml.load(stream) fa_wei = hardcoded_params["StructuralNetworkWeighting"][ "fa_weighting"][0] fiber_density = hardcoded_params["StructuralNetworkWeighting"][ "fiber_density"][0] overlap_thr = hardcoded_params["StructuralNetworkWeighting"][ "overlap_thr"][0] roi_neighborhood_tol = \ hardcoded_params['tracking']["roi_neighborhood_tol"][0] stream.close() start = time.time() if float(roi_neighborhood_tol) <= float(error_margin): try: raise ValueError('roi_neighborhood_tol preset cannot be less than ' 'the value of the structural connectome error' '_margin parameter.') except ValueError: import sys sys.exit(1) else: print(f"Using fiber-roi intersection tolerance: {error_margin}...") # Load FA fa_img = nib.load(warped_fa) # Load parcellation roi_img = nib.load(atlas_mni) atlas_data = np.around(np.asarray(roi_img.dataobj)) roi_zooms = roi_img.header.get_zooms() roi_shape = roi_img.shape # Read Streamlines streamlines = [ i.astype(np.float32) for i in Streamlines( load_tractogram( streams, fa_img, to_origin=Origin.NIFTI, to_space=Space.VOXMM).streamlines) ] # from fury import actor, window # renderer = window.Renderer() # template_actor = actor.contour_from_roi(roi_img.get_fdata(), # color=(50, 50, 50), opacity=0.05) # renderer.add(template_actor) # lines_actor = actor.streamtube(streamlines, window.colors.orange, # linewidth=0.3) # renderer.add(lines_actor) # window.show(renderer) roi_img.uncache() if fa_wei is True: fa_weights = values_from_volume( np.asarray(fa_img.dataobj, dtype=np.float32), streamlines, np.eye(4)) global_fa_weights = list(utils.flatten(fa_weights)) min_global_fa_wei = min([i for i in global_fa_weights if i > 0]) max_global_fa_wei = max(global_fa_weights) fa_weights_norm = [] # Here we normalize by global FA for val_list in fa_weights: fa_weights_norm.append( np.nanmean((val_list - min_global_fa_wei) / (max_global_fa_wei - min_global_fa_wei))) # Make streamlines into generators to keep memory at a minimum total_streamlines = len(streamlines) sl = [generate_sl(i) for i in streamlines] del streamlines # Instantiate empty networkX graph object & dictionary and create # voxel-affine mapping lin_T, offset = _mapping_to_voxel(np.eye(4)) mx = len(np.unique(atlas_data.astype("uint16"))) - 1 g = nx.Graph(ecount=0, vcount=mx) edge_dict = defaultdict(int) node_dict = dict( zip(np.unique(atlas_data.astype("uint16"))[1:], np.arange(mx) + 1)) # Add empty vertices with label volume attributes for node in range(1, mx + 1): g.add_node(node, roi_volume=np.sum(atlas_data.astype("uint16") == node)) # Build graph pc = 0 bad_idxs = [] fiberlengths = {} fa_weights_dict = {} print(f"Quantifying fiber-ROI intersection for {atlas}:") for ix, s in enumerate(sl): # Percent counter pcN = int(round(100 * float(ix / total_streamlines))) if pcN % 10 == 0 and ix > 0 and pcN > pc: pc = pcN print(f"{pcN}%") # Map the streamlines coordinates to voxel coordinates and get labels # for label_volume vox_coords = _to_voxel_coordinates(Streamlines(s), lin_T, offset) lab_coords = [ nodemaker.get_sphere(coord, error_margin, roi_zooms, roi_shape) for coord in vox_coords ] [i, j, k] = np.vstack(np.array(lab_coords)).T # get labels for label_volume lab_arr = atlas_data[i, j, k] # print(lab_arr) endlabels = [] for jx, lab in enumerate(np.unique(lab_arr).astype("uint32")): if (lab > 0) and (np.sum(lab_arr == lab) >= overlap_thr): try: endlabels.append(node_dict[lab]) except BaseException: bad_idxs.append(jx) print(f"Label {lab} missing from parcellation. Check " f"registration and ensure valid input parcellation " f"file.") edges = combinations(endlabels, 2) for edge in edges: # Get fiber lengths along edge if fiber_density is True: if not (edge[0], edge[1]) in fiberlengths.keys(): fiberlengths[(edge[0], edge[1])] = [len(vox_coords)] else: fiberlengths[(edge[0], edge[1])].append(len(vox_coords)) # Get FA values along edge if fa_wei is True: if not (edge[0], edge[1]) in fa_weights_dict.keys(): fa_weights_dict[(edge[0], edge[1])] = [fa_weights_norm[ix]] else: fa_weights_dict[(edge[0], edge[1])].append(fa_weights_norm[ix]) lst = tuple([int(node) for node in edge]) edge_dict[tuple(sorted(lst))] += 1 edge_list = [(k[0], k[1], count) for k, count in edge_dict.items()] g.add_weighted_edges_from(edge_list) del lab_coords, lab_arr, endlabels, edges, edge_list gc.collect() # Add fiber density attributes for each edge # Adapted from the nnormalized fiber-density estimation routines of # Sebastian Tourbier. if fiber_density is True: print("Weighting edges by fiber density...") # Summarize total fibers and total label volumes total_fibers = 0 total_volume = 0 u_start = -1 for u, v, d in g.edges(data=True): total_fibers += len(d) if u != u_start: total_volume += g.nodes[int(u)]['roi_volume'] u_start = u ix = 0 for u, v, d in g.edges(data=True): if d['weight'] > 0: edge_fiberlength_mean = np.nanmean(fiberlengths[(u, v)]) fiber_density = (float( ((float(d['weight']) / float(total_fibers)) / float(edge_fiberlength_mean)) * ((2.0 * float(total_volume)) / (g.nodes[int(u)]['roi_volume'] + g.nodes[int(v)]['roi_volume'])))) * 1000 else: fiber_density = 0 g.edges[u, v].update({"fiber_density": fiber_density}) ix += 1 if fa_wei is True: print("Weighting edges by FA...") # Add FA attributes for each edge ix = 0 for u, v, d in g.edges(data=True): if d['weight'] > 0: edge_average_fa = np.nanmean(fa_weights_dict[(u, v)]) else: edge_average_fa = np.nan g.edges[u, v].update({"fa_weight": edge_average_fa}) ix += 1 # Summarize weights if fa_wei is True and fiber_density is True: for u, v, d in g.edges(data=True): g.edges[u, v].update( {"final_weight": (d['fa_weight']) * d['fiber_density']}) elif fiber_density is True and fa_wei is False: for u, v, d in g.edges(data=True): g.edges[u, v].update({"final_weight": d['fiber_density']}) elif fa_wei is True and fiber_density is False: for u, v, d in g.edges(data=True): g.edges[u, v].update({"final_weight": d['fa_weight'] * d['weight']}) else: for u, v, d in g.edges(data=True): g.edges[u, v].update({"final_weight": d['weight']}) # Convert weighted graph to numpy matrix conn_matrix_raw = nx.to_numpy_array(g, weight='final_weight') # Enforce symmetry conn_matrix = np.maximum(conn_matrix_raw, conn_matrix_raw.T) print("Structural graph completed:\n", str(time.time() - start)) if len(bad_idxs) > 0: bad_idxs = sorted(list(set(bad_idxs)), reverse=True) for j in bad_idxs: del labels[j], coords[j] coords = np.array(coords) labels = np.array(labels) assert len(coords) == len(labels) == conn_matrix.shape[0] return (atlas_mni, streams, conn_matrix, track_type, target_samples, dir_path, conn_model, network, node_size, dens_thresh, ID, roi, min_span_tree, disp_filt, parc, prune, atlas, uatlas, labels, coords, norm, binary, directget, min_length, error_margin)
def connectivity_matrix(streamlines, affine, label_volume, inclusive=False, symmetric=True, return_mapping=False, mapping_as_streamlines=False): """Counts the streamlines that start and end at each label pair. Parameters ---------- streamlines : sequence A sequence of streamlines. affine : array_like (4, 4) The mapping from voxel coordinates to streamline coordinates. The voxel_to_rasmm matrix, typically from a NIFTI file. label_volume : ndarray An image volume with an integer data type, where the intensities in the volume map to anatomical structures. inclusive: bool Whether to analyze the entire streamline, as opposed to just the endpoints. Allowing this will increase calculation time and mapping size, especially if mapping_as_streamlines is True. False by default. symmetric : bool, True by default Symmetric means we don't distinguish between start and end points. If symmetric is True, ``matrix[i, j] == matrix[j, i]``. return_mapping : bool, False by default If True, a mapping is returned which maps matrix indices to streamlines. mapping_as_streamlines : bool, False by default If True voxel indices map to lists of streamline objects. Otherwise voxel indices map to lists of integers. Returns ------- matrix : ndarray The number of connection between each pair of regions in `label_volume`. mapping : defaultdict(list) ``mapping[i, j]`` returns all the streamlines that connect region `i` to region `j`. If `symmetric` is True mapping will only have one key for each start end pair such that if ``i < j`` mapping will have key ``(i, j)`` but not key ``(j, i)``. """ # Error checking on label_volume kind = label_volume.dtype.kind labels_positive = ((kind == 'u') or ((kind == 'i') and (label_volume.min() >= 0))) valid_label_volume = (labels_positive and label_volume.ndim == 3) if not valid_label_volume: raise ValueError("label_volume must be a 3d integer array with" "non-negative label values") # If streamlines is an iterator if return_mapping and mapping_as_streamlines: streamlines = list(streamlines) if inclusive: # Create ndarray to store streamline connections edges = np.ndarray(shape=(3, 0), dtype=int) lin_T, offset = _mapping_to_voxel(affine) for sl, _ in enumerate(streamlines): # Convert streamline to voxel coordinates entire = _to_voxel_coordinates(streamlines[sl], lin_T, offset) i, j, k = entire.T if symmetric: # Create list of all labels streamline passes through entirelabels = list(OrderedDict.fromkeys(label_volume[i, j, k])) # Append all connection combinations with streamline number for comb in combinations(entirelabels, 2): edges = np.append(edges, [[comb[0]], [comb[1]], [sl]], axis=1) else: # Create list of all labels streamline passes through, keeping # order and whether a label was entered multiple times entirelabels = list(groupby(label_volume[i, j, k])) # Append connection combinations along with streamline number, # removing duplicates and connections from a label to itself combs = set(combinations([z[0] for z in entirelabels], 2)) for comb in combs: if comb[0] == comb[1]: pass else: edges = np.append(edges, [[comb[0]], [comb[1]], [sl]], axis=1) if symmetric: edges[0:2].sort(0) mx = label_volume.max() + 1 matrix = ndbincount(edges[0:2], shape=(mx, mx)) if symmetric: matrix = np.maximum(matrix, matrix.T) if return_mapping: mapping = defaultdict(list) for i, (a, b, c) in enumerate(edges.T): mapping[a, b].append(c) # Replace each list of indices with the streamlines they index if mapping_as_streamlines: for key in mapping: mapping[key] = [streamlines[i] for i in mapping[key]] return matrix, mapping return matrix else: # take the first and last point of each streamline endpoints = [sl[0::len(sl) - 1] for sl in streamlines] # Map the streamlines coordinates to voxel coordinates lin_T, offset = _mapping_to_voxel(affine) endpoints = _to_voxel_coordinates(endpoints, lin_T, offset) # get labels for label_volume i, j, k = endpoints.T endlabels = label_volume[i, j, k] if symmetric: endlabels.sort(0) mx = label_volume.max() + 1 matrix = ndbincount(endlabels, shape=(mx, mx)) if symmetric: matrix = np.maximum(matrix, matrix.T) if return_mapping: mapping = defaultdict(list) for i, (a, b) in enumerate(endlabels.T): mapping[a, b].append(i) # Replace each list of indices with the streamlines they index if mapping_as_streamlines: for key in mapping: mapping[key] = [streamlines[i] for i in mapping[key]] # Return the mapping matrix and the mapping return matrix, mapping return matrix