def fem_laplacian(points, faces, spectrum_size=10, normalization="areaindex", verbose=False): """ Compute linear finite-element method Laplace-Beltrami spectrum after Martin Reuter's MATLAB code. Comparison of fem_laplacian() with Martin Reuter's Matlab eigenvalues: fem_laplacian() results for Twins-2-1 left hemisphere (6 values): [4.829758648026221e-18, 0.0001284173002467199, 0.0002715181572272745, 0.0003205150847159417, 0.0004701628070486448, 0.0005768904023010318] Martin Reuter's shapeDNA-tria Matlab code: {-4.7207711983791511358e-18 ; 0.00012841730024672144738 ; 0.00027151815722727096853 ; 0.00032051508471592313632 ; 0.0004701628070486902353 ; 0.00057689040230097490998 } fem_laplacian() results for Twins-2-1 left postcentral (1022): [6.3469513010430304e-18, 0.0005178862383467463, 0.0017434911095630772, 0.003667561767487686, 0.005429017880363784, 0.006309346984678924] Martin Reuter's Matlab code: {-2.1954862991027e-18 ; 0.0005178862383468 ; 0.0017434911095628 ; 0.0036675617674875 ; 0.0054290178803611 ; 0.006309346984678 } Julien Lefevre, regarding comparison with Spongy results: "I have done some comparisons between my Matlab codes and yours on python and everything sounds perfect: The evaluation has been done only for one mesh (about 10000 vertices). - L2 error between your A and B matrices and mine are about 1e-16. - I have also compared eigenvalues of the generalized problem; even if the error is slightly increasing, it remains on the order of machine precision. - computation time for 1000 eigenvalues was 67s with python versus 63s in matlab. And it is quite the same behavior for lower orders. - Since the eigenvalues are increasing with order, it is also interesting to look at the relative error... high frequencies are not so much perturbed." Parameters ---------- points : list of lists of 3 floats x,y,z coordinates for each vertex of the structure faces : list of lists of 3 integers 3 indices to vertices that form a triangle on the mesh spectrum_size : integer number of eigenvalues to be computed (the length of the spectrum) normalization : string the method used to normalize eigenvalues if None, no normalization is used if "area", use area of the 2D structure as in Reuter et al. 2006 if "index", divide eigenvalue by index to account for linear trend if "areaindex", do both (default) verbose : bool print statements? Returns ------- spectrum : list first spectrum_size eigenvalues for Laplace-Beltrami spectrum Examples -------- >>> import numpy as np >>> from mindboggle.shapes.laplace_beltrami import fem_laplacian >>> # Define a cube: >>> points = [[0,0,0], [0,1,0], [1,1,0], [1,0,0], ... [0,0,1], [0,1,1], [1,1,1], [1,0,1]] >>> faces = [[0,1,2], [2,3,0], [4,5,6], [6,7,4], [0,4,7], [7,3,0], ... [0,4,5], [5,1,0], [1,5,6], [6,2,1], [3,7,6], [6,2,3]] >>> spectrum = fem_laplacian(points, faces, spectrum_size=3, ... normalization=None, verbose=False) >>> [np.float("{0:.{1}f}".format(x, 5)) for x in spectrum[1::]] [4.58359, 4.8] >>> spectrum = fem_laplacian(points, faces, spectrum_size=3, ... normalization="area", verbose=False) >>> [np.float("{0:.{1}f}".format(x, 5)) for x in spectrum[1::]] [27.50155, 28.8] >>> # Spectrum for entire left hemisphere of Twins-2-1: >>> from mindboggle.mio.vtks import read_vtk >>> from mindboggle.mio.fetch_data import prep_tests >>> urls, fetch_data = prep_tests() >>> label_file = fetch_data(urls['left_freesurfer_labels'], '', '.vtk') >>> points, f1,f2, faces, labels, f3,f4,f5 = read_vtk(label_file) >>> spectrum = fem_laplacian(points, faces, spectrum_size=6, ... normalization=None, verbose=False) >>> [np.float("{0:.{1}f}".format(x, 5)) for x in spectrum[1::]] [0.00013, 0.00027, 0.00032, 0.00047, 0.00058] >>> # Spectrum for Twins-2-1 left postcentral pial surface (22): >>> from mindboggle.guts.mesh import keep_faces, reindex_faces_points >>> I22 = [i for i,x in enumerate(labels) if x==1022] # postcentral >>> faces = keep_faces(faces, I22) >>> faces, points, o1 = reindex_faces_points(faces, points) >>> spectrum = fem_laplacian(points, faces, spectrum_size=6, ... normalization=None, verbose=False) >>> [np.float("{0:.{1}f}".format(x, 5)) for x in spectrum[1::]] [0.00057, 0.00189, 0.00432, 0.00691, 0.00775] >>> # Area-normalized spectrum for a single label (postcentral): >>> spectrum = fem_laplacian(points, faces, spectrum_size=6, ... normalization="area", verbose=False) >>> [np.float("{0:.{1}f}".format(x, 5)) for x in spectrum[1::]] [2.69259, 8.97865, 20.44857, 32.74477, 36.739] """ from scipy.sparse.linalg import eigsh, lobpcg import numpy as np from mindboggle.shapes.laplace_beltrami import computeAB # ---------------------------------------------------------------- # Compute A and B matrices (from Reuter et al., 2009): # ---------------------------------------------------------------- A, B = computeAB(points, faces) if A.shape[0] <= spectrum_size: if verbose: print( "The 3D shape has too few vertices ({0} <= {1}). Skip.".format( A.shape[0], spectrum_size)) return None # ---------------------------------------------------------------- # Use the eigsh eigensolver: # ---------------------------------------------------------------- try: # eigs is for nonsymmetric matrices while # eigsh is for real-symmetric or complex-Hermitian matrices: # Martin Reuter: "small sigma shift helps prevent numerical # instabilities with zero eigenvalue" eigenvalues, eigenvectors = eigsh(A, k=spectrum_size, M=B, sigma=-0.01) spectrum = eigenvalues.tolist() # ---------------------------------------------------------------- # Use the lobpcg eigensolver: # ---------------------------------------------------------------- except RuntimeError: if verbose: print("eigsh() failed. Now try lobpcg.") print("Warning: lobpcg can produce different results from " "Reuter (2006) shapeDNA-tria software.") # Initial eigenvector values: init_eigenvecs = np.random.random((A.shape[0], spectrum_size)) # maxiter = 40 forces lobpcg to use 20 iterations. # Strangely, largest=false finds largest eigenvalues # and largest=True gives the smallest eigenvalues: eigenvalues, eigenvectors = lobpcg(A, init_eigenvecs, B=B, largest=True, maxiter=40) # Extract the real parts: spectrum = [value.real for value in eigenvalues] # For some reason, the eigenvalues from lobpcg are not sorted: spectrum.sort() # ---------------------------------------------------------------- # Normalize by area: # ---------------------------------------------------------------- if normalization == "area": spectrum = area_normalize(points, faces, spectrum) if verbose: print("Compute area-normalized linear FEM Laplace-Beltrami " "spectrum") elif normalization == "index": spectrum = index_normalize(spectrum) if verbose: print("Compute index-normalized linear FEM Laplace-Beltrami" " spectrum") elif normalization == "areaindex": spectrum = index_normalize(spectrum) spectrum = area_normalize(points, faces, spectrum) if verbose: print("Compute area and index-normalized linear FEM " "Laplace-Beltrami spectrum") else: if verbose: print("Compute linear FEM Laplace-Beltrami spectrum") return spectrum
def fem_laplacian(points, faces, spectrum_size=10, normalization=None): """ Compute linear finite-element method Laplace-Beltrami spectrum after Martin Reuter's MATLAB code. Note :: Compare fem_laplacian() with Martin Reuter's Matlab eigenvalues: fem_laplacian() results for Twins-2-1 left hemisphere (6 values): [4.829758648026221e-18, 0.0001284173002467199, 0.0002715181572272745, 0.0003205150847159417, 0.0004701628070486448, 0.0005768904023010318] Martin Reuter's shapeDNA-tria Matlab code: {-4.7207711983791511358e-18 ; 0.00012841730024672144738 ; 0.00027151815722727096853 ; 0.00032051508471592313632 ; 0.0004701628070486902353 ; 0.00057689040230097490998 } fem_laplacian() results for Twins-2-1 left postcentral (1022): [6.3469513010430304e-18, 0.0005178862383467463, 0.0017434911095630772, 0.003667561767487686, 0.005429017880363784, 0.006309346984678924] Martin Reuter's Matlab code: {-2.1954862991027e-18 ; 0.0005178862383468 ; 0.0017434911095628 ; 0.0036675617674875 ; 0.0054290178803611 ; 0.006309346984678 } Julien Lefevre, regarding comparison with Spongy results: "I have done some comparisons between my Matlab codes and yours on python and everything sounds perfect: The evaluation has been done only for one mesh (about 10000 vertices)." - L2 error between your A and B matrices and mine are about 1e-16. - I have also compared eigenvalues of the generalized problem; even if the error is slightly increasing, it remains on the order of machine precision. - Another good point: computation time for 1000 eigenvalues was 67s with python versus 63s in matlab. And it is quite the same behavior for lower orders. - Since the eigenvalues are increasing with order, it is also interesting to look at the relative error... high frequencies are not so much perturbed." Parameters ---------- points : list of lists of 3 floats x,y,z coordinates for each vertex of the structure faces : list of lists of 3 integers 3 indices to vertices that form a triangle on the mesh spectrum_size : integer number of eigenvalues to be computed (the length of the spectrum) 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 Returns ------- spectrum : list first spectrum_size eigenvalues for Laplace-Beltrami spectrum Examples -------- >>> from mindboggle.shapes.laplace_beltrami import fem_laplacian >>> # Define a cube: >>> points = [[0,0,0], [0,1,0], [1,1,0], [1,0,0], >>> [0,0,1], [0,1,1], [1,1,1], [1,0,1]] >>> faces = [[0,1,2], [2,3,0], [4,5,6], [6,7,4], [0,4,7], [7,3,0], >>> [0,4,5], [5,1,0], [1,5,6], [6,2,1], [3,7,6], [6,2,3]] >>> fem_laplacian(points, faces, spectrum_size=3, normalization=None) [7.401486830834377e-17, 4.58359213500127, 4.799999999999998] >>> fem_laplacian(points, faces, spectrum_size=3, normalization="area") [1.2335811384723967e-17, 0.76393202250021175, 0.79999999999999949] >>> # Spectrum for entire left hemisphere of Twins-2-1: >>> import os >>> from mindboggle.mio.vtks import read_faces_points >>> from mindboggle.shapes.laplace_beltrami import fem_laplacian >>> path = os.environ['MINDBOGGLE_DATA'] >>> vtk_file = os.path.join(path, 'arno', 'labels', >>> 'lh.labels.DKT25.manual.vtk') >>> faces, points, npoints = read_faces_points(vtk_file) >>> fem_laplacian(points, faces, spectrum_size=6, normalization=None) [4.829758648026222e-18, 0.0001284173002467197, 0.000271518157227274, 0.00032051508471594065, 0.0004701628070486444, 0.0005768904023010318] >>> # Spectrum for Twins-2-1 left postcentral pial surface (22): >>> import os >>> from mindboggle.mio.vtks import read_vtk >>> from mindboggle.guts.mesh import remove_faces, reindex_faces_points >>> from mindboggle.shapes.laplace_beltrami import fem_laplacian >>> path = os.environ['MINDBOGGLE_DATA'] >>> label_file = os.path.join(path, 'arno', 'labels', 'lh.labels.DKT31.manual.vtk') >>> faces, u1,u2, points, u3, labels, u4,u5 = read_vtk(label_file) >>> I22 = [i for i,x in enumerate(labels) if x==22] # postcentral >>> faces = remove_faces(faces, I22) >>> faces, points, o1 = reindex_faces_points(faces, points) >>> #from mindboggle.mio.vtks import read_faces_points >>> #label_file = os.path.join(path, 'arno', 'labels', 'label22.vtk') >>> #faces, points, npoints = read_faces_points(label_file) >>> fem_laplacian(points, faces, spectrum_size=6, normalization=None) [6.3469513010430304e-18, 0.0005178862383467462, 0.0017434911095630795, 0.0036675617674876856, 0.005429017880363785, 0.006309346984678933] >>> # Area-normalized spectrum for a single label (postcentral): >>> fem_laplacian(points, faces, spectrum_size=6, normalization="area") [1.1410192787181146e-21, 9.3102680973672063e-08, 3.1343504525679647e-07, 6.5933366810380741e-07, 9.7599836081654446e-07, 1.1342589857996233e-06] """ from scipy.sparse.linalg import eigsh, lobpcg import numpy as np from mindboggle.shapes.laplace_beltrami import computeAB #----------------------------------------------------------------- # Compute A and B matrices (from Reuter et al., 2009): #----------------------------------------------------------------- A, B = computeAB(points, faces) if A.shape[0] <= spectrum_size: print("The 3D shape has too few vertices ({0} <= {1}). Skip.". format(A.shape[0], spectrum_size)) return None #----------------------------------------------------------------- # Use the eigsh eigensolver: #----------------------------------------------------------------- try : # eigs is for nonsymmetric matrices while # eigsh is for real-symmetric or complex-Hermitian matrices: eigenvalues, eigenvectors = eigsh(A, k=spectrum_size, M=B, sigma=0) spectrum = eigenvalues.tolist() #----------------------------------------------------------------- # Use the lobpcg eigensolver: #----------------------------------------------------------------- except RuntimeError: print("eigsh() failed. Now try lobpcg.") print("Warning: lobpcg can produce different results from " "Reuter (2006) shapeDNA-tria software.") # Initial eigenvector values: init_eigenvecs = np.random.random((A.shape[0], spectrum_size)) # maxiter = 40 forces lobpcg to use 20 iterations. # Strangely, largest=false finds largest eigenvalues # and largest=True gives the smallest eigenvalues: eigenvalues, eigenvectors = lobpcg(A, init_eigenvecs, B=B, largest=True, maxiter=40) # Extract the real parts: spectrum = [value.real for value in eigenvalues] # For some reason, the eigenvalues from lobpcg are not sorted: spectrum.sort() #----------------------------------------------------------------- # Normalize by area: #----------------------------------------------------------------- if normalization == "area": spectrum = area_normalize(points, faces, spectrum) print("Compute area-normalized linear FEM Laplace-Beltrami spectrum") else: print("Compute linear FEM Laplace-Beltrami spectrum") return spectrum
def fem_laplacian(points, faces, spectrum_size=10, normalization=None): """ Compute linear finite-element method Laplace-Beltrami spectrum after Martin Reuter's MATLAB code. Note :: Compare fem_laplacian() with Martin Reuter's Matlab eigenvalues: fem_laplacian() results for Twins-2-1 left hemisphere (6 values): [4.829758648026221e-18, 0.0001284173002467199, 0.0002715181572272745, 0.0003205150847159417, 0.0004701628070486448, 0.0005768904023010318] Martin Reuter's shapeDNA-tria Matlab code: {-4.7207711983791511358e-18 ; 0.00012841730024672144738 ; 0.00027151815722727096853 ; 0.00032051508471592313632 ; 0.0004701628070486902353 ; 0.00057689040230097490998 } fem_laplacian() results for Twins-2-1 left postcentral (1022): [6.3469513010430304e-18, 0.0005178862383467463, 0.0017434911095630772, 0.003667561767487686, 0.005429017880363784, 0.006309346984678924] Martin Reuter's Matlab code: {-2.1954862991027e-18 ; 0.0005178862383468 ; 0.0017434911095628 ; 0.0036675617674875 ; 0.0054290178803611 ; 0.006309346984678 } Julien Lefevre, regarding comparison with Spongy results: "I have done some comparisons between my Matlab codes and yours on python and everything sounds perfect: The evaluation has been done only for one mesh (about 10000 vertices)." - L2 error between your A and B matrices and mine are about 1e-16. - I have also compared eigenvalues of the generalized problem; even if the error is slightly increasing, it remains on the order of machine precision. - Another good point: computation time for 1000 eigenvalues was 67s with python versus 63s in matlab. And it is quite the same behavior for lower orders. - Since the eigenvalues are increasing with order, it is also interesting to look at the relative error... high frequencies are not so much perturbed." Parameters ---------- points : list of lists of 3 floats x,y,z coordinates for each vertex of the structure faces : list of lists of 3 integers 3 indices to vertices that form a triangle on the mesh spectrum_size : integer number of eigenvalues to be computed (the length of the spectrum) 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 Returns ------- spectrum : list first spectrum_size eigenvalues for Laplace-Beltrami spectrum Examples -------- >>> from mindboggle.shapes.laplace_beltrami import fem_laplacian >>> # Define a cube: >>> points = [[0,0,0], [0,1,0], [1,1,0], [1,0,0], >>> [0,0,1], [0,1,1], [1,1,1], [1,0,1]] >>> faces = [[0,1,2], [2,3,0], [4,5,6], [6,7,4], [0,4,7], [7,3,0], >>> [0,4,5], [5,1,0], [1,5,6], [6,2,1], [3,7,6], [6,2,3]] >>> fem_laplacian(points, faces, spectrum_size=3, normalization=None) [7.401486830834377e-17, 4.58359213500127, 4.799999999999998] >>> fem_laplacian(points, faces, spectrum_size=3, normalization="area") [1.2335811384723967e-17, 0.76393202250021175, 0.79999999999999949] >>> # Spectrum for entire left hemisphere of Twins-2-1: >>> import os >>> from mindboggle.utils.io_vtk import read_faces_points >>> from mindboggle.shapes.laplace_beltrami import fem_laplacian >>> path = os.environ['MINDBOGGLE_DATA'] >>> vtk_file = os.path.join(path, 'arno', 'labels', >>> 'lh.labels.DKT25.manual.vtk') >>> faces, points, npoints = read_faces_points(vtk_file) >>> fem_laplacian(points, faces, spectrum_size=6, normalization=None) [4.829758648026222e-18, 0.0001284173002467197, 0.000271518157227274, 0.00032051508471594065, 0.0004701628070486444, 0.0005768904023010318] >>> # Spectrum for Twins-2-1 left postcentral pial surface (22): >>> import os >>> from mindboggle.utils.io_vtk import read_vtk >>> from mindboggle.utils.mesh import remove_faces, reindex_faces_points >>> from mindboggle.shapes.laplace_beltrami import fem_laplacian >>> path = os.environ['MINDBOGGLE_DATA'] >>> label_file = os.path.join(path, 'arno', 'labels', 'lh.labels.DKT31.manual.vtk') >>> faces, u1,u2, points, u3, labels, u4,u5 = read_vtk(label_file) >>> I22 = [i for i,x in enumerate(labels) if x==22] # postcentral >>> faces = remove_faces(faces, I22) >>> faces, points, o1 = reindex_faces_points(faces, points) >>> #from mindboggle.utils.io_vtk import read_faces_points >>> #label_file = os.path.join(path, 'arno', 'labels', 'label22.vtk') >>> #faces, points, npoints = read_faces_points(label_file) >>> fem_laplacian(points, faces, spectrum_size=6, normalization=None) [6.3469513010430304e-18, 0.0005178862383467462, 0.0017434911095630795, 0.0036675617674876856, 0.005429017880363785, 0.006309346984678933] >>> # Area-normalized spectrum for a single label (postcentral): >>> fem_laplacian(points, faces, spectrum_size=6, normalization="area") [1.1410192787181146e-21, 9.3102680973672063e-08, 3.1343504525679647e-07, 6.5933366810380741e-07, 9.7599836081654446e-07, 1.1342589857996233e-06] """ from scipy.sparse.linalg import eigsh, lobpcg import numpy as np from mindboggle.shapes.laplace_beltrami import computeAB #----------------------------------------------------------------- # Compute A and B matrices (from Reuter et al., 2009): #----------------------------------------------------------------- A, B = computeAB(points, faces) if A.shape[0] <= spectrum_size: print("The 3D shape has too few vertices ({0} <= {1}). Skip.". format(A.shape[0], spectrum_size)) return None #----------------------------------------------------------------- # Use the eigsh eigensolver: #----------------------------------------------------------------- try : # eigs is for nonsymmetric matrices while # eigsh is for real-symmetric or complex-Hermitian matrices: eigenvalues, eigenvectors = eigsh(A, k=spectrum_size, M=B, sigma=0) spectrum = eigenvalues.tolist() #----------------------------------------------------------------- # Use the lobpcg eigensolver: #----------------------------------------------------------------- except RuntimeError: print("eigsh() failed. Now try lobpcg.") print("Warning: lobpcg can produce different results from " "Reuter (2006) shapeDNA-tria software.") # Initial eigenvector values: init_eigenvecs = np.random.random((A.shape[0], spectrum_size)) # maxiter = 40 forces lobpcg to use 20 iterations. # Strangely, largest=false finds largest eigenvalues # and largest=True gives the smallest eigenvalues: eigenvalues, eigenvectors = lobpcg(A, init_eigenvecs, B=B, largest=True, maxiter=40) # Extract the real parts: spectrum = [value.real for value in eigenvalues] # For some reason, the eigenvalues from lobpcg are not sorted: spectrum.sort() #----------------------------------------------------------------- # Normalize by area: #----------------------------------------------------------------- if normalization == "area": spectrum = area_normalize(points, faces, spectrum) print("Compute area-normalized linear FEM Laplace-Beltrami spectrum") else: print("Compute linear FEM Laplace-Beltrami spectrum") return spectrum
def fem_laplacian(points, faces, spectrum_size=10, normalization=None, verbose=False): """ Compute linear finite-element method Laplace-Beltrami spectrum after Martin Reuter's MATLAB code. Comparison of fem_laplacian() with Martin Reuter's Matlab eigenvalues: fem_laplacian() results for Twins-2-1 left hemisphere (6 values): [4.829758648026221e-18, 0.0001284173002467199, 0.0002715181572272745, 0.0003205150847159417, 0.0004701628070486448, 0.0005768904023010318] Martin Reuter's shapeDNA-tria Matlab code: {-4.7207711983791511358e-18 ; 0.00012841730024672144738 ; 0.00027151815722727096853 ; 0.00032051508471592313632 ; 0.0004701628070486902353 ; 0.00057689040230097490998 } fem_laplacian() results for Twins-2-1 left postcentral (1022): [6.3469513010430304e-18, 0.0005178862383467463, 0.0017434911095630772, 0.003667561767487686, 0.005429017880363784, 0.006309346984678924] Martin Reuter's Matlab code: {-2.1954862991027e-18 ; 0.0005178862383468 ; 0.0017434911095628 ; 0.0036675617674875 ; 0.0054290178803611 ; 0.006309346984678 } Julien Lefevre, regarding comparison with Spongy results: "I have done some comparisons between my Matlab codes and yours on python and everything sounds perfect: The evaluation has been done only for one mesh (about 10000 vertices). - L2 error between your A and B matrices and mine are about 1e-16. - I have also compared eigenvalues of the generalized problem; even if the error is slightly increasing, it remains on the order of machine precision. - computation time for 1000 eigenvalues was 67s with python versus 63s in matlab. And it is quite the same behavior for lower orders. - Since the eigenvalues are increasing with order, it is also interesting to look at the relative error... high frequencies are not so much perturbed." Parameters ---------- points : list of lists of 3 floats x,y,z coordinates for each vertex of the structure faces : list of lists of 3 integers 3 indices to vertices that form a triangle on the mesh spectrum_size : integer number of eigenvalues to be computed (the length of the spectrum) 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 verbose : bool print statements? Returns ------- spectrum : list first spectrum_size eigenvalues for Laplace-Beltrami spectrum Examples -------- >>> import numpy as np >>> from mindboggle.shapes.laplace_beltrami import fem_laplacian >>> # Define a cube: >>> points = [[0,0,0], [0,1,0], [1,1,0], [1,0,0], ... [0,0,1], [0,1,1], [1,1,1], [1,0,1]] >>> faces = [[0,1,2], [2,3,0], [4,5,6], [6,7,4], [0,4,7], [7,3,0], ... [0,4,5], [5,1,0], [1,5,6], [6,2,1], [3,7,6], [6,2,3]] >>> spectrum = fem_laplacian(points, faces, spectrum_size=3, ... normalization=None, verbose=False) >>> print(np.array_str(np.array(spectrum[1::]), ... precision=5, suppress_small=True)) [ 4.58359 4.8 ] >>> spectrum = fem_laplacian(points, faces, spectrum_size=3, ... normalization="area", verbose=False) >>> print(np.array_str(np.array(spectrum[1::]), ... precision=5, suppress_small=True)) [ 27.50155 28.8 ] >>> # Spectrum for entire left hemisphere of Twins-2-1: >>> from mindboggle.mio.vtks import read_vtk >>> from mindboggle.mio.fetch_data import prep_tests >>> urls, fetch_data = prep_tests() >>> label_file = fetch_data(urls['left_freesurfer_labels'], '', '.vtk') >>> points, f1,f2, faces, labels, f3,f4,f5 = read_vtk(label_file) >>> spectrum = fem_laplacian(points, faces, spectrum_size=6, ... normalization=None, verbose=False) >>> print(np.array_str(np.array(spectrum[1::]), ... precision=5, suppress_small=True)) [ 0.00013 0.00027 0.00032 0.00047 0.00058] >>> # Spectrum for Twins-2-1 left postcentral pial surface (22): >>> from mindboggle.guts.mesh import keep_faces, reindex_faces_points >>> I22 = [i for i,x in enumerate(labels) if x==1022] # postcentral >>> faces = keep_faces(faces, I22) >>> faces, points, o1 = reindex_faces_points(faces, points) >>> spectrum = fem_laplacian(points, faces, spectrum_size=6, ... normalization=None, verbose=False) >>> print(np.array_str(np.array(spectrum[1::]), ... precision=5, suppress_small=True)) [ 0.00057 0.00189 0.00432 0.00691 0.00775] >>> # Area-normalized spectrum for a single label (postcentral): >>> spectrum = fem_laplacian(points, faces, spectrum_size=6, ... normalization="area", verbose=False) >>> print(np.array_str(np.array(spectrum[1::]), ... precision=5, suppress_small=True)) [ 2.69259 8.97865 20.44857 32.74477 36.739 ] """ from scipy.sparse.linalg import eigsh, lobpcg import numpy as np from mindboggle.shapes.laplace_beltrami import computeAB # ---------------------------------------------------------------- # Compute A and B matrices (from Reuter et al., 2009): # ---------------------------------------------------------------- A, B = computeAB(points, faces) if A.shape[0] <= spectrum_size: if verbose: print("The 3D shape has too few vertices ({0} <= {1}). Skip.". format(A.shape[0], spectrum_size)) return None # ---------------------------------------------------------------- # Use the eigsh eigensolver: # ---------------------------------------------------------------- try : # eigs is for nonsymmetric matrices while # eigsh is for real-symmetric or complex-Hermitian matrices: eigenvalues, eigenvectors = eigsh(A, k=spectrum_size, M=B, sigma=-0.01) spectrum = eigenvalues.tolist() # ---------------------------------------------------------------- # Use the lobpcg eigensolver: # ---------------------------------------------------------------- except RuntimeError: if verbose: print("eigsh() failed. Now try lobpcg.") print("Warning: lobpcg can produce different results from " "Reuter (2006) shapeDNA-tria software.") # Initial eigenvector values: init_eigenvecs = np.random.random((A.shape[0], spectrum_size)) # maxiter = 40 forces lobpcg to use 20 iterations. # Strangely, largest=false finds largest eigenvalues # and largest=True gives the smallest eigenvalues: eigenvalues, eigenvectors = lobpcg(A, init_eigenvecs, B=B, largest=True, maxiter=40) # Extract the real parts: spectrum = [value.real for value in eigenvalues] # For some reason, the eigenvalues from lobpcg are not sorted: spectrum.sort() # ---------------------------------------------------------------- # Normalize by area: # ---------------------------------------------------------------- if normalization == "area": spectrum = area_normalize(points, faces, spectrum) if verbose: print("Compute area-normalized linear FEM Laplace-Beltrami " "spectrum") else: if verbose: print("Compute linear FEM Laplace-Beltrami spectrum") return spectrum
def fem_laplacian(points, faces, n_eigenvalues=6, normalization=None): """ Compute linear finite-element method Laplace-Beltrami spectrum after Martin Reuter's MATLAB code. Note :: Compare fem_laplacian() with Martin Reuter's Matlab code output: fem_laplacian() results for Twins-2-1 left hemisphere: [4.829758648026223e-18, 0.00012841730024671977, 0.0002715181572272744, 0.00032051508471594173, 0.000470162807048644, 0.0005768904023010327] Martin Reuter's Matlab code: Creator: ./shapeDNA-tria Refine: 0 Degree: 1 Dimension: 2 Elements: 290134 DoF: 145069 NumEW: 6 Area: 110016 Volume: 534346 BLength: 0 EulerChar: 2 Time(pre) : 2 Time(calcAB) : 0 Time(calcEW) : 7 Time(total ) : 9 Eigenvalues: {-4.7207711983791511358e-18 ; 0.00012841730024672144738 ; 0.00027151815722727096853 ; 0.00032051508471592313632 ; 0.0004701628070486902353 ; 0.00057689040230097490998 } fem_laplacian() results for Twins-2-1 left hemisphere postcentral: [6.346951301043029e-18, 0.0005178862383467465, 0.0017434911095630787, 0.0036675617674876916, 0.005429017880363785, 0.006309346984678927] Martin Reuter's Matlab code: -2.1954862991027e-18 0.0005178862383468 0.0017434911095628 0.0036675617674875 0.0054290178803611 0.006309346984678 Parameters ---------- points : list of lists of 3 floats x,y,z coordinates for each vertex of the structure faces : list of lists of 3 integers 3 indices to vertices that form a triangle on the mesh n_eigenvalues : integer number of eigenvalues to be computed (the length of the spectrum) 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 Returns ------- spectrum : list first n_eigenvalues eigenvalues for Laplace-Beltrami spectrum Examples -------- >>> from mindboggle.shapes.laplace_beltrami import fem_laplacian >>> # Define a cube: >>> points = [[0,0,0], [0,1,0], [1,1,0], [1,0,0], >>> [0,0,1], [0,1,1], [1,1,1], [1,0,1]] >>> faces = [[0,1,2], [2,3,0], [4,5,6], [6,7,4], [0,4,7], [7,3,0], >>> [0,4,5], [5,1,0], [1,5,6], [6,2,1], [3,7,6], [6,2,3]] >>> fem_laplacian(points, faces, n_eigenvalues=3, normalization=None) [7.401486830834377e-17, 4.58359213500127, 4.799999999999998] >>> fem_laplacian(points, faces, n_eigenvalues=3, normalization="area") [1.2335811384723967e-17, 0.76393202250021175, 0.79999999999999949] >>> # Spectrum for entire left hemisphere of Twins-2-1: >>> import os >>> from mindboggle.utils.io_vtk import read_faces_points >>> from mindboggle.shapes.laplace_beltrami import fem_laplacian >>> path = os.environ['MINDBOGGLE_DATA'] >>> vtk_file = os.path.join(path, 'arno', 'labels', >>> 'lh.labels.DKT25.manual.vtk') >>> faces, points, npoints = read_faces_points(vtk_file) >>> fem_laplacian(points, faces, n_eigenvalues=6, normalization=None) [4.829758648026223e-18, 0.00012841730024671904, 0.00027151815722727406, 0.00032051508471594146, 0.0004701628070486449, 0.0005768904023010303] >>> # Spectrum for Twins-2-1 left hemisphere postcentral (label 22): >>> import os >>> from mindboggle.utils.io_vtk import read_faces_points >>> from mindboggle.shapes.laplace_beltrami import fem_laplacian >>> path = os.environ['MINDBOGGLE_DATA'] >>> label_file = os.path.join(path, 'arno', 'labels', 'label22.vtk') >>> faces, points, npoints = read_faces_points(label_file) >>> print("{0}".format(fem_laplacian(points, faces, n_eigenvalues=6, >>> normalization=None))) [6.346951301043029e-18, 0.0005178862383467465, 0.0017434911095630787, 0.0036675617674876916, 0.005429017880363785, 0.006309346984678927] >>> # Area-normalized spectrum for a single label (postcentral): >>> print("{0}".format(fem_laplacian(points, faces, n_eigenvalues=6, >>> normalization="area"))) [1.1410192787181146e-21, 9.310268097367214e-08, 3.1343504525679715e-07, 6.593336681038091e-07, 9.759983608165455e-07, 1.1342589857996225e-06] >>> # testing LBO on previously failed folds >>> import subprocess >>> cmd = ["find", "/media/USBDATA/data/Mindboggle_MRI/MB101/results/features/", "-name", "fold_*.vtk"] >>> process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) >>> out, err = process.communicate() >>> pblm_vtks = out.split() >>> import mindboggle.shapes.laplace_beltrami >>> for vtk_file in pblm_vtks: mindboggle.shapes.laplace_beltrami.spectrum_from_file(vtk_file) """ from scipy.sparse.linalg import eigsh, lobpcg import numpy as np from mindboggle.shapes.laplace_beltrami import computeAB #----------------------------------------------------------------- # Compute A and B matrices (from Reuter et al., 2009): #----------------------------------------------------------------- A, B = computeAB(points, faces) if A.shape[0] <= n_eigenvalues: print("The 3D shape has too few vertices ({0} <= {1}). Skip.". format(A.shape[0], n_eigenvalues)) return None #----------------------------------------------------------------- # Use the eigsh eigensolver: #----------------------------------------------------------------- try : # eigs is for nonsymmetric matrices while # eigsh is for real-symmetric or complex-Hermitian matrices: eigenvalues, eigenvectors = eigsh(A, k=n_eigenvalues, M=B, sigma=0) spectrum = eigenvalues.tolist() #----------------------------------------------------------------- # Use the lobpcg eigensolver: #----------------------------------------------------------------- except RuntimeError: print("eigsh() failed. Now try lobpcg.") print("Warning: lobpcg can produce different results from Reuter" "et al.'s (2006) shapeDNA-tria software.") # Initial eigenvector values: init_eigenvecs = np.random.random((A.shape[0], n_eigenvalues)) # maxiter = 40 forces lobpcg to use 20 iterations. # Strangely, largest=false finds largest eigenvalues # and largest=True gives the smallest eigenvalues: eigenvalues, eigenvectors = lobpcg(A, init_eigenvecs, B=B, largest=True, maxiter=40) # Extract the real parts: spectrum = [value.real for value in eigenvalues] # For some reason, the eigenvalues from lobpcg are not sorted: spectrum.sort() #----------------------------------------------------------------- # Normalize by area: #----------------------------------------------------------------- if normalization == "area": spectrum = area_normalize(points, faces, spectrum) print("Compute area-normalized linear FEM Laplace-Beltrami spectrum") else: print("Compute linear FEM Laplace-Beltrami spectrum") return spectrum