def propagate_fundus_lines(surf_file, fundus_lines_file, thickness_file): """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.utils.io_vtk import read_vtk, read_scalars faces, _, _, points, num_points, fundus_lines, _, _ = read_vtk( surf_file, return_first=True, return_array=True) fundus_lines, _ = read_scalars(fundus_lines_file) fundus_line_indices = [i for i, x in enumerate(fundus_lines) if x > 0.5] thickness, _ = read_scalars(thickness_file, return_first=True, return_array=True) return propagate_fundus_lines( points, faces, fundus_line_indices, thickness)
def decimate_file(input_vtk, reduction=0.5, smooth_steps=100, save_vtk=False, output_vtk=''): """ Decimate vtk triangular mesh file with vtk.vtkDecimatePro. Parameters ---------- input_vtk : string input vtk file with triangular surface mesh reduction : float fraction of mesh faces to remove do_smooth : Boolean smooth after decimation? save_vtk : Boolean output decimated vtk file? output_vtk : string output decimated vtk file name Returns ------- output_vtk : string output decimated vtk file Examples -------- >>> import os >>> from mindboggle.utils.mesh import decimate_file >>> from mindboggle.utils.plots import plot_surfaces >>> path = os.environ['MINDBOGGLE_DATA'] >>> input_vtk = os.path.join(path, 'arno', 'labels', 'label22.vtk') >>> #input_vtk='/drop/MB/data/arno/labels/lh.labels.DKT31.manual.vtk' >>> save_vtk = True >>> output_vtk = '' >>> reduction = 0.5 >>> smooth_steps = 0 >>> decimate_file(input_vtk, reduction, smooth_steps, save_vtk, output_vtk) >>> # View: >>> plot_surfaces('decimated.vtk') # doctest: +SKIP """ from mindboggle.utils.io_vtk import read_vtk from mindboggle.utils.mesh import decimate # Read VTK surface mesh file: faces, u1, u2, points, u4, scalars, u5, u6 = read_vtk(input_vtk) # Decimate vtk triangular mesh with vtk.vtkDecimatePro points, faces, scalars, output_vtk = decimate(points, faces, reduction, smooth_steps, scalars, save_vtk, output_vtk) return output_vtk
def main() : parser = ArgumentParser() parser.add_argument('vtk_file') ns = parser.parse_args() faces, lines, indices, points, npoints, scalars, scalar_names, input_vtk = read_vtk(ns.vtk_file) points = [[0,0,0], [1,0,0], [0,0,1], [0,1,1], [1,0,1], [0,1,0], [1,1,1], [1,1,0]] faces = [[0,2,4], [0,1,4], [2,3,4], [3,4,5], [3,5,6], [0,1,7]] zernike_moments( array(points), array(faces) )
def decimate_file(input_vtk, reduction=0.5, smooth_steps=100, save_vtk=False, output_vtk=''): """ Decimate vtk triangular mesh file with vtk.vtkDecimatePro. Parameters ---------- input_vtk : string input vtk file with triangular surface mesh reduction : float fraction of mesh faces to remove do_smooth : Boolean smooth after decimation? save_vtk : Boolean output decimated vtk file? output_vtk : string output decimated vtk file name Returns ------- output_vtk : string output decimated vtk file Examples -------- >>> import os >>> from mindboggle.utils.mesh import decimate_file >>> from mindboggle.utils.plots import plot_vtk >>> path = os.environ['MINDBOGGLE_DATA'] >>> input_vtk = os.path.join(path, 'arno', 'labels', 'label22.vtk') >>> #input_vtk='/drop/MB/data/arno/labels/lh.labels.DKT31.manual.vtk' >>> save_vtk = True >>> output_vtk = '' >>> reduction = 0.5 >>> smooth_steps = 0 >>> decimate_file(input_vtk, reduction, smooth_steps, save_vtk, output_vtk) >>> # View: >>> plot_vtk('decimated.vtk') # doctest: +SKIP """ from mindboggle.utils.io_vtk import read_vtk from mindboggle.utils.mesh import decimate # Read VTK surface mesh file: faces, u1, u2, points, u4, scalars, u5, u6 = read_vtk(input_vtk) # Decimate vtk triangular mesh with vtk.vtkDecimatePro points, faces, scalars, output_vtk = decimate(points, faces, reduction, smooth_steps, scalars, save_vtk, output_vtk) return output_vtk
def main(): zernike_fn = zernike_moments parser = argparse.ArgumentParser() parser.add_argument( '--debug', nargs='?', default=None, const='debug', choices=['debug', 'info', 'warning', 'error', 'critical']) parser.add_argument('vtk_file', nargs='?', default=None) parser.add_argument('-o', '--order', type=int, default=3) parser.add_argument('-p', '--profile', nargs='?', default=None, const='stdout') parser.add_argument('-t', '--timecall', default=False, action='store_true') parser.add_argument('-v', '--validate', default=False, action='store_true') ns = parser.parse_args() if ns.debug is not None: logging.basicConfig(level=getattr(logging, ns.debug.upper())) if ns.profile is not None: filename = ns.profile if ns.profile == 'stdout': filename = None zernike_fn = profilehooks.profile(zernike_fn, immediate=False, filename=filename) if ns.timecall: zernike_fn = profilehooks.timecall(zernike_fn) if ns.vtk_file is not None: faces, lines, indices, points, n_points, depths, name, input_vtk = read_vtk( ns.vtk_file) print len(faces), len(points) X = zernike_fn(points, faces, order=ns.order, scale_input=True) if ns.validate: Y = zernike_fn(points, faces, order=ns.order, scale_input=True, pl_cls=MultiprocPipeline) assert np.allclose(X, Y) else: example1()
def decimate_file(input_vtk, reduction=0.5, smooth_steps=100, output_vtk=''): """ Decimate vtk triangular mesh file with vtk.vtkDecimatePro. Parameters ---------- input_vtk : string input vtk file with triangular surface mesh reduction : float fraction of mesh faces to remove do_smooth : Boolean smooth after decimation? output_vtk : string output decimated vtk file Returns ------- output_vtk : string output decimated vtk file Examples -------- >>> import os >>> from mindboggle.utils.mesh import decimate_file >>> path = os.environ['MINDBOGGLE_DATA'] >>> input_vtk = os.path.join(path, 'arno', 'labels', 'label22.vtk') >>> output_vtk = 'decimated_file.vtk' >>> reduction = 0.9 >>> smooth_steps = 0 >>> decimate_file(input_vtk, reduction=reduction, >>> smooth_steps=smooth_steps, output_vtk=output_vtk) >>> # View: >>> os.system('mayavi2 -d ' + output_vtk + ' -m Surface &') """ import os import vtk from mindboggle.utils.io_vtk import read_vtk # Read VTK surface mesh file: faces, u1, u2, points, u4, labels, u5, u6 = read_vtk(input_vtk) # vtk points: vtk_points = vtk.vtkPoints() [vtk_points.InsertPoint(i, x[0], x[1], x[2]) for i,x in enumerate(points)] # vtk faces: vtk_faces = vtk.vtkCellArray() for face in faces: vtk_face = vtk.vtkPolygon() vtk_face.GetPointIds().SetNumberOfIds(3) vtk_face.GetPointIds().SetId(0, face[0]) vtk_face.GetPointIds().SetId(1, face[1]) vtk_face.GetPointIds().SetId(2, face[2]) vtk_faces.InsertNextCell(vtk_face) # vtkPolyData: polydata = vtk.vtkPolyData() polydata.SetPoints(vtk_points) polydata.SetPolys(vtk_faces) # We want to preserve topology (not let any cracks form). # This may limit the total reduction possible. decimate = vtk.vtkDecimatePro() decimate.SetInput(polydata) decimate.SetTargetReduction(reduction) decimate.PreserveTopologyOn() # Export output: if not output_vtk: output_vtk = os.path.join(os.getcwd(), 'decimated_file.vtk') exporter = vtk.vtkPolyDataWriter() if smooth_steps > 0: smoother = vtk.vtkSmoothPolyDataFilter() smoother.SetInputConnection(decimate.GetOutputPort()) smoother.SetNumberOfIterations(smooth_steps) exporter.SetInput(smoother.GetOutput()) else: exporter.SetInput(decimate.GetOutput()) exporter.SetFileName(output_vtk) exporter.Write() return output_vtk
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.utils.segment import extract_borders import mindboggle.utils.graph as go from mindboggle.utils.io_vtk import read_vtk, read_scalars, write_vtk from mindboggle.utils.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 rewrite_scalars(input_vtk, output_vtk, new_scalars, new_scalar_names=['scalars'], filter_scalars=[]): """ Load VTK format file and save a subset of scalars into a new file. Parameters ---------- input_vtk : string input VTK file name output_vtk : string output VTK file name new_scalars : list of lists of floats (or single list or array of floats) each list (lookup table) contains new values to assign to the vertices new_scalar_names : string or list of strings each element is the new name for a lookup table filter_scalars : list or numpy array (optional) scalar values used to filter faces (values > -1 retained) Returns ------- output_vtk : string output VTK file name Examples -------- >>> # Write vtk file with curvature values on sulci >>> import os >>> from mindboggle.utils.io_vtk import read_scalars, rewrite_scalars >>> path = os.environ['MINDBOGGLE_DATA'] >>> curv_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.mean_curvature.vtk') >>> curvs, name = read_scalars(curv_file, True,True) >>> sulci_file = os.path.join(path, 'arno', 'features', 'sulci.vtk') >>> sulci, name = read_scalars(sulci_file) >>> # >>> rewrite_scalars(curv_file, 'rewrite_scalars.vtk', >>> [curvs, sulci], ['curvs', 'sulci'], sulci) >>> # >>> # View: >>> from mindboggle.utils.plots import plot_vtk >>> plot_vtk('rewrite_scalars.vtk') """ import os import numpy as np from mindboggle.utils.mesh import remove_faces from mindboggle.utils.io_vtk import write_header, write_points, \ write_vertices, write_faces, write_scalars, read_vtk, \ scalars_checker # Convert numpy arrays to lists if isinstance(new_scalars, np.ndarray): new_scalars = new_scalars.tolist() if isinstance(filter_scalars, np.ndarray): filter_scalars = filter_scalars.tolist() # Output VTK file to current working directory output_vtk = os.path.join(os.getcwd(), output_vtk) # Load VTK file faces, lines, indices, points, npoints, scalars, name, input_vtk = read_vtk(input_vtk) # Find indices to nonzero values indices = range(npoints) if filter_scalars: indices_filter = [i for i,x in enumerate(filter_scalars) if x > -1] indices_remove = [i for i,x in enumerate(filter_scalars) if x == -1] # Remove surface mesh faces whose three vertices are not all in indices faces = remove_faces(faces, indices_filter) # Write VTK file Fp = open(output_vtk,'w') write_header(Fp) write_points(Fp, points) if indices: write_vertices(Fp, indices) if faces: write_faces(Fp, faces) if new_scalars: new_scalars, new_scalar_names = scalars_checker(new_scalars, new_scalar_names) for i, new_scalar_list in enumerate(new_scalars): if filter_scalars: for iremove in indices_remove: new_scalar_list[iremove] = -1 if i == 0: new_scalar_name = new_scalar_names[0] write_scalars(Fp, new_scalar_list, new_scalar_name) else: if len(new_scalar_names) < i + 1: new_scalar_name = new_scalar_names[0] else: new_scalar_name = new_scalar_names[i] write_scalars(Fp, new_scalar_list, new_scalar_name, begin_scalars=False) else: print('Error: new_scalars is empty') exit() Fp.close() return output_vtk
def spectrum_from_file(vtk_file, spectrum_size=10, exclude_labels=[-1], normalization=None, area_file=''): """ Compute Laplace-Beltrami spectrum of a 3D shape in a VTK file. Parameters ---------- vtk_file : string the input vtk file spectrum_size : integer number of eigenvalues to be computed (the length of the spectrum) exclude_labels : list of integers labels to be excluded normalization : string the method used to normalize eigenvalues ('area' or None) if "area", use area of the 2D structure as in Reuter et al. 2006 area_file : string name of VTK file with surface area scalar values Returns ------- spectrum : list of floats first spectrum_size of Laplace-Beltrami spectrum Examples -------- >>> # Spectrum for entire left hemisphere of Twins-2-1: >>> import os >>> from mindboggle.shapes.laplace_beltrami import spectrum_from_file >>> path = os.environ['MINDBOGGLE_DATA'] >>> vtk_file = os.path.join(path, 'arno', 'labels', 'lh.labels.DKT25.manual.vtk') >>> spectrum_from_file(vtk_file, spectrum_size=6) [4.829758648026223e-18, 0.00012841730024671977, 0.0002715181572272744, 0.00032051508471594173, 0.000470162807048644, 0.0005768904023010327] >>> # Spectrum for Twins-2-1 left postcentral pial surface (22) >>> # (after running explode_scalars() with reindex=True): >>> import os >>> from mindboggle.shapes.laplace_beltrami import spectrum_from_file >>> path = os.environ['MINDBOGGLE_DATA'] >>> vtk_file = os.path.join(path, 'arno', 'labels', 'label22.vtk') >>> spectrum_from_file(vtk_file, spectrum_size=6) [6.3469513010430304e-18, 0.0005178862383467463, 0.0017434911095630772, 0.003667561767487686, 0.005429017880363784, 0.006309346984678924] >>> # Loop thru all MB 101 brains >>> from mindboggle.shapes.laplace_beltrami import spectrum_from_file >>> for hemidir in os.listdir(header): >>> print hemidir >>> sulci_file = os.path.join(header, hemidir, "sulci.vtk") >>> spectrum = spectrum_from_file(sulci_file) """ from mindboggle.utils.io_vtk import read_vtk, read_scalars from mindboggle.shapes.laplace_beltrami import spectrum_of_largest faces, u1, u2, points, u4, u5, u6, u7 = read_vtk(vtk_file) # Area file: if area_file: areas, u1 = read_scalars(area_file) else: areas = None spectrum = spectrum_of_largest(points, faces, spectrum_size, exclude_labels, normalization, areas) return spectrum
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.utils.io_vtk import read_scalars, rewrite_scalars >>> from mindboggle.features.sulci import extract_sulci >>> from mindboggle.utils.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.utils.io_vtk import read_scalars, read_vtk, rewrite_scalars from mindboggle.utils.mesh import find_neighbors from mindboggle.utils.segment import extract_borders, propagate, segment from mindboggle.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 write_face_vertex_averages(input_file, area_file="", delimiter=","): """ Make table of average vertex values per face. Parameters ---------- input_file : string name of VTK file with scalars to average area_file : string name of VTK file with surface area scalar values delimiter : string delimiter between columns, such as ',' Returns ------- output_table : string output table filename Examples -------- >>> import os >>> from mindboggle.utils.io_vtk import read_scalars >>> from mindboggle.utils.io_table import write_face_vertex_averages >>> path = os.environ['MINDBOGGLE_DATA'] >>> #input_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.mean_curvature.vtk') >>> #input_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.travel_depth.vtk') >>> input_file = os.path.join(path, 'arno', 'shapes', 'lh.thickness.vtk') >>> area_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.area.vtk') >>> delimiter = ',' >>> # >>> write_face_vertex_averages(input_file, area_file, delimiter) """ import os import numpy as np from mindboggle.utils.io_vtk import read_vtk, read_scalars from mindboggle.utils.io_table import write_columns faces, lines, indices, points, npoints, scalars, name, input_vtk = read_vtk(input_file, True, True) if area_file: area_scalars, name = read_scalars(area_file, True, True) # --------------------------------------------------------------------- # For each face, average vertex values: # --------------------------------------------------------------------- output_table = os.path.join(os.getcwd(), "average_face_values.csv") columns = [] for face in faces: values = [] for index in face: if area_file: values.append(scalars[index] / area_scalars[index]) else: values.append(scalars[index]) columns.append(np.mean(values)) # ----------------------------------------------------------------- # Write to table: # ----------------------------------------------------------------- write_columns(columns, "", output_table, delimiter, quote=False) return output_table
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.utils.io_vtk import read_scalars, rewrite_scalars >>> from mindboggle.utils.mesh import find_neighbors_from_file >>> from mindboggle.features.folds import extract_subfolds >>> from mindboggle.utils.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.utils.io_vtk import rewrite_scalars, read_vtk from mindboggle.utils.mesh import find_neighbors from mindboggle.utils.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 relabel_surface(vtk_file, hemi='', old_labels=[], new_labels=[], erase_remaining=True, erase_labels=[], erase_value=-1, output_file=''): """ Relabel surface in a VTK file. Parameters ---------- vtk_file : string input labeled VTK file hemi : string hemisphere ('lh' or 'rh' or '') if set, add 1000 to left and 2000 to right hemisphere labels; old_labels : list of integers old labels (empty list if labels drawn from vtk scalars); may be used in conjunction with hemi new_labels : list of integers new labels (empty list if labels drawn from vtk scalars); may be used in conjunction with hemi erase_remaining : Boolean set all values not in old_labels to erase_value? erase_labels : list of integers values to erase (set to erase_value) erase_value : integer set vertices with labels in erase_labels to this value output_file : string new vtk file name Returns ------- output_file : string new vtk file name Examples -------- >>> import os >>> from mindboggle.labels.relabel import relabel_surface >>> from mindboggle.utils.plots import plot_surfaces >>> path = os.environ['MINDBOGGLE_DATA'] >>> vtk_file = os.path.join(path, 'arno', 'labels', 'lh.labels.DKT25.manual.vtk') >>> hemi = 'lh' >>> old_labels = [1003,1009,1030] >>> new_labels = [3,9,30] >>> erase_remaining = True >>> erase_labels = [0] >>> erase_value = -1 >>> output_file = '' >>> # >>> relabel_surface(vtk_file, hemi, old_labels, new_labels, erase_remaining, erase_labels, erase_value, output_file) >>> # View >>> plot_surfaces('relabeled_FreeSurfer_cortex_labels.vtk') """ import os import numpy as np from mindboggle.utils.io_vtk import read_vtk, write_vtk # Load labeled vtk surfaces: faces, lines, indices, points, npoints, scalars, \ name, input_vtk = read_vtk(vtk_file, return_first=True, return_array=True) new_scalars = scalars[:] # Raise an error if inputs set incorrectly: if (new_labels and not old_labels) or \ (hemi and hemi not in ['lh','rh']) or \ (erase_remaining and not old_labels): raise IOError("Please check inputs for relabel_surface().") # Loop through unique labels in scalars: ulabels = np.unique(scalars) for label in ulabels: I = np.where(scalars == label)[0] # If label in erase_labels list, replace with erase_value: if label in erase_labels: new_scalars[I] = erase_value # If label in old_labels list, replace with corresponding new label, # and if hemi set, add 1000 or 2000 to the new label: elif label in old_labels and (len(old_labels) == len(new_labels)): new_label = new_labels[old_labels.index(label)] if hemi == 'lh': new_scalars[I] = 1000 + new_label elif hemi == 'rh': new_scalars[I] = 2000 + new_label else: new_scalars[I] = new_label # If labels not set then optionally add hemi value: elif hemi and not new_labels: if hemi == 'lh': new_scalars[I] = 1000 + label elif hemi == 'rh': new_scalars[I] = 2000 + label # If label unaccounted for and erase_remaining, set to erase_value: elif erase_remaining: new_scalars[I] = erase_value # Ensure that the new scalars are integer values: new_scalars = [int(x) for x in new_scalars] # Write output VTK file: if not output_file: output_file = os.path.join(os.getcwd(), 'relabeled_' + os.path.basename(vtk_file)) write_vtk(output_file, points, indices, lines, faces, [new_scalars], ['Labels'], scalar_type='int') if not os.path.exists(output_file): s = "relabel_surface() did not create " + output_file + "." raise (IOError(s)) return output_file
def spectrum_from_file(vtk_file, n_eigenvalues=6, exclude_labels=[-1], normalization=None, area_file=''): """ Compute Laplace-Beltrami spectrum of a 3D shape in a VTK file. Parameters ---------- vtk_file : string the input vtk file n_eigenvalues : integer number of eigenvalues to be computed (the length of the spectrum) exclude_labels : list of integers labels to be excluded normalization : string the method used to normalize eigenvalues ('area' or None) if "area", use area of the 2D structure as in Reuter et al. 2006 area_file : string name of VTK file with surface area scalar values Returns ------- spectrum : list of floats first n_eigenvalues of Laplace-Beltrami spectrum Examples -------- >>> # Spectrum for entire left hemisphere of Twins-2-1: >>> import os >>> from mindboggle.shapes.laplace_beltrami import spectrum_from_file >>> path = os.environ['MINDBOGGLE_DATA'] >>> vtk_file = os.path.join(path, 'arno', 'labels', 'lh.labels.DKT25.manual.vtk') >>> spectrum_from_file(vtk_file, n_eigenvalues=6) Load "Labels" scalars from lh.labels.DKT25.manual.vtk Linear FEM Laplace-Beltrami spectrum: [4.829758648026221e-18, 0.00012841730024672036, 0.00027151815722727465, 0.00032051508471594065, 0.0004701628070486447, 0.0005768904023010338] >>> # Spectrum for label 22 (postcentral) (after running explode_scalars()): >>> import os >>> from mindboggle.shapes.laplace_beltrami import spectrum_from_file >>> path = os.environ['MINDBOGGLE_DATA'] >>> vtk_file = os.path.join(path, 'arno', 'labels', 'label22.vtk') >>> spectrum_from_file(vtk_file, n_eigenvalues=6) Load "scalars" scalars from label22.vtk Linear FEM Laplace-Beltrami spectrum: [6.3469513010430304e-18, 0.0005178862383467466, 0.0017434911095630806, 0.003667561767487689, 0.005429017880363778, 0.006309346984678918] >>> # Loop thru all MB 101 brains >>> from mindboggle.shapes.laplace_beltrami import spectrum_from_file >>> for hemidir in os.listdir(header): >>> print hemidir >>> sulci_file = os.path.join(header, hemidir, "sulci.vtk") >>> spectrum = spectrum_from_file(sulci_file) """ from mindboggle.utils.io_vtk import read_vtk, read_scalars from mindboggle.shapes.laplace_beltrami import spectrum_of_largest faces, u1, u2, points, u4, u5, u6, u7 = read_vtk(vtk_file) # Area file: if area_file: areas, u1 = read_scalars(area_file) else: areas = None spectrum = spectrum_of_largest(points, faces, n_eigenvalues, exclude_labels, normalization, areas) return spectrum
def plot_mask_surface(vtk_file, mask_file='', nonmask_value=-1, masked_output='', remove_nonmask=False, program='vtkviewer', use_colormap=False, colormap_file=''): """ Use vtkviewer or mayavi2 to visualize VTK surface mesh data. If a mask_file is provided, a temporary masked file is saved, and it is this file that is viewed. If using vtkviewer, can optionally provide colormap file or set $COLORMAP environment variable. Parameters ---------- vtk_file : string name of VTK surface mesh file mask_file : string name of VTK surface mesh file to mask vtk_file vertices nonmask_value : integer nonmask (usually background) value masked_output : string temporary masked output file name remove_nonmask : Boolean remove vertices that are not in mask? (otherwise assign nonmask_value) program : string {'vtkviewer', 'mayavi2'} program to visualize VTK file use_colormap : Boolean use Paraview-style XML colormap file set by $COLORMAP env variable? colormap_file : string use colormap in given file if use_colormap==True? if empty and use_colormap==True, use file set by $COLORMAP environment variable Examples -------- >>> import os >>> from mindboggle.utils.plots import plot_mask_surface >>> path = os.environ['MINDBOGGLE_DATA'] >>> vtk_file = os.path.join(path, 'arno', 'labels', 'lh.labels.DKT31.manual.vtk') >>> mask_file = os.path.join(path, 'test_one_label.vtk') >>> nonmask_value = 0 #-1 >>> masked_output = '' >>> remove_nonmask = True >>> program = 'vtkviewer' >>> use_colormap = True >>> colormap_file = '' #'/software/mindboggle_tools/colormap.xml' >>> plot_mask_surface(vtk_file, mask_file, nonmask_value, masked_output, remove_nonmask, program, use_colormap, colormap_file) """ import os import numpy as np from mindboggle.utils.mesh import remove_faces, reindex_faces_points from mindboggle.utils.utils import execute from mindboggle.utils.plots import plot_surfaces from mindboggle.utils.io_vtk import read_scalars, rewrite_scalars, \ read_vtk, write_vtk #------------------------------------------------------------------------- # Filter mesh with non-background values from a second (same-size) mesh: #------------------------------------------------------------------------- if mask_file: mask, name = read_scalars(mask_file, True, True) if not masked_output: masked_output = os.path.join(os.getcwd(), 'temp.vtk') file_to_plot = masked_output #--------------------------------------------------------------------- # Remove nonmask-valued vertices: #--------------------------------------------------------------------- if remove_nonmask: #----------------------------------------------------------------- # Load VTK files: #----------------------------------------------------------------- faces, lines, indices, points, npoints, scalars, scalar_names, \ o1 = read_vtk(vtk_file, True, True) #----------------------------------------------------------------- # Find mask indices, remove nonmask faces, and reindex: #----------------------------------------------------------------- Imask = [i for i,x in enumerate(mask) if x != nonmask_value] mask_faces = remove_faces(faces, Imask) mask_faces, points, \ original_indices = reindex_faces_points(mask_faces, points) #----------------------------------------------------------------- # Write VTK file with scalar values: #----------------------------------------------------------------- if np.ndim(scalars) == 1: scalar_type = type(scalars[0]).__name__ elif np.ndim(scalars) == 2: scalar_type = type(scalars[0][0]).__name__ else: print("Undefined scalar type!") write_vtk(file_to_plot, points, [], [], mask_faces, scalars[original_indices].tolist(), scalar_names, scalar_type=scalar_type) else: scalars, name = read_scalars(vtk_file, True, True) scalars[mask == nonmask_value] = nonmask_value rewrite_scalars(vtk_file, file_to_plot, scalars) else: file_to_plot = vtk_file #------------------------------------------------------------------------- # Display with vtkviewer.py: #------------------------------------------------------------------------- if program == 'vtkviewer': plot_surfaces(file_to_plot, use_colormap=use_colormap, colormap_file=colormap_file) #------------------------------------------------------------------------- # Display with mayavi2: #------------------------------------------------------------------------- elif program == 'mayavi2': cmd = ["mayavi2", "-d", file_to_plot, "-m", "Surface", "&"] execute(cmd, 'os')
def spectrum_per_label(vtk_file, n_eigenvalues=3, exclude_labels=[-1], normalization='area', area_file=''): """ Compute Laplace-Beltrami spectrum per labeled region in a file. Parameters ---------- vtk_file : string name of VTK surface mesh file containing index scalars (labels) n_eigenvalues : integer number of eigenvalues to be computed (the length of the spectrum) exclude_labels : list of integers labels to be excluded normalization : string the method used to normalize eigenvalues ('area' or None) if "area", use area of the 2D structure as in Reuter et al. 2006 area_file : string name of VTK file with surface area scalar values Returns ------- spectrum_lists : list of lists first eigenvalues for each label's Laplace-Beltrami spectrum label_list : list of integers list of unique labels for which spectra are obtained Examples -------- >>> # Spectrum for label 22 (postcentral) in Twins-2-1: >>> import os >>> from mindboggle.shapes.laplace_beltrami import laplacian_per_label >>> path = os.environ['MINDBOGGLE_DATA'] >>> vtk_file = os.path.join(path, 'arno', 'labels', 'lh.labels.DKT25.manual.vtk') >>> area_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.area.vtk') >>> n_eigenvalues = 6 >>> exclude_labels = [0] #[-1] >>> laplacian_per_label(vtk_file, n_eigenvalues, exclude_labels, >>> normalization=None, area_file=area_file) Load "Labels" scalars from lh.labels.DKT25.manual.vtk Load "scalars" scalars from lh.pial.area.vtk 7819 vertices for label 22 Reduced 290134 to 15230 triangular faces Linear FEM Laplace-Beltrami spectrum: ([[6.3469513010430304e-18, 0.0005178862383467463, 0.0017434911095630772, 0.003667561767487686, 0.005429017880363784, 0.006309346984678924]], [22]) """ from mindboggle.utils.io_vtk import read_vtk, read_scalars from mindboggle.utils.mesh import remove_faces from mindboggle.shapes.laplace_beltrami import spectrum_of_largest # Read VTK surface mesh file: faces, u1, u2, points, u4, labels, u5, u6 = read_vtk(vtk_file) # Area file: if area_file: areas, u1 = read_scalars(area_file) else: areas = None # Loop through labeled regions: ulabels = [] [ulabels.append(int(x)) for x in labels if x not in ulabels if x not in exclude_labels] label_list = [] spectrum_lists = [] for label in ulabels: #if label==22: # Determine the indices per label: label_indices = [i for i,x in enumerate(labels) if x == label] print('{0} vertices for label {1}'.format(len(label_indices), label)) # Remove background faces: select_faces = remove_faces(faces, label_indices) # Compute Laplace-Beltrami spectrum for the label: spectrum = spectrum_of_largest(points, select_faces, n_eigenvalues, exclude_labels, normalization, areas) # Append to a list of lists of spectra: spectrum_lists.append(spectrum) label_list.append(label) return spectrum_lists, label_list
def spectrum_per_label(vtk_file, spectrum_size=10, exclude_labels=[-1], normalization='area', area_file='', largest_segment=True): """ Compute Laplace-Beltrami spectrum per labeled region in a file. Parameters ---------- vtk_file : string name of VTK surface mesh file containing index scalars (labels) spectrum_size : integer number of eigenvalues to be computed (the length of the spectrum) exclude_labels : list of integers labels to be excluded normalization : string the method used to normalize eigenvalues ('area' or None) if "area", use area of the 2D structure as in Reuter et al. 2006 area_file : string name of VTK file with surface area scalar values largest_segment : Boolean compute spectrum only for largest segment with a given label? Returns ------- spectrum_lists : list of lists first eigenvalues for each label's Laplace-Beltrami spectrum label_list : list of integers list of unique labels for which spectra are obtained Examples -------- >>> # Uncomment "if label==22:" below to run example: >>> # Spectrum for Twins-2-1 left postcentral (22) pial surface: >>> import os >>> from mindboggle.shapes.laplace_beltrami import spectrum_per_label >>> path = os.environ['MINDBOGGLE_DATA'] >>> vtk_file = os.path.join(path, 'arno', 'labels', 'lh.labels.DKT31.manual.vtk') >>> area_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.area.vtk') >>> spectrum_size = 6 >>> exclude_labels = [0] #[-1] >>> largest_segment = True >>> spectrum_per_label(vtk_file, spectrum_size, exclude_labels, None, >>> area_file, largest_segment) ([[6.3469513010430304e-18, 0.0005178862383467463, 0.0017434911095630772, 0.003667561767487686, 0.005429017880363784, 0.006309346984678924]], [22]) """ from mindboggle.utils.io_vtk import read_vtk, read_scalars from mindboggle.utils.mesh import remove_faces, reindex_faces_points from mindboggle.shapes.laplace_beltrami import fem_laplacian,\ spectrum_of_largest # Read VTK surface mesh file: faces, u1, u2, points, u4, labels, u5, u6 = read_vtk(vtk_file) # Area file: if area_file: areas, u1 = read_scalars(area_file) else: areas = None # Loop through labeled regions: ulabels = [] [ulabels.append(int(x)) for x in labels if x not in ulabels if x not in exclude_labels] label_list = [] spectrum_lists = [] for label in ulabels: #if label == 22: # print("DEBUG: COMPUTE FOR ONLY ONE LABEL") # Determine the indices per label: Ilabel = [i for i,x in enumerate(labels) if x == label] print('{0} vertices for label {1}'.format(len(Ilabel), label)) # Remove background faces: pick_faces = remove_faces(faces, Ilabel) pick_faces, pick_points, o1 = reindex_faces_points(pick_faces, points) # Compute Laplace-Beltrami spectrum for the label: if largest_segment: exclude_labels_inner = [-1] spectrum = spectrum_of_largest(pick_points, pick_faces, spectrum_size, exclude_labels_inner, normalization, areas) else: spectrum = fem_laplacian(pick_points, pick_faces, spectrum_size, normalization) # Append to a list of lists of spectra: spectrum_lists.append(spectrum) label_list.append(label) return spectrum_lists, label_list
def relabel_surface(vtk_file, hemi='', old_labels=[], new_labels=[], output_file=''): """ Relabel surface in a VTK file. Parameters ---------- vtk_file : string input labeled VTK file hemi : string hemisphere ('lh' or 'rh' or '') if set, add 1000 to left and 2000 to right hemisphere labels; old_labels : list of integers old labels (empty list if labels drawn from vtk scalars); may be used in conjunction with hemi new_labels : list of integers new labels (empty list if labels drawn from vtk scalars); may be used in conjunction with hemi output_file : string new vtk file name Returns ------- output_file : string new vtk file name Examples -------- >>> import os >>> from mindboggle.labels.relabel import relabel_surface >>> from mindboggle.utils.plots import plot_vtk >>> path = os.environ['MINDBOGGLE_DATA'] >>> vtk_file = os.path.join(path, 'arno', 'labels', 'lh.labels.DKT25.manual.vtk') >>> hemi = 'lh' >>> old_labels = [] >>> new_labels = [] >>> output_file = '' >>> # >>> relabel_surface(vtk_file, hemi, old_labels, new_labels, output_file) >>> # View >>> plot_vtk('relabeled_lh.labels.DKT25.manual.vtk') """ import os import numpy as np from mindboggle.utils.io_vtk import read_vtk, write_vtk # Load labeled vtk surfaces: faces, lines, indices, points, npoints, scalars, \ name, input_vtk = read_vtk(vtk_file, return_first=True, return_array=True) # Add a hemisphere value to each unique label drawn from scalars: if hemi and not old_labels and not new_labels: ulabels = np.unique(scalars) for label in ulabels: I = np.where(scalars == int(label))[0] if hemi == 'lh': scalars[I] = 1000 + int(label) elif hemi == 'rh': scalars[I] = 2000 + int(label) # OR replace each old label with a corresponding new label # (hemisphere setting optionally adds 1000 or 2000 to the new label): else: for ilabel, new_label in enumerate(new_labels): I = np.where(scalars == int(old_labels[ilabel]))[0] if hemi == 'lh': scalars[I] = 1000 + int(new_label) elif hemi == 'rh': scalars[I] = 2000 + int(new_label) else: scalars[I] = int(new_label) if not output_file: output_file = os.path.join(os.getcwd(), 'relabeled_' + os.path.basename(vtk_file)) write_vtk(output_file, points, indices, lines, faces, [scalars.tolist()], ['Labels']) if not os.path.exists(output_file): raise(IOError(output_file + " not found")) return output_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.utils.io_vtk import read_scalars, rewrite_scalars >>> from mindboggle.utils.mesh import find_neighbors_from_file >>> from mindboggle.features.folds import extract_subfolds >>> from mindboggle.utils.plots import plot_vtk >>> 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_vtk('subfolds.vtk') """ import os import numpy as np from time import time from mindboggle.utils.io_vtk import rewrite_scalars, read_vtk from mindboggle.utils.mesh import find_neighbors from mindboggle.utils.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 realign_boundaries_to_fundus_lines( surf_file, init_label_file, fundus_lines_file, out_label_file=None): """ Fix label boundaries to fundus lines. Parameters ---------- surf_file : file containing the surface geometry in vtk format init_label_file : file containing scalars that represent the initial guess at labels fundus_lines_file : file containing scalars representing fundus lines. out_label_file : if specified, the realigned labels will be writen to this file Returns ------- numpy array representing the realigned label for each surface vertex. """ # import os import numpy as np from mindboggle.labels.labels import extract_borders import mindboggle.utils.graph as go from mindboggle.utils.io_vtk import read_vtk, read_scalars, write_vtk # import mindboggle.utils.kernels as kernels from mindboggle.utils.mesh import find_neighbors # from mindboggle.labels.protocol import dkt_protocol # # protocol = 'DKT25' # sulcus_names, sulcus_label_pair_lists, unique_sulcus_label_pairs, \ # label_names, label_numbers, cortex_names, cortex_numbers, \ # noncortex_names, noncortex_numbers = dkt_protocol(protocol) ## read files faces, _, indices, points, num_points, _, _, _ = read_vtk( surf_file, return_first=True, return_array=True) indices = range(num_points) init_labels, _ = read_scalars(init_label_file, return_first=True, return_array=True) fundus_lines, _ = read_scalars(fundus_lines_file, return_first=True, return_array=True) ## setup seeds from initial label boundaries neighbor_lists = find_neighbors(faces, num_points) # extract all vertices that are on a boundary between labels boundary_indices, label_pairs, _ = extract_borders( indices, init_labels, neighbor_lists, return_label_pairs=True) # split boundary vertices into segments with common boundary pairs. boundary_segments = {} for boundary_index, label_pair in zip(boundary_indices, label_pairs): key = ((label_pair[0], label_pair[1]) if label_pair[0] < label_pair[1] else (label_pair[1], label_pair[0])) if key not in boundary_segments: boundary_segments[key] = [] boundary_segments[key].append(boundary_index) boundary_matrix, boundary_matrix_keys = _build_boundary_matrix( boundary_segments, num_points) # build the affinity matrix affinity_matrix = go.weight_graph( np.array(points), indices, np.array(faces), sigma=10, add_to_graph=False) ## propagate boundaries to fundus line vertices learned_matrix = _propagate_labels( affinity_matrix, boundary_matrix, boundary_indices, 1000, 1) # assign labels to fundus line vertices based on highest probability new_boundaries = -1 * np.ones(init_labels.shape) fundus_line_indices = [i for i, x in enumerate(fundus_lines) if x > 0.5] # TODO: this currently only works for fundus lines that tile the # surface into connected components (which is fine when you want # to test this method on fundus lines generated from manual # labeling). However, to work on real data, fundus lines will # need to be connected together using shortest paths. # split surface into connected components connected_component_faces = _remove_boundary_faces( points, faces, fundus_line_indices) # label components based on most probable label assignment new_labels = _label_components( connected_component_faces, num_points, boundary_indices, learned_matrix, boundary_matrix_keys) # propagate new labels to fill holes label_matrix, label_map = _build_label_matrix(new_labels) new_learned_matrix = _propagate_labels( affinity_matrix, label_matrix, [i for i in range(num_points) if new_labels[i] >= 0], 100, 1) # assign most probable labels for idx in [i for i in range(num_points) if new_labels[i] == -1]: max_idx = np.argmax(new_learned_matrix[idx]) new_labels[idx] = label_map[max_idx] # save if out_label_file is not None: write_vtk(out_label_file, points, faces=faces, scalars=new_labels.tolist()) return new_labels
def extract_fundi(folds, sulci, curv_file, depth_file, min_separation=10, erode_ratio=0.1, erode_min_size=1, save_file=False): """ Extract fundi from folds. A fundus is a branching curve that runs along the deepest and most highly curved portions of a sulcus fold. Steps :: 1. Find fundus endpoints (outer anchors) with find_outer_anchors(). 2. Include inner anchor points. 3. Connect anchor points using connect_points_erosion(). 4. Segment fundi by sulcus definitions. Possible postprocessing step: smooth with smooth_skeleton(). Parameters ---------- folds : list of integers fold number for each vertex curv_file : string surface mesh file in VTK format with mean curvature values depth_file : string surface mesh file in VTK format with rescaled depth values sulci : list of integers sulcus number for each vertex likelihoods : list of integers fundus likelihood value for each vertex min_separation : integer minimum number of edges between inner/outer anchor points erode_ratio : float fraction of indices to test for removal at each iteration in connect_points_erosion() save_file : Boolean save output VTK file? Returns ------- fundi : list of integers fundus numbers for all vertices (-1 for non-fundus vertices) n_fundi : integer number of fundi fundi_file : string (if save_file) name of output VTK file with fundus numbers (-1 for non-fundus vertices) Examples -------- >>> # Extract fundus from one or more folds: >>> single_fold = True >>> import os >>> from mindboggle.utils.io_vtk import read_scalars >>> from mindboggle.features.fundi import extract_fundi >>> from mindboggle.utils.plots import plot_vtk >>> path = os.environ['MINDBOGGLE_DATA'] >>> sulci_file = os.path.join(path, 'arno', 'features', 'sulci.vtk') >>> sulci, name = read_scalars(sulci_file, True, True) >>> curv_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.mean_curvature.vtk') >>> depth_file = os.path.join(path, 'arno', 'shapes', 'travel_depth_rescaled.vtk') >>> folds_file = os.path.join(path, 'arno', 'features', 'folds.vtk') >>> folds, name = read_scalars(folds_file, True, True) >>> if single_fold: >>> fold_number = 2 #11 >>> folds[folds != fold_number] = -1 >>> min_separation = 10 >>> erode_ratio = 0.10 >>> erode_min_size = 10 >>> save_file = True >>> fundi, n_fundi, fundi_file = extract_fundi(folds, sulci, curv_file, >>> depth_file, min_separation, erode_ratio, erode_min_size, save_file) >>> # >>> # View: >>> plot_vtk(fundi_file) """ # Extract a skeleton to connect endpoints in a fold: import os import numpy as np from time import time from mindboggle.utils.io_vtk import read_scalars, read_vtk, rewrite_scalars from mindboggle.utils.compute import median_abs_dev from mindboggle.utils.paths import find_max_values from mindboggle.utils.mesh import find_neighbors_from_file from mindboggle.utils.paths import find_outer_anchors, connect_points_erosion # Load values, threshold, and neighbors: u1,u2,u3, points, npoints, curvs, u4,u5 = read_vtk(curv_file, True,True) depths, name = read_scalars(depth_file, True, True) values = curvs * depths values0 = [x for x in values if x > 0] thr = np.median(values0) + 2 * median_abs_dev(values0) neighbor_lists = find_neighbors_from_file(curv_file) #------------------------------------------------------------------------- # Loop through folds: #------------------------------------------------------------------------- t1 = time() skeletons = [] unique_fold_IDs = [x for x in np.unique(folds) if x != -1] if len(unique_fold_IDs) == 1: print("Extract a fundus from 1 fold...") else: print("Extract a fundus from each of {0} folds...". format(len(unique_fold_IDs))) for fold_ID in unique_fold_IDs: indices_fold = [i for i,x in enumerate(folds) if x == fold_ID] if indices_fold: print(' Fold {0}:'.format(int(fold_ID))) #----------------------------------------------------------------- # Find outer anchor points on the boundary of the surface region, # to serve as fundus endpoints : #----------------------------------------------------------------- outer_anchors, tracks = find_outer_anchors(indices_fold, neighbor_lists, values, depths, min_separation) #----------------------------------------------------------------- # Find inner anchor points: #----------------------------------------------------------------- inner_anchors = find_max_values(points, values, min_separation, thr) #----------------------------------------------------------------- # Connect endpoints to create skeleton: #----------------------------------------------------------------- B = -1 * np.ones(npoints) B[indices_fold] = 1 skeleton = connect_points_erosion(B, neighbor_lists, outer_anchors, inner_anchors, values, erode_ratio, erode_min_size, save_steps=[], save_vtk='') if skeleton: skeletons.extend(skeleton) #------------------------------------------------------------------------- # Create fundi by segmenting skeletons with overlapping sulcus labels: #------------------------------------------------------------------------- fundi = -1 * np.ones(npoints) indices = [x for x in skeletons if sulci[x] != -1] fundi[indices] = sulci[indices] n_fundi = len([x for x in np.unique(fundi) if x != -1]) if n_fundi == 1: sdum = 'fundus' else: sdum = 'fundi' print(' ...Extracted {0} {1} ({2:.2f} seconds)'. format(n_fundi, sdum, time() - t1)) #------------------------------------------------------------------------- # Return fundi, number of fundi, and file name: #------------------------------------------------------------------------- fundi = fundi.tolist() if save_file: fundi_file = os.path.join(os.getcwd(), 'fundi.vtk') rewrite_scalars(curv_file, fundi_file, fundi, 'fundi', folds) else: fundi_file = None return fundi, n_fundi, fundi_file
def extract_borders_2nd_surface(labels_file, mask_file="", values_file=""): """ Extract borders (between labels) on a surface. Options: Mask out values; extract border values on a second surface. Parameters ---------- labels_file : string file name for surface mesh with labels mask_file : string file name for surface mesh with mask (>-1) values values_file : string file name for surface mesh with values to extract along borders Returns ------- border_file : string file name for surface mesh with label borders (-1 background values) border_values : numpy array values for all vertices (-1 for vertices not along label borders) Examples -------- >>> # Extract depth values along label borders in sulci (mask): >>> import os >>> from mindboggle.labels.labels import extract_borders_2nd_surface >>> from mindboggle.utils.plots import plot_vtk >>> path = os.environ['MINDBOGGLE_DATA'] >>> labels_file = os.path.join(path, 'arno', 'labels', 'lh.labels.DKT25.manual.vtk') >>> mask_file = os.path.join(path, 'arno', 'features', 'sulci.vtk') >>> values_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.travel_depth.vtk') >>> # >>> border_file, border_values = extract_borders_2nd_surface(labels_file, mask_file, values_file) >>> # >>> plot_vtk(border_file) """ import os import numpy as np from mindboggle.utils.io_vtk import read_scalars, read_vtk, rewrite_scalars from mindboggle.utils.mesh import find_neighbors from mindboggle.labels.labels import extract_borders # Load labeled surface file faces, foo1, foo2, foo3, npoints, labels, foo4, foo5 = read_vtk(labels_file, return_first=True, return_array=True) # Detect borders neighbor_lists = find_neighbors(faces, npoints) indices_borders, foo1, foo2 = extract_borders(range(npoints), labels, neighbor_lists) # Filter values with label borders border_values = -1 * np.ones(npoints) if values_file: values, name = read_scalars(values_file, return_first=True, return_array=True) border_values[indices_borders] = values[indices_borders] else: border_values[indices_borders] = 1 # Mask values (for mask >-1) if mask_file: mask_values, name = read_scalars(mask_file) else: mask_values = [] # Write out label boundary vtk file border_file = os.path.join(os.getcwd(), "borders_" + os.path.basename(labels_file)) rewrite_scalars(labels_file, border_file, border_values, "label_borders_in_mask", mask_values) if not os.path.exists(border_file): raise (IOError(border_file + " not found")) return border_file, border_values
def write_vertex_measures( table_file, labels_or_file, sulci=[], fundi=[], affine_transform_file="", transform_format="itk", area_file="", mean_curvature_file="", travel_depth_file="", geodesic_depth_file="", convexity_file="", thickness_file="", delimiter=",", ): """ Make a table of shape values per vertex. Parameters ---------- table_file : output filename (without path) labels_or_file : list or string label number for each vertex or name of VTK file with index scalars sulci : list of integers indices to sulci, one per vertex, with -1 indicating no sulcus fundi : list of integers indices to fundi, one per vertex, with -1 indicating no fundus affine_transform_file : string affine transform file to standard space transform_format : string format for transform file Ex: 'txt' for text, 'itk' for ITK, and 'mat' for Matlab format area_file : string name of VTK file with surface area scalar values mean_curvature_file : string name of VTK file with mean curvature scalar values travel_depth_file : string name of VTK file with travel depth scalar values geodesic_depth_file : string name of VTK file with geodesic depth scalar values convexity_file : string name of VTK file with convexity scalar values thickness_file : string name of VTK file with thickness scalar values delimiter : string delimiter between columns, such as ',' Returns ------- shape_table : table file name for vertex shape values Examples -------- >>> import os >>> from mindboggle.utils.io_vtk import read_scalars >>> from mindboggle.tables.all_shapes import write_vertex_measures >>> # >>> table_file = 'vertex_shapes.csv' >>> path = os.environ['MINDBOGGLE_DATA'] >>> labels_or_file = os.path.join(path, 'arno', 'labels', 'lh.labels.DKT25.manual.vtk') >>> sulci_file = os.path.join(path, 'arno', 'features', 'sulci.vtk') >>> fundi_file = os.path.join(path, 'arno', 'features', 'fundi.vtk') >>> sulci, name = read_scalars(sulci_file) >>> fundi, name = read_scalars(fundi_file) >>> affine_transform_file = os.path.join(path, 'arno', 'mri', >>> 't1weighted_brain.MNI152Affine.txt') >>> transform_format = 'itk' >>> area_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.area.vtk') >>> mean_curvature_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.mean_curvature.vtk') >>> travel_depth_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.travel_depth.vtk') >>> geodesic_depth_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.geodesic_depth.vtk') >>> convexity_file = '' >>> thickness_file = '' >>> delimiter = ',' >>> # >>> write_vertex_measures(table_file, labels_or_file, sulci, fundi, >>> affine_transform_file, transform_format, area_file, >>> mean_curvature_file, travel_depth_file, geodesic_depth_file, >>> convexity_file, thickness_file, delimiter) """ import os import numpy as np from mindboggle.utils.io_vtk import read_scalars, read_vtk, apply_affine_transform from mindboggle.utils.io_table import write_columns # Make sure inputs are lists: if isinstance(labels_or_file, np.ndarray): labels = labels_or_file.tolist() elif isinstance(labels_or_file, list): labels = labels_or_file elif isinstance(labels_or_file, str): labels, name = read_scalars(labels_or_file) if isinstance(sulci, np.ndarray): sulci = sulci.tolist() if isinstance(fundi, np.ndarray): fundi = fundi.tolist() # Feature names and corresponding feature lists: feature_names = ["label", "sulcus", "fundus"] feature_lists = [labels, sulci, fundi] # Shape names corresponding to shape files below: shape_names = ["area", "mean curvature", "travel depth", "geodesic depth", "convexity", "thickness"] # Load shape files as a list of numpy arrays of per-vertex shape values: shape_files = [ area_file, mean_curvature_file, travel_depth_file, geodesic_depth_file, convexity_file, thickness_file, ] # Append columns of per-vertex scalar values: columns = [] column_names = [] for ifeature, values in enumerate(feature_lists): if values: columns.append(values) column_names.append(feature_names[ifeature]) first_pass = True for ishape, shape_file in enumerate(shape_files): if os.path.exists(shape_file): if first_pass: u1, u2, u3, points, u4, scalars, u5, u6 = read_vtk(shape_file) columns.append(points) column_names.append("coordinates") first_pass = False if affine_transform_file: affine_points, foo1 = apply_affine_transform(affine_transform_file, points, transform_format) columns.append(affine_points) column_names.append("coordinates in standard space") else: scalars, name = read_scalars(shape_file) if len(scalars): columns.append(scalars) column_names.append(shape_names[ishape]) # Prepend with column of indices and write table shapes_table = os.path.join(os.getcwd(), table_file) write_columns(range(len(columns[0])), "index", shapes_table, delimiter) write_columns(columns, column_names, shapes_table, delimiter, quote=True, input_table=shapes_table) return shapes_table
def close_surface_pair_from_files(patch_surface1, whole_surface2, background_value=-1, output_vtk=''): """ 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 :: The first VTK file contains scalar values different than background for a surface patch. The second VTK file contains the (entire) surface 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 ---------- patch_surface1 : string vtk file with surface patch of non-background scalar values whole_surface2 : string second vtk file with 1-to-1 vertex correspondence with patch_surface1 (whole surface so as to derive vertex neighbor lists) background_value : integer scalar value for background vertices output_vtk : string output vtk file name with closed surface patch Returns ------- output_vtk : string output vtk file name with closed surface patch Examples -------- >>> import os >>> from mindboggle.utils.morph import close_surface_pair_from_files >>> from mindboggle.utils.plots import plot_surfaces >>> from mindboggle.utils.io_vtk 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') >>> points = read_points(folds_file) >>> folds, name = read_scalars(folds_file, True, True) >>> fold_number = 11 >>> folds[folds != 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) >>> write_vtk(patch_surface1, points, [], [], faces, folds, name) >>> write_vtk(whole_surface2, points2, [], [], faces, folds, name) >>> background_value = -1 >>> output_vtk = '' >>> close_surface_pair_from_files(patch_surface1, whole_surface2, background_value, output_vtk) >>> # View: >>> plot_surfaces('closed.vtk') # doctest: +SKIP """ import os import numpy as np from mindboggle.utils.io_vtk import read_vtk, write_vtk from mindboggle.utils.morph import close_surface_pair # Read VTK surface mesh files: u1, u2, u3, points1, N, scalars, name, u4 = read_vtk(patch_surface1, True, True) faces, u1, u2, points2, N, u3, u4, u5 = read_vtk(whole_surface2, True, True) # Close surface: closed_faces, closed_points, closed_scalars = close_surface_pair(faces, points1, points2, scalars, background_value) # Write output file: if not output_vtk: output_vtk = os.path.join(os.getcwd(), 'closed.vtk') # closed_scalars is a list if np.ndim(closed_scalars) == 1: scalar_type = type(closed_scalars[0]).__name__ elif np.ndim(closed_scalars) == 2: scalar_type = type(closed_scalars[0][0]).__name__ else: print("Undefined scalar type!") write_vtk(output_vtk, closed_points, [], [], closed_faces, closed_scalars, name, scalar_type=scalar_type) return output_vtk
def write_average_face_values_per_label( input_indices_vtk, input_values_vtk="", area_file="", output_stem="", exclude_values=[-1], background_value=-1 ): """ Write out a separate VTK file for each integer (>-1) in (the first) scalar list of an input VTK file. Optionally write the values drawn from a second VTK file. Parameters ---------- input_indices_vtk : string path of the input VTK file that contains indices as scalars input_values_vtk : string path of the input VTK file that contains values as scalars output_stem : string path and stem of the output VTK file exclude_values : list or array values to exclude background_value : integer or float background value in output VTK files scalar_name : string name of a lookup table of scalars values Examples -------- >>> import os >>> from mindboggle.utils.io_table import write_average_face_values_per_label >>> path = os.environ['MINDBOGGLE_DATA'] >>> input_indices_vtk = os.path.join(path, 'allen', 'labels', 'lh.DKTatlas100.gcs.vtk') >>> input_values_vtk = os.path.join(path, 'allen', 'shapes', 'lh.thickness.vtk') >>> area_file = os.path.join(path, 'allen', 'shapes', 'lh.pial.area.vtk') >>> output_stem = 'labels_thickness' >>> exclude_values = [-1] >>> background_value = -1 >>> # >>> write_average_face_values_per_label(input_indices_vtk, >>> input_values_vtk, area_file, output_stem, exclude_values, background_value) >>> # >>> # View: >>> #example_vtk = os.path.join(os.getcwd(), output_stem + '0.vtk') >>> #from mindboggle.utils.plots import plot_vtk >>> #plot_vtk(example_vtk) """ import os import numpy as np from mindboggle.utils.io_vtk import read_scalars, read_vtk, write_vtk from mindboggle.utils.io_table import write_columns from mindboggle.utils.mesh import remove_faces # Load VTK file: faces, lines, indices, points, npoints, scalars, scalar_names, foo1 = read_vtk(input_indices_vtk, True, True) if area_file: area_scalars, name = read_scalars(area_file, True, True) print("Explode the scalar list in {0}".format(os.path.basename(input_indices_vtk))) if input_values_vtk != input_indices_vtk: values, name = read_scalars(input_values_vtk, True, True) print( "Explode the scalar list of values in {0} " "with the scalar list of indices in {1}".format( os.path.basename(input_values_vtk), os.path.basename(input_indices_vtk) ) ) else: values = np.copy(scalars) # Loop through unique (non-excluded) scalar values: unique_scalars = [int(x) for x in np.unique(scalars) if x not in exclude_values] for scalar in unique_scalars: keep_indices = [x for sublst in faces for x in sublst] new_faces = remove_faces(faces, keep_indices) # Create array and indices for scalar value: select_scalars = np.copy(scalars) select_scalars[scalars != scalar] = background_value scalar_indices = [i for i, x in enumerate(select_scalars) if x == scalar] print(" Scalar {0}: {1} vertices".format(scalar, len(scalar_indices))) # --------------------------------------------------------------------- # For each face, average vertex values: # --------------------------------------------------------------------- output_table = os.path.join(os.getcwd(), output_stem + str(scalar) + ".csv") columns = [] for face in new_faces: values = [] for index in face: if area_file: values.append(scalars[index] / area_scalars[index]) else: values.append(scalars[index]) columns.append(np.mean(values)) # ----------------------------------------------------------------- # Write to table: # ----------------------------------------------------------------- write_columns(columns, "", output_table, delimiter=",", quote=False)
def extract_sulci(labels_file, folds_or_file, hemi, sulcus_label_pair_lists, unique_sulcus_label_pairs, min_boundary=1, sulcus_names=[]): """ Identify sulci from folds in a brain surface according to a labeling protocol that includes a list of label pairs defining each sulcus. A fold is a group of connected, deep vertices. Steps for each fold :: 1. Remove fold if it has fewer than two labels. 2. Remove fold if its labels do not contain a sulcus label pair. 3. Find vertices with labels that are in only one of the fold's label boundary pairs. Assign the vertices the sulcus with the label pair if they are connected to the label boundary for that pair. 4. If there are remaining vertices, segment into sets of vertices connected to label boundaries, and assign a unique ID to each segment. Parameters ---------- labels_file : string file name for surface mesh VTK containing labels for all vertices folds_or_file : list or string fold number for each vertex or name of VTK file containing folds scalars hemi : string hemisphere ('lh' or 'rh') sulcus_label_pair_lists : list of two lists of multiple lists of integer pairs list containing left and right lists, each with multiple lists of integer pairs corresponding to label boundaries / sulcus / fundus unique_sulcus_label_pairs : list of unique pairs of integers unique label pairs min_boundary : integer minimum number of vertices for a sulcus label boundary segment sulcus_names : list of strings names of sulci Returns ------- sulci : list of integers sulcus numbers for all vertices (-1 for non-sulcus vertices) n_sulci : integers number of sulci sulci_file : string name of output VTK file with sulcus numbers (-1 for non-sulcus vertices) Examples -------- >>> import os >>> from mindboggle.utils.io_vtk import read_scalars, rewrite_scalars >>> from mindboggle.labels.protocol import dkt_protocol >>> from mindboggle.features.sulci import extract_sulci >>> from mindboggle.utils.plots import plot_vtk >>> path = os.environ['MINDBOGGLE_DATA'] >>> # Load labels, folds, neighbor lists, and sulcus names and label pairs >>> labels_file = os.path.join(path, 'arno', 'labels', 'relabeled_lh.DKTatlas40.gcs.vtk') >>> folds_file = os.path.join(path, 'arno', 'features', 'folds.vtk') >>> folds_or_file, name = read_scalars(folds_file) >>> protocol = 'DKT31' >>> hemi = 'lh' >>> sulcus_names, sulcus_label_pair_lists, unique_sulcus_label_pairs, ... label_names, label_numbers, cortex_names, cortex_numbers, ... noncortex_names, noncortex_numbers = dkt_protocol(protocol) >>> min_boundary = 10 >>> # >>> sulci, n_sulci, sulci_file = extract_sulci(labels_file, folds_or_file, >>> hemi, sulcus_label_pair_lists, unique_sulcus_label_pairs, >>> min_boundary, sulcus_names) >>> # View: >>> plot_vtk('sulci.vtk') """ import os from time import time import numpy as np from mindboggle.utils.io_vtk import read_scalars, read_vtk, rewrite_scalars from mindboggle.utils.mesh import find_neighbors from mindboggle.labels.labels import extract_borders from mindboggle.utils.segment import propagate, segment # Load fold numbers if folds_or_file is a string if isinstance(folds_or_file, str): folds, name = read_scalars(folds_or_file) elif isinstance(folds_or_file, list): folds = folds_or_file if hemi == 'lh': sulcus_label_pair_lists = sulcus_label_pair_lists[0] elif hemi == 'rh': sulcus_label_pair_lists = sulcus_label_pair_lists[1] else: print("Warning: hemisphere not properly specified ('lh' or 'rh').") # Load points, faces, and neighbors faces, foo1, foo2, points, npoints, labels, foo3, foo4 = read_vtk(labels_file) neighbor_lists = find_neighbors(faces, npoints) # Array of sulcus IDs for fold vertices, initialized as -1. # Since we do not touch gyral vertices and vertices whose labels # are not in the label list, or vertices having only one label, # their sulcus IDs will remain -1. sulci = -1 * np.ones(npoints) #------------------------------------------------------------------------- # Loop through folds #------------------------------------------------------------------------- fold_numbers = [int(x) for x in np.unique(folds) if x > -1] n_folds = len(fold_numbers) print("Extract sulci from {0} folds...".format(n_folds)) t0 = time() for n_fold in fold_numbers: fold = [i for i,x in enumerate(folds) if x == n_fold] len_fold = len(fold) # List the labels in this fold (greater than zero) fold_labels = [labels[x] for x in fold] unique_fold_labels = [int(x) for x in np.unique(fold_labels) if x > 0] #--------------------------------------------------------------------- # NO MATCH -- fold has fewer than two labels #--------------------------------------------------------------------- if len(unique_fold_labels) < 2: # Ignore: sulci already initialized with -1 values if not unique_fold_labels: print(" Fold {0} ({1} vertices): NO MATCH -- fold has no labels". format(n_fold, len_fold)) else: print(" Fold {0} ({1} vertices): " "NO MATCH -- fold has only one label ({2})". format(n_fold, len_fold, unique_fold_labels[0])) # Ignore: sulci already initialized with -1 values else: # Find all label boundary pairs within the fold indices_fold_pairs, fold_pairs, unique_fold_pairs = extract_borders( fold, labels, neighbor_lists, ignore_values=[], return_label_pairs=True) # Find fold label pairs in the protocol (pairs are already sorted) fold_pairs_in_protocol = [x for x in unique_fold_pairs if x in unique_sulcus_label_pairs] if unique_fold_labels: print(" Fold {0} labels: {1} ({2} vertices)".format(n_fold, ', '.join([str(x) for x in unique_fold_labels]), len_fold)) #----------------------------------------------------------------- # NO MATCH -- fold has no sulcus label pair #----------------------------------------------------------------- if not fold_pairs_in_protocol: print(" Fold {0}: NO MATCH -- fold has no sulcus label pair". format(n_fold, len_fold)) #----------------------------------------------------------------- # Possible matches #----------------------------------------------------------------- else: print(" Fold {0} label pairs in protocol: {1}".format(n_fold, ', '.join([str(x) for x in fold_pairs_in_protocol]))) # Labels in the protocol (includes repeats across label pairs) labels_in_pairs = [x for lst in fold_pairs_in_protocol for x in lst] # Labels that appear in one or more than one sulcus label boundary unique_labels = [] nonunique_labels = [] for label in np.unique(labels_in_pairs): if len([x for x in labels_in_pairs if x == label]) == 1: unique_labels.append(label) else: nonunique_labels.append(label) #------------------------------------------------------------- # Vertices whose labels are in only one sulcus label pair #------------------------------------------------------------- # Find vertices with a label that is in only one of the fold's # label pairs (the other label in the pair can exist # in other pairs). Assign the vertices the sulcus with the label # pair if they are connected to the label boundary for that pair. #------------------------------------------------------------- if len(unique_labels): for pair in fold_pairs_in_protocol: # If one or both labels in label pair is/are unique unique_labels_in_pair = [x for x in pair if x in unique_labels] n_unique = len(unique_labels_in_pair) if n_unique: ID = [i for i,x in enumerate(sulcus_label_pair_lists) if pair in x][0] # Construct seeds from label boundary vertices # (fold_pairs and pair already sorted) indices_pair = [x for i,x in enumerate(indices_fold_pairs) if fold_pairs[i] == pair] # Identify vertices with unique label(s) in pair indices_unique_labels = [fold[i] for i,x in enumerate(fold_labels) if x in unique_sulcus_label_pairs] # Propagate from seeds to labels in label pair sulci2 = segment(indices_unique_labels, neighbor_lists, min_region_size=1, seed_lists=[indices_pair], keep_seeding=False, spread_within_labels=True, labels=labels) sulci[sulci2 > -1] = ID # Print statement if n_unique == 1: ps1 = '1 label' else: ps1 = 'Both labels' if len(sulcus_names): ps2 = sulcus_names[ID] else: ps2 = '' print(" {0} unique to one fold pair: {1} {2}". format(ps1, ps2, unique_labels_in_pair)) #------------------------------------------------------------- # Vertex labels shared by multiple label pairs #------------------------------------------------------------- # Propagate labels from label borders to vertices with labels # that are shared by multiple label pairs in the fold. #------------------------------------------------------------- if len(nonunique_labels): # For each label shared by different label pairs for label in nonunique_labels: # Print statement print(" Propagate sulcus label borders with label {0}". format(int(label))) # Construct seeds from label boundary vertices seeds = -1 * np.ones(len(points)) for ID, label_pair_list in enumerate(sulcus_label_pair_lists): label_pairs = [x for x in label_pair_list if label in x] for label_pair in label_pairs: indices_pair = [x for i,x in enumerate(indices_fold_pairs) if np.sort(fold_pairs[i]).tolist() == label_pair] if indices_pair: # Do not include short boundary segments if min_boundary > 1: indices_pair2 = [] seeds2 = segment(indices_pair, neighbor_lists) for seed2 in range(int(max(seeds2))+1): iseed2 = [i for i,x in enumerate(seeds2) if x == seed2] if len(iseed2) >= min_boundary: indices_pair2.extend(iseed2) else: if len(iseed2) == 1: print(" Remove assignment " "of ID {0} from 1 vertex". format(seed2)) else: print(" Remove assignment " "of ID {0} from {1} vertices". format(seed2, len(iseed2))) indices_pair = indices_pair2 # Assign sulcus IDs to seeds seeds[indices_pair] = ID # Identify vertices with the label label_array = -1 * np.ones(len(points)) indices_label = [fold[i] for i,x in enumerate(fold_labels) if x == label] if len(indices_label): label_array[indices_label] = 1 # Propagate from seeds to vertices with label #indices_seeds = [] #for seed in range(int(max(seeds))+1): # indices_seeds.append([i for i,x in enumerate(seeds) # if x == seed]) #sulci2 = segment(indices_label, neighbor_lists, # 50, indices_seeds, False, True, labels) sulci2 = propagate(points, faces, label_array, seeds, sulci, max_iters=10000, tol=0.001, sigma=5) sulci[sulci2 > -1] = sulci2[sulci2 > -1] #------------------------------------------------------------------------- # Print out assigned sulci #------------------------------------------------------------------------- sulcus_numbers = [int(x) for x in np.unique(sulci) if x > -1] n_sulci = len(sulcus_numbers) print("Extracted {0} sulci from {1} folds ({2:.1f}s):". format(n_sulci, n_folds, time()-t0)) if len(sulcus_names): for sulcus_number in sulcus_numbers: print(" {0}: {1}".format(sulcus_number, sulcus_names[sulcus_number])) else: print(" " + ", ".join([str(x) for x in sulcus_numbers])) #------------------------------------------------------------------------- # Print out unresolved sulci #------------------------------------------------------------------------- unresolved = [i for i in range(len(sulcus_label_pair_lists)) if i not in sulcus_numbers] if len(unresolved) == 1: print("The following sulcus is unaccounted for:") else: print("The following {0} sulci are unaccounted for:".format(len(unresolved))) if len(sulcus_names): for sulcus_number in unresolved: print(" {0}: {1}".format(sulcus_number, sulcus_names[sulcus_number])) else: print(" " + ", ".join([str(x) for x in unresolved])) #------------------------------------------------------------------------- # Return sulci, number of sulci, and file name #------------------------------------------------------------------------- sulci_file = os.path.join(os.getcwd(), 'sulci.vtk') rewrite_scalars(labels_file, sulci_file, sulci, 'sulci', sulci) sulci.tolist() return sulci, n_sulci, sulci_file
def zernike_moments_per_label(vtk_file, order=10, exclude_labels=[-1], scale_input=True, decimate_fraction=0, decimate_smooth=25): """ Compute the Zernike moments per labeled region in a file. Optionally decimate the input mesh. Parameters ---------- vtk_file : string name of VTK surface mesh file containing index scalars (labels) order : integer number of moments to compute exclude_labels : list of integers labels to be excluded scale_input : Boolean translate and scale each object so it is bounded by a unit sphere? (this is the expected input to zernike_moments()) decimate_fraction : float fraction of mesh faces to remove for decimation (1 for no decimation) decimate_smooth : integer number of smoothing steps for decimation Returns ------- descriptors_lists : list of lists of floats Zernike descriptors per label label_list : list of integers list of unique labels for which moments are computed Examples -------- >>> # Uncomment "if label==22:" below to run example: >>> # Twins-2-1 left postcentral (22) pial surface: >>> import os >>> from mindboggle.shapes.zernike.zernike import zernike_moments_per_label >>> path = os.environ['MINDBOGGLE_DATA'] >>> vtk_file = os.path.join(path, 'arno', 'labels', 'lh.labels.DKT25.manual.vtk') >>> order = 3 >>> exclude_labels = [0] >>> scale_input = True >>> zernike_moments_per_label(vtk_file, order, exclude_labels, scale_input) ([[0.00528486237819844, 0.009571754617699853, 0.0033489494903015944, 0.00875603468268444, 0.0015879536633349918, 0.0008080165707033097]], [22]) ([[0.0018758013185778298, 0.001757973693050823, 0.002352403177686726, 0.0032281044369938286, 0.002215900343702539, 0.0019646380916703856]], [14.0]) Arthur Mikhno's result: 1.0e+07 * 0.0000 0.0179 0.0008 4.2547 0.0534 4.4043 """ import numpy as np from mindboggle.utils.io_vtk import read_vtk from mindboggle.utils.mesh import remove_faces from mindboggle.shapes.zernike.zernike import zernike_moments min_points_faces = 4 #------------------------------------------------------------------------- # Read VTK surface mesh file: #------------------------------------------------------------------------- faces, u1,u2, points, u3, labels, u4,u5 = read_vtk(vtk_file) #------------------------------------------------------------------------- # Loop through labeled regions: #------------------------------------------------------------------------- ulabels = [x for x in np.unique(labels) if x not in exclude_labels] label_list = [] descriptors_lists = [] for label in ulabels: #if label == 22: # print("DEBUG: COMPUTE FOR ONLY ONE LABEL") if label == 14: #--------------------------------------------------------------------- # Determine the indices per label: #--------------------------------------------------------------------- Ilabel = [i for i,x in enumerate(labels) if x == label] print(' {0} vertices for label {1}'.format(len(Ilabel), label)) if len(Ilabel) > min_points_faces: #----------------------------------------------------------------- # Remove background faces: #----------------------------------------------------------------- pick_faces = remove_faces(faces, Ilabel) if len(pick_faces) > min_points_faces: #------------------------------------------------------------- # Compute Zernike moments for the label: #------------------------------------------------------------- descriptors = zernike_moments(points, pick_faces, order, scale_input, decimate_fraction, decimate_smooth) #------------------------------------------------------------- # Append to a list of lists of spectra: #------------------------------------------------------------- descriptors_lists.append(descriptors) label_list.append(label) return descriptors_lists, label_list
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.utils.io_vtk import read_vtk, read_scalars, write_vtk from mindboggle.utils.mesh import find_neighbors, remove_faces from mindboggle.utils.segment import extract_borders from mindboggle.utils.compute import source_to_target_distances from mindboggle.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 getFeatures(InputFiles, Type, Options): '''Loads input files of different types and extraction types, and pass them to functions that really does fundi/pits/sulci extraction Parameters =========== Type: string If Types == 'FreeSurfer, the VTK files should at least be a curvature/convexity and a surface file. If Type == 'vtk', The user needs to specify using which map to threshold the surface and using which map to extract pits and fundi. mapThreshold: list a list of per-vertex values that will be used to threshold the cortical surface to get sulcal basins, e.g., curvature/convexity mapExtract: list a list of per-vertex values that will be used to extract fundi/pits, e.g., curvature/convexity mapFeature: lists of lists of floats lists of list of per-vertex values that will be used as structure feature vectors, e.g., thickness PrefixBasin: string the prefix for all outputs that are only related to basin extraction, e.g., connected components, basins and gyri. PrefixExtract: string the prefix for all outputs that are the result of extraction, e.g., pits and fundi. Maps: dictionary Keys are scalar names, e.g., depth and meancurv. Values are float-value lists, representing maps. Each map is of size 1 by #vertices Notes ====== 12/23/2011: We are now rewriting the interface from getFundi to libbasin.getBaisn, fundiFromPits and fundiFromSkel Now variables passed into them are data rather than file names ''' if Type == 'FreeSurfer': print "\t FreeSurfer mode\n" [SurfFile, ThickFile, CurvFile, ConvFile,\ FundiVTK, PitsVTK, SulciVTK, Use, SulciThld]\ = InputFiles Clouchoux = False Maps = {} if ThickFile != "": Maps["thickness"] = freesurfer.read_curvature(ThickFile) if CurvFile != "": Maps["meancurv"] = freesurfer.read_curvature(CurvFile) if ConvFile != "": Maps["conv"] = freesurfer.read_curvature(ConvFile) if Use == 'conv': Extract_Sulci_on_Map = "conv" elif Use == 'curv': Extract_Sulci_on_Map = "meancurv" else: print "[ERROR] Unrecognized map to use:", Use exit() Mesh = freesurfer.read_surface(SurfFile) Extract_Fundi_on_Map = "conv" elif Type == 'vtk': print "\t Joachim's VTK mode\n" [DepthVTK, ConvexityFile, ThickFile, MeanCurvVTK, GaussCurvVTK, FundiVTK, PitsVTK, SulciVTK, SulciThld, Clouchoux] = InputFiles Maps = {} print " Loading depth map" Faces, Lines, Vertexes, Points, nPoints, Depth, name, input_vtk = io_vtk.read_vtk(DepthVTK) Maps['depth'] = Depth if MeanCurvVTK != "": print " Loading mean curvature map" Faces, Lines, Vertexes, Points, nPoints, Maps['meancurv'], name, input_vtk = io_vtk.read_vtk(MeanCurvVTK) if GaussCurvVTK != "": print " Loading Gaussian curvature map" Faces, Lines, Vertexes, Points, nPoints, Maps['gausscurv'], name, input_vtk = io_vtk.read_vtk(GaussCurvVTK) if ThickFile != '': Maps['thickness'] = freesurfer.read_curvature(ThickFile) if ConvexityFile != '': Maps['sulc'] = freesurfer.read_curvature(ConvexityFile) Mesh = [Vertexes, Faces] Extract_Sulci_on_Map = 'depth' # This will become an option for users later. Extract_Fundi_on_Map = 'depth' # This will become an option for users later. ## common parts for both FreeSurfer and vtk type if Clouchoux: libbasin.getBasin_and_Pits(Maps, Mesh, SulciVTK, PitsVTK, SulciThld = SulciThld, PitsThld =0, Quick=False, Clouchoux=True, SulciMap =Extract_Sulci_on_Map) # extract depth map from sulci and pits from mean and Gaussian curvatures else: libbasin.getBasin_and_Pits(Maps, Mesh, SulciVTK, PitsVTK, SulciThld = SulciThld, PitsThld =0, Quick=False, Clouchoux=False, SulciMap =Extract_Sulci_on_Map) # by default, extract sulci and pits from depth map Pits=io_vtk.read_vertices(PitsVTK) fundiFromPits(Pits, Maps, Mesh, FundiVTK, SulciThld, Extract_Sulci_on_Map, Extract_Fundi_on_Map)
def apply_affine_transform(transform_file, vtk_or_points, transform_format='itk', vtk_file_stem='affine_'): """ Transform coordinates using an affine matrix. Parameters ---------- transform file : string name of affine transform file vtk_or_points : string or list of lists of three integers name of VTK file containing point coordinate data, or the data (if vtk file, assumes scalars are a list of floats or integers) transform_format : string format for transform file (currently 'itk'); complications arise with other formats, such as 'txt' for text, or 'mat' for Matlab format, since software-specific assignment of parameters such as the origin need to be taken into account vtk_file_stem : string save transformed coordinates in a vtk file with this file append (empty string if vtk_or_points is points) Returns ------- affine_points : list of lists of floats transformed coordinates output_file : string or None (if not vtk_file_stem or vtk_or_points is points) name of VTK file containing transformed point data Examples -------- >>> import os >>> from mindboggle.utils.io_vtk import apply_affine_transform >>> from mindboggle.utils.plots import plot_surfaces >>> #path = os.environ['MINDBOGGLE_DATA'] >>> #transform_file = os.path.join(path, 'arno', 'mri', >>> # 't1weighted_brain.MNI152Affine.txt') >>> transform_file = '/Users/arno/mindboggle_working/OASIS-TRT-20-1/Mindboggle/Compose_affine_transform/affine.txt' >>> vtk_or_points = '/Users/arno/mindboggle_working/OASIS-TRT-20-1/Mindboggle/_hemi_lh/Surface_to_vtk/lh.pial.vtk' >>> transform_format = 'itk' >>> vtk_file_stem = True >>> affine_points, output_file = apply_affine_transform(transform_file, vtk_or_points, transform_format, vtk_file_stem) >>> # View >>> plot_surfaces('affine_lh.pial.vtk') """ import os import numpy as np from scipy.io import loadmat from mindboggle.utils.io_vtk import read_vtk, write_vtk, read_itk_transform from mindboggle.utils.ants import antsApplyTransformsToPoints # Read affine transform file: if transform_format == 'itk': #pass transform = read_itk_transform(transform_file) elif transform_format == 'txt': transform = np.loadtxt(transform_file) elif transform_format == 'mat': transform = loadmat(transform_file) else: import sys sys.exit('Transform file format not understood.') # Read VTK file: if isinstance(vtk_or_points, str): faces, lines, indices, points, npoints, scalars, name, \ foo1 = read_vtk(vtk_or_points) points = np.array(points) elif isinstance(vtk_or_points, list): points = np.array(vtk_or_points) vtk_file_stem = '' elif isinstance(vtk_or_points, np.ndarray): points = vtk_or_points.copy() vtk_file_stem = '' # Transform points: if transform_format == 'itk': affine_points = antsApplyTransformsToPoints(points, [transform_file], [0]) #points = np.concatenate((points, # np.ones((np.shape(points)[0],1))), axis=1) #affine_points = np.transpose(np.dot(transform, # np.transpose(points)))[:,0:3] #affine_points = [x.tolist() for x in affine_points] else: points = np.concatenate((points, np.ones((np.shape(points)[0],1))), axis=1) affine_points = np.transpose(np.dot(transform, np.transpose(points)))[:,0:3] affine_points = [x.tolist() for x in affine_points] # Write transformed VTK file: if vtk_file_stem and isinstance(vtk_or_points, str): output_file = os.path.join(os.getcwd(), vtk_file_stem + os.path.basename(vtk_or_points)) if np.size(scalars): if np.ndim(scalars) == 1: scalar_type = type(scalars[0]).__name__ elif np.ndim(scalars) == 2: scalar_type = type(scalars[0][0]).__name__ else: print("Undefined scalar type!") else: scalars = [] scalar_type = 'int' write_vtk(output_file, affine_points, indices, lines, faces, scalars, name, scalar_type) else: output_file = None return affine_points, output_file
def extract_fundi(folds, curv_file, depth_file, min_separation=10, erode_ratio=0.1, erode_min_size=1, save_file=False): """ Extract fundi from folds. A fundus is a branching curve that runs along the deepest and most highly curved portions of a fold. Steps :: 1. Find fundus endpoints (outer anchors) with find_outer_anchors(). 2. Include inner anchor points. 3. Connect anchor points using connect_points_erosion(); inner anchors are removed if they result in endpoints. Parameters ---------- folds : numpy array or list of integers fold number for each vertex curv_file : string surface mesh file in VTK format with mean curvature values depth_file : string surface mesh file in VTK format with rescaled depth values likelihoods : list of integers fundus likelihood value for each vertex min_separation : integer minimum number of edges between inner/outer anchor points erode_ratio : float fraction of indices to test for removal at each iteration in connect_points_erosion() save_file : Boolean save output VTK file? Returns ------- fundus_per_fold : list of integers fundus numbers for all vertices, labeled by fold (-1 for non-fundus vertices) n_fundi_in_folds : integer number of fundi fundus_per_fold_file : string (if save_file) output VTK file with fundus numbers (-1 for non-fundus vertices) Examples -------- >>> # Extract fundus from one or more folds: >>> single_fold = True >>> import os >>> from mindboggle.utils.io_vtk import read_scalars >>> from mindboggle.features.fundi import extract_fundi >>> from mindboggle.utils.plots import plot_surfaces >>> path = os.environ['MINDBOGGLE_DATA'] >>> curv_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.mean_curvature.vtk') >>> depth_file = os.path.join(path, 'arno', 'shapes', 'travel_depth_rescaled.vtk') >>> folds_file = os.path.join(path, 'arno', 'features', 'folds.vtk') >>> folds, name = read_scalars(folds_file, True, True) >>> if single_fold: >>> fold_number = 2 #11 >>> folds[folds != fold_number] = -1 >>> min_separation = 10 >>> erode_ratio = 0.10 >>> erode_min_size = 10 >>> save_file = True >>> o1, o2, fundus_per_fold_file = extract_fundi(folds, curv_file, ... depth_file, min_separation, erode_ratio, erode_min_size, save_file) >>> # >>> # View: >>> plot_surfaces(fundi_file) """ # Extract a skeleton to connect endpoints in a fold: import os import numpy as np from time import time from mindboggle.utils.io_vtk import read_scalars, read_vtk, rewrite_scalars from mindboggle.utils.compute import median_abs_dev from mindboggle.utils.paths import find_max_values from mindboggle.utils.mesh import find_neighbors_from_file, find_complete_faces from mindboggle.utils.paths import find_outer_anchors, connect_points_erosion if isinstance(folds, list): folds = np.array(folds) # Load values, inner anchor threshold, and neighbors: faces, u1, u2, points, npoints, curvs, u3, u4 = read_vtk( curv_file, True, True) depths, name = read_scalars(depth_file, True, True) values = curvs * depths values0 = [x for x in values if x > 0] thr = np.median(values0) + 2 * median_abs_dev(values0) neighbor_lists = find_neighbors_from_file(curv_file) #------------------------------------------------------------------------- # Loop through folds: #------------------------------------------------------------------------- t1 = time() skeletons = [] unique_fold_IDs = [x for x in np.unique(folds) if x != -1] if len(unique_fold_IDs) == 1: print("Extract a fundus from 1 fold...") else: print("Extract a fundus from each of {0} folds...".format( len(unique_fold_IDs))) for fold_ID in unique_fold_IDs: indices_fold = [i for i, x in enumerate(folds) if x == fold_ID] if indices_fold: print(' Fold {0}:'.format(int(fold_ID))) #----------------------------------------------------------------- # Find outer anchor points on the boundary of the surface region, # to serve as fundus endpoints: #----------------------------------------------------------------- outer_anchors, tracks = find_outer_anchors(indices_fold, neighbor_lists, values, depths, min_separation) #----------------------------------------------------------------- # Find inner anchor points: #----------------------------------------------------------------- inner_anchors = find_max_values(points, values, min_separation, thr) #----------------------------------------------------------------- # Connect anchor points to create skeleton: #----------------------------------------------------------------- B = -1 * np.ones(npoints) B[indices_fold] = 1 skeleton = connect_points_erosion(B, neighbor_lists, outer_anchors, inner_anchors, values, erode_ratio, erode_min_size, save_steps=[], save_vtk='') if skeleton: skeletons.extend(skeleton) #----------------------------------------------------------------- # Remove fundus vertices if they complete triangle faces: #----------------------------------------------------------------- Iremove = find_complete_faces(skeletons, faces) if Iremove: skeletons = list(frozenset(skeletons).difference(Iremove)) indices = [x for x in skeletons if folds[x] != -1] fundus_per_fold = -1 * np.ones(npoints) fundus_per_fold[indices] = folds[indices] n_fundi_in_folds = len([x for x in np.unique(fundus_per_fold) if x != -1]) if n_fundi_in_folds == 1: sdum = 'fold fundus' else: sdum = 'fold fundi' print(' ...Extracted {0} {1}; {2} total ({3:.2f} seconds)'.format( n_fundi_in_folds, sdum, n_fundi_in_folds, time() - t1)) #------------------------------------------------------------------------- # Return fundi, number of fundi, and file name: #------------------------------------------------------------------------- if n_fundi_in_folds > 0: fundus_per_fold = [int(x) for x in fundus_per_fold] if save_file: fundus_per_fold_file = os.path.join(os.getcwd(), 'fundus_per_fold.vtk') rewrite_scalars(curv_file, fundus_per_fold_file, fundus_per_fold, 'fundi', folds) if not os.path.exists(fundus_per_fold_file): raise (IOError(fundus_per_fold_file + " not found")) else: fundus_per_fold_file = None return fundus_per_fold, n_fundi_in_folds, fundus_per_fold_file
def transform_to_volume(vtk_file, volume_file, output_volume=''): """ Transform vtk coordinates to voxel index coordinates in a target volume by using the header transformation. This function assumes that the nibabel-readable volume has LPI orientation. Parameters ---------- vtk_file : string name of VTK file containing point coordinate data volume_file : string name of target nibabel-readable image volume file output_volume : string name of output nibabel-readable image volume file Returns ------- output_volume : string name of nifti file containing transformed point data Examples -------- >>> import os >>> from mindboggle.utils.io_vtk import transform_to_volume >>> from mindboggle.utils.plots import plot_volumes >>> path = os.environ['MINDBOGGLE_DATA'] >>> vtk_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.mean_curvature.vtk') >>> volume_file = os.path.join(path, 'arno', 'mri', 't1weighted_brain.nii.gz') >>> output_volume = '' >>> # >>> transform_to_volume(vtk_file, volume_file, output_volume) >>> # View >>> plot_volumes(['affine_lh.pial.mean_curvature.vtk.nii.gz', volume_file]) """ import os import numpy as np import nibabel as nb from mindboggle.utils.io_vtk import read_vtk # Read vtk file: u1, u2, u3, xyz, npoints, scalars, u4, u5 = read_vtk(vtk_file) # Read target image volume header information: img = nb.load(volume_file) hdr = img.get_header() dims = img.get_shape() ndims = len(dims) affine = img.get_affine() inv_transform = np.linalg.inv(affine) # Transform vtk coordinates: xyz = np.array(xyz) xyz = np.concatenate((xyz, np.ones((npoints,1))), axis=1) voxels = np.transpose(np.dot(inv_transform, np.transpose(xyz)))[:,0:ndims] voxels = np.reshape([int(np.round(x)) for lst in voxels for x in lst], (-1,ndims)) # Write vtk scalar values to voxels: data = np.zeros(dims) for ivoxel, ijk in enumerate(voxels): data[ijk[0], ijk[1], ijk[2]] = scalars[ivoxel] # Write output image volume: if not output_volume: output_volume = os.path.join(os.getcwd(), os.path.basename(vtk_file) + '_to_volume.nii.gz') img = nb.Nifti1Image(data, affine, header=hdr) img.to_filename(output_volume) if not os.path.exists(output_volume): raise(IOError(output_volume + " not found")) return output_volume
adjacency_matrix, W, Path, Degree, TreeNbr = M.mst_prim(adjacency_matrix, [Root], [], M.degree, M.tree_nbr) return W, Path, Degree, TreeNbr # Example use of the minimum spanning tree algorithm if __name__ == "__main__" : import os import networkx as nx from mindboggle.utils.io_vtk import read_vtk, rewrite_scalars from mindboggle.utils.mesh import find_neighbors, remove_faces from mindboggle.utils.mesh import min_span_tree from mindboggle.utils.plots import plot_vtk data_path = os.environ['MINDBOGGLE_DATA'] sulci_file = os.path.join(data_path, 'arno', 'features', 'sulci.vtk') faces, lines, indices, points, npoints, sulci, name, input_vtk = read_vtk(sulci_file) sulcus_ID = 1 sulcus_indices = [i for i,x in enumerate(sulci) if x == sulcus_ID] sulcus_faces = remove_faces(faces, sulcus_indices) sulcus_neighbor_lists = find_neighbors(sulcus_faces, len(points)) G=nx.Graph() G.add_nodes_from(sulcus_indices) for i, sulcus_neighbor_list in enumerate(sulcus_neighbor_lists): G.add_edges_from([[i,x] for x in sulcus_neighbor_list]) adjacency_matrix = nx.adjacency_matrix(G, nodelist=None, weight='weight') indices_to_connect = [0, len(sulcus_indices)-1] adjacency_matrix2, W, Path, Degree, TreeNbr = min_span_tree(adjacency_matrix, indices_to_connect) # Write results to vtk file and view: MST = np.zeros(len(points))
def rewrite_scalars(input_vtk, output_vtk, new_scalars, new_scalar_names=['scalars'], filter_scalars=[], background_value=-1): """ Load VTK format file and save a subset of scalars into a new file. Parameters ---------- input_vtk : string input VTK file name output_vtk : string output VTK file name new_scalars : list of lists of floats (or single list or array of floats) each list (lookup table) contains new values to assign to the vertices new_scalar_names : string or list of strings each element is the new name for a lookup table filter_scalars : list or numpy array (optional) scalar values used to filter faces (foreground values retained) background_value : integer background value Returns ------- output_vtk : string output VTK file name Examples -------- >>> # Write vtk file with curvature values on sulci >>> import os >>> from mindboggle.utils.io_vtk import read_scalars, rewrite_scalars >>> path = os.environ['MINDBOGGLE_DATA'] >>> input_vtk = os.path.join(path, 'arno', 'shapes', 'lh.pial.mean_curvature.vtk') >>> sulci_file = os.path.join(path, 'arno', 'features', 'sulci.vtk') >>> output_vtk = 'rewrite_scalars.vtk' >>> curvs, name = read_scalars(input_vtk, True,True) >>> sulci, name = read_scalars(sulci_file) >>> new_scalars = [curvs, sulci] >>> new_scalar_names = ['curvs', 'sulci'] >>> filter_scalars = sulci >>> background_value = -1 >>> # >>> rewrite_scalars(input_vtk, output_vtk, new_scalars, new_scalar_names, filter_scalars, background_value) >>> # >>> # View: >>> from mindboggle.utils.plots import plot_surfaces >>> plot_surfaces('rewrite_scalars.vtk') """ import os import numpy as np from mindboggle.utils.mesh import remove_faces from mindboggle.utils.io_vtk import write_header, write_points, \ write_vertices, write_faces, write_scalars, read_vtk, scalars_checker # Convert numpy arrays to lists if isinstance(new_scalars, np.ndarray): new_scalars = new_scalars.tolist() if isinstance(filter_scalars, np.ndarray): filter_scalars = filter_scalars.tolist() # Output VTK file to current working directory output_vtk = os.path.join(os.getcwd(), output_vtk) # Load VTK file faces, lines, indices, points, npoints, scalars, name, \ input_vtk = read_vtk(input_vtk) # Find indices to foreground values if filter_scalars: indices_keep = [i for i,x in enumerate(filter_scalars) if x != background_value] indices_remove = [i for i,x in enumerate(filter_scalars) if x == background_value] # Remove surface faces whose three vertices are not all in indices faces = remove_faces(faces, indices_keep) # Write VTK file Fp = open(output_vtk,'w') write_header(Fp) write_points(Fp, points) if indices: write_vertices(Fp, indices) if faces: write_faces(Fp, faces) if new_scalars: new_scalars, new_scalar_names = scalars_checker(new_scalars, new_scalar_names) # scalars_checker() returns a list of lists for scalars: for i, new_scalar_list in enumerate(new_scalars): if filter_scalars: for iremove in indices_remove: new_scalar_list[iremove] = background_value if np.ndim(new_scalar_list) == 1: scalar_type = type(new_scalar_list[0]).__name__ elif np.ndim(new_scalar_list) == 2: scalar_type = type(new_scalar_list[0][0]).__name__ else: print("Undefined scalar type!") if i == 0: new_scalar_name = new_scalar_names[0] write_scalars(Fp, new_scalar_list, new_scalar_name, begin_scalars=True, scalar_type=scalar_type) else: if len(new_scalar_names) < i + 1: new_scalar_name = new_scalar_names[0] else: new_scalar_name = new_scalar_names[i] write_scalars(Fp, new_scalar_list, new_scalar_name, begin_scalars=False, scalar_type=scalar_type) else: print('Error: new_scalars is empty') exit() Fp.close() if not os.path.exists(output_vtk): raise(IOError(output_vtk + " not found")) return output_vtk
exporter.SetFileName(output_vtk) exporter.Write() return output_vtk if __name__ == "__main__": import os from mindboggle.utils.io_vtk import read_vtk, write_vtk from mindboggle.utils.mesh import decimate path = os.environ['MINDBOGGLE_DATA'] input_vtk = os.path.join(path, 'arno', 'labels', 'label22.vtk') reduction = 0.5 smooth_steps = 100 faces, lines, indices, points, npoints, labels, o1, o2 = read_vtk(input_vtk) import vtk # vtk points: vtk_points = vtk.vtkPoints() [vtk_points.InsertPoint(i, x[0], x[1], x[2]) for i,x in enumerate(points)] # vtk faces: vtk_faces = vtk.vtkCellArray() for face in faces: vtk_face = vtk.vtkPolygon() vtk_face.GetPointIds().SetNumberOfIds(3) vtk_face.GetPointIds().SetId(0, face[0]) vtk_face.GetPointIds().SetId(1, face[1]) vtk_face.GetPointIds().SetId(2, face[2])
def explode_scalars(input_indices_vtk, input_values_vtk='', output_stem='', exclude_values=[-1], background_value=-1, output_scalar_name='scalars', remove_background_faces=True, reindex=True): """ Write out a separate VTK file for each integer (not in exclude_values) in (the first) scalar list of an input VTK file. Optionally write the values drawn from a second VTK file, remove background values, and reindex indices. Parameters ---------- input_indices_vtk : string path of the input VTK file that contains indices as scalars (assumes that the scalars are a list of floats or integers) input_values_vtk : string path of the input VTK file that contains values as scalars output_stem : string path and stem of the output VTK file exclude_values : list or array values to exclude background_value : integer or float background value in output VTK files remove_background_faces : Boolean remove all faces whose three vertices are not all a given index? reindex : Boolean reindex all indices in faces? Examples -------- >>> # Example 1: explode sulci with thickness values >>> import os >>> from mindboggle.utils.io_vtk import explode_scalars >>> from mindboggle.utils.plots import plot_surfaces >>> path = os.environ['MINDBOGGLE_DATA'] >>> input_indices_vtk = os.path.join(path, 'arno', 'features', 'sulci.vtk') >>> input_values_vtk = os.path.join(path, 'arno', 'shapes', 'lh.pial.travel_depth.vtk') >>> output_stem = 'sulci_depth' >>> # >>> explode_scalars(input_indices_vtk, input_values_vtk, output_stem) >>> # >>> # View: >>> example_vtk = os.path.join(os.getcwd(), output_stem + '0.vtk') >>> plot_surfaces(example_vtk) >>> # >>> # Example 2: explode labels >>> import os >>> from mindboggle.utils.io_vtk import explode_scalars >>> from mindboggle.utils.plots import plot_surfaces >>> path = os.environ['MINDBOGGLE_DATA'] >>> input_values_vtk = os.path.join(path, 'arno', 'labels', >>> 'lh.labels.DKT25.manual.vtk') >>> input_indices_vtk = input_values_vtk >>> output_stem = 'label' >>> exclude_values = [-1] >>> background_value = -1, >>> output_scalar_name = 'scalars' >>> remove_background_faces = True >>> reindex = True >>> # >>> explode_scalars(input_indices_vtk, input_values_vtk, output_stem, >>> exclude_values, background_value, >>> output_scalar_name, remove_background_faces, reindex) >>> # View: >>> example_vtk = os.path.join(os.getcwd(), output_stem + '2.vtk') >>> plot_surfaces(example_vtk) """ import os import numpy as np from mindboggle.utils.io_vtk import read_scalars, read_vtk, write_vtk from mindboggle.utils.mesh import reindex_faces_points, remove_faces # Load VTK file: faces, lines, indices, points, npoints, scalars, scalar_names, \ foo1 = read_vtk(input_indices_vtk, True, True) print("Explode the scalar list in {0}". format(os.path.basename(input_indices_vtk))) if input_values_vtk != input_indices_vtk: values, name = read_scalars(input_values_vtk, True, True) print("Explode the scalar list of values in {0} " "with the scalar list of indices in {1}". format(os.path.basename(input_values_vtk), os.path.basename(input_indices_vtk))) else: values = np.copy(scalars) # Loop through unique (non-excluded) scalar values: unique_scalars = np.unique(scalars) if all(unique_scalars==np.round(unique_scalars)): unique_scalars = [int(x) for x in unique_scalars if x not in exclude_values] else: unique_scalars = [x for x in unique_scalars if x not in exclude_values] for scalar in unique_scalars: # Remove background (keep only faces with the scalar): if remove_background_faces: scalar_indices = [i for i,x in enumerate(scalars) if x == scalar] scalar_faces = remove_faces(faces, scalar_indices) else: scalar_faces = faces # Reindex: if reindex: scalar_faces, select_points, \ o1 = reindex_faces_points(scalar_faces, points) else: select_points = points # Create array and indices for scalar value: if reindex: len_indices = len(select_points) select_values = scalar * np.ones(len_indices) else: select_values = np.copy(values) select_values[scalars != scalar] = background_value len_indices = len([i for i,x in enumerate(select_values) if x != background_value]) print(" Scalar {0}: {1} vertices".format(scalar, len_indices)) # Write VTK file with scalar values (list of values): if np.ndim(select_values) == 1: scalar_type = type(select_values[0]).__name__ elif np.ndim(select_values) == 2: scalar_type = type(select_values[0][0]).__name__ else: print("Undefined scalar type!") output_vtk = os.path.join(os.getcwd(), output_stem + str(scalar) + '.vtk') write_vtk(output_vtk, select_points, indices, lines, scalar_faces, select_values.tolist(), output_scalar_name, scalar_type=scalar_type)
def extract_folds(depth_file, min_fold_size=50, tiny_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. 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 call fill_holes() with the argument exclude_range set close to zero to retain these areas. 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) tiny_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.utils.io_vtk import read_scalars >>> from mindboggle.utils.mesh import find_neighbors_from_file >>> from mindboggle.utils.plots import plot_vtk >>> from mindboggle.features.folds import extract_folds >>> path = os.environ['MINDBOGGLE_DATA'] >>> depth_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.travel_depth.vtk') >>> neighbor_lists = find_neighbors_from_file(depth_file) >>> min_fold_size = 50 >>> tiny_depth = 0.001 >>> save_file = True >>> # >>> folds, n_folds, thr, bins, bin_edges, folds_file = extract_folds(depth_file, >>> min_fold_size, tiny_depth, save_file) >>> # >>> # View folds: >>> plot_vtk('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.utils.io_vtk import rewrite_scalars, read_vtk from mindboggle.utils.mesh import find_neighbors from mindboggle.utils.morph import fill_holes from mindboggle.utils.segment import segment do_fill_holes = True 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 #------------------------------------------------------------------------- min_vertices = 10000 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, tiny_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') #------------------------------------------------------------------------- # 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.tolist(), n_folds, depth_threshold, bins, bin_edges, 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.utils.segment import extract_borders import mindboggle.utils.graph as go from mindboggle.utils.io_vtk import read_vtk, read_scalars, write_vtk from mindboggle.utils.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 zernike_moments_per_label(vtk_file, order=10, exclude_labels=[-1], scale_input=True, decimate_fraction=0, decimate_smooth=25): """ Compute the Zernike moments per labeled region in a file. Optionally decimate the input mesh. Parameters ---------- vtk_file : string name of VTK surface mesh file containing index scalars (labels) order : integer number of moments to compute exclude_labels : list of integers labels to be excluded scale_input : Boolean translate and scale each object so it is bounded by a unit sphere? (this is the expected input to zernike_moments()) decimate_fraction : float fraction of mesh faces to remove for decimation (1 for no decimation) decimate_smooth : integer number of smoothing steps for decimation Returns ------- descriptors_lists : list of lists of floats Zernike descriptors per label label_list : list of integers list of unique labels for which moments are computed Examples -------- >>> # Uncomment "if label==22:" below to run example: >>> # Twins-2-1 left postcentral (22) pial surface: >>> import os >>> from mindboggle.shapes.zernike.zernike import zernike_moments_per_label >>> path = os.path.join(os.environ['HOME'], 'mindboggled', 'OASIS-TRT-20-1') >>> vtk_file = os.path.join(path, 'labels', 'left_surface', 'relabeled_classifier.vtk') >>> order = 3 >>> exclude_labels = [-1, 0] >>> scale_input = True >>> zernike_moments_per_label(vtk_file, order, exclude_labels, scale_input) ([[0.00528486237819844, 0.009571754617699853, 0.0033489494903015944, 0.00875603468268444, 0.0015879536633349918, 0.0008080165707033097]], [22]) ([[0.0018758013185778298, 0.001757973693050823, 0.002352403177686726, 0.0032281044369938286, 0.002215900343702539, 0.0019646380916703856]], [14.0]) Arthur Mikhno's result: 1.0e+07 * 0.0000 0.0179 0.0008 4.2547 0.0534 4.4043 """ import numpy as np from mindboggle.utils.io_vtk import read_vtk from mindboggle.utils.mesh import remove_faces from mindboggle.shapes.zernike.zernike import zernike_moments min_points_faces = 4 #------------------------------------------------------------------------- # Read VTK surface mesh file: #------------------------------------------------------------------------- faces, u1, u2, points, u3, labels, u4, u5 = read_vtk(vtk_file) #------------------------------------------------------------------------- # Loop through labeled regions: #------------------------------------------------------------------------- ulabels = [x for x in np.unique(labels) if x not in exclude_labels] label_list = [] descriptors_lists = [] for label in ulabels: #if label == 1022: # 22: # print("DEBUG: COMPUTE FOR ONLY ONE LABEL") #--------------------------------------------------------------------- # Determine the indices per label: #--------------------------------------------------------------------- Ilabel = [i for i, x in enumerate(labels) if x == label] print(' {0} vertices for label {1}'.format(len(Ilabel), label)) if len(Ilabel) > min_points_faces: #----------------------------------------------------------------- # Remove background faces: #----------------------------------------------------------------- pick_faces = remove_faces(faces, Ilabel) if len(pick_faces) > min_points_faces: #------------------------------------------------------------- # Compute Zernike moments for the label: #------------------------------------------------------------- descriptors = zernike_moments(points, pick_faces, order, scale_input, decimate_fraction, decimate_smooth) #------------------------------------------------------------- # Append to a list of lists of spectra: #------------------------------------------------------------- descriptors_lists.append(descriptors) label_list.append(label) return descriptors_lists, label_list
def close_surface_pair_from_files(patch_surface1, whole_surface2, background_value=-1, output_vtk=''): """ 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 :: The first VTK file contains scalar values different than background for a surface patch. The second VTK file contains the (entire) surface 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 ---------- patch_surface1 : string vtk file with surface patch of non-background scalar values whole_surface2 : string second vtk file with 1-to-1 vertex correspondence with patch_surface1 (whole surface so as to derive vertex neighbor lists) background_value : integer scalar value for background vertices output_vtk : string output vtk file name with closed surface patch Returns ------- output_vtk : string output vtk file name with closed surface patch Examples -------- >>> import os >>> from mindboggle.utils.morph import close_surface_pair_from_files >>> from mindboggle.utils.plots import plot_surfaces >>> from mindboggle.utils.io_vtk 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') >>> points = read_points(folds_file) >>> folds, name = read_scalars(folds_file, True, True) >>> fold_number = 11 >>> folds[folds != 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) >>> write_vtk(patch_surface1, points, [], [], faces, folds, name) >>> write_vtk(whole_surface2, points2, [], [], faces, folds, name) >>> background_value = -1 >>> output_vtk = '' >>> close_surface_pair_from_files(patch_surface1, whole_surface2, background_value, output_vtk) >>> # View: >>> plot_surfaces('closed.vtk') # doctest: +SKIP """ import os import numpy as np from mindboggle.utils.io_vtk import read_vtk, write_vtk from mindboggle.utils.morph import close_surface_pair # Read VTK surface mesh files: u1, u2, u3, points1, N, scalars, name, u4 = read_vtk( patch_surface1, True, True) faces, u1, u2, points2, N, u3, u4, u5 = read_vtk(whole_surface2, True, True) # Close surface: closed_faces, closed_points, closed_scalars = close_surface_pair( faces, points1, points2, scalars, background_value) # Write output file: if not output_vtk: output_vtk = os.path.join(os.getcwd(), 'closed.vtk') # closed_scalars is a list if np.ndim(closed_scalars) == 1: scalar_type = type(closed_scalars[0]).__name__ elif np.ndim(closed_scalars) == 2: scalar_type = type(closed_scalars[0][0]).__name__ else: print("Undefined scalar type!") write_vtk(output_vtk, closed_points, [], [], closed_faces, closed_scalars, name, scalar_type=scalar_type) return output_vtk
def write_shape_stats( labels_or_file, sulci=[], fundi=[], affine_transform_file="", transform_format="itk", area_file="", mean_curvature_file="", travel_depth_file="", geodesic_depth_file="", convexity_file="", thickness_file="", labels_spectra=[], labels_spectra_IDs=[], sulci_spectra=[], sulci_spectra_IDs=[], exclude_labels=[-1], delimiter=",", ): """ Make tables of shape statistics per label, fundus, and/or sulcus. Parameters ---------- labels_or_file : list or string label number for each vertex or name of VTK file with index scalars sulci : list of integers indices to sulci, one per vertex, with -1 indicating no sulcus fundi : list of integers indices to fundi, one per vertex, with -1 indicating no fundus affine_transform_file : string affine transform file to standard space transform_format : string format for transform file Ex: 'txt' for text, 'itk' for ITK, and 'mat' for Matlab format area_file : string name of VTK file with surface area scalar values mean_curvature_file : string name of VTK file with mean curvature scalar values travel_depth_file : string name of VTK file with travel depth scalar values geodesic_depth_file : string name of VTK file with geodesic depth scalar values convexity_file : string name of VTK file with convexity scalar values thickness_file : string name of VTK file with thickness scalar values labels_spectra : list of lists of floats Laplace-Beltrami spectra for labeled regions labels_spectra_IDs : list of integers unique ID numbers (labels) for labels_spectra sulci_spectra : list of lists of floats Laplace-Beltrami spectra for sulci sulci_spectra_IDs : list of integers unique ID numbers (labels) for sulci_spectra exclude_labels : list of lists of integers indices to be excluded (in addition to -1) delimiter : string delimiter between columns, such as ',' Returns ------- label_table : string output table filename for label shapes sulcus_table : string output table filename for sulcus shapes fundus_table : string output table filename for fundus shapes Examples -------- >>> import os >>> from mindboggle.utils.io_vtk import read_scalars >>> from mindboggle.utils.io_table import write_shape_stats >>> path = os.environ['MINDBOGGLE_DATA'] >>> labels_or_file = os.path.join(path, 'arno', 'labels', 'lh.labels.DKT25.manual.vtk') >>> sulci_file = os.path.join(path, 'arno', 'features', 'sulci.vtk') >>> fundi_file = os.path.join(path, 'arno', 'features', 'fundi.vtk') >>> sulci, name = read_scalars(sulci_file) >>> fundi, name = read_scalars(fundi_file) >>> affine_transform_file = os.path.join(path, 'arno', 'mri', >>> # 'affine_to_template.mat') >>> 't1weighted_brain.MNI152Affine.txt') >>> #transform_format = 'mat' >>> transform_format = 'itk' >>> area_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.area.vtk') >>> mean_curvature_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.mean_curvature.vtk') >>> travel_depth_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.travel_depth.vtk') >>> geodesic_depth_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.geodesic_depth.vtk') >>> convexity_file = '' >>> thickness_file = '' >>> delimiter = ',' >>> # >>> import numpy as np >>> labels, name = read_scalars(labels_or_file) >>> labels_spectra = [[1,2,3] for x in labels] >>> labels_spectra_IDs = np.unique(labels).tolist() >>> sulci_spectra = [[1,2,3] for x in sulci] >>> sulci_spectra_IDs = np.unique(sulci).tolist() >>> exclude_labels = [-1] >>> # >>> write_shape_stats(labels_or_file, sulci, fundi, >>> affine_transform_file, transform_format, area_file, >>> mean_curvature_file, travel_depth_file, geodesic_depth_file, >>> convexity_file, thickness_file, labels_spectra, >>> labels_spectra_IDs, sulci_spectra, >>> sulci_spectra_IDs, exclude_labels, delimiter) """ import os import numpy as np from mindboggle.shapes.measure import means_per_label, stats_per_label, sum_per_label from mindboggle.utils.io_vtk import read_scalars, read_vtk, apply_affine_transform from mindboggle.utils.io_table import write_columns # Make sure inputs are lists: if isinstance(labels_or_file, np.ndarray): labels = labels_or_file.tolist() elif isinstance(labels_or_file, list): labels = labels_or_file elif isinstance(labels_or_file, str): labels, name = read_scalars(labels_or_file) if isinstance(sulci, np.ndarray): sulci = sulci.tolist() if isinstance(fundi, np.ndarray): fundi = fundi.tolist() # ------------------------------------------------------------------------- # Feature lists, shape names, and shape files: # ------------------------------------------------------------------------- # Feature lists: feature_lists = [labels, sulci, fundi] feature_names = ["label", "sulcus", "fundus"] spectra_lists = [labels_spectra, sulci_spectra] spectra_ID_lists = [labels_spectra_IDs, sulci_spectra_IDs] spectra_names = ["label spectrum", "sulcus spectrum"] table_names = ["label_shapes.csv", "sulcus_shapes.csv", "fundus_shapes.csv"] # Shape names corresponding to shape files below: shape_names = ["area", "mean curvature", "travel depth", "geodesic depth", "convexity", "thickness"] # Load shape files as a list of numpy arrays of per-vertex shape values: shape_files = [ area_file, mean_curvature_file, travel_depth_file, geodesic_depth_file, convexity_file, thickness_file, ] shape_arrays = [] column_names = [] first_pass = True area_array = [] for ishape, shape_file in enumerate(shape_files): if os.path.exists(shape_file): if first_pass: faces, lines, indices, points, npoints, scalars_array, name, input_vtk = read_vtk( shape_file, True, True ) points = np.array(points) first_pass = False if affine_transform_file: affine_points, foo1 = apply_affine_transform( affine_transform_file, points, transform_format, save_file=False ) affine_points = np.array(affine_points) else: scalars_array, name = read_scalars(shape_file, True, True) if scalars_array.size: shape_arrays.append(scalars_array) # Store area array: if ishape == 0: area_array = scalars_array.copy() # Initialize table file names: sulcus_table = None fundus_table = None # Loop through features / tables: for itable, feature_list in enumerate(feature_lists): table_column_names = [] # --------------------------------------------------------------------- # For each feature, construct a table of average shape values: # --------------------------------------------------------------------- table_file = os.path.join(os.getcwd(), table_names[itable]) if feature_list: feature_name = feature_names[itable] columns = [] # ----------------------------------------------------------------- # Mean positions in the original space: # ----------------------------------------------------------------- # Compute mean position per feature: positions, sdevs, label_list, foo = means_per_label(points, feature_list, exclude_labels, area_array) # Append mean position per feature to columns: table_column_names.append("mean position") columns.append(positions) # ----------------------------------------------------------------- # Mean positions in standard space: # ----------------------------------------------------------------- if affine_transform_file: # Compute standard space mean position per feature: standard_positions, sdevs, label_list, foo = means_per_label( affine_points, feature_list, exclude_labels, area_array ) # Append standard space mean position per feature to columns: table_column_names.append("mean position in standard space") columns.append(standard_positions) # ----------------------------------------------------------------- # Loop through shape measures: # ----------------------------------------------------------------- table_column_names.extend(column_names[:]) for ishape, shape_array in enumerate(shape_arrays): shape_name = shape_names[ishape] print(" Compute statistics on {0} {1}".format(feature_name, shape_name)) # Append shape names and values per feature to columns: pr = feature_name + ": " + shape_name + ": " if np.size(area_array): po = " (weighted)" else: po = "" # ------------------------------------------------------------- # Append total feature areas to columns: # ------------------------------------------------------------- if ishape == 0 and np.size(area_array): sums, label_list = sum_per_label(shape_array, feature_list, exclude_labels) table_column_names.append(pr + "total") columns.append(sums) # ------------------------------------------------------------- # Append feature shape statistics to columns: # ------------------------------------------------------------- else: medians, mads, means, sdevs, skews, kurts, lower_quarts, upper_quarts, label_list = stats_per_label( shape_array, feature_list, exclude_labels, area_array, precision=1 ) table_column_names.append(pr + "median" + po) table_column_names.append(pr + "median absolute deviation" + po) table_column_names.append(pr + "mean" + po) table_column_names.append(pr + "standard deviation" + po) table_column_names.append(pr + "skew" + po) table_column_names.append(pr + "kurtosis" + po) table_column_names.append(pr + "lower quartile" + po) table_column_names.append(pr + "upper quartile" + po) columns.append(medians) columns.append(mads) columns.append(means) columns.append(sdevs) columns.append(skews) columns.append(kurts) columns.append(lower_quarts) columns.append(upper_quarts) # ----------------------------------------------------------------- # Laplace-Beltrami spectra: # ----------------------------------------------------------------- if itable in [0, 1]: spectra = spectra_lists[itable] spectra_name = spectra_names[itable] spectra_IDs = spectra_ID_lists[itable] # Order spectra into a list: spectrum_list = [] for label in label_list: if label in spectra_IDs: spectrum = spectra[spectra_IDs.index(label)] spectrum_list.append(spectrum) else: spectrum_list.append("") # Append spectral shape name and values to relevant columns: columns.append(spectrum_list) table_column_names.append(spectra_name) # ----------------------------------------------------------------- # Write labels/IDs and values to table: # ----------------------------------------------------------------- # Write labels/IDs to table: write_columns(label_list, feature_name, table_file, delimiter) # Append columns of shape values to table: if columns: write_columns(columns, table_column_names, table_file, delimiter, quote=True, input_table=table_file) else: # Write something to table: write_columns([], "", table_file, delimiter) # --------------------------------------------------------------------- # Return correct table file name: # --------------------------------------------------------------------- if itable == 0: label_table = table_file elif itable == 1: sulcus_table = table_file elif itable == 2: fundus_table = table_file return label_table, sulcus_table, fundus_table
def extract_folds(depth_file, min_fold_size=50, tiny_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. 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 call fill_holes() with the argument exclude_range set close to zero to retain these areas. 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) tiny_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.utils.io_vtk import read_scalars >>> from mindboggle.utils.mesh import find_neighbors_from_file >>> from mindboggle.utils.plots import plot_surfaces >>> from mindboggle.features.folds import extract_folds >>> path = os.environ['MINDBOGGLE_DATA'] >>> depth_file = os.path.join(path, 'arno', 'shapes', 'lh.pial.travel_depth.vtk') >>> neighbor_lists = find_neighbors_from_file(depth_file) >>> min_fold_size = 50 >>> tiny_depth = 0.001 >>> save_file = True >>> # >>> folds, n_folds, thr, bins, bin_edges, folds_file = extract_folds(depth_file, >>> min_fold_size, tiny_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.utils.io_vtk import rewrite_scalars, read_vtk from mindboggle.utils.mesh import find_neighbors from mindboggle.utils.morph import fill_holes from mindboggle.utils.segment import segment do_fill_holes = True 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 #------------------------------------------------------------------------- min_vertices = 10000 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, tiny_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