def watershed(depths, points, indices, neighbor_lists, min_size=1, depth_factor=0.25, depth_ratio=0.1, tolerance=0.01, regrow=True): """ Segment vertices of a surface mesh into contiguous "watershed basins" by seed growing from an iterative selection of the deepest vertices. Steps :: 1. Grow segments from an iterative selection of the deepest seeds. 2. Regrow segments from the resulting seeds, until each seed's segment touches a boundary. 3. Use the segment() function to fill in the rest. 4. Merge segments if their seeds are too close to each other or their depths are very different. Note :: Despite the above precautions, the order of seed selection in segment() could possibly influence the resulting borders between adjoining segments (vs. propagate(), which is slower and insensitive to depth, but is not biased by seed order). Parameters ---------- depths : numpy array of floats depth values for all vertices (default -1) points : list of lists of floats each element is a list of 3-D coordinates of a vertex on a surface mesh indices : list of integers indices to mesh vertices to be segmented min_size : index the minimum number of vertices in a basin neighbor_lists : list of lists of integers each list contains indices to neighboring vertices for each vertex depth_factor : float factor to determine whether to merge two neighboring watershed catchment basins -- they are merged if the Euclidean distance between their basin seeds is less than this fraction of the maximum Euclidean distance between points having minimum and maximum depths depth_ratio : float the minimum fraction of depth for a neighboring shallower watershed catchment basin (otherwise merged with the deeper basin) tolerance : float tolerance for detecting differences in depth between vertices regrow : Boolean regrow segments from watershed seeds? Returns ------- segments : list of integers region numbers for all vertices (default -1) seed_indices : list of integers list of indices to seed vertices Examples -------- >>> # Perform watershed segmentation on the deeper portions of a surface: >>> import os >>> import numpy as np >>> from mindboggle.utils.mesh import find_neighbors >>> from mindboggle.utils.plots import plot_vtk >>> from mindboggle.utils.segment import watershed, segment >>> from mindboggle.utils.io_vtk import read_vtk, read_scalars, rewrite_scalars >>> path = os.environ['MINDBOGGLE_DATA'] >>> depth_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.travel_depth.vtk') >>> faces, lines, indices, points, npoints, depths, name, input_vtk = read_vtk(depth_file, >>> return_first=True, return_array=True) >>> indices = np.where(depths > 0.01)[0] # high to speed up >>> neighbor_lists = find_neighbors(faces, npoints) >>> min_size = 50 >>> depth_factor = 0.25 >>> depth_ratio = 0.1 >>> tolerance = 0.01 >>> regrow = True >>> # >>> segments, seed_indices = watershed(depths, points, >>> indices, neighbor_lists, min_size, depth_factor, depth_ratio, >>> tolerance, regrow) >>> # >>> # Write results to vtk file and view: >>> rewrite_scalars(depth_file, 'watershed.vtk', >>> segments, 'segments', segments) >>> plot_vtk('watershed.vtk') >>> # View watershed seeds: >>> seeds = -1 * np.ones(len(depths)) >>> for i, s in enumerate(seed_indices): >>> seeds[s] = i >>> rewrite_scalars(depth_file, 'watershed_seeds.vtk', >>> seeds, 'seeds', seeds) >>> plot_vtk('watershed_seeds.vtk') """ import numpy as np from time import time from mindboggle.labels.labels import extract_borders from mindboggle.utils.segment import segment from mindboggle.utils.compute import point_distance # Make sure argument is a list if isinstance(indices, np.ndarray): indices.tolist() print('Segment {0} vertices by a surface watershed algorithm'. format(len(indices))) verbose = False merge = True t0 = time() tiny = 0.000001 use_depth_ratio = True #------------------------------------------------------------------------- # Find the borders of the given mesh vertices (indices): #------------------------------------------------------------------------- D = np.ones(len(depths)) D[indices] = 2 borders, foo1, foo2 = extract_borders(range(len(depths)), D, neighbor_lists, ignore_values=[], return_label_pairs=False) #------------------------------------------------------------------------- # Select deepest vertex as initial seed: #------------------------------------------------------------------------- index_deepest = indices[np.argmax(depths[indices])] seed_list = [index_deepest] basin_depths = [] original_indices = indices[:] #------------------------------------------------------------------------- # Loop until all vertices have been segmented. # This limits the number of possible seeds: #------------------------------------------------------------------------- segments = -1 * np.ones(len(depths)) seed_indices = [] seed_points = [] all_regions = [] region = [] counter = 0 terminate = False while not terminate: # Add seeds to region: region.extend(seed_list) all_regions.extend(seed_list) # Remove seeds from vertices to segment: indices = list(frozenset(indices).difference(seed_list)) if indices: # Identify neighbors of seeds: neighbors = [] [neighbors.extend(neighbor_lists[x]) for x in seed_list] # Select neighbors that have not been previously selected # and are among the vertices to segment: old_seed_list = seed_list[:] seed_list = list(frozenset(neighbors).intersection(indices)) seed_list = list(frozenset(seed_list).difference(all_regions)) # For each vertex, select neighbors that are shallower: seed_neighbors = [] for seed in old_seed_list: seed_neighbors.extend([x for x in neighbor_lists[seed] if depths[x] - tolerance <= depths[seed]]) seed_list = list(frozenset(seed_list).intersection(seed_neighbors)) else: seed_list = [] # If there are no seeds remaining: if not len(seed_list): # If there is at least min_size points, assign counter to # segmented region, store index, and increment counter: if len(region) >= min_size: segments[region] = counter seed_indices.append(index_deepest) seed_points.append(points[index_deepest]) counter += 1 # Compute basin depth (max - min): Imax = region[np.argmax(depths[region])] Imin = region[np.argmin(depths[region])] max_depth = point_distance(points[Imax], [points[Imin]])[0] basin_depths.append(max_depth) # If vertices left to segment, re-initialize parameters: if indices: # Initialize new region/basin: region = [] # Select deepest unsegmented vertex as new seed # if its rescaled depth is close to 1: index_deepest = indices[np.argmax(depths[indices])] seed_list = [index_deepest] # Termination criteria: if not len(indices): terminate = True # Display current number and size of region: if verbose: print(" {0} vertices remain".format(len(indices))) print(' ...Segmented {0} initial watershed regions ({1:.2f} seconds)'. format(counter, time() - t0)) #------------------------------------------------------------------------- # Regrow from (deep) watershed seeds, stopping at borders: #------------------------------------------------------------------------- if regrow: print(' Regrow segments from watershed seeds, stopping at borders') indices = original_indices[:] segments = -1 * np.ones(len(depths)) all_regions = [] for iseed, seed_index in enumerate(seed_indices): seed_list = [seed_index] region = [] terminate = False while not terminate: # Add seeds to region: region.extend(seed_list) all_regions.extend(seed_list) # Remove seeds from vertices to segment: indices = list(frozenset(indices).difference(seed_list)) if indices: # Identify neighbors of seeds: neighbors = [] [neighbors.extend(neighbor_lists[x]) for x in seed_list] # Select neighbors that have not been previously selected # and are among the vertices to segment: old_seed_list = seed_list[:] seed_list = list(frozenset(neighbors).intersection(indices)) seed_list = list(frozenset(seed_list).difference(all_regions)) # For each vertex, select neighbors that are shallower: seed_neighbors = [] for seed in old_seed_list: seed_neighbors.extend([x for x in neighbor_lists[seed] if depths[x] - tolerance <= depths[seed]]) seed_list = list(frozenset(seed_list).intersection(seed_neighbors)) # Remove seed list if it contains a border vertex: if seed_list: if list(frozenset(seed_list).intersection(borders)): seed_list = [] else: seed_list = [] # Terminate growth for this seed if the seed_list is empty: if not len(seed_list): terminate = True # If there is at least min_size points, store index: if len(region) >= min_size: segments[region] = iseed # Display current number and size of region: if verbose: print(" {0} vertices remain".format(len(indices))) #--------------------------------------------------------------------- # Continue growth until there are no more vertices to segment: #--------------------------------------------------------------------- # Note: As long as keep_seeding=False, the segment values in `segments` # are equal to the order of the `basin_depths` and `seed_points` below. seed_lists = [[i for i,x in enumerate(segments) if x==s] for s in np.unique(segments) if s!=-1] segments = segment(indices, neighbor_lists, min_region_size=1, seed_lists=seed_lists, keep_seeding=False, spread_within_labels=False, labels=[], label_lists=[], values=[], max_steps='', verbose=False) print(' ...Regrew {0} watershed regions from seeds ({1:.2f} seconds)'. format(iseed+1, time() - t0)) #------------------------------------------------------------------------- # Merge watershed catchment basins: #------------------------------------------------------------------------- if merge: # Extract segments pairs at borders between watershed basins: print(' Merge watershed catchment basins with deeper neighboring basins') if verbose: print(' Extract basin borders') foo1, foo2, pairs = extract_borders(original_indices, segments, neighbor_lists, ignore_values=[-1], return_label_pairs=True) # Sort basin depths (descending order) -- return segment indices: Isort = np.argsort(basin_depths).tolist() Isort.reverse() # Find neighboring basins to each of the sorted basins: if verbose: print(" Find neighboring basins") basin_pairs = [] for index in Isort: index_neighbors = [int(list(frozenset(x).difference([index]))[0]) for x in pairs if index in x] if index_neighbors: # Store neighbors whose depth is less than a fraction of the # basin's depth and farther away than half the basin's depth: if use_depth_ratio: index_neighbors = [[x, index] for x in index_neighbors if basin_depths[x] / (basin_depths[index]+tiny) < depth_ratio if point_distance(seed_points[x], [seed_points[index]])[0] > depth_factor * max([basin_depths[x], basin_depths[index]])] # Store neighbors farther away than half the basin's depth: else: index_neighbors = [[x, index] for x in index_neighbors if point_distance(seed_points[x], [seed_points[index]])[0] > depth_factor * max([basin_depths[x], basin_depths[index]])] if index_neighbors: basin_pairs.extend(index_neighbors) # Merge shallow watershed catchment basins: if basin_pairs: if verbose: print(' Merge basins with deeper neighboring basins') for basin_pair in basin_pairs: segments[np.where(segments == basin_pair[0])] = basin_pair[1] # Renumber segments so they are sequential: renumber_segments = segments.copy() segment_numbers = [int(x) for x in np.unique(segments) if x != -1] for i_segment, n_segment in enumerate(segment_numbers): segment = [i for i,x in enumerate(segments) if x == n_segment] renumber_segments[segment] = i_segment segments = renumber_segments # Print statement: print(' ...Merged segments to form {0} watershed regions ({1:.2f} seconds)'. format(i_segment + 1, time() - t0)) return segments.tolist(), seed_indices
# List of indices to fold vertices fold_indices = [i for i,x in enumerate(folds) if x > 0] # Calculate neighbor lists for all points print('Find neighbors to all vertices...') neighbor_lists = find_neighbors(faces, npoints) # Prepare list of all unique sorted label pairs in the labeling protocol print('Prepare a list of unique, sorted label pairs in the protocol...') n_fundi = len(sulcus_label_pair_lists) # Find label boundary points in any of the folds print('Find label boundary points in any of the folds...') border_indices, border_label_tuples, unique_border_label_tuples = \ extract_borders(fold_indices, labels, neighbor_lists) if not len(border_indices): sys.exit('There are no label boundary points!') # Initialize an array of label boundaries fundus IDs # (label boundary vertices that define sulci in the labeling protocol) print('Build an array of label boundary fundus IDs...') label_boundary_fundi = np.zeros(npoints) # For each list of sorted label pairs (corresponding to a sulcus) for isulcus, label_pairs in enumerate(sulcus_label_pair_lists): print(' Sulcus ' + str(isulcus + 1)) # Keep the boundary points with label pair labels fundus_indices = [x for i,x in enumerate(border_indices) if np.unique(border_label_tuples[i]).tolist()
def segment_by_filling_borders(regions, neighbor_lists): """ Fill borders (contours) on a surface mesh to segment vertices into contiguous regions. Steps :: 1. Extract region borders (assumed to be closed contours) 2. Segment borders into separate, contiguous borders 3. For each boundary 4. Find the neighbors to either side of the boundary 5. Segment the neighbors into exterior and interior sets of neighbors 6. Find the interior (smaller) sets of neighbors 7. Fill the contours formed by the interior neighbors Parameters ---------- regions : numpy array of integers region numbers for all vertices (default -1) neighbor_lists : list of lists of integers each list contains indices to neighboring vertices for each vertex Returns ------- segments : numpy array of integers region numbers for all vertices (default -1) Examples -------- >>> # Segment folds by extracting their borders and filling them in separately: >>> import os >>> import numpy as np >>> from mindboggle.utils.mesh import find_neighbors >>> from mindboggle.utils.segment import segment_by_filling_borders >>> from mindboggle.utils.io_vtk import read_vtk, rewrite_scalars >>> path = os.environ['MINDBOGGLE_DATA'] >>> depth_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.travel_depth.vtk') >>> faces, lines, indices, points, npoints, depths, name, input_vtk = read_vtk(depth_file, >>> return_first=True, return_array=True) >>> regions = -1 * np.ones(npoints) >>> regions[depths > 0.50] = 1 >>> neighbor_lists = find_neighbors(faces, npoints) >>> # >>> folds = segment_by_filling_borders(regions, neighbor_lists) >>> # >>> # Write results to vtk file and view: >>> rewrite_scalars(depth_file, 'segment_by_filling_borders.vtk', folds, 'folds', folds) >>> from mindboggle.utils.plots import plot_vtk >>> plot_vtk('segment_by_filling_borders.vtk') """ import numpy as np from mindboggle.labels.labels import extract_borders from mindboggle.utils.segment import segment include_boundary = False # Make sure arguments are numpy arrays if not isinstance(regions, np.ndarray): regions = np.array(regions) print('Segment vertices using region borders') # Extract region borders (assumed to be closed contours) print(' Extract region borders (assumed to be closed contours)') indices_borders, foo1, foo2 = extract_borders(range(len(regions)), regions, neighbor_lists) # Extract background indices_background = list(frozenset(range(len(regions))). difference(indices_borders)) # Segment borders into separate, contiguous borders print(' Segment borders into separate, contiguous borders') borders = segment(indices_borders, neighbor_lists, 1) # For each boundary unique_borders = [x for x in np.unique(borders) if x > -1] segments = -1 * np.ones(len(regions)) for boundary_number in unique_borders: print(' Boundary {0} of {1}:'.format(int(boundary_number), len(unique_borders))) border_indices = [i for i,x in enumerate(borders) if x == boundary_number] # Find the neighbors to either side of the boundary indices_neighbors = [] [indices_neighbors.extend(neighbor_lists[i]) for i in border_indices] #indices_neighbors2 = indices_neighbors[:] #[indices_neighbors2.extend(neighbor_lists[i]) for i in indices_neighbors] indices_neighbors = list(frozenset(indices_neighbors). difference(indices_borders)) # Segment the neighbors into exterior and interior sets of neighbors print(' Segment the neighbors into exterior and interior sets of neighbors') neighbors = segment(indices_neighbors, neighbor_lists, 1) # Find the interior (smaller) sets of neighbors print(' Find the interior (smaller) sets of neighbors') seed_lists = [] unique_neighbors = [x for x in np.unique(neighbors) if x > -1] max_neighbor = 0 max_len = 0 for ineighbor, neighbor in enumerate(unique_neighbors): indices_neighbor = [i for i,x in enumerate(neighbors) if x == neighbor] seed_lists.append(indices_neighbor) if len(indices_neighbor) > max_len: max_len = len(indices_neighbor) max_neighbor = ineighbor seed_lists = [x for i,x in enumerate(seed_lists) if i != max_neighbor] seed_list = [] [seed_list.extend(x) for x in seed_lists if len(x) > 2] # Fill the contours formed by the interior neighbors print(' Fill the contour formed by the interior neighbors') vertices_to_segment = list(frozenset(indices_background). difference(indices_borders)) segment_region = segment(vertices_to_segment, neighbor_lists, 1, [seed_list]) if include_boundary: segment_region[border_indices] = 1 segments[segment_region > -1] = boundary_number return segments
def extract_sulci(labels_file, folds_or_file, hemi, sulcus_label_pair_lists, unique_sulcus_label_pairs, min_boundary=1, sulcus_names=[]): """ Identify sulci from folds in a brain surface according to a labeling protocol that includes a list of label pairs defining each sulcus. A fold is a group of connected, deep vertices. Steps for each fold :: 1. Remove fold if it has fewer than two labels. 2. Remove fold if its labels do not contain a sulcus label pair. 3. Find vertices with labels that are in only one of the fold's label boundary pairs. Assign the vertices the sulcus with the label pair if they are connected to the label boundary for that pair. 4. If there are remaining vertices, segment into sets of vertices connected to label boundaries, and assign a unique ID to each segment. Parameters ---------- labels_file : string file name for surface mesh VTK containing labels for all vertices folds_or_file : list or string fold number for each vertex or name of VTK file containing folds scalars hemi : string hemisphere ('lh' or 'rh') sulcus_label_pair_lists : list of two lists of multiple lists of integer pairs list containing left and right lists, each with multiple lists of integer pairs corresponding to label boundaries / sulcus / fundus unique_sulcus_label_pairs : list of unique pairs of integers unique label pairs min_boundary : integer minimum number of vertices for a sulcus label boundary segment sulcus_names : list of strings names of sulci Returns ------- sulci : list of integers sulcus numbers for all vertices (-1 for non-sulcus vertices) n_sulci : integers number of sulci sulci_file : string name of output VTK file with sulcus numbers (-1 for non-sulcus vertices) Examples -------- >>> import os >>> from mindboggle.utils.io_vtk import read_scalars, rewrite_scalars >>> from mindboggle.labels.protocol import dkt_protocol >>> from mindboggle.features.sulci import extract_sulci >>> from mindboggle.utils.plots import plot_vtk >>> path = os.environ['MINDBOGGLE_DATA'] >>> # Load labels, folds, neighbor lists, and sulcus names and label pairs >>> labels_file = os.path.join(path, 'arno', 'labels', 'relabeled_lh.DKTatlas40.gcs.vtk') >>> folds_file = os.path.join(path, 'arno', 'features', 'folds.vtk') >>> folds_or_file, name = read_scalars(folds_file) >>> protocol = 'DKT31' >>> hemi = 'lh' >>> sulcus_names, sulcus_label_pair_lists, unique_sulcus_label_pairs, ... label_names, label_numbers, cortex_names, cortex_numbers, ... noncortex_names, noncortex_numbers = dkt_protocol(protocol) >>> min_boundary = 10 >>> # >>> sulci, n_sulci, sulci_file = extract_sulci(labels_file, folds_or_file, >>> hemi, sulcus_label_pair_lists, unique_sulcus_label_pairs, >>> min_boundary, sulcus_names) >>> # View: >>> plot_vtk('sulci.vtk') """ import os from time import time import numpy as np from mindboggle.utils.io_vtk import read_scalars, read_vtk, rewrite_scalars from mindboggle.utils.mesh import find_neighbors from mindboggle.labels.labels import extract_borders from mindboggle.utils.segment import propagate, segment # Load fold numbers if folds_or_file is a string if isinstance(folds_or_file, str): folds, name = read_scalars(folds_or_file) elif isinstance(folds_or_file, list): folds = folds_or_file if hemi == 'lh': sulcus_label_pair_lists = sulcus_label_pair_lists[0] elif hemi == 'rh': sulcus_label_pair_lists = sulcus_label_pair_lists[1] else: print("Warning: hemisphere not properly specified ('lh' or 'rh').") # Load points, faces, and neighbors faces, foo1, foo2, points, npoints, labels, foo3, foo4 = read_vtk(labels_file) neighbor_lists = find_neighbors(faces, npoints) # Array of sulcus IDs for fold vertices, initialized as -1. # Since we do not touch gyral vertices and vertices whose labels # are not in the label list, or vertices having only one label, # their sulcus IDs will remain -1. sulci = -1 * np.ones(npoints) #------------------------------------------------------------------------- # Loop through folds #------------------------------------------------------------------------- fold_numbers = [int(x) for x in np.unique(folds) if x > -1] n_folds = len(fold_numbers) print("Extract sulci from {0} folds...".format(n_folds)) t0 = time() for n_fold in fold_numbers: fold = [i for i,x in enumerate(folds) if x == n_fold] len_fold = len(fold) # List the labels in this fold (greater than zero) fold_labels = [labels[x] for x in fold] unique_fold_labels = [int(x) for x in np.unique(fold_labels) if x > 0] #--------------------------------------------------------------------- # NO MATCH -- fold has fewer than two labels #--------------------------------------------------------------------- if len(unique_fold_labels) < 2: # Ignore: sulci already initialized with -1 values if not unique_fold_labels: print(" Fold {0} ({1} vertices): NO MATCH -- fold has no labels". format(n_fold, len_fold)) else: print(" Fold {0} ({1} vertices): " "NO MATCH -- fold has only one label ({2})". format(n_fold, len_fold, unique_fold_labels[0])) # Ignore: sulci already initialized with -1 values else: # Find all label boundary pairs within the fold indices_fold_pairs, fold_pairs, unique_fold_pairs = extract_borders( fold, labels, neighbor_lists, ignore_values=[], return_label_pairs=True) # Find fold label pairs in the protocol (pairs are already sorted) fold_pairs_in_protocol = [x for x in unique_fold_pairs if x in unique_sulcus_label_pairs] if unique_fold_labels: print(" Fold {0} labels: {1} ({2} vertices)".format(n_fold, ', '.join([str(x) for x in unique_fold_labels]), len_fold)) #----------------------------------------------------------------- # NO MATCH -- fold has no sulcus label pair #----------------------------------------------------------------- if not fold_pairs_in_protocol: print(" Fold {0}: NO MATCH -- fold has no sulcus label pair". format(n_fold, len_fold)) #----------------------------------------------------------------- # Possible matches #----------------------------------------------------------------- else: print(" Fold {0} label pairs in protocol: {1}".format(n_fold, ', '.join([str(x) for x in fold_pairs_in_protocol]))) # Labels in the protocol (includes repeats across label pairs) labels_in_pairs = [x for lst in fold_pairs_in_protocol for x in lst] # Labels that appear in one or more than one sulcus label boundary unique_labels = [] nonunique_labels = [] for label in np.unique(labels_in_pairs): if len([x for x in labels_in_pairs if x == label]) == 1: unique_labels.append(label) else: nonunique_labels.append(label) #------------------------------------------------------------- # Vertices whose labels are in only one sulcus label pair #------------------------------------------------------------- # Find vertices with a label that is in only one of the fold's # label pairs (the other label in the pair can exist # in other pairs). Assign the vertices the sulcus with the label # pair if they are connected to the label boundary for that pair. #------------------------------------------------------------- if len(unique_labels): for pair in fold_pairs_in_protocol: # If one or both labels in label pair is/are unique unique_labels_in_pair = [x for x in pair if x in unique_labels] n_unique = len(unique_labels_in_pair) if n_unique: ID = [i for i,x in enumerate(sulcus_label_pair_lists) if pair in x][0] # Construct seeds from label boundary vertices # (fold_pairs and pair already sorted) indices_pair = [x for i,x in enumerate(indices_fold_pairs) if fold_pairs[i] == pair] # Identify vertices with unique label(s) in pair indices_unique_labels = [fold[i] for i,x in enumerate(fold_labels) if x in unique_sulcus_label_pairs] # Propagate from seeds to labels in label pair sulci2 = segment(indices_unique_labels, neighbor_lists, min_region_size=1, seed_lists=[indices_pair], keep_seeding=False, spread_within_labels=True, labels=labels) sulci[sulci2 > -1] = ID # Print statement if n_unique == 1: ps1 = '1 label' else: ps1 = 'Both labels' if len(sulcus_names): ps2 = sulcus_names[ID] else: ps2 = '' print(" {0} unique to one fold pair: {1} {2}". format(ps1, ps2, unique_labels_in_pair)) #------------------------------------------------------------- # Vertex labels shared by multiple label pairs #------------------------------------------------------------- # Propagate labels from label borders to vertices with labels # that are shared by multiple label pairs in the fold. #------------------------------------------------------------- if len(nonunique_labels): # For each label shared by different label pairs for label in nonunique_labels: # Print statement print(" Propagate sulcus label borders with label {0}". format(int(label))) # Construct seeds from label boundary vertices seeds = -1 * np.ones(len(points)) for ID, label_pair_list in enumerate(sulcus_label_pair_lists): label_pairs = [x for x in label_pair_list if label in x] for label_pair in label_pairs: indices_pair = [x for i,x in enumerate(indices_fold_pairs) if np.sort(fold_pairs[i]).tolist() == label_pair] if indices_pair: # Do not include short boundary segments if min_boundary > 1: indices_pair2 = [] seeds2 = segment(indices_pair, neighbor_lists) for seed2 in range(int(max(seeds2))+1): iseed2 = [i for i,x in enumerate(seeds2) if x == seed2] if len(iseed2) >= min_boundary: indices_pair2.extend(iseed2) else: if len(iseed2) == 1: print(" Remove assignment " "of ID {0} from 1 vertex". format(seed2)) else: print(" Remove assignment " "of ID {0} from {1} vertices". format(seed2, len(iseed2))) indices_pair = indices_pair2 # Assign sulcus IDs to seeds seeds[indices_pair] = ID # Identify vertices with the label label_array = -1 * np.ones(len(points)) indices_label = [fold[i] for i,x in enumerate(fold_labels) if x == label] if len(indices_label): label_array[indices_label] = 1 # Propagate from seeds to vertices with label #indices_seeds = [] #for seed in range(int(max(seeds))+1): # indices_seeds.append([i for i,x in enumerate(seeds) # if x == seed]) #sulci2 = segment(indices_label, neighbor_lists, # 50, indices_seeds, False, True, labels) sulci2 = propagate(points, faces, label_array, seeds, sulci, max_iters=10000, tol=0.001, sigma=5) sulci[sulci2 > -1] = sulci2[sulci2 > -1] #------------------------------------------------------------------------- # Print out assigned sulci #------------------------------------------------------------------------- sulcus_numbers = [int(x) for x in np.unique(sulci) if x > -1] n_sulci = len(sulcus_numbers) print("Extracted {0} sulci from {1} folds ({2:.1f}s):". format(n_sulci, n_folds, time()-t0)) if len(sulcus_names): for sulcus_number in sulcus_numbers: print(" {0}: {1}".format(sulcus_number, sulcus_names[sulcus_number])) else: print(" " + ", ".join([str(x) for x in sulcus_numbers])) #------------------------------------------------------------------------- # Print out unresolved sulci #------------------------------------------------------------------------- unresolved = [i for i in range(len(sulcus_label_pair_lists)) if i not in sulcus_numbers] if len(unresolved) == 1: print("The following sulcus is unaccounted for:") else: print("The following {0} sulci are unaccounted for:".format(len(unresolved))) if len(sulcus_names): for sulcus_number in unresolved: print(" {0}: {1}".format(sulcus_number, sulcus_names[sulcus_number])) else: print(" " + ", ".join([str(x) for x in unresolved])) #------------------------------------------------------------------------- # Return sulci, number of sulci, and file name #------------------------------------------------------------------------- sulci_file = os.path.join(os.getcwd(), 'sulci.vtk') rewrite_scalars(labels_file, sulci_file, sulci, 'sulci', sulci) sulci.tolist() return sulci, n_sulci, sulci_file
def extract_borders_2nd_surface(labels_file, mask_file="", values_file=""): """ Extract borders (between labels) on a surface. Options: Mask out values; extract border values on a second surface. Parameters ---------- labels_file : string file name for surface mesh with labels mask_file : string file name for surface mesh with mask (>-1) values values_file : string file name for surface mesh with values to extract along borders Returns ------- border_file : string file name for surface mesh with label borders (-1 background values) border_values : numpy array values for all vertices (-1 for vertices not along label borders) Examples -------- >>> # Extract depth values along label borders in sulci (mask): >>> import os >>> from mindboggle.labels.labels import extract_borders_2nd_surface >>> from mindboggle.utils.plots import plot_vtk >>> path = os.environ['MINDBOGGLE_DATA'] >>> labels_file = os.path.join(path, 'arno', 'labels', 'lh.labels.DKT25.manual.vtk') >>> mask_file = os.path.join(path, 'arno', 'features', 'sulci.vtk') >>> values_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.travel_depth.vtk') >>> # >>> border_file, border_values = extract_borders_2nd_surface(labels_file, mask_file, values_file) >>> # >>> plot_vtk(border_file) """ import os import numpy as np from mindboggle.utils.io_vtk import read_scalars, read_vtk, rewrite_scalars from mindboggle.utils.mesh import find_neighbors from mindboggle.labels.labels import extract_borders # Load labeled surface file faces, foo1, foo2, foo3, npoints, labels, foo4, foo5 = read_vtk(labels_file, return_first=True, return_array=True) # Detect borders neighbor_lists = find_neighbors(faces, npoints) indices_borders, foo1, foo2 = extract_borders(range(npoints), labels, neighbor_lists) # Filter values with label borders border_values = -1 * np.ones(npoints) if values_file: values, name = read_scalars(values_file, return_first=True, return_array=True) border_values[indices_borders] = values[indices_borders] else: border_values[indices_borders] = 1 # Mask values (for mask >-1) if mask_file: mask_values, name = read_scalars(mask_file) else: mask_values = [] # Write out label boundary vtk file border_file = os.path.join(os.getcwd(), "borders_" + os.path.basename(labels_file)) rewrite_scalars(labels_file, border_file, border_values, "label_borders_in_mask", mask_values) if not os.path.exists(border_file): raise (IOError(border_file + " not found")) return border_file, border_values
def realign_boundaries_to_fundus_lines( surf_file, init_label_file, fundus_lines_file, out_label_file=None): """ Fix label boundaries to fundus lines. Parameters ---------- surf_file : file containing the surface geometry in vtk format init_label_file : file containing scalars that represent the initial guess at labels fundus_lines_file : file containing scalars representing fundus lines. out_label_file : if specified, the realigned labels will be writen to this file Returns ------- numpy array representing the realigned label for each surface vertex. """ # import os import numpy as np from mindboggle.labels.labels import extract_borders import mindboggle.utils.graph as go from mindboggle.utils.io_vtk import read_vtk, read_scalars, write_vtk # import mindboggle.utils.kernels as kernels from mindboggle.utils.mesh import find_neighbors # from mindboggle.labels.protocol import dkt_protocol # # protocol = 'DKT25' # sulcus_names, sulcus_label_pair_lists, unique_sulcus_label_pairs, \ # label_names, label_numbers, cortex_names, cortex_numbers, \ # noncortex_names, noncortex_numbers = dkt_protocol(protocol) ## read files faces, _, indices, points, num_points, _, _, _ = read_vtk( surf_file, return_first=True, return_array=True) indices = range(num_points) init_labels, _ = read_scalars(init_label_file, return_first=True, return_array=True) fundus_lines, _ = read_scalars(fundus_lines_file, return_first=True, return_array=True) ## setup seeds from initial label boundaries neighbor_lists = find_neighbors(faces, num_points) # extract all vertices that are on a boundary between labels boundary_indices, label_pairs, _ = extract_borders( indices, init_labels, neighbor_lists, return_label_pairs=True) # split boundary vertices into segments with common boundary pairs. boundary_segments = {} for boundary_index, label_pair in zip(boundary_indices, label_pairs): key = ((label_pair[0], label_pair[1]) if label_pair[0] < label_pair[1] else (label_pair[1], label_pair[0])) if key not in boundary_segments: boundary_segments[key] = [] boundary_segments[key].append(boundary_index) boundary_matrix, boundary_matrix_keys = _build_boundary_matrix( boundary_segments, num_points) # build the affinity matrix affinity_matrix = go.weight_graph( np.array(points), indices, np.array(faces), sigma=10, add_to_graph=False) ## propagate boundaries to fundus line vertices learned_matrix = _propagate_labels( affinity_matrix, boundary_matrix, boundary_indices, 1000, 1) # assign labels to fundus line vertices based on highest probability new_boundaries = -1 * np.ones(init_labels.shape) fundus_line_indices = [i for i, x in enumerate(fundus_lines) if x > 0.5] # TODO: this currently only works for fundus lines that tile the # surface into connected components (which is fine when you want # to test this method on fundus lines generated from manual # labeling). However, to work on real data, fundus lines will # need to be connected together using shortest paths. # split surface into connected components connected_component_faces = _remove_boundary_faces( points, faces, fundus_line_indices) # label components based on most probable label assignment new_labels = _label_components( connected_component_faces, num_points, boundary_indices, learned_matrix, boundary_matrix_keys) # propagate new labels to fill holes label_matrix, label_map = _build_label_matrix(new_labels) new_learned_matrix = _propagate_labels( affinity_matrix, label_matrix, [i for i in range(num_points) if new_labels[i] >= 0], 100, 1) # assign most probable labels for idx in [i for i in range(num_points) if new_labels[i] == -1]: max_idx = np.argmax(new_learned_matrix[idx]) new_labels[idx] = label_map[max_idx] # save if out_label_file is not None: write_vtk(out_label_file, points, faces=faces, scalars=new_labels.tolist()) return new_labels
def concatenate_sulcus_scalars(scalar_files, fold_files, label_files): """ Prepare data for estimating scalar distributions along and outside fundi. Extract (e.g., depth, curvature) scalar values in folds, along sulcus label boundaries as well as outside the sulcus label boundaries. Concatenate these scalar values across multiple files. Parameters ---------- scalar_files : list of strings names of surface mesh VTK files with scalar values to concatenate fold_files : list of strings (corr. to each list in scalar_files) VTK files with fold numbers as scalars (-1 for non-fold vertices) label_files : list of strings (corr. to fold_files) VTK files with label numbers (-1 for unlabeled vertices) Returns ------- border_scalars : list of floats concatenated scalar values within folds along sulcus label boundaries nonborder_scalars : list of floats concatenated scalar values within folds outside sulcus label boundaries Examples -------- >>> # Concatenate (duplicate) depth scalars: >>> import os >>> from mindboggle.shapes.likelihood import concatenate_sulcus_scalars >>> path = os.environ['MINDBOGGLE_DATA'] >>> depth_file = os.path.join(path, 'arno', 'shapes', 'depth_rescaled.vtk') >>> folds_file = os.path.join(path, 'arno', 'features', 'folds.vtk') >>> labels_file = os.path.join(path, 'arno', 'labels', 'lh.labels.DKT25.manual.vtk') >>> scalar_files = [depth_file, depth_file] >>> fold_files = [folds_file, folds_file] >>> label_files = [labels_file, labels_file] >>> # >>> S = concatenate_sulcus_scalars(scalar_files, fold_files, label_files) """ import numpy as np from mindboggle.utils.io_vtk import read_scalars from mindboggle.utils.mesh import find_neighbors_from_file from mindboggle.labels.labels import extract_borders from mindboggle.labels.protocol import dkt_protocol protocol = 'DKT25' sulcus_names, sulcus_label_pair_lists, unique_sulcus_label_pairs, \ label_names, label_numbers, cortex_names, cortex_numbers, \ noncortex_names, noncortex_numbers = dkt_protocol(protocol) # Prepare (non-unique) list of sulcus label pairs: protocol_label_pairs = [x for lst in sulcus_label_pair_lists for x in lst] border_scalars = [] nonborder_scalars = [] # Loop through files with the scalar values: for ifile, scalar_file in enumerate(scalar_files): print(scalar_file) # Load scalars, folds, and labels: folds_file = fold_files[ifile] labels_file = label_files[ifile] scalars, name = read_scalars(scalar_file, True, True) if scalars.shape: folds, name = read_scalars(folds_file) labels, name = read_scalars(labels_file) indices_folds = [i for i,x in enumerate(folds) if x != -1] neighbor_lists = find_neighbors_from_file(labels_file) # Find all label border pairs within the folds: indices_label_pairs, label_pairs, unique_pairs = extract_borders( indices_folds, labels, neighbor_lists, ignore_values=[-1], return_label_pairs=True) indices_label_pairs = np.array(indices_label_pairs) # Find vertices with label pairs in the sulcus labeling protocol: Ipairs_in_protocol = [i for i,x in enumerate(label_pairs) if x in protocol_label_pairs] indices_label_pairs = indices_label_pairs[Ipairs_in_protocol] indices_outside_pairs = list(frozenset(indices_folds).difference( indices_label_pairs)) # Store scalar values in folds along label border pairs: border_scalars.extend(scalars[indices_label_pairs].tolist()) # Store scalar values in folds outside label border pairs: nonborder_scalars.extend(scalars[indices_outside_pairs].tolist()) return border_scalars, nonborder_scalars