def find_neighbors_from_file(input_vtk): """ Generate the list of unique, sorted indices of neighboring vertices for all vertices in the faces of a triangular mesh in a VTK file. Parameters ---------- input_vtk : string name of input VTK file containing surface mesh Returns ------- neighbor_lists : list of lists of integers each list contains indices to neighboring vertices for each vertex Examples -------- >>> import numpy as np >>> from mindboggle.guts.mesh import find_neighbors_from_file >>> from mindboggle.mio.fetch_data import prep_tests >>> urls, fetch_data = prep_tests() >>> vtk_file = fetch_data(urls['left_mean_curvature']) >>> neighbor_lists = find_neighbors_from_file(vtk_file) >>> neighbor_lists[0:3] [[1, 4, 48, 49], [0, 4, 5, 49, 2], [1, 5, 6, 49, 50, 54]] Write results to vtk file and view (skip test): >>> from mindboggle.mio.vtks import rewrite_scalars # doctest: +SKIP >>> from mindboggle.mio.plots import plot_surfaces # doctest: +SKIP >>> index = 0 # doctest: +SKIP >>> IDs = -1 * np.ones(npoints) # doctest: +SKIP >>> IDs[index] = 1 # doctest: +SKIP >>> IDs[neighbor_lists[index]] = 2 # doctest: +SKIP >>> rewrite_scalars(vtk_file, 'find_neighbors_from_file.vtk', IDs, 'neighbors', IDs) # doctest: +SKIP >>> plot_surfaces('find_neighbors_from_file.vtk') # doctest: +SKIP """ from mindboggle.mio.vtks import read_faces_points from mindboggle.guts.mesh import find_neighbors faces, points, npoints = read_faces_points(input_vtk) neighbor_lists = find_neighbors(faces, npoints) return neighbor_lists
def find_neighbors_from_file(input_vtk): """ Generate the list of unique, sorted indices of neighboring vertices for all vertices in the faces of a triangular mesh in a VTK file. Parameters ---------- input_vtk : string name of input VTK file containing surface mesh Returns ------- neighbor_lists : list of lists of integers each list contains indices to neighboring vertices for each vertex Examples -------- >>> import os >>> import numpy as np >>> from mindboggle.guts.mesh import find_neighbors_from_file >>> from mindboggle.mio.vtks import rewrite_scalars >>> from mindboggle.mio.plots import plot_surfaces >>> path = os.environ['MINDBOGGLE_DATA'] >>> vtk_file = os.path.join(path, 'arno', 'freesurfer', 'lh.pial.vtk') >>> # >>> neighbor_lists = find_neighbors_from_file(vtk_file) >>> # >>> # Write results to vtk file and view: >>> index = 0 >>> IDs = -1 * np.ones(npoints) >>> IDs[index] = 1 >>> IDs[neighbor_lists[index]] = 2 >>> rewrite_scalars(vtk_file, 'find_neighbors_from_file.vtk', IDs, 'neighbors', IDs) >>> plot_surfaces('find_neighbors_from_file.vtk') """ from mindboggle.mio.vtks import read_faces_points from mindboggle.guts.mesh import find_neighbors faces, points, npoints = read_faces_points(input_vtk) neighbor_lists = find_neighbors(faces, npoints) return neighbor_lists
def propagate_fundus_lines(points, faces, fundus_line_indices, thickness): """Propagate fundus lines to tile the surface. Parameters ---------- surf_file: file containing the surface geometry in vtk format fundus_lines_file: file containing scalars representing fundus lines thickness_file: file containing cortical thickness scalar data (for masking out the medial wall only) Returns ------- scalars indicating whether each vertex is part of the closed fundus lines or not """ from mindboggle.guts.mesh import find_neighbors import numpy as np num_points = len(points) neighbor_lists = find_neighbors(faces, num_points) # Find the boundary of the cc and call that a fundus line cc_inds = [x for x in range(num_points) if thickness[x] < 0.001] cc_boundary = [x for x in cc_inds if len([y for y in neighbor_lists[x] if y not in cc_inds])] fundus_line_indices += cc_boundary endpoints = _find_fundus_line_endpoints( fundus_line_indices, neighbor_lists) closed_fundus_lines = _close_fundus_lines(points, fundus_line_indices, neighbor_lists, endpoints) closed_fundus_line_indices = np.where( np.array(closed_fundus_lines) > 0)[0].tolist() new_endpoints = _find_fundus_line_endpoints(closed_fundus_line_indices, neighbor_lists) new_closed_fundus_lines = _close_fundus_lines( points, closed_fundus_line_indices, neighbor_lists, new_endpoints) return new_closed_fundus_lines, points, faces
def propagate_fundus_lines(points, faces, fundus_line_indices, thickness): """Propagate fundus lines to tile the surface. Parameters ---------- surf_file: file containing the surface geometry in vtk format fundus_lines_file: file containing scalars representing fundus lines thickness_file: file containing cortical thickness scalar data (for masking out the medial wall only) Returns ------- scalars indicating whether each vertex is part of the closed fundus lines or not """ from mindboggle.guts.mesh import find_neighbors import numpy as np num_points = len(points) neighbor_lists = find_neighbors(faces, num_points) # Find the boundary of the cc and call that a fundus line cc_inds = [x for x in xrange(num_points) if thickness[x] < 0.001] cc_boundary = [x for x in cc_inds if len([y for y in neighbor_lists[x] if y not in cc_inds])] fundus_line_indices += cc_boundary endpoints = _find_fundus_line_endpoints( fundus_line_indices, neighbor_lists) closed_fundus_lines = _close_fundus_lines(points, fundus_line_indices, neighbor_lists, endpoints) closed_fundus_line_indices = np.where( np.array(closed_fundus_lines) > 0)[0].tolist() new_endpoints = _find_fundus_line_endpoints(closed_fundus_line_indices, neighbor_lists) new_closed_fundus_lines = _close_fundus_lines( points, closed_fundus_line_indices, neighbor_lists, new_endpoints) return new_closed_fundus_lines, points, faces
def _label_components(component_faces, num_points, boundary_indices, boundary_probability_matrix, boundary_matrix_keys): """Label the connected components of a surface with the most probable label based on the boundary_probability_matrix. """ import numpy as np from mindboggle.guts.mesh import find_neighbors neighbor_lists = find_neighbors(component_faces, num_points) result_labels = -1 * np.ones((num_points)) # find all the connected components point_visited = num_points * [False] components = {} component_boundaries = {} print "Finding connected components" while True: first_vertex = None try: first_vertex = next(i for i, v in enumerate(point_visited) if not v) except: break open_vertices = [first_vertex] point_visited[first_vertex] = True component_vertices = [] component_boundary_vertices = [] while len(open_vertices) > 0: this_vertex = open_vertices.pop() component_vertices.append(this_vertex) if this_vertex in boundary_indices: component_boundary_vertices.append(this_vertex) for neighbor in neighbor_lists[this_vertex]: if not point_visited[neighbor]: open_vertices.append(neighbor) point_visited[neighbor] = True components[len(component_vertices)] = component_vertices component_boundaries[len(component_vertices)] = \ component_boundary_vertices # compute the most probable label for each connected # component. Only boundary indices are considered when computing # label probability. # Note: Here we assume that components and component_boundaries # have the same keys. used_labels = [] print "Computing most probable labels" for component in sorted(components.keys(), None, None, True): label_likelihoods = {} for vertex in component_boundaries[component]: for index, boundary_prob in \ enumerate(boundary_probability_matrix[vertex]): labels = boundary_matrix_keys[index] for label in labels: # if label in used_labels: # continue if label not in label_likelihoods: label_likelihoods[label] = 0 label_likelihoods[label] += boundary_prob # assign the most likely label max_label = None max_label_likelihood = None for key, val in label_likelihoods.iteritems(): if max_label is None or val > max_label_likelihood: max_label = key max_label_likelihood = val if max_label is not None: result_labels[components[component]] = max_label used_labels.append(max_label) return result_labels
def extract_subfolds(depth_file, folds, min_size=10, depth_factor=0.25, depth_ratio=0.1, tolerance=0.01, save_file=False): """ Use depth to segment folds into subfolds in a triangular surface mesh. Note :: The function extract_sulci() performs about the same whether folds or subfolds are used as input. The latter leads to some loss of small subfolds and possibly holes for small subfolds in the middle of other subfolds. Note about the watershed() function: The watershed() function performs individual seed growing from deep seeds, repeats segmentation from the resulting seeds until each seed's segment touches a boundary. The function segment() fills in the rest. Finally segments are joined if their seeds are too close to each other. Despite these precautions, the order of seed selection in segment() could possibly influence the resulting borders between adjoining segments. [The propagate() function is slower and insensitive to depth, but is not biased by seed order.] Parameters ---------- depth_file : string surface mesh file in VTK format with faces and depth scalar values folds : list of integers fold numbers for all vertices (-1 for non-fold vertices) min_size : integer minimum number of vertices for a subfold depth_factor : float watershed() depth_factor: 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 watershed() depth_ratio: the minimum fraction of depth for a neighboring shallower watershed catchment basin (otherwise merged with the deeper basin) tolerance : float watershed() tolerance: tolerance for detecting differences in depth between vertices save_file : Boolean save output VTK file? Returns ------- subfolds : list of integers fold numbers for all vertices (-1 for non-fold vertices) n_subfolds : int number of subfolds subfolds_file : string (if save_file) name of output VTK file with fold IDs (-1 for non-fold vertices) Examples -------- >>> import os >>> from mindboggle.mio.vtks import read_scalars, rewrite_scalars >>> from mindboggle.guts.mesh import find_neighbors_from_file >>> from mindboggle.features.folds import extract_subfolds >>> from mindboggle.mio.plots import plot_surfaces >>> path = os.environ['MINDBOGGLE_DATA'] >>> depth_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.travel_depth.vtk') >>> folds_file = os.path.join(path, 'arno', 'features', 'folds.vtk') >>> folds, name = read_scalars(folds_file) >>> min_size = 10 >>> depth_factor = 0.5 >>> depth_ratio = 0.1 >>> tolerance = 0.01 >>> # >>> subfolds, n_subfolds, subfolds_file = extract_subfolds(depth_file, >>> folds, min_size, depth_factor, depth_ratio, tolerance, True) >>> # >>> # View: >>> rewrite_scalars(depth_file, 'subfolds.vtk', subfolds, 'subfolds', subfolds) >>> plot_surfaces('subfolds.vtk') """ import os import numpy as np from time import time from mindboggle.mio.vtks import rewrite_scalars, read_vtk from mindboggle.guts.mesh import find_neighbors from mindboggle.guts.segment import segment, propagate, watershed print("Segment folds into subfolds") t0 = time() #------------------------------------------------------------------------- # Load depth values for all vertices #------------------------------------------------------------------------- faces, lines, indices, points, npoints, depths, \ name, input_vtk = read_vtk(depth_file, return_first=True, return_array=True) #------------------------------------------------------------------------- # Find neighbors for each vertex #------------------------------------------------------------------------- neighbor_lists = find_neighbors(faces, npoints) #------------------------------------------------------------------------- # Segment folds into "watershed basins" #------------------------------------------------------------------------- indices_folds = [i for i,x in enumerate(folds) if x != -1] subfolds, seed_indices = watershed(depths, points, indices_folds, neighbor_lists, min_size, depth_factor=0.25, depth_ratio=0.1, tolerance=0.01, regrow=True) # Print statement n_subfolds = len([x for x in np.unique(subfolds) if x != -1]) print(' ...Extracted {0} subfolds ({1:.2f} seconds)'. format(n_subfolds, time() - t0)) #------------------------------------------------------------------------- # Return subfolds, number of subfolds, file name #------------------------------------------------------------------------- if save_file: subfolds_file = os.path.join(os.getcwd(), 'subfolds.vtk') rewrite_scalars(depth_file, subfolds_file, subfolds, 'subfolds', subfolds) if not os.path.exists(subfolds_file): raise(IOError(subfolds_file + " not found")) else: subfolds_file = None return subfolds, n_subfolds, subfolds_file
def extract_folds(depth_file, min_vertices=10000, min_fold_size=50, do_fill_holes=False, min_hole_depth=0.001, save_file=False): """ Use depth to extract folds from a triangular surface mesh. Steps :: 1. Compute histogram of depth measures. 2. Define a depth threshold and find the deepest vertices. 3. Segment deep vertices as an initial set of folds. 4. Remove small folds. 5. Find and fill holes in the folds (optional). 6. Renumber folds. Step 2 :: To extract an initial set of deep vertices from the surface mesh, we anticipate that there will be a rapidly decreasing distribution of low depth values (on the outer surface) with a long tail of higher depth values (in the folds), so we smooth the histogram's bin values, convolve to compute slopes, and find the depth value for the first bin with slope = 0. This is our threshold. Step 5 :: The folds could have holes in areas shallower than the depth threshold. Calling fill_holes() could accidentally include very shallow areas (in an annulus-shaped fold, for example), so we include the argument exclude_range to check for any values from zero to min_hole_depth; holes are not filled if they contains values within this range. Parameters ---------- depth_file : string surface mesh file in VTK format with faces and depth scalar values min_fold_size : integer minimum fold size (number of vertices) do_fill_holes : Boolean fill holes in the folds? min_hole_depth : float largest non-zero depth value that will stop a hole from being filled save_file : Boolean save output VTK file? Returns ------- folds : list of integers fold numbers for all vertices (-1 for non-fold vertices) n_folds : int number of folds depth_threshold : float threshold defining the minimum depth for vertices to be in a fold bins : list of integers histogram bins: each is the number of vertices within a range of depth values bin_edges : list of floats histogram bin edge values defining the bin ranges of depth values folds_file : string (if save_file) name of output VTK file with fold IDs (-1 for non-fold vertices) Examples -------- >>> import os >>> import numpy as np >>> import pylab >>> from scipy.ndimage.filters import gaussian_filter1d >>> from mindboggle.mio.vtks import read_scalars >>> from mindboggle.guts.mesh import find_neighbors_from_file >>> from mindboggle.mio.plots import plot_surfaces >>> from mindboggle.features.folds import extract_folds >>> path = os.environ['MINDBOGGLE_DATA'] >>> depth_file = 'travel_depth.vtk' #os.path.join(path, 'arno', 'shapes', 'lh.pial.travel_depth.vtk') >>> neighbor_lists = find_neighbors_from_file(depth_file) >>> min_vertices = 10000 >>> min_fold_size = 50 >>> do_fill_holes = False #True >>> min_hole_depth = 0.001 >>> save_file = True >>> # >>> folds, n_folds, thr, bins, bin_edges, folds_file = extract_folds(depth_file, >>> min_vertices, min_fold_size, do_fill_holes, min_hole_depth, save_file) >>> # >>> # View folds: >>> plot_surfaces('folds.vtk') >>> # Plot histogram and depth threshold: >>> depths, name = read_scalars(depth_file) >>> nbins = np.round(len(depths) / 100.0) >>> a,b,c = pylab.hist(depths, bins=nbins) >>> pylab.plot(thr*np.ones((100,1)), np.linspace(0, max(bins), 100), 'r.') >>> pylab.show() >>> # Plot smoothed histogram: >>> bins_smooth = gaussian_filter1d(bins.tolist(), 5) >>> pylab.plot(range(len(bins)), bins, '.', range(len(bins)), bins_smooth,'-') >>> pylab.show() """ import os import sys import numpy as np from time import time from scipy.ndimage.filters import gaussian_filter1d from mindboggle.mio.vtks import rewrite_scalars, read_vtk from mindboggle.guts.mesh import find_neighbors from mindboggle.guts.morph import fill_holes from mindboggle.guts.segment import segment print("Extract folds in surface mesh") t0 = time() #------------------------------------------------------------------------- # Load depth values for all vertices #------------------------------------------------------------------------- faces, lines, indices, points, npoints, depths, name, input_vtk = read_vtk(depth_file, return_first=True, return_array=True) #------------------------------------------------------------------------- # Find neighbors for each vertex #------------------------------------------------------------------------- neighbor_lists = find_neighbors(faces, npoints) #------------------------------------------------------------------------- # Compute histogram of depth measures #------------------------------------------------------------------------- if npoints > min_vertices: nbins = np.round(npoints / 100.0) else: sys.err(" Expecting at least {0} vertices to create depth histogram". format(min_vertices)) bins, bin_edges = np.histogram(depths, bins=nbins) #------------------------------------------------------------------------- # Anticipating that there will be a rapidly decreasing distribution # of low depth values (on the outer surface) with a long tail of higher # depth values (in the folds), smooth the bin values (Gaussian), convolve # to compute slopes, and find the depth for the first bin with slope = 0. #------------------------------------------------------------------------- bins_smooth = gaussian_filter1d(bins.tolist(), 5) window = [-1, 0, 1] bin_slopes = np.convolve(bins_smooth, window, mode='same') / (len(window) - 1) ibins0 = np.where(bin_slopes == 0)[0] if ibins0.shape: depth_threshold = bin_edges[ibins0[0]] else: depth_threshold = np.median(depths) #------------------------------------------------------------------------- # Find the deepest vertices #------------------------------------------------------------------------- indices_deep = [i for i,x in enumerate(depths) if x >= depth_threshold] if indices_deep: #--------------------------------------------------------------------- # Segment deep vertices as an initial set of folds #--------------------------------------------------------------------- print(" Segment vertices deeper than {0:.2f} as folds".format(depth_threshold)) t1 = time() folds = segment(indices_deep, neighbor_lists) # Slightly slower alternative -- fill boundaries: #regions = -1 * np.ones(len(points)) #regions[indices_deep] = 1 #folds = segment_by_filling_borders(regions, neighbor_lists) print(' ...Segmented folds ({0:.2f} seconds)'.format(time() - t1)) #--------------------------------------------------------------------- # Remove small folds #--------------------------------------------------------------------- if min_fold_size > 1: print(' Remove folds smaller than {0}'.format(min_fold_size)) unique_folds = [x for x in np.unique(folds) if x != -1] for nfold in unique_folds: indices_fold = [i for i,x in enumerate(folds) if x == nfold] if len(indices_fold) < min_fold_size: folds[indices_fold] = -1 #--------------------------------------------------------------------- # Find and fill holes in the folds # Note: Surfaces surrounded by folds can be mistaken for holes, # so exclude_range includes outer surface values close to zero. #--------------------------------------------------------------------- if do_fill_holes: print(" Find and fill holes in the folds") folds = fill_holes(folds, neighbor_lists, values=depths, exclude_range=[0, min_hole_depth]) #--------------------------------------------------------------------- # Renumber folds so they are sequential #--------------------------------------------------------------------- renumber_folds = -1 * np.ones(len(folds)) fold_numbers = [int(x) for x in np.unique(folds) if x != -1] for i_fold, n_fold in enumerate(fold_numbers): fold = [i for i,x in enumerate(folds) if x == n_fold] renumber_folds[fold] = i_fold folds = renumber_folds n_folds = i_fold + 1 # Print statement print(' ...Extracted {0} folds ({1:.2f} seconds)'. format(n_folds, time() - t0)) else: print(' No deep vertices') folds = [int(x) for x in folds] #------------------------------------------------------------------------- # Return folds, number of folds, file name #------------------------------------------------------------------------- if save_file: folds_file = os.path.join(os.getcwd(), 'folds.vtk') rewrite_scalars(depth_file, folds_file, folds, 'folds', folds) if not os.path.exists(folds_file): raise(IOError(folds_file + " not found")) else: folds_file = None return folds, n_folds, depth_threshold, bins, bin_edges, folds_file
def close_surface_pair(faces, points1, points2, scalars, background_value=-1): """ Close a surface patch by connecting its border vertices with corresponding vertices in a second surface file. Assumes no lines or indices when reading VTK files in. Note :: Scalar values different than background define the surface patch. The two sets of points have a 1-to-1 mapping; they are from two surfaces whose corresponding vertices are shifted in position. For pial vs. gray-white matter, the two surfaces are not parallel, so connecting the vertices leads to intersecting faces. Parameters ---------- faces : list of lists of integers each sublist contains 3 indices of vertices that form a face on a surface mesh points1 : list of lists of floats each sublist contains 3-D coordinates of a vertex on a surface mesh points2 : list of lists of floats points from second surface with 1-to-1 correspondence with points1 scalars : numpy array of integers labels used to find foreground vertices background_value : integer scalar value for background vertices Returns ------- closed_faces : list of lists of integers indices of vertices that form a face on the closed surface mesh closed_points : list of lists of floats 3-D coordinates from points1 and points2 closed_scalars : list of integers scalar values for points1 and points2 Examples -------- >>> # Example 1: build a cube by closing two parallel planes: >>> import os >>> from mindboggle.guts.morph import close_surface_pair >>> from mindboggle.mio.plots import plot_surfaces >>> from mindboggle.mio.vtks import write_vtk >>> # Build plane: >>> background_value = -1 >>> n = 10 # plane edge length >>> points1 = [] >>> for x in range(n): >>> for y in range(n): >>> points1.append([x,y,0]) >>> points2 = [[x[0],x[1],1] for x in points1] >>> scalars = [background_value for x in range(len(points1))] >>> p = n*(n-1)/2 - 1 >>> for i in [p, p+1, p+n, p+n+1]: >>> scalars[i] = 1 >>> faces = [] >>> for x in range(n-1): >>> for y in range(n-1): >>> faces.append([x+y*n,x+n+y*n,x+n+1+y*n]) >>> faces.append([x+y*n,x+1+y*n,x+n+1+y*n]) >>> #write_vtk('plane.vtk', points1, [], [], faces, scalars) >>> #plot_surfaces('plane.vtk') # doctest: +SKIP >>> closed_faces, closed_points, closed_scalars = close_surface_pair(faces, points1, points2, scalars, background_value) >>> # View: >>> write_vtk('cube.vtk', closed_points, [], [], closed_faces, closed_scalars, 'int') >>> plot_surfaces('cube.vtk') # doctest: +SKIP >>> # >>> # Example 2: Gray and white cortical brain surfaces: >>> import os >>> from mindboggle.guts.morph import close_surface_pair >>> from mindboggle.mio.plots import plot_surfaces >>> from mindboggle.mio.vtks import read_scalars, read_vtk, read_points, write_vtk >>> path = os.environ['MINDBOGGLE_DATA'] >>> patch_surface1 = 'fold.pial.vtk' >>> whole_surface2 = 'fold.white.vtk' >>> # Select a single fold: >>> folds_file = os.path.join(path, 'arno', 'features', 'folds.vtk') >>> points1 = read_points(folds_file) >>> scalars, name = read_scalars(folds_file, True, True) >>> fold_number = 11 >>> scalars[scalars != fold_number] = -1 >>> white_surface = os.path.join(path, 'arno', 'freesurfer', 'lh.white.vtk') >>> points2, indices, lines, faces, scalars, scalar_names, npoints, input_vtk = read_vtk(white_surface) >>> background_value = -1 >>> closed_faces, closed_points, closed_scalars = close_surface_pair(faces, points1, points2, scalars, background_value) >>> # View: >>> write_vtk('closed.vtk', closed_points, [], [], closed_faces, closed_scalars, name, 'int') >>> plot_surfaces('closed.vtk') # doctest: +SKIP """ import sys import numpy as np from mindboggle.guts.mesh import find_neighbors, remove_faces from mindboggle.guts.segment import extract_borders if isinstance(scalars, list): scalars = np.array(scalars) N = len(points1) closed_points = points1 + points2 # Find all vertex neighbors and surface patch border vertices: neighbor_lists = find_neighbors(faces, N) I = np.where(scalars != background_value)[0] scalars[scalars == background_value] = background_value + 1 scalars[I] = background_value + 2 scalars = scalars.tolist() borders, u1, u2 = extract_borders(range(N), scalars, neighbor_lists) if not len(borders): sys.exit('There are no border vertices!') borders = [x for x in borders if x in I] # Reindex copy of faces and combine with original (both zero-index): indices = range(N) indices2 = range(N, 2 * N) reindex = dict([(index, indices2[i]) for i, index in enumerate(indices)]) faces = remove_faces(faces, I) faces2 = [[reindex[i] for i in face] for face in faces] closed_faces = faces + faces2 # Connect border vertices between surface patches and add new faces: add_faces = [] taken_already = [] for index in borders: if index not in taken_already: neighbors = list(set(neighbor_lists[index]).intersection(borders)) taken_already.append(index) #taken_already.extend([index] + neighbors) for neighbor in neighbors: add_faces.append([index, index + N, neighbor]) add_faces.append([index + N, neighbor, neighbor + N]) closed_faces = closed_faces + add_faces closed_scalars = scalars * 2 return closed_faces, closed_points, closed_scalars
def extract_folds(depth_file, depth_threshold=2, min_fold_size=50, save_file=False, output_file='', background_value=-1, verbose=False): """ Use depth threshold to extract folds from a triangular surface mesh. A fold is a group of connected, deep vertices. To extract folds, a depth threshold is used to segment deep vertices of the surface mesh. We have observed in the histograms of travel depth measures of cortical surfaces that there is a rapidly decreasing distribution of low depth values (corresponding to the outer surface, or gyral crowns) with a long tail of higher depth values (corresponding to the folds). The find_depth_threshold function therefore computes a histogram of travel depth measures, smooths the histogram's bin values, convolves to compute slopes, and finds the depth value for the first bin with zero slope. The extract_folds function uses this depth value, segments deep vertices, and removes extremely small folds (empirically set at 50 vertices or fewer out of a total mesh size of over 100,000 vertices). Steps :: 1. Segment deep vertices as an initial set of folds. 2. Remove small folds. 3. Renumber folds. Note :: Removed option: Find and fill holes in the folds: Folds could have holes in areas shallower than the depth threshold. Calling fill_holes() could accidentally include very shallow areas (in an annulus-shaped fold, for example). However, we could include the argument exclude_range to check for any values from zero to min_hole_depth; holes would not be filled if they were to contain values within this range. Parameters ---------- depth_file : string surface mesh file in VTK format with faces and depth scalar values depth_threshold : float threshold defining the minimum depth for vertices to be in a fold min_fold_size : integer minimum fold size (number of vertices) save_file : bool save output VTK file? output_file : string name of output file in VTK format background_value : integer or float background value verbose : bool print statements? Returns ------- folds : list of integers fold numbers for all vertices (-1 for non-fold vertices) n_folds : int number of folds folds_file : string (if save_file) name of output VTK file with fold IDs (-1 for non-fold vertices) Examples -------- >>> from mindboggle.features.folds import extract_folds >>> from mindboggle.mio.fetch_data import prep_tests >>> urls, fetch_data = prep_tests() >>> depth_file = fetch_data(urls['left_travel_depth'], '', '.vtk') >>> depth_threshold = 2.36089 >>> min_fold_size = 50 >>> save_file = True >>> output_file = 'extract_folds.vtk' >>> background_value = -1 >>> verbose = False >>> folds, n_folds, folds_file = extract_folds(depth_file, ... depth_threshold, min_fold_size, save_file, output_file, ... background_value, verbose) >>> n_folds 33 >>> lens = [len([x for x in folds if x == y]) for y in range(n_folds)] >>> lens[0:10] [726, 67241, 2750, 5799, 1151, 6360, 1001, 505, 228, 198] View folds (skip test): >>> from mindboggle.mio.plots import plot_surfaces # doctest: +SKIP >>> plot_surfaces('extract_folds.vtk') # doctest: +SKIP View folds without background (skip test): >>> from mindboggle.mio.plots import plot_surfaces # doctest: +SKIP >>> from mindboggle.mio.vtks import rewrite_scalars # doctest: +SKIP >>> rewrite_scalars(depth_file, 'extract_folds_no_background.vtk', folds, ... 'just_folds', folds, -1) # doctest: +SKIP >>> plot_surfaces('extract_folds_no_background.vtk') # doctest: +SKIP """ import os import numpy as np from time import time from mindboggle.mio.vtks import rewrite_scalars, read_vtk from mindboggle.guts.mesh import find_neighbors from mindboggle.guts.segment import segment_regions if verbose: print("Extract folds in surface mesh") t0 = time() # ------------------------------------------------------------------------ # Load depth values for all vertices # ------------------------------------------------------------------------ points, indices, lines, faces, depths, scalar_names, npoints, \ input_vtk = read_vtk(depth_file, return_first=True, return_array=True) # ------------------------------------------------------------------------ # Find the deepest vertices # ------------------------------------------------------------------------ indices_deep = [i for i, x in enumerate(depths) if x >= depth_threshold] if indices_deep: # -------------------------------------------------------------------- # Find neighbors for each vertex # -------------------------------------------------------------------- neighbor_lists = find_neighbors(faces, npoints) # -------------------------------------------------------------------- # Segment deep vertices as an initial set of folds # -------------------------------------------------------------------- if verbose: print(" Segment vertices deeper than {0:.2f} as folds".format( depth_threshold)) t1 = time() folds = segment_regions(indices_deep, neighbor_lists, 1, [], False, False, [], [], [], '', background_value, False) if verbose: print(' ...Segmented folds ({0:.2f} seconds)'.format(time() - t1)) # -------------------------------------------------------------------- # Remove small folds # -------------------------------------------------------------------- if min_fold_size > 1: if verbose: print(' Remove folds smaller than {0}'.format(min_fold_size)) unique_folds = [ x for x in np.unique(folds) if x != background_value ] for nfold in unique_folds: indices_fold = [i for i, x in enumerate(folds) if x == nfold] if len(indices_fold) < min_fold_size: folds[indices_fold] = background_value # -------------------------------------------------------------------- # Find and fill holes in the folds # Note: Surfaces surrounded by folds can be mistaken for holes, # so exclude_range includes outer surface values close to zero. # -------------------------------------------------------------------- # folds = fill_holes(folds, neighbor_lists, values=depths, # exclude_range=[0, min_hole_depth]) # -------------------------------------------------------------------- # Renumber folds so they are sequential. # NOTE: All vertices are included (-1 for non-fold vertices). # -------------------------------------------------------------------- renumber_folds = background_value * np.ones(npoints) fold_numbers = [x for x in np.unique(folds) if x != background_value] for i_fold, n_fold in enumerate(fold_numbers): fold_indices = [i for i, x in enumerate(folds) if x == n_fold] renumber_folds[fold_indices] = i_fold folds = renumber_folds folds = [int(x) for x in folds] n_folds = i_fold + 1 # Print statement if verbose: print(' ...Extracted {0} folds ({1:.2f} seconds)'.format( n_folds, time() - t0)) else: if verbose: print(' No deep vertices') # ------------------------------------------------------------------------ # Return folds, number of folds, file name # ------------------------------------------------------------------------ if save_file: if output_file: folds_file = output_file else: folds_file = os.path.join(os.getcwd(), 'folds.vtk') rewrite_scalars(depth_file, folds_file, folds, 'folds', [], background_value) if not os.path.exists(folds_file): raise IOError(folds_file + " not found") else: folds_file = None return folds, n_folds, folds_file
def evaluate_deep_features(features_file, labels_file, sulci_file='', hemi='', excludeIDs=[-1], output_vtk_name='', verbose=True): """ Evaluate deep surface features by computing the minimum distance from each label border vertex to all of the feature vertices in the same sulcus, and from each feature vertex to all of the label border vertices in the same sulcus. The label borders run along the deepest parts of sulci and correspond to fundi in the DKT cortical labeling protocol. Parameters ---------- features_file : string VTK surface file with feature numbers for vertex scalars labels_file : string VTK surface file with label numbers for vertex scalars sulci_file : string VTK surface file with sulcus numbers for vertex scalars excludeIDs : list of integers feature/sulcus/label IDs to exclude (background set to -1) output_vtk_name : Boolean if not empty, output a VTK file beginning with output_vtk_name that contains a surface with mean distances as scalars verbose : Boolean print mean distances to standard output? Returns ------- feature_to_border_mean_distances : numpy array [number of features x 1] mean distance from each feature to sulcus label border feature_to_border_sd_distances : numpy array [number of features x 1] standard deviations of feature-to-border distances feature_to_border_distances_vtk : string VTK surface file containing feature-to-border distances border_to_feature_mean_distances : numpy array [number of features x 1] mean distances from each sulcus label border to feature border_to_feature_sd_distances : numpy array [number of features x 1] standard deviations of border-to-feature distances border_to_feature_distances_vtk : string VTK surface file containing border-to-feature distances """ import os import sys import numpy as np from mindboggle.mio.vtks import read_vtk, read_scalars, write_vtk from mindboggle.guts.mesh import find_neighbors, remove_faces from mindboggle.guts.segment import extract_borders from mindboggle.guts.compute import source_to_target_distances from mindboggle.mio.labels import DKTprotocol dkt = DKTprotocol() #------------------------------------------------------------------------- # Load labels, features, and sulci: #------------------------------------------------------------------------- faces, lines, indices, points, npoints, labels, scalar_names, \ input_vtk = read_vtk(labels_file, True, True) features, name = read_scalars(features_file, True, True) if sulci_file: sulci, name = read_scalars(sulci_file, True, True) # List of indices to sulcus vertices: sulcus_indices = [i for i, x in enumerate(sulci) if x != -1] segmentIDs = sulci sulcus_faces = remove_faces(faces, sulcus_indices) else: sulcus_indices = range(len(labels)) segmentIDs = [] sulcus_faces = faces #------------------------------------------------------------------------- # Prepare neighbors, label pairs, border IDs, and outputs: #------------------------------------------------------------------------- # Calculate neighbor lists for all points: print('Find neighbors for all vertices...') neighbor_lists = find_neighbors(faces, npoints) # Find label border points in any of the sulci: print('Find label border points in any of the sulci...') border_indices, border_label_tuples, unique_border_label_tuples = \ extract_borders(sulcus_indices, labels, neighbor_lists, ignore_values=[], return_label_pairs=True) if not len(border_indices): sys.exit('There are no label border points!') # Initialize an array of label border IDs # (label border vertices that define sulci in the labeling protocol): print('Build an array of label border IDs...') label_borders = -1 * np.ones(npoints) if hemi == 'lh': nsulcus_lists = len(dkt.left_sulcus_label_pair_lists) else: nsulcus_lists = len(dkt.right_sulcus_label_pair_lists) feature_to_border_mean_distances = -1 * np.ones(nsulcus_lists) feature_to_border_sd_distances = -1 * np.ones(nsulcus_lists) border_to_feature_mean_distances = -1 * np.ones(nsulcus_lists) border_to_feature_sd_distances = -1 * np.ones(nsulcus_lists) feature_to_border_distances_vtk = '' border_to_feature_distances_vtk = '' #------------------------------------------------------------------------- # Loop through sulci: #------------------------------------------------------------------------- # For each list of sorted label pairs (corresponding to a sulcus): for isulcus, label_pairs in enumerate(dkt.sulcus_label_pair_lists): # Keep the border points with label pair labels: label_pair_border_indices = [ x for i, x in enumerate(border_indices) if np.unique(border_label_tuples[i]).tolist() in label_pairs ] # Store the points as sulcus IDs in the border IDs array: if label_pair_border_indices: label_borders[label_pair_border_indices] = isulcus if len(np.unique(label_borders)) > 1: #--------------------------------------------------------------------- # Construct a feature-to-border distance matrix and VTK file: #--------------------------------------------------------------------- # Construct a distance matrix: print('Construct a feature-to-border distance matrix...') sourceIDs = features targetIDs = label_borders distances, distance_matrix = source_to_target_distances( sourceIDs, targetIDs, points, segmentIDs, excludeIDs) # Compute mean distances for each feature: nfeatures = min(np.shape(distance_matrix)[1], nsulcus_lists) for ifeature in range(nfeatures): feature_distances = [ x for x in distance_matrix[:, ifeature] if x != -1 ] feature_to_border_mean_distances[ifeature] = \ np.mean(feature_distances) feature_to_border_sd_distances[ifeature] = \ np.std(feature_distances) if verbose: print('Feature-to-border mean distances:') print(feature_to_border_mean_distances) print('Feature-to-border standard deviations of distances:') print(feature_to_border_sd_distances) # Write resulting feature-label border distances to VTK file: if output_vtk_name: feature_to_border_distances_vtk = os.path.join( os.getcwd(), output_vtk_name + '_feature_to_border_mean_distances.vtk') print('Write feature-to-border distances to {0}...'.format( feature_to_border_distances_vtk)) write_vtk(feature_to_border_distances_vtk, points, [], [], sulcus_faces, [distances], ['feature-to-border_distances'], 'float') #--------------------------------------------------------------------- # Construct a border-to-feature distance matrix and VTK file: #--------------------------------------------------------------------- # Construct a distance matrix: print('Construct a border-to-feature distance matrix...') sourceIDs = label_borders targetIDs = features distances, distance_matrix = source_to_target_distances( sourceIDs, targetIDs, points, segmentIDs, excludeIDs) # Compute mean distances for each feature: nfeatures = min(np.shape(distance_matrix)[1], nsulcus_lists) for ifeature in range(nfeatures): border_distances = [ x for x in distance_matrix[:, ifeature] if x != -1 ] border_to_feature_mean_distances[ifeature] = \ np.mean(border_distances) border_to_feature_sd_distances[ifeature] = \ np.std(border_distances) if verbose: print('border-to-feature mean distances:') print(border_to_feature_mean_distances) print('border-to-feature standard deviations of distances:') print(border_to_feature_sd_distances) # Write resulting feature-label border distances to VTK file: if output_vtk_name: border_to_feature_distances_vtk = os.path.join( os.getcwd(), output_vtk_name + '_border_to_feature_mean_distances.vtk') print('Write border-to-feature distances to {0}...'.format( border_to_feature_distances_vtk)) write_vtk(border_to_feature_distances_vtk, points, [], [], sulcus_faces, [distances], ['border-to-feature_distances'], 'float') #------------------------------------------------------------------------- # Return outputs: #------------------------------------------------------------------------- return feature_to_border_mean_distances, feature_to_border_sd_distances,\ feature_to_border_distances_vtk,\ border_to_feature_mean_distances, border_to_feature_sd_distances,\ border_to_feature_distances_vtk
def label_adjacency_matrix(label_file, ignore_values=[-1, 999], add_value=0, save_table=True, output_format='csv', verbose=True): """ Extract surface or volume label boundaries, find unique label pairs, and write adjacency matrix (useful for constructing a colormap). Each row of the (upper triangular) adjacency matrix corresponds to an index to a unique label, where each column has a 1 if the label indexed by that column is adjacent to the label indexed by the row. Parameters ---------- label_file : string path to VTK surface file or nibabel-readable volume file with labels ignore_values : list of integers labels to ignore add_value : integer value to add to labels matrix : pandas dataframe adjacency matrix save_table : Boolean output table file? output_format : string format of adjacency table file name (currently only 'csv') verbose : Boolean print to stdout? Returns ------- labels : list label numbers matrix : pandas DataFrame adjacency matrix output_table : string adjacency table file name Examples -------- >>> from mindboggle.mio.colors import label_adjacency_matrix >>> from mindboggle.mio.fetch_data import prep_tests >>> urls, fetch_data = prep_tests() >>> ignore_values = [-1, 0] >>> add_value = 0 >>> save_table = False >>> output_format = 'csv' >>> verbose = False >>> label_file = fetch_data(urls['left_manual_labels'], '', '.vtk') >>> labels, matrix, output_table = label_adjacency_matrix(label_file, ... ignore_values, add_value, save_table, output_format, verbose) >>> matrix.lookup([20,21,22,23,24,25,26,27,28,29], ... [35,35,35,35,35,35,35,35,35,35]) array([ 0., 1., 0., 0., 0., 0., 0., 1., 1., 1.]) >>> label_file = fetch_data(urls['freesurfer_labels'], '', '.nii.gz') >>> labels, matrix, output_table = label_adjacency_matrix(label_file, ... ignore_values, add_value, save_table, output_format, verbose) >>> matrix.lookup([4,5,7,8,10,11,12,13,14,15], [4,4,4,4,4,4,4,4,4,4]) array([ 1., 1., 0., 0., 0., 1., 0., 0., 1., 0.]) """ import numpy as np import pandas as pd from nibabel import load from scipy import ndimage from mindboggle.guts.mesh import find_neighbors from mindboggle.guts.segment import extract_borders from mindboggle.mio.vtks import read_vtk # Use Mindboggle's extract_borders() function for surface VTK files: if label_file.endswith('.vtk'): f1,f2,f3, faces, labels, f4, npoints, f5 = read_vtk(label_file, True, True) neighbor_lists = find_neighbors(faces, npoints) return_label_pairs = True indices_borders, label_pairs, f1 = extract_borders(list(range(npoints)), labels, neighbor_lists, ignore_values, return_label_pairs) output_table = 'adjacent_surface_labels.' + output_format # Use scipy to dilate volume files to find neighboring labels: elif label_file.endswith('.nii.gz'): L = load(label_file).get_data() unique_volume_labels = np.unique(L) label_pairs = [] for label in unique_volume_labels: if label not in ignore_values: B = L * np.logical_xor(ndimage.binary_dilation(L==int(label)), (L==int(label))) neighbor_labels = np.unique(np.ravel(B)) for neigh in neighbor_labels: if neigh > 0 and neigh in unique_volume_labels: # and neigh%2==(int(label)%2): label_pairs.append([int(label), int(neigh)]) output_table = 'adjacent_volume_labels.' + output_format else: raise IOError("Use appropriate input file type.") # Find unique pairs (or first two of each list): pairs = [] for pair in label_pairs: new_pair = [int(pair[0]) + add_value, int(pair[1]) + add_value] if new_pair not in pairs: pairs.append(new_pair) # Write adjacency matrix: unique_labels = np.unique(pairs) nlabels = np.size(unique_labels) matrix = np.zeros((nlabels, nlabels)) for pair in pairs: index1 = [i for i, x in enumerate(unique_labels) if x == pair[0]] index2 = [i for i, x in enumerate(unique_labels) if x == pair[1]] matrix[index1, index2] = 1 df1 = pd.DataFrame({'ID': unique_labels}, index=None) df2 = pd.DataFrame(matrix, index=None) df2.columns = unique_labels matrix = pd.concat([df1, df2], axis=1) if save_table: if output_format == 'csv': matrix.to_csv(output_table, index=False) if verbose: print("Adjacency matrix saved to {0}".format(output_table)) else: raise IOError("Set appropriate output file format.") else: output_table = None labels = list(unique_labels) return labels, matrix, output_table
def close_surface_pair(faces, points1, points2, scalars, background_value=-1): """ Close a surface patch by connecting its border vertices with corresponding vertices in a second surface file. Assumes no lines or indices when reading VTK files in. Note :: Scalar values different than background define the surface patch. The two sets of points have a 1-to-1 mapping; they are from two surfaces whose corresponding vertices are shifted in position. For pial vs. gray-white matter, the two surfaces are not parallel, so connecting the vertices leads to intersecting faces. Parameters ---------- faces : list of lists of integers each sublist contains 3 indices of vertices that form a face on a surface mesh points1 : list of lists of floats each sublist contains 3-D coordinates of a vertex on a surface mesh points2 : list of lists of floats points from second surface with 1-to-1 correspondence with points1 scalars : numpy array of integers labels used to find foreground vertices background_value : integer scalar value for background vertices Returns ------- closed_faces : list of lists of integers indices of vertices that form a face on the closed surface mesh closed_points : list of lists of floats 3-D coordinates from points1 and points2 closed_scalars : list of integers scalar values for points1 and points2 Examples -------- >>> # Example 1: build a cube by closing two parallel planes: >>> import os >>> from mindboggle.guts.morph import close_surface_pair >>> from mindboggle.mio.plots import plot_surfaces >>> from mindboggle.mio.vtks import write_vtk >>> # Build plane: >>> background_value = -1 >>> n = 10 # plane edge length >>> points1 = [] >>> for x in range(n): >>> for y in range(n): >>> points1.append([x,y,0]) >>> points2 = [[x[0],x[1],1] for x in points1] >>> scalars = [background_value for x in range(len(points1))] >>> p = n*(n-1)/2 - 1 >>> for i in [p, p+1, p+n, p+n+1]: >>> scalars[i] = 1 >>> faces = [] >>> for x in range(n-1): >>> for y in range(n-1): >>> faces.append([x+y*n,x+n+y*n,x+n+1+y*n]) >>> faces.append([x+y*n,x+1+y*n,x+n+1+y*n]) >>> #write_vtk('plane.vtk', points1, [], [], faces, scalars) >>> #plot_surfaces('plane.vtk') # doctest: +SKIP >>> closed_faces, closed_points, closed_scalars = close_surface_pair(faces, points1, points2, scalars, background_value) >>> # View: >>> write_vtk('cube.vtk', closed_points, [], [], closed_faces, closed_scalars, 'int') >>> plot_surfaces('cube.vtk') # doctest: +SKIP >>> # >>> # Example 2: Gray and white cortical brain surfaces: >>> import os >>> from mindboggle.guts.morph import close_surface_pair >>> from mindboggle.mio.plots import plot_surfaces >>> from mindboggle.mio.vtks import read_scalars, read_vtk, read_points, write_vtk >>> path = os.environ['MINDBOGGLE_DATA'] >>> patch_surface1 = 'fold.pial.vtk' >>> whole_surface2 = 'fold.white.vtk' >>> # Select a single fold: >>> folds_file = os.path.join(path, 'arno', 'features', 'folds.vtk') >>> points1 = read_points(folds_file) >>> scalars, name = read_scalars(folds_file, True, True) >>> fold_number = 11 >>> scalars[scalars != fold_number] = -1 >>> white_surface = os.path.join(path, 'arno', 'freesurfer', 'lh.white.vtk') >>> faces, u1, u2, points2, N, u3, u4, u5 = read_vtk(white_surface) >>> background_value = -1 >>> closed_faces, closed_points, closed_scalars = close_surface_pair(faces, points1, points2, scalars, background_value) >>> # View: >>> write_vtk('closed.vtk', closed_points, [], [], closed_faces, closed_scalars, name, 'int') >>> plot_surfaces('closed.vtk') # doctest: +SKIP """ import sys import numpy as np from mindboggle.guts.mesh import find_neighbors, remove_faces from mindboggle.guts.segment import extract_borders if isinstance(scalars, list): scalars = np.array(scalars) N = len(points1) closed_points = points1 + points2 # Find all vertex neighbors and surface patch border vertices: neighbor_lists = find_neighbors(faces, N) I = np.where(scalars != background_value)[0] scalars[scalars == background_value] = background_value + 1 scalars[I] = background_value + 2 scalars = scalars.tolist() borders, u1, u2 = extract_borders(range(N), scalars, neighbor_lists) if not len(borders): sys.exit('There are no border vertices!') borders = [x for x in borders if x in I] # Reindex copy of faces and combine with original (both zero-index): indices = range(N) indices2 = range(N, 2 * N) reindex = dict([(index, indices2[i]) for i, index in enumerate(indices)]) faces = remove_faces(faces, I) faces2 = [[reindex[i] for i in face] for face in faces] closed_faces = faces + faces2 # Connect border vertices between surface patches and add new faces: add_faces = [] taken_already = [] for index in borders: if index not in taken_already: neighbors = list(set(neighbor_lists[index]).intersection(borders)) taken_already.append(index) #taken_already.extend([index] + neighbors) for neighbor in neighbors: add_faces.append([index, index + N, neighbor]) add_faces.append([index + N, neighbor, neighbor + N]) closed_faces = closed_faces + add_faces closed_scalars = scalars * 2 return closed_faces, closed_points, closed_scalars
def extract_folds(depth_file, min_vertices=10000, min_fold_size=50, do_fill_holes=False, min_hole_depth=0.001, save_file=False): """ Use depth to extract folds from a triangular surface mesh. Steps :: 1. Compute histogram of depth measures. 2. Define a depth threshold and find the deepest vertices. 3. Segment deep vertices as an initial set of folds. 4. Remove small folds. 5. Find and fill holes in the folds (optional). 6. Renumber folds. Step 2 :: To extract an initial set of deep vertices from the surface mesh, we anticipate that there will be a rapidly decreasing distribution of low depth values (on the outer surface) with a long tail of higher depth values (in the folds), so we smooth the histogram's bin values, convolve to compute slopes, and find the depth value for the first bin with slope = 0. This is our threshold. Step 5 :: The folds could have holes in areas shallower than the depth threshold. Calling fill_holes() could accidentally include very shallow areas (in an annulus-shaped fold, for example), so we include the argument exclude_range to check for any values from zero to min_hole_depth; holes are not filled if they contains values within this range. Parameters ---------- depth_file : string surface mesh file in VTK format with faces and depth scalar values min_fold_size : integer minimum fold size (number of vertices) do_fill_holes : Boolean fill holes in the folds? min_hole_depth : float largest non-zero depth value that will stop a hole from being filled save_file : Boolean save output VTK file? Returns ------- folds : list of integers fold numbers for all vertices (-1 for non-fold vertices) n_folds : int number of folds depth_threshold : float threshold defining the minimum depth for vertices to be in a fold bins : list of integers histogram bins: each is the number of vertices within a range of depth values bin_edges : list of floats histogram bin edge values defining the bin ranges of depth values folds_file : string (if save_file) name of output VTK file with fold IDs (-1 for non-fold vertices) Examples -------- >>> import os >>> import numpy as np >>> import pylab >>> from scipy.ndimage.filters import gaussian_filter1d >>> from mindboggle.mio.vtks import read_scalars >>> from mindboggle.guts.mesh import find_neighbors_from_file >>> from mindboggle.mio.plots import plot_surfaces >>> from mindboggle.features.folds import extract_folds >>> path = os.environ['MINDBOGGLE_DATA'] >>> depth_file = 'travel_depth.vtk' #os.path.join(path, 'arno', 'shapes', 'lh.pial.travel_depth.vtk') >>> neighbor_lists = find_neighbors_from_file(depth_file) >>> min_vertices = 10000 >>> min_fold_size = 50 >>> do_fill_holes = False #True >>> min_hole_depth = 0.001 >>> save_file = True >>> # >>> folds, n_folds, thr, bins, bin_edges, folds_file = extract_folds(depth_file, >>> min_vertices, min_fold_size, do_fill_holes, min_hole_depth, save_file) >>> # >>> # View folds: >>> plot_surfaces('folds.vtk') >>> # Plot histogram and depth threshold: >>> depths, name = read_scalars(depth_file) >>> nbins = np.round(len(depths) / 100.0) >>> a,b,c = pylab.hist(depths, bins=nbins) >>> pylab.plot(thr*np.ones((100,1)), np.linspace(0, max(bins), 100), 'r.') >>> pylab.show() >>> # Plot smoothed histogram: >>> bins_smooth = gaussian_filter1d(bins.tolist(), 5) >>> pylab.plot(range(len(bins)), bins, '.', range(len(bins)), bins_smooth,'-') >>> pylab.show() """ import os import sys import numpy as np from time import time from scipy.ndimage.filters import gaussian_filter1d from mindboggle.mio.vtks import rewrite_scalars, read_vtk from mindboggle.guts.mesh import find_neighbors from mindboggle.guts.morph import fill_holes from mindboggle.guts.segment import segment print("Extract folds in surface mesh") t0 = time() #------------------------------------------------------------------------- # Load depth values for all vertices #------------------------------------------------------------------------- points, indices, lines, faces, depths, scalar_names, npoints, \ input_vtk = read_vtk(depth_file, return_first=True, return_array=True) #------------------------------------------------------------------------- # Find neighbors for each vertex #------------------------------------------------------------------------- neighbor_lists = find_neighbors(faces, npoints) #------------------------------------------------------------------------- # Compute histogram of depth measures #------------------------------------------------------------------------- if npoints > min_vertices: nbins = np.round(npoints / 100.0) else: sys.err(" Expecting at least {0} vertices to create depth histogram". format(min_vertices)) bins, bin_edges = np.histogram(depths, bins=nbins) #------------------------------------------------------------------------- # Anticipating that there will be a rapidly decreasing distribution # of low depth values (on the outer surface) with a long tail of higher # depth values (in the folds), smooth the bin values (Gaussian), convolve # to compute slopes, and find the depth for the first bin with slope = 0. #------------------------------------------------------------------------- bins_smooth = gaussian_filter1d(bins.tolist(), 5) window = [-1, 0, 1] bin_slopes = np.convolve(bins_smooth, window, mode='same') / (len(window) - 1) ibins0 = np.where(bin_slopes == 0)[0] if ibins0.shape: depth_threshold = bin_edges[ibins0[0]] else: depth_threshold = np.median(depths) #------------------------------------------------------------------------- # Find the deepest vertices #------------------------------------------------------------------------- indices_deep = [i for i, x in enumerate(depths) if x >= depth_threshold] if indices_deep: #--------------------------------------------------------------------- # Segment deep vertices as an initial set of folds #--------------------------------------------------------------------- print(" Segment vertices deeper than {0:.2f} as folds".format( depth_threshold)) t1 = time() folds = segment(indices_deep, neighbor_lists) # Slightly slower alternative -- fill boundaries: #regions = -1 * np.ones(len(points)) #regions[indices_deep] = 1 #folds = segment_by_filling_borders(regions, neighbor_lists) print(' ...Segmented folds ({0:.2f} seconds)'.format(time() - t1)) #--------------------------------------------------------------------- # Remove small folds #--------------------------------------------------------------------- if min_fold_size > 1: print(' Remove folds smaller than {0}'.format(min_fold_size)) unique_folds = [x for x in np.unique(folds) if x != -1] for nfold in unique_folds: indices_fold = [i for i, x in enumerate(folds) if x == nfold] if len(indices_fold) < min_fold_size: folds[indices_fold] = -1 #--------------------------------------------------------------------- # Find and fill holes in the folds # Note: Surfaces surrounded by folds can be mistaken for holes, # so exclude_range includes outer surface values close to zero. #--------------------------------------------------------------------- if do_fill_holes: print(" Find and fill holes in the folds") folds = fill_holes(folds, neighbor_lists, values=depths, exclude_range=[0, min_hole_depth]) #--------------------------------------------------------------------- # Renumber folds so they are sequential #--------------------------------------------------------------------- renumber_folds = -1 * np.ones(len(folds)) fold_numbers = [int(x) for x in np.unique(folds) if x != -1] for i_fold, n_fold in enumerate(fold_numbers): fold = [i for i, x in enumerate(folds) if x == n_fold] renumber_folds[fold] = i_fold folds = renumber_folds n_folds = i_fold + 1 # Print statement print(' ...Extracted {0} folds ({1:.2f} seconds)'.format( n_folds, time() - t0)) else: print(' No deep vertices') folds = [int(x) for x in folds] #------------------------------------------------------------------------- # Return folds, number of folds, file name #------------------------------------------------------------------------- if save_file: folds_file = os.path.join(os.getcwd(), 'folds.vtk') rewrite_scalars(depth_file, folds_file, folds, 'folds', folds) if not os.path.exists(folds_file): raise (IOError(folds_file + " not found")) else: folds_file = None return folds, n_folds, depth_threshold, bins, bin_edges, folds_file
def extract_folds(depth_file, depth_threshold=2, min_fold_size=50, save_file=False, output_file='', background_value=-1, verbose=False): """ Use depth threshold to extract folds from a triangular surface mesh. A fold is a group of connected, deep vertices. To extract folds, a depth threshold is used to segment deep vertices of the surface mesh. We have observed in the histograms of travel depth measures of cortical surfaces that there is a rapidly decreasing distribution of low depth values (corresponding to the outer surface, or gyral crowns) with a long tail of higher depth values (corresponding to the folds). The find_depth_threshold function therefore computes a histogram of travel depth measures, smooths the histogram's bin values, convolves to compute slopes, and finds the depth value for the first bin with zero slope. The extract_folds function uses this depth value, segments deep vertices, and removes extremely small folds (empirically set at 50 vertices or fewer out of a total mesh size of over 100,000 vertices). Steps :: 1. Segment deep vertices as an initial set of folds. 2. Remove small folds. 3. Renumber folds. Note :: Removed option: Find and fill holes in the folds: Folds could have holes in areas shallower than the depth threshold. Calling fill_holes() could accidentally include very shallow areas (in an annulus-shaped fold, for example). However, we could include the argument exclude_range to check for any values from zero to min_hole_depth; holes would not be filled if they were to contain values within this range. Parameters ---------- depth_file : string surface mesh file in VTK format with faces and depth scalar values depth_threshold : float threshold defining the minimum depth for vertices to be in a fold min_fold_size : integer minimum fold size (number of vertices) save_file : bool save output VTK file? output_file : string name of output file in VTK format background_value : integer or float background value verbose : bool print statements? Returns ------- folds : list of integers fold numbers for all vertices (-1 for non-fold vertices) n_folds : int number of folds folds_file : string (if save_file) name of output VTK file with fold IDs (-1 for non-fold vertices) Examples -------- >>> from mindboggle.features.folds import extract_folds >>> from mindboggle.mio.fetch_data import prep_tests >>> urls, fetch_data = prep_tests() >>> depth_file = fetch_data(urls['left_travel_depth'], '', '.vtk') >>> depth_threshold = 2.36089 >>> min_fold_size = 50 >>> save_file = True >>> output_file = 'extract_folds.vtk' >>> background_value = -1 >>> verbose = False >>> folds, n_folds, folds_file = extract_folds(depth_file, ... depth_threshold, min_fold_size, save_file, output_file, ... background_value, verbose) >>> n_folds 33 >>> lens = [len([x for x in folds if x == y]) for y in range(n_folds)] >>> lens[0:10] [726, 67241, 2750, 5799, 1151, 6360, 1001, 505, 228, 198] View folds (skip test): >>> from mindboggle.mio.plots import plot_surfaces # doctest: +SKIP >>> plot_surfaces('extract_folds.vtk') # doctest: +SKIP View folds without background (skip test): >>> from mindboggle.mio.plots import plot_surfaces # doctest: +SKIP >>> from mindboggle.mio.vtks import rewrite_scalars # doctest: +SKIP >>> rewrite_scalars(depth_file, 'extract_folds_no_background.vtk', folds, ... 'just_folds', folds, -1) # doctest: +SKIP >>> plot_surfaces('extract_folds_no_background.vtk') # doctest: +SKIP """ import os import numpy as np from time import time from mindboggle.mio.vtks import rewrite_scalars, read_vtk from mindboggle.guts.mesh import find_neighbors from mindboggle.guts.segment import segment_regions if verbose: print("Extract folds in surface mesh") t0 = time() # ------------------------------------------------------------------------ # Load depth values for all vertices # ------------------------------------------------------------------------ points, indices, lines, faces, depths, scalar_names, npoints, \ input_vtk = read_vtk(depth_file, return_first=True, return_array=True) # ------------------------------------------------------------------------ # Find the deepest vertices # ------------------------------------------------------------------------ indices_deep = [i for i,x in enumerate(depths) if x >= depth_threshold] if indices_deep: # -------------------------------------------------------------------- # Find neighbors for each vertex # -------------------------------------------------------------------- neighbor_lists = find_neighbors(faces, npoints) # -------------------------------------------------------------------- # Segment deep vertices as an initial set of folds # -------------------------------------------------------------------- if verbose: print(" Segment vertices deeper than {0:.2f} as folds".format(depth_threshold)) t1 = time() folds = segment_regions(indices_deep, neighbor_lists, 1, [], False, False, [], [], [], '', background_value, False) if verbose: print(' ...Segmented folds ({0:.2f} seconds)'.format(time() - t1)) # -------------------------------------------------------------------- # Remove small folds # -------------------------------------------------------------------- if min_fold_size > 1: if verbose: print(' Remove folds smaller than {0}'.format(min_fold_size)) unique_folds = [x for x in np.unique(folds) if x != background_value] for nfold in unique_folds: indices_fold = [i for i,x in enumerate(folds) if x == nfold] if len(indices_fold) < min_fold_size: folds[indices_fold] = background_value # -------------------------------------------------------------------- # Find and fill holes in the folds # Note: Surfaces surrounded by folds can be mistaken for holes, # so exclude_range includes outer surface values close to zero. # -------------------------------------------------------------------- # folds = fill_holes(folds, neighbor_lists, values=depths, # exclude_range=[0, min_hole_depth]) # -------------------------------------------------------------------- # Renumber folds so they are sequential. # NOTE: All vertices are included (-1 for non-fold vertices). # -------------------------------------------------------------------- renumber_folds = background_value * np.ones(npoints) fold_numbers = [x for x in np.unique(folds) if x != background_value] for i_fold, n_fold in enumerate(fold_numbers): fold_indices = [i for i,x in enumerate(folds) if x == n_fold] renumber_folds[fold_indices] = i_fold folds = renumber_folds folds = [int(x) for x in folds] n_folds = i_fold + 1 # Print statement if verbose: print(' ...Extracted {0} folds ({1:.2f} seconds)'. format(n_folds, time() - t0)) else: if verbose: print(' No deep vertices') # ------------------------------------------------------------------------ # Return folds, number of folds, file name # ------------------------------------------------------------------------ if save_file: if output_file: folds_file = output_file else: folds_file = os.path.join(os.getcwd(), 'folds.vtk') rewrite_scalars(depth_file, folds_file, folds, 'folds', [], background_value) if not os.path.exists(folds_file): raise IOError(folds_file + " not found") else: folds_file = None return folds, n_folds, folds_file
def realign_boundaries_to_fundus_lines(surf_file, init_label_file, fundus_lines_file, thickness_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. thickness_file: file containing cortical thickness scalar data (for masking out the medial wall only) 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 numpy as np from mindboggle.guts.segment import extract_borders import mindboggle.guts.graph as go from mindboggle.mio.vtks import read_vtk, read_scalars, write_vtk from mindboggle.guts.mesh import find_neighbors import propagate_fundus_lines ## read files points, indices, lines, faces, scalars, scalar_names, num_points, \ input_vtk = 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) thickness, _ = read_scalars(thickness_file, return_first=True, return_array=True) # remove labels from vertices with zero thickness (get around # DKT40 annotations having the label '3' for all the Corpus # Callosum vertices). cc_inds = [x for x in indices if thickness[x] < 0.001] init_labels[cc_inds] = 0 ## 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, 100, 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] # tile the surface into connected components delimited by fundus lines closed_fundus_lines, _, _ = propagate_fundus_lines.propagate_fundus_lines( points, faces, fundus_line_indices, thickness) closed_fundus_line_indices = np.where(closed_fundus_lines > 0)[0] # split surface into connected components connected_component_faces = _remove_boundary_faces( points, faces, closed_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=[int(x) for x in new_labels], scalar_type='int') return new_labels
def extract_sulci( labels_file, folds_or_file, hemi, min_boundary=1, sulcus_names=[], save_file=False, output_file="", background_value=-1, verbose=False, ): """ Identify sulci from folds in a brain surface according to a labeling protocol that includes a list of label pairs defining each sulcus. Since folds are defined as deep, connected areas of a surface, and since folds may be connected to each other in ways that differ across brains, there usually does not exist a one-to-one mapping between folds of one brain and those of another. To address the correspondence problem then, we need to find just those portions of the folds that correspond across brains. To accomplish this, Mindboggle segments folds into sulci, which do have a one-to-one correspondence across non-pathological brains. Mindboggle defines a sulcus as a folded portion of cortex whose opposing banks are labeled with one or more sulcus label pairs in the DKT labeling protocol, where each label pair is unique to one sulcus and represents a boundary between two adjacent gyri, and each vertex has one gyrus label. This function assigns vertices in a fold to a sulcus in one of two cases. In the first case, vertices whose labels are in only one label pair in the fold are assigned to the label pair’s sulcus if they are connected through similarly labeled vertices to the boundary between the two labels. In the second case, the segment_regions function propagates labels from label borders to vertices whose labels are in multiple label pairs in the fold. 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 set. Parameters ---------- labels_file : string file name for surface mesh VTK containing labels for all vertices folds_or_file : numpy array, list or string fold number for each vertex / name of VTK file containing fold scalars hemi : string hemisphere abbreviation in {'lh', 'rh'} for sulcus labels min_boundary : integer minimum number of vertices for a sulcus label boundary segment sulcus_names : list of strings names of sulci save_file : bool save output VTK file? output_file : string name of output file in VTK format background_value : integer or float background value verbose : bool print statements? Returns ------- sulci : list of integers sulcus numbers for all vertices (-1 for non-sulcus vertices) n_sulci : integers number of sulci sulci_file : string output VTK file with sulcus numbers (-1 for non-sulcus vertices) Examples -------- >>> # Example 1: Extract sulcus from a fold with one sulcus label pair: >>> import numpy as np >>> from mindboggle.features.sulci import extract_sulci >>> from mindboggle.mio.vtks import read_scalars >>> from mindboggle.mio.fetch_data import prep_tests >>> urls, fetch_data = prep_tests() >>> # Load labels, folds, neighbor lists, and sulcus names and label pairs >>> labels_file = fetch_data(urls['left_freesurfer_labels'], '', '.vtk') >>> folds_file = fetch_data(urls['left_folds'], '', '.vtk') >>> folds_or_file, name = read_scalars(folds_file, True, True) >>> save_file = True >>> output_file = 'extract_sulci_fold4_1sulcus.vtk' >>> background_value = -1 >>> # Limit number of folds to speed up the test: >>> limit_folds = True >>> if limit_folds: ... fold_numbers = [4] #[4, 6] ... i0 = [i for i,x in enumerate(folds_or_file) if x not in fold_numbers] ... folds_or_file[i0] = background_value >>> hemi = 'lh' >>> min_boundary = 10 >>> sulcus_names = [] >>> verbose = False >>> sulci, n_sulci, sulci_file = extract_sulci(labels_file, folds_or_file, ... hemi, min_boundary, sulcus_names, save_file, output_file, ... background_value, verbose) >>> n_sulci # 23 # (if not limit_folds) 1 >>> lens = [len([x for x in sulci if x==y]) ... for y in np.unique(sulci) if y != -1] >>> lens[0:10] # [6358, 3288, 7612, 5205, 4414, 6251, 3493, 2566, 4436, 739] # (if not limit_folds) [1151] View result without background (skip test): >>> from mindboggle.mio.plots import plot_surfaces # doctest: +SKIP >>> from mindboggle.mio.vtks import rewrite_scalars # doctest: +SKIP >>> output = 'extract_sulci_fold4_1sulcus_no_background.vtk' >>> rewrite_scalars(sulci_file, output, sulci, ... 'sulci', sulci) # doctest: +SKIP >>> plot_surfaces(output) # doctest: +SKIP Example 2: Extract sulcus from a fold with multiple sulcus label pairs: >>> folds_or_file, name = read_scalars(folds_file, True, True) >>> output_file = 'extract_sulci_fold7_2sulci.vtk' >>> # Limit number of folds to speed up the test: >>> limit_folds = True >>> if limit_folds: ... fold_numbers = [7] #[4, 6] ... i0 = [i for i,x in enumerate(folds_or_file) if x not in fold_numbers] ... folds_or_file[i0] = background_value >>> sulci, n_sulci, sulci_file = extract_sulci(labels_file, folds_or_file, ... hemi, min_boundary, sulcus_names, save_file, output_file, ... background_value, verbose) >>> n_sulci # 23 # (if not limit_folds) 2 >>> lens = [len([x for x in sulci if x==y]) ... for y in np.unique(sulci) if y != -1] >>> lens[0:10] # [6358, 3288, 7612, 5205, 4414, 6251, 3493, 2566, 4436, 739] # (if not limit_folds) [369, 93] View result without background (skip test): >>> from mindboggle.mio.plots import plot_surfaces # doctest: +SKIP >>> from mindboggle.mio.vtks import rewrite_scalars # doctest: +SKIP >>> output = 'extract_sulci_fold7_2sulci_no_background.vtk' >>> rewrite_scalars(sulci_file, output, sulci, ... 'sulci', sulci) # doctest: +SKIP >>> plot_surfaces(output) # doctest: +SKIP """ import os from time import time import numpy as np from mindboggle.mio.vtks import read_scalars, read_vtk, rewrite_scalars from mindboggle.guts.mesh import find_neighbors from mindboggle.guts.segment import extract_borders, propagate, segment_regions from mindboggle.mio.labels import DKTprotocol # 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 elif isinstance(folds_or_file, np.ndarray): folds = folds_or_file.tolist() dkt = DKTprotocol() if hemi == "lh": pair_lists = dkt.left_sulcus_label_pair_lists elif hemi == "rh": pair_lists = dkt.right_sulcus_label_pair_lists else: raise IOError("Warning: hemisphere not properly specified ('lh' or 'rh').") # Load points, faces, and neighbors: points, indices, lines, faces, labels, scalar_names, npoints, input_vtk = 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 = background_value * np.ones(npoints) # ------------------------------------------------------------------------ # Loop through folds # ------------------------------------------------------------------------ fold_numbers = [int(x) for x in np.unique(folds) if x != background_value] n_folds = len(fold_numbers) if verbose: print("Extract sulci from {0} folds...".format(n_folds)) t0 = time() for n_fold in fold_numbers: fold_indices = [i for i, x in enumerate(folds) if x == n_fold] len_fold = len(fold_indices) # List the labels in this fold: fold_labels = [labels[x] for x in fold_indices] unique_fold_labels = [int(x) for x in np.unique(fold_labels) if x != background_value] # -------------------------------------------------------------------- # NO MATCH -- fold has fewer than two labels # -------------------------------------------------------------------- if verbose and 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_indices, 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 dkt.unique_sulcus_label_pairs] if verbose and 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 verbose and not fold_pairs_in_protocol: print(" Fold {0}: NO MATCH -- fold has no sulcus label pair".format(n_fold, len_fold)) # ---------------------------------------------------------------- # Possible matches # ---------------------------------------------------------------- else: if verbose: 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 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 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 = None for i, pair_list in enumerate(pair_lists): if not isinstance(pair_list, list): pair_list = [pair_list] if pair in pair_list: ID = i break if ID: # 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] # Vertices with unique label(s) in pair: indices_unique_labels = [ fold_indices[i] for i, x in enumerate(fold_labels) if x in unique_labels_in_pair ] # dkt.unique_sulcus_label_pairs] # Propagate sulcus ID from seeds to vertices # with "unique" labels (only exist in one # label pair in a fold); propagation ensures # that sulci consist of contiguous vertices # for each label boundary: sulci2 = segment_regions( indices_unique_labels, neighbor_lists, min_region_size=1, seed_lists=[indices_pair], keep_seeding=False, spread_within_labels=True, labels=labels, label_lists=[], values=[], max_steps="", background_value=background_value, verbose=False, ) sulci[sulci2 != background_value] = ID # Print statement: if verbose: if n_unique == 1: ps1 = "One 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: if verbose: print(" Propagate sulcus borders with label {0}".format(int(label))) # Construct seeds from label boundary vertices: seeds = background_value * np.ones(npoints) for ID, pair_list in enumerate(pair_lists): if not isinstance(pair_list, list): pair_list = [pair_list] label_pairs = [x for x in 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_regions( indices_pair, neighbor_lists, 1, [], False, False, [], [], [], "", background_value, verbose, ) useeds2 = [x for x in np.unique(seeds2) if x != background_value] for seed2 in useeds2: iseed2 = [i for i, x in enumerate(seeds2) if x == seed2] if len(iseed2) >= min_boundary: indices_pair2.extend(iseed2) elif verbose: 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: indices_label = [fold_indices[i] for i, x in enumerate(fold_labels) if x == label] if len(indices_label): # Propagate sulcus ID from seeds to vertices # with a given shared label: seg_vs_prop = False if seg_vs_prop: indices_seeds = [] for seed in [x for x in np.unique(seeds) if x != background_value]: indices_seeds.append([i for i, x in enumerate(seeds) if x == seed]) sulci2 = segment_regions( indices_label, neighbor_lists, 50, indices_seeds, False, True, labels, [], [], "", background_value, verbose, ) else: label_array = background_value * np.ones(npoints) label_array[indices_label] = 1 sulci2 = propagate( points, faces, label_array, seeds, sulci, max_iters=10000, tol=0.001, sigma=5, background_value=background_value, verbose=verbose, ) sulci[sulci2 != background_value] = sulci2[sulci2 != background_value] sulcus_numbers = [int(x) for x in np.unique(sulci) if x != background_value] n_sulci = len(sulcus_numbers) # ------------------------------------------------------------------------ # Print statements # ------------------------------------------------------------------------ if verbose: if n_sulci == 1: sulcus_str = "sulcus" else: sulcus_str = "sulci" if n_folds == 1: folds_str = "fold" else: folds_str = "folds" print("Extracted {0} {1} from {2} {3} ({4:.1f}s):".format(n_sulci, sulcus_str, n_folds, folds_str, time() - t0)) if sulcus_names: for sulcus_number in sulcus_numbers: print(" {0}: {1}".format(sulcus_number, sulcus_names[sulcus_number])) elif sulcus_numbers: print(" " + ", ".join([str(x) for x in sulcus_numbers])) unresolved = [i for i in range(len(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 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 = [int(x) for x in sulci] sulci_file = os.path.join(os.getcwd(), "sulci.vtk") rewrite_scalars(labels_file, sulci_file, sulci, "sulci", [], background_value) if not os.path.exists(sulci_file): raise IOError(sulci_file + " not found") return sulci, n_sulci, sulci_file
def realign_boundaries_to_fundus_lines( surf_file, init_label_file, fundus_lines_file, thickness_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. thickness_file: file containing cortical thickness scalar data (for masking out the medial wall only) 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 numpy as np from mindboggle.guts.segment import extract_borders import mindboggle.guts.graph as go from mindboggle.mio.vtks import read_vtk, read_scalars, write_vtk from mindboggle.guts.mesh import find_neighbors import propagate_fundus_lines ## 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) thickness, _ = read_scalars(thickness_file, return_first=True, return_array=True) # remove labels from vertices with zero thickness (get around # DKT40 annotations having the label '3' for all the Corpus # Callosum vertices). cc_inds = [x for x in indices if thickness[x] < 0.001] init_labels[cc_inds] = 0 ## 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, 100, 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] # tile the surface into connected components delimited by fundus lines closed_fundus_lines, _, _ = propagate_fundus_lines.propagate_fundus_lines( points, faces, fundus_line_indices, thickness) closed_fundus_line_indices = np.where(closed_fundus_lines > 0)[0] # split surface into connected components connected_component_faces = _remove_boundary_faces( points, faces, closed_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=[int(x) for x in new_labels], scalar_type='int') return new_labels
def extract_sulci(labels_file, folds_or_file, hemi, min_boundary=1, sulcus_names=[], verbose=False): """ 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 set. 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 / name of VTK file containing fold scalars hemi : string hemisphere abbreviation in {'lh', 'rh'} for sulcus labels min_boundary : integer minimum number of vertices for a sulcus label boundary segment sulcus_names : list of strings names of sulci verbose : bool print statements? Returns ------- sulci : list of integers sulcus numbers for all vertices (-1 for non-sulcus vertices) n_sulci : integers number of sulci sulci_file : string output VTK file with sulcus numbers (-1 for non-sulcus vertices) Examples -------- >>> from mindboggle.features.sulci import extract_sulci >>> from mindboggle.mio.vtks import read_scalars >>> from mindboggle.mio.fetch_data import prep_tests >>> urls, fetch_data = prep_tests() >>> # Load labels, folds, neighbor lists, and sulcus names and label pairs >>> labels_file = fetch_data(urls['left_freesurfer_labels']) >>> folds_file = fetch_data(urls['left_folds']) >>> folds_or_file, name = read_scalars(folds_file) >>> hemi = 'lh' >>> min_boundary = 10 >>> sulcus_names = [] >>> verbose = False >>> sulci, n_sulci, sulci_file = extract_sulci(labels_file, folds_or_file, ... hemi, min_boundary, sulcus_names, verbose) >>> n_sulci 23 >>> lens = [len([x for x in sulci if x == y]) for y in range(n_sulci)] >>> lens[0:10] [0, 6573, 3366, 6689, 5358, 4049, 6379, 3551, 2632, 4225] >>> lens[10::] [754, 3724, 2197, 5823, 1808, 5122, 513, 2153, 1445, 418, 0, 3556, 1221] View result (skip test): >>> from mindboggle.mio.plots import plot_surfaces >>> plot_surfaces('sulci.vtk') # doctest: +SKIP """ import os from time import time import numpy as np from mindboggle.mio.vtks import read_scalars, read_vtk, rewrite_scalars from mindboggle.guts.mesh import find_neighbors from mindboggle.guts.segment import extract_borders, propagate, segment from mindboggle.mio.labels import DKTprotocol # 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 dkt = DKTprotocol() if hemi == 'lh': pair_lists = dkt.left_sulcus_label_pair_lists elif hemi == 'rh': pair_lists = dkt.right_sulcus_label_pair_lists else: raise IOError("Warning: hemisphere not properly specified ('lh' or 'rh').") # Load points, faces, and neighbors: points, indices, lines, faces, labels, scalar_names, npoints, \ input_vtk = 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) if verbose: 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: fold_labels = [labels[x] for x in fold] unique_fold_labels = [int(x) for x in np.unique(fold_labels) if x != -1] #--------------------------------------------------------------------- # NO MATCH -- fold has fewer than two labels #--------------------------------------------------------------------- if verbose and 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 dkt.unique_sulcus_label_pairs] if verbose and 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 verbose and not fold_pairs_in_protocol: print(" Fold {0}: NO MATCH -- fold has no sulcus label pair". format(n_fold, len_fold)) #----------------------------------------------------------------- # Possible matches #----------------------------------------------------------------- else: if verbose: 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 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 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 = None for i, pair_list in enumerate(pair_lists): if not isinstance(pair_list, list): pair_list = [pair_list] if pair in pair_list: ID = i break if ID: # 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] # Vertices with unique label(s) in pair: indices_unique_labels = [fold[i] for i,x in enumerate(fold_labels) if x in dkt.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 verbose: 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: if verbose: print(" Propagate sulcus borders with label {0}". format(int(label))) # Construct seeds from label boundary vertices: seeds = -1 * np.ones(len(points)) for ID, pair_list in enumerate(pair_lists): if not isinstance(pair_list, list): pair_list = [pair_list] label_pairs = [x for x in 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) useeds2 = [x for x in np.unique(seeds2) if x != -1] for seed2 in useeds2: iseed2 = [i for i,x in enumerate(seeds2) if x == seed2] if len(iseed2) >= min_boundary: indices_pair2.extend(iseed2) elif verbose: 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] sulcus_numbers = [int(x) for x in np.unique(sulci) if x != -1] # if not np.isnan(x)] n_sulci = len(sulcus_numbers) #------------------------------------------------------------------------- # Print statements #------------------------------------------------------------------------- if verbose: print("Extracted {0} sulci from {1} folds ({2:.1f}s):". format(n_sulci, n_folds, time()-t0)) if sulcus_names: for sulcus_number in sulcus_numbers: print(" {0}: {1}".format(sulcus_number, sulcus_names[sulcus_number])) elif sulcus_numbers: print(" " + ", ".join([str(x) for x in sulcus_numbers])) unresolved = [i for i in range(len(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 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 = [int(x) for x in sulci] sulci_file = os.path.join(os.getcwd(), 'sulci.vtk') rewrite_scalars(labels_file, sulci_file, sulci, 'sulci', sulci) if not os.path.exists(sulci_file): raise IOError(sulci_file + " not found") return sulci, n_sulci, sulci_file
def evaluate_deep_features(features_file, labels_file, sulci_file='', hemi='', excludeIDs=[-1], output_vtk_name='', verbose=True): """ Evaluate deep surface features by computing the minimum distance from each label border vertex to all of the feature vertices in the same sulcus, and from each feature vertex to all of the label border vertices in the same sulcus. The label borders run along the deepest parts of sulci and correspond to fundi in the DKT cortical labeling protocol. Parameters ---------- features_file : string VTK surface file with feature numbers for vertex scalars labels_file : string VTK surface file with label numbers for vertex scalars sulci_file : string VTK surface file with sulcus numbers for vertex scalars excludeIDs : list of integers feature/sulcus/label IDs to exclude (background set to -1) output_vtk_name : Boolean if not empty, output a VTK file beginning with output_vtk_name that contains a surface with mean distances as scalars verbose : Boolean print mean distances to standard output? Returns ------- feature_to_border_mean_distances : numpy array [number of features x 1] mean distance from each feature to sulcus label border feature_to_border_sd_distances : numpy array [number of features x 1] standard deviations of feature-to-border distances feature_to_border_distances_vtk : string VTK surface file containing feature-to-border distances border_to_feature_mean_distances : numpy array [number of features x 1] mean distances from each sulcus label border to feature border_to_feature_sd_distances : numpy array [number of features x 1] standard deviations of border-to-feature distances border_to_feature_distances_vtk : string VTK surface file containing border-to-feature distances """ import os import sys import numpy as np from mindboggle.mio.vtks import read_vtk, read_scalars, write_vtk from mindboggle.guts.mesh import find_neighbors, remove_faces from mindboggle.guts.segment import extract_borders from mindboggle.guts.compute import source_to_target_distances from mindboggle.mio.labels import DKTprotocol dkt = DKTprotocol() #------------------------------------------------------------------------- # Load labels, features, and sulci: #------------------------------------------------------------------------- faces, lines, indices, points, npoints, labels, scalar_names, \ input_vtk = read_vtk(labels_file, True, True) features, name = read_scalars(features_file, True, True) if sulci_file: sulci, name = read_scalars(sulci_file, True, True) # List of indices to sulcus vertices: sulcus_indices = [i for i,x in enumerate(sulci) if x != -1] segmentIDs = sulci sulcus_faces = remove_faces(faces, sulcus_indices) else: sulcus_indices = range(len(labels)) segmentIDs = [] sulcus_faces = faces #------------------------------------------------------------------------- # Prepare neighbors, label pairs, border IDs, and outputs: #------------------------------------------------------------------------- # Calculate neighbor lists for all points: print('Find neighbors for all vertices...') neighbor_lists = find_neighbors(faces, npoints) # Find label border points in any of the sulci: print('Find label border points in any of the sulci...') border_indices, border_label_tuples, unique_border_label_tuples = \ extract_borders(sulcus_indices, labels, neighbor_lists, ignore_values=[], return_label_pairs=True) if not len(border_indices): sys.exit('There are no label border points!') # Initialize an array of label border IDs # (label border vertices that define sulci in the labeling protocol): print('Build an array of label border IDs...') label_borders = -1 * np.ones(npoints) if hemi == 'lh': nsulcus_lists = len(dkt.left_sulcus_label_pair_lists) else: nsulcus_lists = len(dkt.right_sulcus_label_pair_lists) feature_to_border_mean_distances = -1 * np.ones(nsulcus_lists) feature_to_border_sd_distances = -1 * np.ones(nsulcus_lists) border_to_feature_mean_distances = -1 * np.ones(nsulcus_lists) border_to_feature_sd_distances = -1 * np.ones(nsulcus_lists) feature_to_border_distances_vtk = '' border_to_feature_distances_vtk = '' #------------------------------------------------------------------------- # Loop through sulci: #------------------------------------------------------------------------- # For each list of sorted label pairs (corresponding to a sulcus): for isulcus, label_pairs in enumerate(dkt.sulcus_label_pair_lists): # Keep the border points with label pair labels: label_pair_border_indices = [x for i,x in enumerate(border_indices) if np.unique(border_label_tuples[i]).tolist() in label_pairs] # Store the points as sulcus IDs in the border IDs array: if label_pair_border_indices: label_borders[label_pair_border_indices] = isulcus if len(np.unique(label_borders)) > 1: #--------------------------------------------------------------------- # Construct a feature-to-border distance matrix and VTK file: #--------------------------------------------------------------------- # Construct a distance matrix: print('Construct a feature-to-border distance matrix...') sourceIDs = features targetIDs = label_borders distances, distance_matrix = source_to_target_distances( sourceIDs, targetIDs, points, segmentIDs, excludeIDs) # Compute mean distances for each feature: nfeatures = min(np.shape(distance_matrix)[1], nsulcus_lists) for ifeature in range(nfeatures): feature_distances = [x for x in distance_matrix[:, ifeature] if x != -1] feature_to_border_mean_distances[ifeature] = \ np.mean(feature_distances) feature_to_border_sd_distances[ifeature] = \ np.std(feature_distances) if verbose: print('Feature-to-border mean distances:') print(feature_to_border_mean_distances) print('Feature-to-border standard deviations of distances:') print(feature_to_border_sd_distances) # Write resulting feature-label border distances to VTK file: if output_vtk_name: feature_to_border_distances_vtk = os.path.join(os.getcwd(), output_vtk_name + '_feature_to_border_mean_distances.vtk') print('Write feature-to-border distances to {0}...'. format(feature_to_border_distances_vtk)) write_vtk(feature_to_border_distances_vtk, points, [], [], sulcus_faces, [distances], ['feature-to-border_distances'], 'float') #--------------------------------------------------------------------- # Construct a border-to-feature distance matrix and VTK file: #--------------------------------------------------------------------- # Construct a distance matrix: print('Construct a border-to-feature distance matrix...') sourceIDs = label_borders targetIDs = features distances, distance_matrix = source_to_target_distances( sourceIDs, targetIDs, points, segmentIDs, excludeIDs) # Compute mean distances for each feature: nfeatures = min(np.shape(distance_matrix)[1], nsulcus_lists) for ifeature in range(nfeatures): border_distances = [x for x in distance_matrix[:, ifeature] if x != -1] border_to_feature_mean_distances[ifeature] = \ np.mean(border_distances) border_to_feature_sd_distances[ifeature] = \ np.std(border_distances) if verbose: print('border-to-feature mean distances:') print(border_to_feature_mean_distances) print('border-to-feature standard deviations of distances:') print(border_to_feature_sd_distances) # Write resulting feature-label border distances to VTK file: if output_vtk_name: border_to_feature_distances_vtk = os.path.join(os.getcwd(), output_vtk_name + '_border_to_feature_mean_distances.vtk') print('Write border-to-feature distances to {0}...'. format(border_to_feature_distances_vtk)) write_vtk(border_to_feature_distances_vtk, points, [], [], sulcus_faces, [distances], ['border-to-feature_distances'], 'float') #------------------------------------------------------------------------- # Return outputs: #------------------------------------------------------------------------- return feature_to_border_mean_distances, feature_to_border_sd_distances,\ feature_to_border_distances_vtk,\ border_to_feature_mean_distances, border_to_feature_sd_distances,\ border_to_feature_distances_vtk
def extract_sulci(labels_file, folds_or_file, hemi, 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 set. 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 / name of VTK file containing fold scalars hemi : string hemisphere abbreviation in {'lh', 'rh'} for sulcus labels 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 output VTK file with sulcus numbers (-1 for non-sulcus vertices) Examples -------- >>> import os >>> from mindboggle.mio.vtks import read_scalars, rewrite_scalars >>> from mindboggle.features.sulci import extract_sulci >>> from mindboggle.mio.plots import plot_surfaces >>> 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) >>> hemi = 'lh' >>> min_boundary = 10 >>> sulcus_names = [] >>> # >>> sulci, n_sulci, sulci_file = extract_sulci(labels_file, folds_or_file, hemi, min_boundary, sulcus_names) >>> # View: >>> plot_surfaces('sulci.vtk') """ import os from time import time import numpy as np from mindboggle.mio.vtks import read_scalars, read_vtk, rewrite_scalars from mindboggle.guts.mesh import find_neighbors from mindboggle.guts.segment import extract_borders, propagate, segment from mindboggle.mio.labels import DKTprotocol # 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 dkt = DKTprotocol() if hemi == 'lh': pair_lists = dkt.left_sulcus_label_pair_lists elif hemi == 'rh': pair_lists = dkt.right_sulcus_label_pair_lists else: print("Warning: hemisphere not properly specified ('lh' or 'rh').") # Load points, faces, and neighbors: faces, o1, o2, points, npoints, labels, o3, o4 = 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: fold_labels = [labels[x] for x in fold] unique_fold_labels = [int(x) for x in np.unique(fold_labels) if x != -1] #--------------------------------------------------------------------- # 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 dkt.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 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 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 = None for i, pair_list in enumerate(pair_lists): if not isinstance(pair_list, list): pair_list = [pair_list] if pair in pair_list: ID = i break if ID: # 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] # Vertices with unique label(s) in pair: indices_unique_labels = [fold[i] for i,x in enumerate(fold_labels) if x in dkt.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 borders with label {0}". format(int(label))) # Construct seeds from label boundary vertices: seeds = -1 * np.ones(len(points)) for ID, pair_list in enumerate(pair_lists): if not isinstance(pair_list, list): pair_list = [pair_list] label_pairs = [x for x in 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) useeds2 = [x for x in np.unique(seeds2) if x != -1] for seed2 in useeds2: 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] # if not np.isnan(x)] n_sulci = len(sulcus_numbers) print("Extracted {0} sulci from {1} folds ({2:.1f}s):". format(n_sulci, n_folds, time()-t0)) if sulcus_names: for sulcus_number in sulcus_numbers: print(" {0}: {1}".format(sulcus_number, sulcus_names[sulcus_number])) elif sulcus_numbers: print(" " + ", ".join([str(x) for x in sulcus_numbers])) #------------------------------------------------------------------------- # Print out unresolved sulci #------------------------------------------------------------------------- unresolved = [i for i in range(len(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 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 = [int(x) for x in sulci] sulci_file = os.path.join(os.getcwd(), 'sulci.vtk') rewrite_scalars(labels_file, sulci_file, sulci, 'sulci', sulci) if not os.path.exists(sulci_file): raise(IOError(sulci_file + " not found")) return sulci, n_sulci, sulci_file
def extract_subfolds(depth_file, folds, min_size=10, depth_factor=0.25, depth_ratio=0.1, tolerance=0.01, save_file=False): """ Use depth to segment folds into subfolds in a triangular surface mesh. Note :: The function extract_sulci() performs about the same whether folds or subfolds are used as input. The latter leads to some loss of small subfolds and possibly holes for small subfolds in the middle of other subfolds. Note about the watershed() function: The watershed() function performs individual seed growing from deep seeds, repeats segmentation from the resulting seeds until each seed's segment touches a boundary. The function segment() fills in the rest. Finally segments are joined if their seeds are too close to each other. Despite these precautions, the order of seed selection in segment() could possibly influence the resulting borders between adjoining segments. [The propagate() function is slower and insensitive to depth, but is not biased by seed order.] Parameters ---------- depth_file : string surface mesh file in VTK format with faces and depth scalar values folds : list of integers fold numbers for all vertices (-1 for non-fold vertices) min_size : integer minimum number of vertices for a subfold depth_factor : float watershed() depth_factor: 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 watershed() depth_ratio: the minimum fraction of depth for a neighboring shallower watershed catchment basin (otherwise merged with the deeper basin) tolerance : float watershed() tolerance: tolerance for detecting differences in depth between vertices save_file : Boolean save output VTK file? Returns ------- subfolds : list of integers fold numbers for all vertices (-1 for non-fold vertices) n_subfolds : int number of subfolds subfolds_file : string (if save_file) name of output VTK file with fold IDs (-1 for non-fold vertices) Examples -------- >>> import os >>> from mindboggle.mio.vtks import read_scalars, rewrite_scalars >>> from mindboggle.guts.mesh import find_neighbors_from_file >>> from mindboggle.features.folds import extract_subfolds >>> from mindboggle.mio.plots import plot_surfaces >>> path = os.environ['MINDBOGGLE_DATA'] >>> depth_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.travel_depth.vtk') >>> folds_file = os.path.join(path, 'arno', 'features', 'folds.vtk') >>> folds, name = read_scalars(folds_file) >>> min_size = 10 >>> depth_factor = 0.5 >>> depth_ratio = 0.1 >>> tolerance = 0.01 >>> # >>> subfolds, n_subfolds, subfolds_file = extract_subfolds(depth_file, >>> folds, min_size, depth_factor, depth_ratio, tolerance, True) >>> # >>> # View: >>> rewrite_scalars(depth_file, 'subfolds.vtk', subfolds, 'subfolds', subfolds) >>> plot_surfaces('subfolds.vtk') """ import os import numpy as np from time import time from mindboggle.mio.vtks import rewrite_scalars, read_vtk from mindboggle.guts.mesh import find_neighbors from mindboggle.guts.segment import segment, propagate, watershed print("Segment folds into subfolds") t0 = time() #------------------------------------------------------------------------- # Load depth values for all vertices #------------------------------------------------------------------------- points, indices, lines, faces, depths, scalar_names, npoints, \ input_vtk = read_vtk(depth_file, return_first=True, return_array=True) #------------------------------------------------------------------------- # Find neighbors for each vertex #------------------------------------------------------------------------- neighbor_lists = find_neighbors(faces, npoints) #------------------------------------------------------------------------- # Segment folds into "watershed basins" #------------------------------------------------------------------------- indices_folds = [i for i, x in enumerate(folds) if x != -1] subfolds, seed_indices = watershed(depths, points, indices_folds, neighbor_lists, min_size, depth_factor=0.25, depth_ratio=0.1, tolerance=0.01, regrow=True) # Print statement n_subfolds = len([x for x in np.unique(subfolds) if x != -1]) print(' ...Extracted {0} subfolds ({1:.2f} seconds)'.format( n_subfolds, time() - t0)) #------------------------------------------------------------------------- # Return subfolds, number of subfolds, file name #------------------------------------------------------------------------- if save_file: subfolds_file = os.path.join(os.getcwd(), 'subfolds.vtk') rewrite_scalars(depth_file, subfolds_file, subfolds, 'subfolds', subfolds) if not os.path.exists(subfolds_file): raise (IOError(subfolds_file + " not found")) else: subfolds_file = None return subfolds, n_subfolds, subfolds_file
def extract_sulci(labels_file, folds_or_file, hemi, min_boundary=1, sulcus_names=[], save_file=False, output_file='', background_value=-1, verbose=False): """ Identify sulci from folds in a brain surface according to a labeling protocol that includes a list of label pairs defining each sulcus. Since folds are defined as deep, connected areas of a surface, and since folds may be connected to each other in ways that differ across brains, there usually does not exist a one-to-one mapping between folds of one brain and those of another. To address the correspondence problem then, we need to find just those portions of the folds that correspond across brains. To accomplish this, Mindboggle segments folds into sulci, which do have a one-to-one correspondence across non-pathological brains. Mindboggle defines a sulcus as a folded portion of cortex whose opposing banks are labeled with one or more sulcus label pairs in the DKT labeling protocol, where each label pair is unique to one sulcus and represents a boundary between two adjacent gyri, and each vertex has one gyrus label. This function assigns vertices in a fold to a sulcus in one of two cases. In the first case, vertices whose labels are in only one label pair in the fold are assigned to the label pair’s sulcus if they are connected through similarly labeled vertices to the boundary between the two labels. In the second case, the segment_regions function propagates labels from label borders to vertices whose labels are in multiple label pairs in the fold. 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 set. Parameters ---------- labels_file : string file name for surface mesh VTK containing labels for all vertices folds_or_file : numpy array, list or string fold number for each vertex / name of VTK file containing fold scalars hemi : string hemisphere abbreviation in {'lh', 'rh'} for sulcus labels min_boundary : integer minimum number of vertices for a sulcus label boundary segment sulcus_names : list of strings names of sulci save_file : bool save output VTK file? output_file : string name of output file in VTK format background_value : integer or float background value verbose : bool print statements? Returns ------- sulci : list of integers sulcus numbers for all vertices (-1 for non-sulcus vertices) n_sulci : integers number of sulci sulci_file : string output VTK file with sulcus numbers (-1 for non-sulcus vertices) Examples -------- >>> # Example 1: Extract sulcus from a fold with one sulcus label pair: >>> import numpy as np >>> from mindboggle.features.sulci import extract_sulci >>> from mindboggle.mio.vtks import read_scalars >>> from mindboggle.mio.fetch_data import prep_tests >>> urls, fetch_data = prep_tests() >>> # Load labels, folds, neighbor lists, and sulcus names and label pairs >>> labels_file = fetch_data(urls['left_freesurfer_labels'], '', '.vtk') >>> folds_file = fetch_data(urls['left_folds'], '', '.vtk') >>> folds_or_file, name = read_scalars(folds_file, True, True) >>> save_file = True >>> output_file = 'extract_sulci_fold4_1sulcus.vtk' >>> background_value = -1 >>> # Limit number of folds to speed up the test: >>> limit_folds = True >>> if limit_folds: ... fold_numbers = [4] #[4, 6] ... i0 = [i for i,x in enumerate(folds_or_file) if x not in fold_numbers] ... folds_or_file[i0] = background_value >>> hemi = 'lh' >>> min_boundary = 10 >>> sulcus_names = [] >>> verbose = False >>> sulci, n_sulci, sulci_file = extract_sulci(labels_file, folds_or_file, ... hemi, min_boundary, sulcus_names, save_file, output_file, ... background_value, verbose) >>> n_sulci # 23 # (if not limit_folds) 1 >>> lens = [len([x for x in sulci if x==y]) ... for y in np.unique(sulci) if y != -1] >>> lens[0:10] # [6358, 3288, 7612, 5205, 4414, 6251, 3493, 2566, 4436, 739] # (if not limit_folds) [1151] View result without background (skip test): >>> from mindboggle.mio.plots import plot_surfaces # doctest: +SKIP >>> from mindboggle.mio.vtks import rewrite_scalars # doctest: +SKIP >>> output = 'extract_sulci_fold4_1sulcus_no_background.vtk' >>> rewrite_scalars(sulci_file, output, sulci, ... 'sulci', sulci) # doctest: +SKIP >>> plot_surfaces(output) # doctest: +SKIP Example 2: Extract sulcus from a fold with multiple sulcus label pairs: >>> folds_or_file, name = read_scalars(folds_file, True, True) >>> output_file = 'extract_sulci_fold7_2sulci.vtk' >>> # Limit number of folds to speed up the test: >>> limit_folds = True >>> if limit_folds: ... fold_numbers = [7] #[4, 6] ... i0 = [i for i,x in enumerate(folds_or_file) if x not in fold_numbers] ... folds_or_file[i0] = background_value >>> sulci, n_sulci, sulci_file = extract_sulci(labels_file, folds_or_file, ... hemi, min_boundary, sulcus_names, save_file, output_file, ... background_value, verbose) >>> n_sulci # 23 # (if not limit_folds) 2 >>> lens = [len([x for x in sulci if x==y]) ... for y in np.unique(sulci) if y != -1] >>> lens[0:10] # [6358, 3288, 7612, 5205, 4414, 6251, 3493, 2566, 4436, 739] # (if not limit_folds) [369, 93] View result without background (skip test): >>> from mindboggle.mio.plots import plot_surfaces # doctest: +SKIP >>> from mindboggle.mio.vtks import rewrite_scalars # doctest: +SKIP >>> output = 'extract_sulci_fold7_2sulci_no_background.vtk' >>> rewrite_scalars(sulci_file, output, sulci, ... 'sulci', sulci) # doctest: +SKIP >>> plot_surfaces(output) # doctest: +SKIP """ import os from time import time import numpy as np from mindboggle.mio.vtks import read_scalars, read_vtk, rewrite_scalars from mindboggle.guts.mesh import find_neighbors from mindboggle.guts.segment import extract_borders, propagate, segment_regions from mindboggle.mio.labels import DKTprotocol # 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 elif isinstance(folds_or_file, np.ndarray): folds = folds_or_file.tolist() dkt = DKTprotocol() if hemi == 'lh': pair_lists = dkt.left_sulcus_label_pair_lists elif hemi == 'rh': pair_lists = dkt.right_sulcus_label_pair_lists else: raise IOError( "Warning: hemisphere not properly specified ('lh' or 'rh').") # Load points, faces, and neighbors: points, indices, lines, faces, labels, scalar_names, npoints, \ input_vtk = 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 = background_value * np.ones(npoints) # ------------------------------------------------------------------------ # Loop through folds # ------------------------------------------------------------------------ fold_numbers = [int(x) for x in np.unique(folds) if x != background_value] n_folds = len(fold_numbers) if verbose: print("Extract sulci from {0} folds...".format(n_folds)) t0 = time() for n_fold in fold_numbers: fold_indices = [i for i, x in enumerate(folds) if x == n_fold] len_fold = len(fold_indices) # List the labels in this fold: fold_labels = [labels[x] for x in fold_indices] unique_fold_labels = [ int(x) for x in np.unique(fold_labels) if x != background_value ] # -------------------------------------------------------------------- # NO MATCH -- fold has fewer than two labels # -------------------------------------------------------------------- if verbose and 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_indices, 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 dkt.unique_sulcus_label_pairs ] if verbose and 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 verbose and not fold_pairs_in_protocol: print(" Fold {0}: NO MATCH -- fold has no sulcus label pair". format(n_fold, len_fold)) # ---------------------------------------------------------------- # Possible matches # ---------------------------------------------------------------- else: if verbose: 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 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 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 = None for i, pair_list in enumerate(pair_lists): if not isinstance(pair_list, list): pair_list = [pair_list] if pair in pair_list: ID = i break if ID: # 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 ] # Vertices with unique label(s) in pair: indices_unique_labels = [ fold_indices[i] for i, x in enumerate(fold_labels) if x in unique_labels_in_pair ] #dkt.unique_sulcus_label_pairs] # Propagate sulcus ID from seeds to vertices # with "unique" labels (only exist in one # label pair in a fold); propagation ensures # that sulci consist of contiguous vertices # for each label boundary: sulci2 = segment_regions( indices_unique_labels, neighbor_lists, min_region_size=1, seed_lists=[indices_pair], keep_seeding=False, spread_within_labels=True, labels=labels, label_lists=[], values=[], max_steps='', background_value=background_value, verbose=False) sulci[sulci2 != background_value] = ID # Print statement: if verbose: if n_unique == 1: ps1 = 'One 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: if verbose: print( " Propagate sulcus borders with label {0}". format(int(label))) # Construct seeds from label boundary vertices: seeds = background_value * np.ones(npoints) for ID, pair_list in enumerate(pair_lists): if not isinstance(pair_list, list): pair_list = [pair_list] label_pairs = [x for x in 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_regions( indices_pair, neighbor_lists, 1, [], False, False, [], [], [], '', background_value, verbose) useeds2 = [ x for x in np.unique(seeds2) if x != background_value ] for seed2 in useeds2: iseed2 = [ i for i, x in enumerate(seeds2) if x == seed2 ] if len(iseed2) >= min_boundary: indices_pair2.extend(iseed2) elif verbose: 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: indices_label = [ fold_indices[i] for i, x in enumerate(fold_labels) if x == label ] if len(indices_label): # Propagate sulcus ID from seeds to vertices # with a given shared label: seg_vs_prop = False if seg_vs_prop: indices_seeds = [] for seed in [ x for x in np.unique(seeds) if x != background_value ]: indices_seeds.append([ i for i, x in enumerate(seeds) if x == seed ]) sulci2 = segment_regions( indices_label, neighbor_lists, 50, indices_seeds, False, True, labels, [], [], '', background_value, verbose) else: label_array = background_value * \ np.ones(npoints) label_array[indices_label] = 1 sulci2 = propagate( points, faces, label_array, seeds, sulci, max_iters=10000, tol=0.001, sigma=5, background_value=background_value, verbose=verbose) sulci[sulci2 != background_value] = \ sulci2[sulci2 != background_value] sulcus_numbers = [ int(x) for x in np.unique(sulci) if x != background_value ] n_sulci = len(sulcus_numbers) # ------------------------------------------------------------------------ # Print statements # ------------------------------------------------------------------------ if verbose: if n_sulci == 1: sulcus_str = 'sulcus' else: sulcus_str = 'sulci' if n_folds == 1: folds_str = 'fold' else: folds_str = 'folds' print("Extracted {0} {1} from {2} {3} ({4:.1f}s):".format( n_sulci, sulcus_str, n_folds, folds_str, time() - t0)) if sulcus_names: for sulcus_number in sulcus_numbers: print(" {0}: {1}".format(sulcus_number, sulcus_names[sulcus_number])) elif sulcus_numbers: print(" " + ", ".join([str(x) for x in sulcus_numbers])) unresolved = [ i for i in range(len(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 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 = [int(x) for x in sulci] sulci_file = os.path.join(os.getcwd(), 'sulci.vtk') rewrite_scalars(labels_file, sulci_file, sulci, 'sulci', [], background_value) if not os.path.exists(sulci_file): raise IOError(sulci_file + " not found") return sulci, n_sulci, sulci_file