Exemple #1
0
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
Exemple #4
0
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