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, 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 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_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 fill_holes(regions, neighbor_lists, values=[], exclude_range=[], background_value=-1): """ Fill holes in regions on a surface mesh by using region boundaries. NOTE: assumes one set of connected vertices per region Steps :: 1. Segment region vertex neighbors into connected vertices (region boundaries). 2. Remove the largest region boundary, presumably the outer contour of the region, leaving smaller boundaries, presumably the contours of holes within the region. 3. Call label_holes() to fill holes with surrounding region numbers. Parameters ---------- regions : numpy array of integers region numbers for all vertices neighbor_lists : list of lists of integers each list contains indices to neighboring vertices for each vertex values : list of integers values for vertices, for use in determining which holes to remove exclude_range : list of two floats hole is not filled if it contains values within this range (prevents cases where surface connected by folds mistaken for holes) background_value : integer background value Returns ------- regions : numpy array of integers region numbers for all vertices Examples -------- >>> import os >>> import numpy as np >>> from mindboggle.guts.mesh import find_neighbors, remove_faces >>> from mindboggle.guts.morph import fill_holes >>> from mindboggle.mio.vtks import read_scalars, read_vtk, write_vtk >>> path = os.environ['MINDBOGGLE_DATA'] >>> # >>> background_value = -1 >>> # Select one fold >>> folds_file = os.path.join(path, 'arno', 'features', 'folds.vtk') >>> folds, name = read_scalars(folds_file, return_first=True, return_array=True) >>> vtk_file = os.path.join(path, 'arno', 'freesurfer', 'lh.pial.vtk') >>> points, indices, lines, faces, scalars, scalar_names, npoints, input_vtk = read_vtk(vtk_file, return_first=True, return_array=True) >>> neighbor_lists = find_neighbors(faces, npoints) >>> n_fold = np.unique(folds)[1] >>> folds[folds != n_fold] = background_value >>> # >>> # Make two holes in fold (background value and excluded values) >>> # Hole 1: >>> # Find a vertex whose removal (with its neighbors) would create a hole >>> I = np.where(folds==n_fold)[0] >>> for index1 in I: >>> N1 = neighbor_lists[index1] >>> stop = True >>> for n in N1: >>> if any(folds[neighbor_lists[n]] == background_value): >>> stop = False >>> break >>> else: >>> for f in neighbor_lists[n]: >>> if any(folds[neighbor_lists[f]] == background_value): >>> stop = False >>> break >>> if stop: >>> break >>> folds[index1] = background_value >>> folds[N1] = background_value >>> # Hole 2: >>> I = np.where(folds==n_fold)[0] >>> for index2 in I: >>> N2 = neighbor_lists[index2] >>> stop = True >>> for n in N2: >>> if any(folds[neighbor_lists[n]] == background_value): >>> stop = False >>> break >>> else: >>> for f in neighbor_lists[n]: >>> if any(folds[neighbor_lists[f]] == background_value): >>> stop = False >>> break >>> if stop: >>> break >>> folds[index2] = background_value >>> folds[N2] = background_value >>> values = np.zeros(len(folds)) >>> values[index2] = 100 >>> values[N2] = 200 >>> # >>> # Write holes to vtk file and view: >>> holes = folds.copy() >>> holes[index1] = 10 >>> holes[N1] = 20 >>> holes[index2] = 30 >>> holes[N2] = 40 >>> indices = [i for i,x in enumerate(holes) if x != background_value] >>> write_vtk('holes.vtk', points, indices, lines, >>> remove_faces(faces, indices), [holes.tolist()], ['holes'], 'int') >>> from mindboggle.mio.plots import plot_surfaces >>> plot_surfaces('holes.vtk') >>> # >>> # Fill Hole 1 but not Hole 2: >>> # (because values has an excluded value in the hole) >>> regions = np.copy(folds) >>> exclude_range = [99,101], >>> regions = fill_holes(regions, neighbor_lists, values, exclude_range, background_value) >>> # >>> # Write results to vtk file and view: >>> indices = [i for i,x in enumerate(regions) if x != background_value] >>> write_vtk('fill_holes.vtk', points, indices, lines, >>> remove_faces(faces, indices), regions.tolist(), 'regions', 'int') >>> from mindboggle.mio.plots import plot_surfaces >>> plot_surfaces('fill_holes.vtk') """ import numpy as np from mindboggle.guts.segment import segment # Make sure argument is a numpy array if not isinstance(regions, np.ndarray): regions = np.array(regions) def label_holes(holes, regions, neighbor_lists): """ Fill holes in regions on a surface mesh. Parameters ---------- holes : list or array of integers hole numbers for all vertices regions : numpy array of integers region numbers for all vertices neighbor_lists : list of lists of integers each list contains indices to neighboring vertices for each vertex Returns ------- regions : numpy array of integers region numbers for all vertices """ import numpy as np # Make sure argument is a numpy array if not isinstance(regions, np.ndarray): regions = np.array(regions) # Identify the vertices for each hole hole_numbers = [x for x in np.unique(holes) if x != background_value] for n_hole in hole_numbers: I = [i for i, x in enumerate(holes) if x == n_hole] # Identify neighbors to these vertices N = [] [N.extend(neighbor_lists[i]) for i in I] if N: # Assign the hole the maximum region ID number of its neighbors regions[I] = max([regions[x] for x in N]) return regions #------------------------------------------------------------------------- # Find boundaries to holes #------------------------------------------------------------------------- hole_boundaries = background_value * np.ones(len(regions)) # Identify vertices for each region region_numbers = [x for x in np.unique(regions) if x != background_value] count = 0 for n_region in region_numbers: region_indices = np.where(regions == n_region)[0] # Identify neighbors to these vertices and their neighbors N = [] [N.extend(neighbor_lists[x]) for x in region_indices] N = list(frozenset(N).difference(region_indices)) N2 = [] [N2.extend(neighbor_lists[x]) for x in N] N.extend(N2) N = list(frozenset(N).difference(region_indices)) if N: # Segment the neighbors into connected vertices (region boundaries) boundaries = segment(N, neighbor_lists) # Remove the largest region boundary, presumably the # outer contour of the region, leaving smaller boundaries, # presumably the contours of holes within the region boundary_numbers = [ x for x in np.unique(boundaries) if x != background_value ] max_size = 0 max_number = 0 for n_boundary in boundary_numbers: border_indices = np.where(boundaries == n_boundary)[0] if len(border_indices) > max_size: max_size = len(border_indices) max_number = n_boundary boundaries[boundaries == max_number] = background_value boundary_numbers = [x for x in boundary_numbers if x != max_number] # Add remaining boundaries to holes array for n_boundary in boundary_numbers: indices = [ i for i, x in enumerate(boundaries) if x == n_boundary ] hole_boundaries[indices] = count count += 1 #------------------------------------------------------------------------- # Fill holes #------------------------------------------------------------------------- # If there are any holes if count > 0: hole_numbers = [ x for x in np.unique(hole_boundaries) if x != background_value ] background = [ i for i, x in enumerate(regions) if x == background_value ] # Grow seeds from hole boundaries to fill holes for n_hole in hole_numbers: seed_list = np.where(hole_boundaries == n_hole)[0].tolist() seed_lists = [list(frozenset(background).intersection(seed_list))] hole = segment(background, neighbor_lists, 1, seed_lists) # Label the vertices for each hole by surrounding region number # if hole does not include values within exclude_range: if len(exclude_range) == 2: Ihole = np.where(hole != background_value)[0] #if not len(frozenset(values[Ihole]).intersection(exclude_range)): if not [ x for x in values[Ihole] if x > exclude_range[0] if x < exclude_range[1] ]: regions = label_holes(hole, regions, neighbor_lists) else: regions = label_holes(hole, regions, neighbor_lists) return regions
def fill_holes(regions, neighbor_lists, values=[], exclude_range=[], background_value=-1): """ Fill holes in regions on a surface mesh by using region boundaries. NOTE: assumes one set of connected vertices per region Steps :: 1. Segment region vertex neighbors into connected vertices (region boundaries). 2. Remove the largest region boundary, presumably the outer contour of the region, leaving smaller boundaries, presumably the contours of holes within the region. 3. Call label_holes() to fill holes with surrounding region numbers. Parameters ---------- regions : numpy array of integers region numbers for all vertices neighbor_lists : list of lists of integers each list contains indices to neighboring vertices for each vertex values : list of integers values for vertices, for use in determining which holes to remove exclude_range : list of two floats hole is not filled if it contains values within this range (prevents cases where surface connected by folds mistaken for holes) background_value : integer background value Returns ------- regions : numpy array of integers region numbers for all vertices Examples -------- >>> import os >>> import numpy as np >>> from mindboggle.guts.mesh import find_neighbors, remove_faces >>> from mindboggle.guts.morph import fill_holes >>> from mindboggle.mio.vtks import read_scalars, read_vtk, write_vtk >>> path = os.environ['MINDBOGGLE_DATA'] >>> # >>> background_value = -1 >>> # Select one fold >>> folds_file = os.path.join(path, 'arno', 'features', 'folds.vtk') >>> folds, name = read_scalars(folds_file, return_first=True, return_array=True) >>> vtk_file = os.path.join(path, 'arno', 'freesurfer', 'lh.pial.vtk') >>> faces, lines, indices, points, npoints, scalars, name, input_vtk = read_vtk(vtk_file, >>> return_first=True, return_array=True) >>> neighbor_lists = find_neighbors(faces, npoints) >>> n_fold = np.unique(folds)[1] >>> folds[folds != n_fold] = background_value >>> # >>> # Make two holes in fold (background value and excluded values) >>> # Hole 1: >>> # Find a vertex whose removal (with its neighbors) would create a hole >>> I = np.where(folds==n_fold)[0] >>> for index1 in I: >>> N1 = neighbor_lists[index1] >>> stop = True >>> for n in N1: >>> if any(folds[neighbor_lists[n]] == background_value): >>> stop = False >>> break >>> else: >>> for f in neighbor_lists[n]: >>> if any(folds[neighbor_lists[f]] == background_value): >>> stop = False >>> break >>> if stop: >>> break >>> folds[index1] = background_value >>> folds[N1] = background_value >>> # Hole 2: >>> I = np.where(folds==n_fold)[0] >>> for index2 in I: >>> N2 = neighbor_lists[index2] >>> stop = True >>> for n in N2: >>> if any(folds[neighbor_lists[n]] == background_value): >>> stop = False >>> break >>> else: >>> for f in neighbor_lists[n]: >>> if any(folds[neighbor_lists[f]] == background_value): >>> stop = False >>> break >>> if stop: >>> break >>> folds[index2] = background_value >>> folds[N2] = background_value >>> values = np.zeros(len(folds)) >>> values[index2] = 100 >>> values[N2] = 200 >>> # >>> # Write holes to vtk file and view: >>> holes = folds.copy() >>> holes[index1] = 10 >>> holes[N1] = 20 >>> holes[index2] = 30 >>> holes[N2] = 40 >>> indices = [i for i,x in enumerate(holes) if x != background_value] >>> write_vtk('holes.vtk', points, indices, lines, >>> remove_faces(faces, indices), [holes.tolist()], ['holes'], 'int') >>> from mindboggle.mio.plots import plot_surfaces >>> plot_surfaces('holes.vtk') >>> # >>> # Fill Hole 1 but not Hole 2: >>> # (because values has an excluded value in the hole) >>> regions = np.copy(folds) >>> exclude_range = [99,101], >>> regions = fill_holes(regions, neighbor_lists, values, exclude_range, background_value) >>> # >>> # Write results to vtk file and view: >>> indices = [i for i,x in enumerate(regions) if x != background_value] >>> write_vtk('fill_holes.vtk', points, indices, lines, >>> remove_faces(faces, indices), regions.tolist(), 'regions', 'int') >>> from mindboggle.mio.plots import plot_surfaces >>> plot_surfaces('fill_holes.vtk') """ import numpy as np from mindboggle.guts.segment import segment # Make sure argument is a numpy array if not isinstance(regions, np.ndarray): regions = np.array(regions) def label_holes(holes, regions, neighbor_lists): """ Fill holes in regions on a surface mesh. Parameters ---------- holes : list or array of integers hole numbers for all vertices regions : numpy array of integers region numbers for all vertices neighbor_lists : list of lists of integers each list contains indices to neighboring vertices for each vertex Returns ------- regions : numpy array of integers region numbers for all vertices """ import numpy as np # Make sure argument is a numpy array if not isinstance(regions, np.ndarray): regions = np.array(regions) # Identify the vertices for each hole hole_numbers = [x for x in np.unique(holes) if x != background_value] for n_hole in hole_numbers: I = [i for i,x in enumerate(holes) if x == n_hole] # Identify neighbors to these vertices N=[]; [N.extend(neighbor_lists[i]) for i in I] if N: # Assign the hole the maximum region ID number of its neighbors regions[I] = max([regions[x] for x in N]) return regions #------------------------------------------------------------------------- # Find boundaries to holes #------------------------------------------------------------------------- hole_boundaries = background_value * np.ones(len(regions)) # Identify vertices for each region region_numbers = [x for x in np.unique(regions) if x != background_value] count = 0 for n_region in region_numbers: region_indices = np.where(regions == n_region)[0] # Identify neighbors to these vertices and their neighbors N = [] [N.extend(neighbor_lists[x]) for x in region_indices] N = list(frozenset(N).difference(region_indices)) N2 = [] [N2.extend(neighbor_lists[x]) for x in N] N.extend(N2) N = list(frozenset(N).difference(region_indices)) if N: # Segment the neighbors into connected vertices (region boundaries) boundaries = segment(N, neighbor_lists) # Remove the largest region boundary, presumably the # outer contour of the region, leaving smaller boundaries, # presumably the contours of holes within the region boundary_numbers = [x for x in np.unique(boundaries) if x != background_value] max_size = 0 max_number = 0 for n_boundary in boundary_numbers: border_indices = np.where(boundaries == n_boundary)[0] if len(border_indices) > max_size: max_size = len(border_indices) max_number = n_boundary boundaries[boundaries == max_number] = background_value boundary_numbers = [x for x in boundary_numbers if x != max_number] # Add remaining boundaries to holes array for n_boundary in boundary_numbers: indices = [i for i,x in enumerate(boundaries) if x == n_boundary] hole_boundaries[indices] = count count += 1 #------------------------------------------------------------------------- # Fill holes #------------------------------------------------------------------------- # If there are any holes if count > 0: hole_numbers = [x for x in np.unique(hole_boundaries) if x != background_value] background = [i for i,x in enumerate(regions) if x == background_value] # Grow seeds from hole boundaries to fill holes for n_hole in hole_numbers: seed_list = np.where(hole_boundaries == n_hole)[0].tolist() seed_lists = [list(frozenset(background).intersection(seed_list))] hole = segment(background, neighbor_lists, 1, seed_lists) # Label the vertices for each hole by surrounding region number # if hole does not include values within exclude_range: if len(exclude_range) == 2: Ihole = np.where(hole != background_value)[0] #if not len(frozenset(values[Ihole]).intersection(exclude_range)): if not [x for x in values[Ihole] if x > exclude_range[0] if x < exclude_range[1]]: regions = label_holes(hole, regions, neighbor_lists) else: regions = label_holes(hole, regions, neighbor_lists) return regions