def assignment_map(target_bundle, model_bundle, no_disks): """ Calculates assignment maps of the target bundle with reference to model bundle centroids. Parameters ---------- target_bundle : streamlines target bundle extracted from subject data in common space model_bundle : streamlines atlas bundle used as reference no_disks : integer, optional Number of disks used for dividing bundle into disks. (Default 100) References ---------- .. [Chandio19] Chandio, B.Q., S. Koudoro, D. Reagan, J. Harezlak, E. Garyfallidis, Bundle Analytics: a computational and statistical analyses framework for tractometric studies, Proceedings of: International Society of Magnetic Resonance in Medicine (ISMRM), Montreal, Canada, 2019. """ mbundle_streamlines = set_number_of_points(model_bundle, nb_points=no_disks) metric = AveragePointwiseEuclideanMetric() qb = QuickBundles(threshold=85., metric=metric) clusters = qb.cluster(mbundle_streamlines) centroids = Streamlines(clusters.centroids) _, indx = cKDTree(centroids.data, 1, copy_data=True).query(target_bundle.data, k=1) return indx
def test_quickbundles_with_python_metric(): class MDFpy(dipymetric.Metric): def are_compatible(self, shape1, shape2): return shape1 == shape2 def dist(self, features1, features2): dist = np.sqrt(np.sum((features1 - features2)**2, axis=1)) dist = np.sum(dist / len(features1)) return dist rdata = streamline_utils.set_number_of_points(data, 10) qb = QuickBundles(threshold=2 * threshold, metric=MDFpy()) clusters = qb.cluster(rdata) # By default `refdata` refers to data being clustered. assert_equal(clusters.refdata, rdata) # Set `refdata` to return indices instead of actual data points. clusters.refdata = None assert_array_equal(list(itertools.chain(*clusters)), list(itertools.chain(*clusters_truth))) # Cluster read-only data for datum in rdata: datum.setflags(write=False) # Cluster data with different dtype (should be converted into float32) for datatype in [np.float64, np.int32, np.int64]: newdata = [datum.astype(datatype) for datum in rdata] clusters = qb.cluster(newdata) assert_equal(clusters.centroids[0].dtype, np.float32)
def find_centroids(streamlines,th,max_clus, folder_name='', vol_weighted = True, weight_by = '1.5_2_AxPasi5'): from dipy.segment.clustering import QuickBundles ''' parameters: streamlines: ArraySequence of streamlines th: float for max dist from a streamline to the centroids. Above this value a new centroid is created as long as the number of centroids < max clus. max_clus: int of max number of clusters :return: centroids: a list of streamilnes centroid representation s_list: a list of lists. Each inner list is the group of streamlines cooresponds to a single centroid. ''' qb = QuickBundles(th,max_nb_clusters=max_clus) qbmap = qb.cluster(streamlines) centroids = qbmap.centroids s_list = [] for i in qbmap.clusters: s_list.append(list(i)) if vol_weighted: bvec_file = load_dwi_files(folder_name)[6] vec_vols = weight_clus_by_vol_img(weight_by,s_list, folder_name, bvec_file) return centroids, s_list, vec_vols else: return centroids, s_list
def read_hcp_atlas_16_bundles(): """ XXX """ bundle_dict = {} _, folder = fetch_hcp_atlas_16_bundles() whole_brain = load_tractogram(op.join(folder, 'Atlas_in_MNI_Space_16_bundles', 'whole_brain', 'whole_brain_MNI.trk'), 'same', bbox_valid_check=False).streamlines bundle_dict['whole_brain'] = whole_brain bundle_files = glob( op.join(folder, "Atlas_in_MNI_Space_16_bundles", "bundles", "*.trk")) for bundle_file in bundle_files: bundle = op.splitext(op.split(bundle_file)[-1])[0] bundle_dict[bundle] = {} bundle_dict[bundle]['sl'] = load_tractogram(bundle_file, 'same', bbox_valid_check=False)\ .streamlines feature = ResampleFeature(nb_points=100) metric = AveragePointwiseEuclideanMetric(feature) qb = QuickBundles(np.inf, metric=metric) cluster = qb.cluster(bundle_dict[bundle]['sl']) bundle_dict[bundle]['centroid'] = cluster.centroids[0] # For some reason, this file-name has a 0 in it, instead of an O: bundle_dict["IFOF_R"] = bundle_dict["IF0F_R"] del bundle_dict["IF0F_R"] return bundle_dict
def test_quickbundles_with_python_metric(): class MDFpy(dipymetric.Metric): def are_compatible(self, shape1, shape2): return shape1 == shape2 def dist(self, features1, features2): dist = np.sqrt(np.sum((features1 - features2)**2, axis=1)) dist = np.sum(dist / len(features1)) return dist rdata = streamline_utils.set_number_of_points(data, 10) qb = QuickBundles(threshold=2 * threshold, metric=MDFpy()) clusters = qb.cluster(rdata) # By default `refdata` refers to data being clustered. assert_equal(clusters.refdata, rdata) # Set `refdata` to return indices instead of actual data points. clusters.refdata = None assert_array_equal(list(itertools.chain(*clusters)), list(itertools.chain(*clusters_truth))) # Cluster read-only data for datum in rdata: datum.setflags(write=False) # Cluster data with different dtype (should be converted into float32) for datatype in [np.float64, np.int32, np.int64]: newdata = [datum.astype(datatype) for datum in rdata] clusters = qb.cluster(newdata) assert_equal(clusters.centroids[0].dtype, np.float32)
def produce_Clusters(truth_list, thresh): feature = ResampleFeature(nb_points=24) metric = AveragePointwiseEuclideanMetric(feature=feature) qb = QuickBundles(threshold = thresh, metric = metric) clusters = qb.cluster(truth_list) return qb, clusters
def get_centroid_streamline(streamlines, nb_points): resample_feature = ResampleFeature(nb_points=nb_points) quick_bundle = QuickBundles( threshold=np.inf, metric=AveragePointwiseEuclideanMetric(resample_feature)) clusters = quick_bundle.cluster(streamlines) centroid_streamlines = clusters.centroids return centroid_streamlines
def test_quickbundles_memory_leaks(): qb = QuickBundles(threshold=2*threshold) type_name_pattern = "memoryview" initial_types_refcount = get_type_refcount(type_name_pattern) qb.cluster(data) # At this point, all memoryviews created during clustering should be freed. assert_equal(get_type_refcount(type_name_pattern), initial_types_refcount)
def test_quickbundles_memory_leaks(): qb = QuickBundles(threshold=2 * threshold) type_name_pattern = "memoryview" initial_types_refcount = get_type_refcount(type_name_pattern) qb.cluster(data) # At this point, all memoryviews created during clustering should be freed. assert_equal(get_type_refcount(type_name_pattern), initial_types_refcount)
def main(): parser = buildArgsParser() args = parser.parse_args() full_tfile = nib.streamlines.load(args.full_tfile) model_tfile = nib.streamlines.load(args.model_tfile) model_mask = nib.load(args.model_mask) # Bring streamlines to voxel space and where coordinate (0,0,0) represents the corner of a voxel. model_tfile.tractogram.apply_affine(np.linalg.inv(model_mask.affine)) model_tfile.streamlines._data += 0.5 # Shift of half a voxel full_tfile.tractogram.apply_affine(np.linalg.inv(model_mask.affine)) full_tfile.streamlines._data += 0.5 # Shift of half a voxel assert(model_mask.get_data().sum() == create_binary_map(model_tfile.streamlines, model_mask).sum()) # Resample streamlines full_streamlines = set_number_of_points(full_tfile.streamlines, args.nb_points_resampling) model_streamlines = set_number_of_points(model_tfile.streamlines, args.nb_points_resampling) # Segment model rng = np.random.RandomState(42) indices = np.arange(len(model_streamlines)) rng.shuffle(indices) qb = QuickBundles(args.qb_threshold) clusters = qb.cluster(model_streamlines, ordering=indices) # Try to find optimal assignment threshold best_threshold = None best_f1_score = -np.inf thresholds = np.arange(-2, 10, 0.2) + args.qb_threshold for threshold in thresholds: indices = qb.find_closest(clusters, full_streamlines, threshold=threshold) nb_assignments = np.sum(indices != -1) mask = create_binary_map(full_tfile.streamlines[indices != -1], model_mask) overlap_per_bundle = _compute_overlap(model_mask.get_data(), mask) overreach_per_bundle = _compute_overreach(model_mask.get_data(), mask) # overreach_norm_gt_per_bundle = _compute_overreach_normalize_gt(model_mask.get_data(), mask) f1_score = _compute_f1_score(overlap_per_bundle, overreach_per_bundle) if best_f1_score < f1_score: best_threshold = threshold best_f1_score = f1_score print("{}:\t {}/{} ({:.1%}) {:.1%}/{:.1%} ({:.1%}) {}/{}".format( threshold, nb_assignments, len(model_streamlines), nb_assignments/len(model_streamlines), overlap_per_bundle, overreach_per_bundle, f1_score, mask.sum(), model_mask.get_data().sum())) if overlap_per_bundle >= 1: break print("Best threshold: {} with F1-Score of {}".format(best_threshold, best_f1_score))
def bench_quickbundles(): dtype = "float32" repeat = 10 nb_points = 18 streams, hdr = nib.trackvis.read(get_data('fornix')) fornix = [s[0].astype(dtype) for s in streams] fornix = streamline_utils.set_number_of_points(fornix, nb_points) # Create eight copies of the fornix to be clustered (one in each octant). streamlines = [] streamlines += [s + np.array([100, 100, 100], dtype) for s in fornix] streamlines += [s + np.array([100, -100, 100], dtype) for s in fornix] streamlines += [s + np.array([100, 100, -100], dtype) for s in fornix] streamlines += [s + np.array([100, -100, -100], dtype) for s in fornix] streamlines += [s + np.array([-100, 100, 100], dtype) for s in fornix] streamlines += [s + np.array([-100, -100, 100], dtype) for s in fornix] streamlines += [s + np.array([-100, 100, -100], dtype) for s in fornix] streamlines += [s + np.array([-100, -100, -100], dtype) for s in fornix] # The expected number of clusters of the fornix using threshold=10 is 4. threshold = 10. expected_nb_clusters = 4 * 8 print("Timing QuickBundles 1.0 vs. 2.0") qb = QB_Old(streamlines, threshold, pts=None) qb1_time = measure("QB_Old(streamlines, threshold, nb_points)", repeat) print("QuickBundles time: {0:.4}sec".format(qb1_time)) assert_equal(qb.total_clusters, expected_nb_clusters) sizes1 = [qb.partitions()[i]['N'] for i in range(qb.total_clusters)] indices1 = [ qb.partitions()[i]['indices'] for i in range(qb.total_clusters) ] qb2 = QB_New(threshold) qb2_time = measure("clusters = qb2.cluster(streamlines)", repeat) print("QuickBundles2 time: {0:.4}sec".format(qb2_time)) print("Speed up of {0}x".format(qb1_time / qb2_time)) clusters = qb2.cluster(streamlines) sizes2 = map(len, clusters) indices2 = map(lambda c: c.indices, clusters) assert_equal(len(clusters), expected_nb_clusters) assert_array_equal(sizes2, sizes1) assert_arrays_equal(indices2, indices1) qb = QB_New(threshold, metric=MDFpy()) qb3_time = measure("clusters = qb.cluster(streamlines)", repeat) print("QuickBundles2_python time: {0:.4}sec".format(qb3_time)) print("Speed up of {0}x".format(qb1_time / qb3_time)) clusters = qb.cluster(streamlines) sizes3 = map(len, clusters) indices3 = map(lambda c: c.indices, clusters) assert_equal(len(clusters), expected_nb_clusters) assert_array_equal(sizes3, sizes1) assert_arrays_equal(indices3, indices1)
def test_quickbundles_with_not_order_invariant_metric(): metric = dipymetric.AveragePointwiseEuclideanMetric() qb = QuickBundles(threshold=np.inf, metric=metric) streamline = np.arange(10*3, dtype=dtype).reshape((-1, 3)) streamlines = [streamline, streamline[::-1]] clusters = qb.cluster(streamlines) assert_equal(len(clusters), 1) assert_array_equal(clusters[0].centroid, streamline)
def test_quickbundles_with_not_order_invariant_metric(): metric = dipymetric.AveragePointwiseEuclideanMetric() qb = QuickBundles(threshold=np.inf, metric=metric) streamline = np.arange(10 * 3, dtype=dtype).reshape((-1, 3)) streamlines = [streamline, streamline[::-1]] clusters = qb.cluster(streamlines) assert_equal(len(clusters), 1) assert_array_equal(clusters[0].centroid, streamline)
def bench_quickbundles(): dtype = "float32" repeat = 10 nb_points = 12 streams, hdr = nib.trackvis.read(get_fnames('fornix')) fornix = [s[0].astype(dtype) for s in streams] fornix = streamline_utils.set_number_of_points(fornix, nb_points) # Create eight copies of the fornix to be clustered (one in each octant). streamlines = [] streamlines += [s + np.array([100, 100, 100], dtype) for s in fornix] streamlines += [s + np.array([100, -100, 100], dtype) for s in fornix] streamlines += [s + np.array([100, 100, -100], dtype) for s in fornix] streamlines += [s + np.array([100, -100, -100], dtype) for s in fornix] streamlines += [s + np.array([-100, 100, 100], dtype) for s in fornix] streamlines += [s + np.array([-100, -100, 100], dtype) for s in fornix] streamlines += [s + np.array([-100, 100, -100], dtype) for s in fornix] streamlines += [s + np.array([-100, -100, -100], dtype) for s in fornix] # The expected number of clusters of the fornix using threshold=10 is 4. threshold = 10. expected_nb_clusters = 4 * 8 print("Timing QuickBundles 1.0 vs. 2.0") qb = QB_Old(streamlines, threshold, pts=None) qb1_time = measure("QB_Old(streamlines, threshold, nb_points)", repeat) print("QuickBundles time: {0:.4}sec".format(qb1_time)) assert_equal(qb.total_clusters, expected_nb_clusters) sizes1 = [qb.partitions()[i]['N'] for i in range(qb.total_clusters)] indices1 = [qb.partitions()[i]['indices'] for i in range(qb.total_clusters)] qb2 = QB_New(threshold) qb2_time = measure("clusters = qb2.cluster(streamlines)", repeat) print("QuickBundles2 time: {0:.4}sec".format(qb2_time)) print("Speed up of {0}x".format(qb1_time / qb2_time)) clusters = qb2.cluster(streamlines) sizes2 = map(len, clusters) indices2 = map(lambda c: c.indices, clusters) assert_equal(len(clusters), expected_nb_clusters) assert_array_equal(list(sizes2), sizes1) assert_arrays_equal(indices2, indices1) qb = QB_New(threshold, metric=MDFpy()) qb3_time = measure("clusters = qb.cluster(streamlines)", repeat) print("QuickBundles2_python time: {0:.4}sec".format(qb3_time)) print("Speed up of {0}x".format(qb1_time / qb3_time)) clusters = qb.cluster(streamlines) sizes3 = map(len, clusters) indices3 = map(lambda c: c.indices, clusters) assert_equal(len(clusters), expected_nb_clusters) assert_array_equal(list(sizes3), sizes1) assert_arrays_equal(indices3, indices1)
def make_kesh_template(bundle_list, keystone_boi, qb_thresh=5., Nsubsamp=20, clsz_thresh=5, keystone2MNI_xfm=None, verbose=False): ''' bundle_list: list of independent bundles (lists) not assumed to be in the same space keystone_boi: bundle (list) of streamlines that will be the anchor bundle all others are registered to for the template qb_thresh: threshold for quickbundle (determines how finely each bundle is clustered) Nsubsamp: subsampling for quickbundles and SLR clsz_thresh: how many streamlines a cluster must have to be included in the template* keystone2MNI_SLR: streamlinear registration between the whole brain keystone and MNI** verbose: if you want to print info about each bundle as it runs set this to True *qb_thresh adn clsz_thresh are related. If you have a fine parcellation (low qb_thresh) then the clsz_threshold should be quite low since clusters will be small. **PROVIDE THIS IF (and only if) YOU WANT THE RESULT TO BE IN MNI SPACE OTHERWISE IT WILL BE IN KEYSTONE SPACE ''' kesh_template_sls = [] rejected_sls = [] boi_sls_subsamp = set_number_of_points(keystone_boi, Nsubsamp) for i, sls in enumerate(bundle_list): print(len(bundle_list) - i) sls_subsamp = set_number_of_points(sls, Nsubsamp) qb = QuickBundles(threshold=qb_thresh) clusters = qb.cluster(sls) cluster_sizes = [len(cl) for cl in clusters] # enforce that clusters smaller than a threshold are not in template centroids = clusters.centroids slr = StreamlineLinearRegistration() srm = slr.optimize(static=boi_sls_subsamp, moving=sls_subsamp) xfmd_centroids = srm.transform(centroids) # NOTE: we actually want to upsample the centroids so the template has # better properties... what's the most efficient way to do that? for j, b in enumerate(xfmd_centroids): if cluster_sizes[j] < clsz_thresh: rejected_sls.append(xfmd_centroids.pop(j)) kesh_template_sls += xfmd_centroids if verbose: print('Bundle %i' % i) print('N centroids: %i' % len(centroids)) print('kept %i rejected %i total %i' % (len(kesh_template_sls), len(rejected_sls), len(clusters))) if keystone2MNI_xfm: print('MNI YAY!') return kesh_template_sls, rejected_sls
def outliers_removal_using_hierarchical_quickbundles(streamlines, min_threshold=0.5, nb_samplings_max=30, sampling_seed=1234): if nb_samplings_max < 2: raise ValueError("'nb_samplings_max' must be >= 2") rng = np.random.RandomState(sampling_seed) metric = "MDF_12points" box_min, box_max = get_streamlines_bounding_box(streamlines) initial_threshold = np.min(np.abs(box_max - box_min)) / 2. # Quickbundle's threshold is divided by 1.2 between hierarchical level. thresholds = list(takewhile( lambda t: t >= min_threshold, (initial_threshold / 1.2**i for i in count()))) ordering = np.arange(len(streamlines)) path_lengths_per_streamline = 0 streamlines_path = np.ones((len(streamlines), len(thresholds), nb_samplings_max), dtype=int) * -1 for i in range(nb_samplings_max): rng.shuffle(ordering) cluster_orderings = [ordering] for j, threshold in enumerate(thresholds): id_cluster = 0 next_cluster_orderings = [] qb = QuickBundles(metric=metric, threshold=threshold) for cluster_ordering in cluster_orderings: clusters = qb.cluster(streamlines, ordering=cluster_ordering) for k, cluster in enumerate(clusters): streamlines_path[cluster.indices, j, i] = id_cluster id_cluster += 1 if len(cluster) > 10: next_cluster_orderings.append(cluster.indices) cluster_orderings = next_cluster_orderings if i <= 1: # Needs at least two orderings to compute stderror. continue path_lengths_per_streamline = np.sum((streamlines_path != -1), axis=1)[:, :i] summary = np.mean(path_lengths_per_streamline, axis=1) / np.max(path_lengths_per_streamline) return summary
def get_centroid_streamline(tractogram, nb_points, distance_threshold): streamlines = tractogram.streamlines resample_feature = ResampleFeature(nb_points=nb_points) quick_bundle = QuickBundles( threshold=distance_threshold, metric=AveragePointwiseEuclideanMetric(resample_feature)) clusters = quick_bundle.cluster(streamlines) centroid_streamlines = clusters.centroids if len(centroid_streamlines) > 1: raise Exception('Multiple centroids found') return Tractogram(centroid_streamlines, affine_to_rasmm=np.eye(4))
def auto_extract(model_cluster_map, rstreamlines, number_pts_per_str=NB_POINTS_RESAMPLE, close_centroids_thr=20, clean_thr=7., disp=False, verbose=False, ordering=None): if ordering is None: ordering = np.arange(len(rstreamlines)) qb = QuickBundles(threshold=REF_BUNDLES_THRESHOLD, metric=AveragePointwiseEuclideanMetric()) closest_bundles = qb.find_closest(model_cluster_map, rstreamlines, clean_thr, ordering=ordering) return ordering[np.where(closest_bundles >= 0)[0]]
def computeQuickBundles(streamlines, threshold): #1. from dipy.segment.clustering import QuickBundles qb = QuickBundles(threshold) clusters = qb.cluster(streamlines) print("Nb. clusters:", len(clusters)) #print("Cluster sizes:", map(len, clusters)) print("Nb. small clusters:", sum(clusters < 10)) #print("Streamlines indices of the first cluster:\n", clusters[0].indices) #print("Centroid of the last cluster:\n", clusters[-1].centroid) return clusters
def computeQuickBundles(streamlines, threshold): #1. from dipy.segment.clustering import QuickBundles qb = QuickBundles(threshold) clusters = qb.cluster(streamlines) print("Nb. clusters:", len(clusters)) #print("Cluster sizes:", map(len, clusters)) print("Nb. small clusters:", sum(clusters < 10)) #print("Streamlines indices of the first cluster:\n", clusters[0].indices) #print("Centroid of the last cluster:\n", clusters[-1].centroid) return clusters
def quickbundles(self, streamlines): """Segment tract with QuickBundles.""" # TODO: implement other metrics try: from dipy.segment.clustering import QuickBundles from dipy.segment.metric import ResampleFeature from dipy.segment.metric import AveragePointwiseEuclideanMetric except ImportError: return None else: feature = ResampleFeature(nb_points=self.qb_points) metric = AveragePointwiseEuclideanMetric(feature=feature) qb = QuickBundles(threshold=self.qb_threshold, metric=metric) clusters = qb.cluster(streamlines) return clusters
def qb_metrics_features(streamlines, threshold=10.0, metric=None, max_nb_clusters=np.iinfo('i4').max): """ Enhancing QuickBundles with different metrics and features metric: 'IF', 'RF', 'CoMF', 'MF', 'AF', 'VBEF', None """ if metric == 'IF': feature = IdentityFeature() metric = AveragePointwiseEuclideanMetric(feature=feature) elif metric == 'RF': feature = ResampleFeature(nb_point=24) metric = AveragePointwiseEuclideanMetric(feature=feature) elif metric == 'CoMF': feature = CenterOfMassFeature() metric = EuclideanMetric(feature) elif metric == 'MF': feature = MidpointFeature() metric = EuclideanMetric(feature) elif metric == 'AF': feature = ArcLengthFeature() metric = EuclideanMetric(feature) elif metric == 'VBEF': feature = VectorOfEndpointsFeature() metric = CosineMetric(feature) else: metric = "MDF_12points" qb = QuickBundles(threshold=threshold, metric=metric, max_nb_clusters=max_nb_clusters) clusters = qb.cluster(streamlines) labels = np.array(len(streamlines) * [None]) N_list = [] for i in range(len(clusters)): N_list.append(clusters[i]['N']) data_clusters = [] for i in range(len(clusters)): labels[clusters[i]['indices']] = i + 1 data_clusters.append(streamlines[clusters[i]['indices']]) return labels, data_clusters, N_list
def get_tract_profile(bundle, metric_img, metric_affine, use_weights=False, flip=True, num_points=100): ''' This function reorients the streamlines and extracts the diffusion metrics along the tract. It essentiall performs step 1. The default number of points along a tract is 100, which can be thought of as %-along a tract. The flip variable signals if you would like to flip the direction of the streamlines after reorientation. For example if after reorientation all the streamlines were motor cortex -> brainstem and you actually wanted brainstem -> motor cortex, then you set flip to True. The default is True because generally we see reorientation result in motor cortex -> brainstem. For the honours project, we were looking for the opposite ''' # Reorient all the streamlines so that they are follwing the same direction feature = ResampleFeature(nb_points=num_points) d_metric = AveragePointwiseEuclideanMetric(feature) qb = QuickBundles(np.inf, metric=d_metric) centroid_bundle = qb.cluster(bundle).centroids[0] oriented_bundle = orient_by_streamline(bundle, centroid_bundle) # Calculate weights for each streamline/node in a bundle, based on a # Mahalanobis distance from the core the bundle, at that node w_bundle = None if use_weights: w_bundle = gaussian_weights(oriented_bundle) # Sample the metric along the tract. The implementation of this function # is based off of work by Yeatman et al. in 2012 profile_bundle = afq_profile(metric_img, oriented_bundle, metric_affine, weights=w_bundle) # Reverse the profile bundle if the direction is not desired if flip: profile_bundle = np.flip(profile_bundle) return profile_bundle
def auto_extract(model_cluster_map, rstreamlines, number_pts_per_str=NB_POINTS_RESAMPLE, close_centroids_thr=20, clean_thr=7., disp=False, verbose=False, ordering=None): if ordering is None: ordering = np.arange(len(rstreamlines)) qb = QuickBundles(threshold=REF_BUNDLES_THRESHOLD, metric=AveragePointwiseEuclideanMetric()) closest_bundles = qb.find_closest(model_cluster_map, rstreamlines, clean_thr, ordering=ordering) return ordering[np.where(closest_bundles >= 0)[0]]
def _prepare_gt_bundles_info(bundles_dir, bundles_masks_dir, gt_bundles_attribs, ref_anat_fname): # Ref bundles will contain {'name': 'name_of_the_bundle', # 'threshold': thres_value, # 'streamlines': list_of_streamlines} dummy_attribs = {'orientation': 'LPS'} qb = QuickBundles(20, metric=AveragePointwiseEuclideanMetric()) ref_bundles = [] for bundle_idx, bundle_f in enumerate(sorted(os.listdir(bundles_dir))): bundle_name = os.path.splitext(os.path.basename(bundle_f))[0] bundle_attribs = gt_bundles_attribs.get(os.path.basename(bundle_f)) if bundle_attribs is None: raise ValueError( "Missing basic bundle attribs for {0}".format(bundle_f)) # Already resample to avoid doing it for each iteration of chunking orig_strl = [ s for s in get_tracts_voxel_space_for_dipy( os.path.join(bundles_dir, bundle_f), ref_anat_fname, dummy_attribs) ] resamp_bundle = set_number_of_points(orig_strl, NB_POINTS_RESAMPLE) resamp_bundle = [s.astype('f4') for s in resamp_bundle] bundle_cluster_map = qb.cluster(resamp_bundle) bundle_cluster_map.refdata = resamp_bundle bundle_mask = nib.load( os.path.join(bundles_masks_dir, bundle_name + '.nii.gz')) ref_bundles.append({ 'name': bundle_name, 'threshold': bundle_attribs['cluster_threshold'], 'cluster_map': bundle_cluster_map, 'mask': bundle_mask }) return ref_bundles
def neuro_cluster(infile, outfile, m, threshold): with open(infile, 'rb') as infile: df_metric = pickle.load(infile) streams = [] for i in range(df_metric.iloc[-1]["route_num"] + 1): df_temp = df_metric[df_metric["route_num"] == i] del df_temp["route_num"] if (not (df_temp.empty)): streams.append(np.array(df_temp.values.tolist())) #metric = GPSDistanceTuto() #qb = QuickBundles(threshold=threshold, metric=metric) qb = QuickBundles(threshold=threshold, metric=m) streams = np.array(streams) clusters = qb.cluster(streams) print(len(clusters)) '''with open(outfile, 'wb') as outfile:
def test_quickbundles_shape_uncompatibility(): # QuickBundles' old default metric (AveragePointwiseEuclideanMetric, aka MDF) # requires that all streamlines have the same number of points. metric = dipymetric.AveragePointwiseEuclideanMetric() qb = QuickBundles(threshold=20., metric=metric) assert_raises(ValueError, qb.cluster, data) # QuickBundles' new default metric (AveragePointwiseEuclideanMetric, aka MDF # combined with ResampleFeature) will automatically resample streamlines so # they all have 18 points. qb = QuickBundles(threshold=20.) clusters1 = qb.cluster(data) feature = dipymetric.ResampleFeature(nb_points=18) metric = dipymetric.AveragePointwiseEuclideanMetric(feature) qb = QuickBundles(threshold=20., metric=metric) clusters2 = qb.cluster(data) assert_array_equal(list(itertools.chain(*clusters1)), list(itertools.chain(*clusters2)))
def computeStats(subjectID,reference,streamlines,classification,outdir): avg_length = [] avg_curv = [] stream_count = [] mean_x = [] mean_y = [] mean_z = [] num_vox = [] tract_index_labels = np.trim_zeros(np.unique(classification['index'].tolist())) qb = QuickBundles(np.inf) for i in tract_index_labels: indices = [ t for t in range(len(classification['index'].tolist())) if classification['index'].tolist()[int(t)] == i ] avg_length.append(np.mean(length(streamlines[indices]))) clusters = qb.cluster(streamlines[indices]) avg_curv.append(mean_curvature(clusters.centroids[0])) orientation = mean_orientation(clusters.centroids[0]) mean_x.append(orientation[0]) mean_y.append(orientation[1]) mean_z.append(orientation[2]) stream_count.append(len(indices)) denmap = density_map(streamlines[indices],reference.affine,reference.shape) num_vox.append(len(denmap[denmap>0])) df = pd.DataFrame([],dtype=object) df['subjectID'] = [ subjectID for f in range(len(classification['names'].tolist())) ] df['structureID'] = [ f for f in classification['names'].tolist() ] df['nodeID'] = [ 1 for f in range(len(df['structureID'])) ] df['streamline_count'] = stream_count df['average_length'] = avg_length df['average_curvature'] = avg_curv df['voxel_count'] = num_vox df['centroid_x'] = mean_x df['centroid_y'] = mean_y df['centroid_z'] = mean_z df.to_csv('%s/output_FiberStats.csv' %outdir,index=False)
def test_quickbundles_streamlines(): rdata = streamline_utils.set_number_of_points(data, 10) qb = QuickBundles(threshold=2*threshold) clusters = qb.cluster(rdata) # By default `refdata` refers to data being clustered. assert_equal(clusters.refdata, rdata) # Set `refdata` to return indices instead of actual data points. clusters.refdata = None assert_array_equal(list(itertools.chain(*clusters)), list(itertools.chain(*clusters_truth))) # Cluster read-only data for datum in rdata: datum.setflags(write=False) # Cluster data with different dtype (should be converted into float32) for datatype in [np.float64, np.int32, np.int64]: newdata = [datum.astype(datatype) for datum in rdata] clusters = qb.cluster(newdata) assert_equal(clusters.centroids[0].dtype, np.float32)
def test_quickbundles_streamlines(): rdata = streamline_utils.set_number_of_points(data, 10) qb = QuickBundles(threshold=2 * threshold) clusters = qb.cluster(rdata) # By default `refdata` refers to data being clustered. assert_equal(clusters.refdata, rdata) # Set `refdata` to return indices instead of actual data points. clusters.refdata = None assert_array_equal(list(itertools.chain(*clusters)), list(itertools.chain(*clusters_truth))) # Cluster read-only data for datum in rdata: datum.setflags(write=False) # Cluster data with different dtype (should be converted into float32) for datatype in [np.float64, np.int32, np.int64]: newdata = [datum.astype(datatype) for datum in rdata] clusters = qb.cluster(newdata) assert_equal(clusters.centroids[0].dtype, np.float32)
def _cluster(self, threshold): qb = QuickBundles(metric=metric, threshold=threshold) self.last_threshold = threshold self.clusters = qb.cluster(self.streamlines) self.clusters_colors = [ color for c, color in zip( self.clusters, distinguishable_colormap(bg=(0, 0, 0), exclude=darkcolors)) ] if threshold < np.inf: print("{} clusters with QB threshold of {}mm".format( len(self.clusters), threshold)) # Keep a flag telling there have been changes. self.has_changed = True if len(self.clusters) == 1: # Keep initial color self.clusters_colors = [self.color ] if self.color is not None else [(0, 0, 1)] vtk_colors = utils.numpy_to_vtk_colors(255 * self.original_colors) vtk_colors.SetName("Colors") self.actor.GetMapper().GetInput().GetPointData().SetScalars( vtk_colors) return for cluster, color in zip(self.clusters, self.clusters_colors): self.streamlines_colors[cluster.indices] = color self.colors = [] for color, streamline in zip(self.streamlines_colors, self.streamlines): self.colors += [color] * len(streamline) vtk_colors = utils.numpy_to_vtk_colors(255 * np.array(self.colors)) vtk_colors.SetName("Colors") self.actor.GetMapper().GetInput().GetPointData().SetScalars(vtk_colors)
def quickClean(streamlines, number_bundles): # set up counter for n_clusters and start threshold distance at 1mm. n_clusters = 0 thrs = 1 # loop through clustering until number_bundles of bundles segmented while n_clusters != number_bundles: qb = QuickBundles(threshold=thrs) clusters = qb.cluster(streamlines) n_clusters = len(clusters) thrs = thrs + 1 if n_clusters == (number_bundles - 1): thrs = thrs - 2 for i in np.linspace(thrs, thrs + 1, num=1000): print(i) qb = QuickBundles(threshold=i) clusters = qb.cluster(streamlines) n_clusters = len(clusters) if n_clusters == number_bundles: break print(thrs) return clusters
def _prepare_gt_bundles_info(bundles_dir, bundles_masks_dir, gt_bundles_attribs, ref_anat_fname): # Ref bundles will contain {'name': 'name_of_the_bundle', # 'threshold': thres_value, # 'streamlines': list_of_streamlines} dummy_attribs = {'orientation': 'LPS'} qb = QuickBundles(20, metric=AveragePointwiseEuclideanMetric()) ref_bundles = [] for bundle_idx, bundle_f in enumerate(sorted(os.listdir(bundles_dir))): bundle_name = os.path.splitext(os.path.basename(bundle_f))[0] bundle_attribs = gt_bundles_attribs.get(os.path.basename(bundle_f)) if bundle_attribs is None: raise ValueError( "Missing basic bundle attribs for {0}".format(bundle_f)) # Already resample to avoid doing it for each iteration of chunking orig_strl = [s for s in get_tracts_voxel_space_for_dipy( os.path.join(bundles_dir, bundle_f), ref_anat_fname, dummy_attribs)] resamp_bundle = set_number_of_points(orig_strl, NB_POINTS_RESAMPLE) resamp_bundle = [s.astype('f4') for s in resamp_bundle] bundle_cluster_map = qb.cluster(resamp_bundle) bundle_cluster_map.refdata = resamp_bundle bundle_mask = nib.load(os.path.join(bundles_masks_dir, bundle_name + '.nii.gz')) ref_bundles.append({'name': bundle_name, 'threshold': bundle_attribs['cluster_threshold'], 'cluster_map': bundle_cluster_map, 'mask': bundle_mask}) return ref_bundles
def get_streamlines_centroid(streamlines, nb_points): """ Compute centroid from streamlines using QuickBundles. Parameters ---------- streamlines: list of ndarray The list of streamlines from which we compute the centroid. nb_points: int Number of points defining the centroid streamline. Returns ------- List of length one, containing a np.ndarray of shape (nb_points, 3) """ resample_feature = ResampleFeature(nb_points=nb_points) quick_bundle = QuickBundles( threshold=np.inf, metric=AveragePointwiseEuclideanMetric(resample_feature)) clusters = quick_bundle.cluster(streamlines) centroid_streamlines = clusters.centroids return centroid_streamlines
def assignment_map(target_bundle, model_bundle, no_disks): """ Calculates assignment maps of the target bundle with reference to model bundle centroids. Parameters ---------- target_bundle : streamlines target bundle extracted from subject data in common space model_bundle : streamlines atlas bundle used as reference no_disks : integer, optional Number of disks used for dividing bundle into disks. (Default 100) References ---------- .. [Chandio2020] Chandio, B.Q., Risacher, S.L., Pestilli, F., Bullock, D., Yeh, FC., Koudoro, S., Rokem, A., Harezlak, J., and Garyfallidis, E. Bundle analytics, a computational framework for investigating the shapes and profiles of brain pathways across populations. Sci Rep 10, 17149 (2020) """ mbundle_streamlines = set_number_of_points(model_bundle, nb_points=no_disks) metric = AveragePointwiseEuclideanMetric() qb = QuickBundles(threshold=85., metric=metric) clusters = qb.cluster(mbundle_streamlines) centroids = Streamlines(clusters.centroids) _, indx = cKDTree(centroids.get_data(), 1, copy_data=True).query(target_bundle.get_data(), k=1) return indx
def tractograms_slr(moving_tractogram, static_tractogram): subjID = ntpath.basename(static_tractogram)[0:6] exID = ntpath.basename(moving_tractogram)[0:6] aff_dir = '/N/dc2/projects/lifebid/giulia/data/HCP3-IU-Giulia/derivatives/slr_transformations' affine_path = '%s/affine_m%s_s%s.npy' % (aff_dir, exID, subjID) affine_fname = './affine_m%s_s%s.npy' % (exID, subjID) if isfile(affine_path): print("Affine already computed. Retrieving past results.") copyfile(affine_path, affine_fname) else: print("Loading tractograms...") moving_tractogram = nib.streamlines.load(moving_tractogram) moving_tractogram = moving_tractogram.streamlines static_tractogram = nib.streamlines.load(static_tractogram) static_tractogram = static_tractogram.streamlines print("Set parameters as in Garyfallidis et al. 2015.") threshold_length = 40.0 # 50mm / 1.25 qb_threshold = 16.0 # 20mm / 1.25 nb_res_points = 20 print("Performing QuickBundles of static tractogram and resampling...") st = np.array( [s for s in static_tractogram if len(s) > threshold_length], dtype=np.object) qb = QuickBundles(threshold=qb_threshold) st_clusters = [cluster.centroid for cluster in qb.cluster(st)] st_clusters = set_number_of_points(st_clusters, nb_res_points) print("Performing QuickBundles of moving tractogram and resampling...") mt = np.array( [s for s in moving_tractogram if len(s) > threshold_length], dtype=np.object) qb = QuickBundles(threshold=qb_threshold) mt_clusters = [cluster.centroid for cluster in qb.cluster(mt)] mt_clusters = set_number_of_points(mt_clusters, nb_res_points) print("Performing Linear Registration...") srr = StreamlineLinearRegistration() srm = srr.optimize(static=st_clusters, moving=mt_clusters) print( "Affine transformation matrix with Streamline Linear Registration:" ) affine = srm.matrix print('%s' % affine) np.save('affine_m%s_s%s.npy' % (exID, subjID), affine) print("Affine for example %s and target %s saved." % (exID, subjID))
def test_quickbundles_shape_uncompatibility(): # QuickBundles' old default metric (AveragePointwiseEuclideanMetric, # aka MDF) requires that all streamlines have the same number of points. metric = dipymetric.AveragePointwiseEuclideanMetric() qb = QuickBundles(threshold=20., metric=metric) assert_raises(ValueError, qb.cluster, data) # QuickBundles' new default metric (AveragePointwiseEuclideanMetric, # aka MDF combined with ResampleFeature) will automatically resample # streamlines so they all have 18 points. qb = QuickBundles(threshold=20.) clusters1 = qb.cluster(data) feature = dipymetric.ResampleFeature(nb_points=18) metric = dipymetric.AveragePointwiseEuclideanMetric(feature) qb = QuickBundles(threshold=20., metric=metric) clusters2 = qb.cluster(data) assert_array_equal(list(itertools.chain(*clusters1)), list(itertools.chain(*clusters2)))
def tractograms_slr(moving_tractogram, static_tractogram): subjID = ntpath.basename(static_tractogram)[0:6] exID = ntpath.basename(moving_tractogram)[0:6] print("Loading tractograms...") moving_tractogram = nib.streamlines.load(moving_tractogram) moving_tractogram = moving_tractogram.streamlines static_tractogram = nib.streamlines.load(static_tractogram) static_tractogram = static_tractogram.streamlines print("Set parameters as in Garyfallidis et al. 2015.") threshold_length = 40.0 # 50mm / 1.25 qb_threshold = 16.0 # 20mm / 1.25 nb_res_points = 20 print("Performing QuickBundles of static tractogram and resampling...") st = np.array([s for s in static_tractogram if len(s) > threshold_length], dtype=np.object) qb = QuickBundles(threshold=qb_threshold) st_clusters = [cluster.centroid for cluster in qb.cluster(st)] st_clusters = set_number_of_points(st_clusters, nb_res_points) print("Performing QuickBundles of moving tractogram and resampling...") mt = np.array([s for s in moving_tractogram if len(s) > threshold_length], dtype=np.object) qb = QuickBundles(threshold=qb_threshold) mt_clusters = [cluster.centroid for cluster in qb.cluster(mt)] mt_clusters = set_number_of_points(mt_clusters, nb_res_points) print("Performing Linear Registration...") srr = StreamlineLinearRegistration() srm = srr.optimize(static=st_clusters, moving=mt_clusters) print("Affine transformation matrix with Streamline Linear Registration:") affine = srm.matrix print('%s' % affine) np.save('affine_m%s_s%s.npy' % (exID, subjID), affine) print("Affine for example %s and target %s saved." % (exID, subjID))
def bundle_analysis(model_bundle_folder, bundle_folder, orig_bundle_folder, metric_folder, group, subject, no_disks=100, out_dir=''): """ Applies statistical analysis on bundles and saves the results in a directory specified by ``out_dir``. Parameters ---------- model_bundle_folder : string Path to the input model bundle files. This path may contain wildcards to process multiple inputs at once. bundle_folder : string Path to the input bundle files in common space. This path may contain wildcards to process multiple inputs at once. orig_folder : string Path to the input bundle files in native space. This path may contain wildcards to process multiple inputs at once. metric_folder : string Path to the input dti metric or/and peak files. It will be used as metric for statistical analysis of bundles. group : string what group subject belongs to e.g. control or patient subject : string subject id e.g. 10001 no_disks : integer, optional Number of disks used for dividing bundle into disks. (Default 100) out_dir : string, optional Output directory (default input file directory) References ---------- .. [Chandio19] Chandio, B.Q., S. Koudoro, D. Reagan, J. Harezlak, E. Garyfallidis, Bundle Analytics: a computational and statistical analyses framework for tractometric studies, Proceedings of: International Society of Magnetic Resonance in Medicine (ISMRM), Montreal, Canada, 2019. """ dt = dict() mb = os.listdir(model_bundle_folder) mb.sort() bd = os.listdir(bundle_folder) bd.sort() org_bd = os.listdir(orig_bundle_folder) org_bd.sort() n = len(org_bd) for io in range(n): mbundles, _ = load_trk(os.path.join(model_bundle_folder, mb[io])) bundles, _ = load_trk(os.path.join(bundle_folder, bd[io])) orig_bundles, _ = load_trk(os.path.join(orig_bundle_folder, org_bd[io])) mbundle_streamlines = set_number_of_points(mbundles, nb_points=no_disks) metric = AveragePointwiseEuclideanMetric() qb = QuickBundles(threshold=25., metric=metric) clusters = qb.cluster(mbundle_streamlines) centroids = Streamlines(clusters.centroids) print('Number of centroids ', len(centroids.data)) print('Model bundle ', mb[io]) print('Number of streamlines in bundle in common space ', len(bundles)) print('Number of streamlines in bundle in original space ', len(orig_bundles)) _, indx = cKDTree(centroids.data, 1, copy_data=True).query(bundles.data, k=1) metric_files_names = os.listdir(metric_folder) _, affine = load_nifti(os.path.join(metric_folder, "fa.nii.gz")) affine_r = np.linalg.inv(affine) transformed_orig_bundles = transform_streamlines(orig_bundles, affine_r) for mn in range(0, len(metric_files_names)): ind = np.array(indx) fm = metric_files_names[mn][:2] bm = mb[io][:-4] dt = dict() metric_name = os.path.join(metric_folder, metric_files_names[mn]) if metric_files_names[mn][2:] == '.nii.gz': metric, _ = load_nifti(metric_name) dti_measures(transformed_orig_bundles, metric, dt, fm, bm, subject, group, ind, out_dir) else: fm = metric_files_names[mn][:3] metric = load_peaks(metric_name) peak_values(bundles, metric, dt, fm, bm, subject, group, ind, out_dir)
from dipy.segment.clustering import QuickBundles from dipy.segment.metric import IdentityFeature from dipy.segment.metric import AveragePointwiseEuclideanMetric # Get some streamlines. streamlines = get_streamlines() # Previously defined. # Make sure our streamlines have the same number of points. from dipy.tracking.streamline import set_number_of_points streamlines = set_number_of_points(streamlines, nb_points=12) # Create an instance of `IdentityFeature` and tell metric to use it. feature = IdentityFeature() metric = AveragePointwiseEuclideanMetric(feature=feature) qb = QuickBundles(threshold=10., metric=metric) clusters = qb.cluster(streamlines) print("Nb. clusters:", len(clusters)) print("Cluster sizes:", list(map(len, clusters))) """ :: Nb. clusters: 4 Cluster sizes: [64, 191, 47, 1] .. _clustering-examples-ResampleFeature:
tractogram_fn = args[1] output_dir = args[2] subject = args[3] tract_name = args[4] max_num_centroids = int(args[5]) points_per_sl = int(args[6]) # Open the file and extract streamlines streams, header = trackvis.read(tractogram_fn) streamlines = [sl[0] for sl in streams] # Run quickbundles with chosen parameters feature = ResampleFeature(nb_points=points_per_sl) metric = AveragePointwiseEuclideanMetric(feature) qb = QuickBundles(threshold=10., max_nb_clusters=max_num_centroids, metric=metric) clusters = qb.cluster(streamlines) # Extract the centroids centroids = [cluster.centroid for cluster in clusters] # If not enough generated, fill with empty streamlines diff = max_num_centroids - len(centroids) if diff > 0: print( "Not enough centroids generated, so generating empty streamlines for padding." ) empty_sl = np.zeros((points_per_sl, 3), dtype=np.float32) for num in range(diff): centroids.append(empty_sl)
""" streams, hdr = tv.read(fname) streamlines = [i[0] for i in streams] """ Perform QuickBundles clustering using the MDF metric and a 10mm distance threshold. Keep in mind that since the MDF metric requires streamlines to have the same number of points, the clustering algorithm will internally use a representation of streamlines that have been automatically downsampled/upsampled so they have only 12 points (To set manually the number of points, see :ref:`clustering-examples-ResampleFeature`). """ qb = QuickBundles(threshold=10.) clusters = qb.cluster(streamlines) """ `clusters` is a `ClusterMap` object which contains attributes that provide information about the clustering result. """ print("Nb. clusters:", len(clusters)) print("Cluster sizes:", map(len, clusters)) print("Small clusters:", clusters < 10) print("Streamlines indices of the first cluster:\n", clusters[0].indices) print("Centroid of the last cluster:\n", clusters[-1].centroid) """
""" Fiber clustering ---------------- Based on an agglomerative clustering, and a geometric distance. """ clustering_outdir = os.path.join(outdir, "clustering") cluster_file = os.path.join(clustering_outdir, "clusters.json") if not os.path.isdir(clustering_outdir): os.mkdir(clustering_outdir) if not os.path.isfile(cluster_file): fibers_18 = [resample(track, nb_pol=18) for track in fibers] qb = QuickBundles(threshold=10.) clusters_ = qb.cluster(fibers_18) clusters = {} for cnt, cluster in enumerate(clusters_): clusters[str(cnt)] = {"indices": cluster.indices} with open(cluster_file, "w") as open_file: json.dump(clusters, open_file, indent=4) else: with open(cluster_file) as open_file: clusters = json.load(open_file) if 1: #use_vtk: ren = pvtk.ren() colors = numpy.ones((len(fibers),)) nb_clusters = len(clusters) for clusterid, item in clusters.items():
def auto_extract_VCs(streamlines, ref_bundles): # Streamlines = list of all streamlines VC = 0 VC_idx = set() found_vbs_info = {} for bundle in ref_bundles: found_vbs_info[bundle['name']] = {'nb_streamlines': 0, 'streamlines_indices': set()} # Need to bookkeep because we chunk for big datasets processed_strl_count = 0 chunk_size = 5000 chunk_it = 0 nb_bundles = len(ref_bundles) bundles_found = [False] * nb_bundles logging.debug("Starting scoring VCs") qb = QuickBundles(threshold=20, metric=AveragePointwiseEuclideanMetric()) # Start loop here for big datasets while processed_strl_count < len(streamlines): logging.debug("Starting chunk: {0}".format(chunk_it)) strl_chunk = streamlines[chunk_it * chunk_size: (chunk_it + 1) * chunk_size] processed_strl_count += len(strl_chunk) cur_chunk_VC_idx, cur_chunk_IC_idx, cur_chunk_VCWP_idx = set(), set(), set() # Already resample and run quickbundles on the submission chunk, # to avoid doing it at every call of auto_extract rstreamlines = set_number_of_points(strl_chunk, NB_POINTS_RESAMPLE) # qb.cluster had problem with f8 rstreamlines = [s.astype('f4') for s in rstreamlines] chunk_cluster_map = qb.cluster(rstreamlines) chunk_cluster_map.refdata = strl_chunk logging.debug("Starting VC identification through auto_extract") for bundle_idx, ref_bundle in enumerate(ref_bundles): # The selected indices are from [0, len(strl_chunk)] selected_streamlines_indices = auto_extract(ref_bundle['cluster_map'], chunk_cluster_map, clean_thr=ref_bundle['threshold']) # Remove duplicates, when streamlines are assigned to multiple VBs. selected_streamlines_indices = set(selected_streamlines_indices) - \ cur_chunk_VC_idx cur_chunk_VC_idx |= selected_streamlines_indices nb_selected_streamlines = len(selected_streamlines_indices) if nb_selected_streamlines: bundles_found[bundle_idx] = True VC += nb_selected_streamlines # Shift indices to match the real number of streamlines global_select_strl_indices = set([v + chunk_it * chunk_size for v in selected_streamlines_indices]) vb_info = found_vbs_info.get(ref_bundle['name']) vb_info['nb_streamlines'] += nb_selected_streamlines vb_info['streamlines_indices'] |= global_select_strl_indices VC_idx |= global_select_strl_indices else: global_select_strl_indices = set() chunk_it += 1 # Compute bundle overlap, overreach and f1_scores and update found_vbs_info for bundle_idx, ref_bundle in enumerate(ref_bundles): bundle_name = ref_bundle["name"] bundle_mask = ref_bundle["mask"] vb_info = found_vbs_info[bundle_name] # Streamlines are in voxel space since that's how they were # loaded in the scoring function. tractogram = Tractogram(streamlines=(streamlines[i] for i in vb_info['streamlines_indices']), affine_to_rasmm=bundle_mask.affine) scores = {} if len(tractogram) > 0: scores = compute_bundle_coverage_scores(tractogram, bundle_mask) vb_info['overlap'] = scores.get("OL", 0) vb_info['overreach'] = scores.get("OR", 0) vb_info['overreach_norm'] = scores.get("ORn", 0) vb_info['f1_score'] = scores.get("F1", 0) return VC_idx, found_vbs_info
def score_from_files(filename, masks_dir, bundles_dir, tracts_attribs, basic_bundles_attribs, save_segmented=False, save_IBs=False, save_VBs=False, save_VCWPs=False, segmented_out_dir='', segmented_base_name='', verbose=False): """ Computes all metrics in order to score a tractogram. Given a ``tck`` file of streamlines and a folder containing masks, compute the percent of: Valid Connections (VC), Invalid Connections (IC), Valid Connections but Wrong Path (VCWP), No Connections (NC), Average Bundle Coverage (ABC), Average ROIs Coverage (ARC), coverage per bundles and coverage per ROIs. It also provides the number of: Valid Bundles (VB), Invalid Bundles (IB) and streamlines per bundles. Parameters ------------ filename : str name of a tracts file masks_dir : str name of the directory containing the masks save_segmented : bool if true, saves the segmented VC, IC, VCWP and NC Returns --------- scores : dict dictionnary containing a score for each metric indices : dict dictionnary containing the indices of streamlines composing VC, IC, VCWP and NC """ if verbose: logging.basicConfig(level=logging.DEBUG) rois_dir = masks_dir + "rois/" bundles_masks_dir = masks_dir + "bundles/" wm_file = masks_dir + "wm.nii.gz" wm = nib.load(wm_file) streamlines = load_streamlines(filename, wm_file, tracts_attribs) ROIs = [nib.load(rois_dir + f) for f in sorted(os.listdir(rois_dir))] bundles_masks = [nib.load(bundles_masks_dir + f) for f in sorted(os.listdir(bundles_masks_dir))] ref_bundles = [] # Ref bundles will contain {'name': 'name_of_the_bundle', 'threshold': thres_value, # 'streamlines': list_of_streamlines} dummy_attribs = {'orientation': 'LPS'} qb = QuickBundles(threshold=REF_BUNDLES_THRESHOLD, metric=AveragePointwiseEuclideanMetric()) out_centroids_dir = os.path.join(segmented_out_dir, os.path.pardir, "centroids") if not os.path.isdir(out_centroids_dir): os.mkdir(out_centroids_dir) rng = np.random.RandomState(42) for bundle_idx, bundle_f in enumerate(sorted(os.listdir(bundles_dir))): bundle_attribs = basic_bundles_attribs.get(os.path.basename(bundle_f)) if bundle_attribs is None: raise ValueError("Missing basic bundle attribs for {0}".format(bundle_f)) # # Already resample to avoid doing it for each iteration of chunking # orig_strl = [s for s in get_tracts_voxel_space_for_dipy( # os.path.join(bundles_dir, bundle_f), # wm_file, dummy_attribs)] orig_strl = load_streamlines(os.path.join(bundles_dir, bundle_f), wm_file, dummy_attribs) resamp_bundle = set_number_of_points(orig_strl, NB_POINTS_RESAMPLE) # resamp_bundle = [s.astype('f4') for s in resamp_bundle] indices = np.arange(len(resamp_bundle)) rng.shuffle(indices) bundle_cluster_map = qb.cluster(resamp_bundle, ordering=indices) # bundle_cluster_map.refdata = resamp_bundle bundle_mask_inv = nib.Nifti1Image((1 - bundles_masks[bundle_idx].get_data()) * wm.get_data(), bundles_masks[bundle_idx].get_affine()) ref_bundles.append({'name': os.path.basename(bundle_f).replace('.fib', '').replace('.tck', ''), 'threshold': bundle_attribs['cluster_threshold'], 'cluster_map': bundle_cluster_map, 'mask': bundles_masks[bundle_idx], 'mask_inv': bundle_mask_inv}) logging.debug("{}: {} centroids".format(ref_bundles[-1]['name'], len(bundle_cluster_map))) nib.streamlines.save(nib.streamlines.Tractogram(bundle_cluster_map.centroids, affine_to_rasmm=np.eye(4)), os.path.join(out_centroids_dir, ref_bundles[-1]['name'] + ".tck")) score_func = score_auto_extract_auto_IBs return score_func(streamlines, bundles_masks, ref_bundles, ROIs, wm, save_segmented=save_segmented, save_IBs=save_IBs, save_VBs=save_VBs, save_VCWPs=save_VCWPs, out_segmented_strl_dir=segmented_out_dir, base_out_segmented_strl=segmented_base_name, ref_anat_fname=wm_file)
def _auto_extract_VCs(streamlines, ref_bundles): # Streamlines = list of all streamlines # TODO check what is neede # VC = 0 VC_idx = set() found_vbs_info = {} for bundle in ref_bundles: found_vbs_info[bundle['name']] = {'nb_streamlines': 0, 'streamlines_indices': set()} # TODO probably not needed # already_assigned_streamlines_idx = set() # Need to bookkeep because we chunk for big datasets processed_strl_count = 0 chunk_size = len(streamlines) chunk_it = 0 # nb_bundles = len(ref_bundles) # bundles_found = [False] * nb_bundles #bundles_potential_VCWP = [set()] * nb_bundles logging.debug("Starting scoring VCs") # Start loop here for big datasets while processed_strl_count < len(streamlines): if processed_strl_count > 0: raise NotImplementedError("Not supposed to have more than one chunk!") logging.debug("Starting chunk: {0}".format(chunk_it)) strl_chunk = streamlines[chunk_it * chunk_size: (chunk_it + 1) * chunk_size] processed_strl_count += len(strl_chunk) # Already resample and run quickbundles on the submission chunk, # to avoid doing it at every call of auto_extract rstreamlines = set_number_of_points(nib.streamlines.ArraySequence(strl_chunk), NB_POINTS_RESAMPLE) # qb.cluster had problem with f8 # rstreamlines = [s.astype('f4') for s in rstreamlines] # chunk_cluster_map = qb.cluster(rstreamlines) # chunk_cluster_map.refdata = strl_chunk # # Merge clusters # all_bundles = ClusterMapCentroid() # cluster_id_to_bundle_id = [] # for bundle_idx, ref_bundle in enumerate(ref_bundles): # clusters = ref_bundle["cluster_map"] # cluster_id_to_bundle_id.extend([bundle_idx] * len(clusters)) # all_bundles.add_cluster(*clusters) # logging.debug("Starting VC identification through auto_extract") # qb = QuickBundles(threshold=10, metric=AveragePointwiseEuclideanMetric()) # closest_bundles = qb.find_closest(all_bundles, rstreamlines, threshold=7) # print("Unassigned streamlines: {}".format(np.sum(closest_bundles == -1))) # for cluster_id, bundle_id in enumerate(cluster_id_to_bundle_id): # indices = np.where(closest_bundles == cluster_id)[0] # print("{}/{} ({}) Found {}".format(cluster_id, len(cluster_id_to_bundle_id), ref_bundles[bundle_id]['name'], len(indices))) # if len(indices) == 0: # continue # vb_info = found_vbs_info.get(ref_bundles[bundle_id]['name']) # indices = set(indices) # vb_info['nb_streamlines'] += len(indices) # vb_info['streamlines_indices'] |= indices # VC_idx |= indices qb = QuickBundles(threshold=10, metric=AveragePointwiseEuclideanMetric()) ordering = np.arange(len(rstreamlines)) logging.debug("Starting VC identification through auto_extract") for bundle_idx, ref_bundle in enumerate(ref_bundles): print(ref_bundle['name'], ref_bundle['threshold'], len(ref_bundle['cluster_map'])) # The selected indices are from [0, len(strl_chunk)] # selected_streamlines_indices = auto_extract(ref_bundle['cluster_map'], # rstreamlines, # clean_thr=ref_bundle['threshold'], # ordering=ordering) closest_bundles = qb.find_closest(ref_bundle['cluster_map'], rstreamlines[ordering], ref_bundle['threshold']) selected_streamlines_indices = ordering[closest_bundles >= 0] ordering = ordering[closest_bundles == -1] # Remove duplicates, when streamlines are assigned to multiple VBs. # TODO better handling of this case # selected_streamlines_indices = set(selected_streamlines_indices) - cur_chunk_VC_idx # cur_chunk_VC_idx |= selected_streamlines_indices nb_selected_streamlines = len(selected_streamlines_indices) print("{} assigned".format(nb_selected_streamlines)) if nb_selected_streamlines: # bundles_found[bundle_idx] = True # VC += nb_selected_streamlines # Shift indices to match the real number of streamlines global_select_strl_indices = set([v + chunk_it * chunk_size for v in selected_streamlines_indices]) vb_info = found_vbs_info.get(ref_bundle['name']) vb_info['nb_streamlines'] += nb_selected_streamlines vb_info['streamlines_indices'] |= global_select_strl_indices VC_idx |= global_select_strl_indices # already_assigned_streamlines_idx |= global_select_strl_indices chunk_it += 1 return VC_idx, found_vbs_info
from dipy.segment.metric import (AveragePointwiseEuclideanMetric, ResampleFeature) from dipy.segment.clustering import QuickBundles feature = ResampleFeature(nb_points=100) metric = AveragePointwiseEuclideanMetric(feature) """ Since we are going to include all of the streamlines in the single cluster from the streamlines, we set the threshold to `np.inf`. We pull out the centroid as the standard. """ qb = QuickBundles(np.inf, metric=metric) cluster_cst_l = qb.cluster(model_cst_l) standard_cst_l = cluster_cst_l.centroids[0] cluster_af_l = qb.cluster(model_af_l) standard_af_l = cluster_af_l.centroids[0] """ We use the centroid streamline for each atlas bundle as the standard to orient all of the streamlines in each bundle from the individual subject. Here, the affine used is the one from the transform between the atlas and individual tractogram. This is so that the orienting is done relative to the space of the individual, and not relative to the atlas space. """
def slr_with_qb(static, moving, x0='affine', rm_small_clusters=50, maxiter=100, select_random=None, verbose=False, greater_than=50, less_than=250, qb_thr=15, nb_pts=20, progressive=True, num_threads=None): """ Utility function for registering large tractograms. For efficiency we apply the registration on cluster centroids and remove small clusters. Parameters ---------- static : Streamlines moving : Streamlines x0 : str rigid, similarity or affine transformation model (default affine) rm_small_clusters : int Remove clusters that have less than `rm_small_clusters` (default 50) verbose : bool, If True then information about the optimization is shown. select_random : int If not None select a random number of streamlines to apply clustering Default None. options : None or dict, Extra options to be used with the selected method. num_threads : int Number of threads. If None (default) then all available threads will be used. Only metrics using OpenMP will use this variable. Notes ----- The order of operations is the following. First short or long streamlines are removed. Second the tractogram or a random selection of the tractogram is clustered with QuickBundles. Then SLR [Garyfallidis15]_ is applied. References ---------- .. [Garyfallidis15] Garyfallidis et al. "Robust and efficient linear registration of white-matter fascicles in the space of streamlines" , NeuroImage, 117, 124--140, 2015 .. [Garyfallidis14] Garyfallidis et al., "Direct native-space fiber bundle alignment for group comparisons", ISMRM, 2014. .. [Garyfallidis17] Garyfallidis et al. Recognition of white matter bundles using local and global streamline-based registration and clustering, Neuroimage, 2017. """ if verbose: print('Static streamlines size {}'.format(len(static))) print('Moving streamlines size {}'.format(len(moving))) def check_range(streamline, gt=greater_than, lt=less_than): if (length(streamline) > gt) & (length(streamline) < lt): return True else: return False # TODO change this to the new Streamlines API streamlines1 = [s for s in static if check_range(s)] streamlines2 = [s for s in moving if check_range(s)] if verbose: print('Static streamlines after length reduction {}' .format(len(streamlines1))) print('Moving streamlines after length reduction {}' .format(len(streamlines2))) if select_random is not None: rstreamlines1 = select_random_set_of_streamlines(streamlines1, select_random) else: rstreamlines1 = streamlines1 rstreamlines1 = set_number_of_points(rstreamlines1, nb_pts) qb1 = QuickBundles(threshold=qb_thr) rstreamlines1 = [s.astype('f4') for s in rstreamlines1] cluster_map1 = qb1.cluster(rstreamlines1) clusters1 = remove_clusters_by_size(cluster_map1, rm_small_clusters) qb_centroids1 = [cluster.centroid for cluster in clusters1] if select_random is not None: rstreamlines2 = select_random_set_of_streamlines(streamlines2, select_random) else: rstreamlines2 = streamlines2 rstreamlines2 = set_number_of_points(rstreamlines2, nb_pts) qb2 = QuickBundles(threshold=qb_thr) rstreamlines2 = [s.astype('f4') for s in rstreamlines2] cluster_map2 = qb2.cluster(rstreamlines2) clusters2 = remove_clusters_by_size(cluster_map2, rm_small_clusters) qb_centroids2 = [cluster.centroid for cluster in clusters2] if verbose: t = time() if not progressive: slr = StreamlineLinearRegistration(x0=x0, options={'maxiter': maxiter}, num_threads=num_threads) slm = slr.optimize(qb_centroids1, qb_centroids2) else: bounds = DEFAULT_BOUNDS slm = progressive_slr(qb_centroids1, qb_centroids2, x0=x0, metric=None, bounds=bounds, num_threads=num_threads) if verbose: print('QB static centroids size %d' % len(qb_centroids1,)) print('QB moving centroids size %d' % len(qb_centroids2,)) duration = time() - t print('SLR finished in %0.3f seconds.' % (duration,)) if slm.iterations is not None: print('SLR iterations: %d ' % (slm.iterations,)) moved = slm.transform(moving) return moved, slm.matrix, qb_centroids1, qb_centroids2