Eigenvalue geometric features for cortical surface morphology analysis
EigenMorph bridges 3D point cloud analysis with computational neuroanatomy by treating cortical surface meshes as spatial point clouds and computing eigenvalue-based geometric descriptors from local vertex neighbourhoods at multiple scales.
For each vertex on the cortical surface, EigenMorph finds all neighbours within a given radius, builds a 3Γ3 spatial covariance matrix, decomposes it into eigenvalues Ξ»β β₯ Ξ»β β₯ Ξ»β, and derives seven complementary geometric features that together form a rich morphological fingerprint.
- Seven core eigenvalue descriptors β linearity, planarity, sphericity, omnivariance, anisotropy, eigenentropy, and surface variation
- Multi-scale analysis β compute features at fine-to-coarse radii (3β20 mm) to capture geometry from individual sulcal branches to lobar shape
- Weighted neighbourhood schemes β uniform, Gaussian, or inverse-distance weighting for the covariance matrix
- Extended descriptors β shape index, curvedness, verticality, normal displacement, surface gradient magnitude, and fractal dimension
- Parcellation tools β aggregate vertex-wise features into brain regions (Schaefer, DK, Brainnetome, etc.) and compute morphological distance matrices
- Classical comparison β quantify how much information eigenvalue features add beyond FreeSurfer thickness / curvature / sulcal depth
- Group-level statistics β vertex-wise t-tests, GLM, permutation testing, FDR/Bonferroni correction, and effect sizes (Cohen's d, Ξ·Β²)
- Full I/O β loaders for FreeSurfer surfaces, morphometry overlays, annotations, GIFTI, and HDF5/NPZ serialisation
- Publication-quality visualisation β surface renders, multi-scale profiles, ternary diagrams, radar fingerprints, hero composite figures, and distance matrices
- Interactive 3D rendering β FURY (VTK) powered RGB identity maps, feature landscapes, scale sweeps, exploded views, and neighbourhood explorers (graceful fallback to matplotlib)
- Synthetic data β generate realistic cortical surfaces for testing and demonstration without external data
For each vertex v with spatial neighbourhood N(v, r) = { pα΅’ : βpα΅’ β vβ < r }, the 3Γ3 covariance matrix is:
C = (1/N) Ξ£α΅’ (pα΅’ β ΞΌ)(pα΅’ β ΞΌ)α΅
Eigendecomposition yields Ξ»β β₯ Ξ»β β₯ Ξ»β β₯ 0, from which:
| Feature | Formula | Cortical interpretation |
|---|---|---|
| Linearity | (Ξ»β β Ξ»β) / Ξ»β | Ridge-like geometry β sulcal fundi |
| Planarity | (Ξ»β β Ξ»β) / Ξ»β | Planar geometry β gyral crowns |
| Sphericity | Ξ»β / Ξ»β | Isotropic geometry β sulcal pits |
| Omnivariance | (Ξ»β Ξ»β Ξ»β)^(1/3) | Overall spatial dispersion |
| Anisotropy | (Ξ»β β Ξ»β) / Ξ»β | Directional bias (β L + P) |
| Eigenentropy | βΞ£ Ξ»Μα΅’ ln(Ξ»Μα΅’) | Shape complexity / disorder |
| Surface variation | Ξ»β / Σλ | Local roughness / change of curvature |
where Ξ»Μα΅’ = Ξ»α΅’ / Σλ are normalised eigenvalues.
The multi-scale approach computes these at 4 default radii (3, 5, 10, 20 mm), yielding 28 features per vertex that capture cortical geometry from fine sulcal branches to lobar shape.
# Core (numpy, scipy, matplotlib)
pip install eigenmorph
# With neuroimaging I/O (nibabel)
pip install eigenmorph[neuro]
# With interactive 3D visualization (FURY)
pip install eigenmorph[interactive]
# Everything
pip install eigenmorph[all]From source:
git clone https://github.com/rdneuro/eigenmorph.git
cd eigenmorph
pip install -e ".[all]"import eigenmorph as em
# Generate a synthetic cortical surface (no data needed)
mesh, classical = em.generate_synthetic_cortex(n_vertices=10_000, seed=42)
# Compute multi-scale eigenvalue features
ms = em.compute_multiscale_eigenfeatures(
mesh,
radii=[3.0, 5.0, 10.0, 20.0],
weighting="uniform",
)
# β 28 features per vertex (7 features Γ 4 scales)
# Compare with classical FreeSurfer morphometrics
comp = em.compare_with_classical(ms, classical, verbose=True)
# Publication-quality hero figure
em.viz.plot_hero_figure(mesh, ms.scales[1], ms, comparison=comp,
save_path="hero.png")import eigenmorph as em
mesh = em.io.load_freesurfer_surface("subjects/sub-01/surf/lh.white",
hemisphere="lh")
features = em.compute_eigenfeatures(mesh, radius=5.0, weighting="gaussian")
print(features.summary())
em.viz.plot_feature_overview(mesh, features, save_path="overview.png")# Compute core features with eigenvectors (needed for verticality)
feat = em.compute_eigenfeatures(mesh, radius=5.0, store_eigenvectors=True)
ms = em.compute_multiscale_eigenfeatures(mesh)
# All extended features in one call
extended = em.compute_all_extended_features(mesh, feat, ms)
# β dict with shape_index, curvedness, verticality,
# normal_displacement, surface_gradient, fractal_dimension# Parcellate into brain regions
labels = em.io.load_freesurfer_annot("subjects/sub-01/label/lh.aparc.annot")
parc = em.parcellate_features(ms, labels, n_parcels=34)
# Morphological distance matrix between regions
dist = em.morphological_distance_matrix(parc, metric="correlation")
em.viz.plot_distance_matrix(dist, save_path="morph_distance.png")
# Group-level vertex-wise comparison
group1 = [...] # list of MultiScaleFeatures
group2 = [...]
t_stats, p_vals = em.vertex_wise_ttest(group1, group2, feature="linearity")
reject, p_corrected = em.fdr_correction(p_vals, alpha=0.05)# RGB identity map (LβRed, PβGreen, SβBlue)
em.viz.plot_rgb_identity(mesh, features, size=(1200, 800),
save_path="rgb_identity.png")
# Feature landscape (deformed surface encoding a feature as height)
em.viz.plot_feature_landscape(mesh, features.eigenentropy,
title="Eigenentropy Landscape")
# Animated scale sweep (fine β coarse as GIF)
em.viz.render_scale_sweep(mesh, ms, save_path="scale_sweep.gif")# Save computed features (compressed NPZ)
em.io.save_multiscale("sub-01_eigenmorph.npz", ms)
# Reload later
ms_loaded = em.io.load_multiscale("sub-01_eigenmorph.npz")eigenmorph/
βββ core.py # SurfaceMesh, EigenFeatures, compute_eigenfeatures()
βββ features.py # Extended descriptors (shape index, fractal dim, β¦)
βββ parcellation.py # Region aggregation, classical comparison, distance
βββ io.py # FreeSurfer, GIFTI, NPZ loaders/savers
βββ stats.py # Vertex-wise tests, permutation, FDR correction
βββ synthetic.py # Synthetic cortex generation for demos & testing
βββ utils.py # Adjacency, smoothing, normalization, mesh ops
βββ viz/
βββ styles.py # Colour palettes and publication defaults
βββ static.py # Matplotlib: overviews, ternary, radar, hero fig
βββ interactive.py # FURY (VTK): RGB maps, landscapes, scale sweeps
pip install -e ".[dev]"
pytest tests/ -vThe eigenvalue feature framework draws from 3D point cloud analysis in remote sensing and computer vision:
- Weinmann, M., et al. (2015). Semantic point cloud interpretation based on optimal neighborhoods, relevant features and efficient classifiers. ISPRS J. Photogramm. Remote Sens., 105, 286β304.
- West, K.H.P., et al. (2018). Revisiting the eigenvalues: features for 3D point cloud segmentation. Int. Arch. Photogramm. Remote Sens., XLII-2, 1179β1184.
- DemantkΓ©, J., et al. (2011). Dimensionality based scale selection in 3D lidar point clouds. ISPRS XXII, 97β102.
Neuroimaging foundations:
- Fischl, B. (2012). FreeSurfer. NeuroImage, 62(2), 774β781.
- Dale, A.M., Fischl, B., & Sereno, M.I. (1999). Cortical surface-based analysis: I. Segmentation and surface reconstruction. NeuroImage, 9, 179β194.
MIT β see LICENSE.
Contributions welcome! Please open an issue to discuss your idea before submitting a PR.
git clone https://github.com/rdneuro/eigenmorph.git
cd eigenmorph
pip install -e ".[dev]"
pytest tests/ -vDeveloped for research at the Instituto Nacional de NeurociΓͺncia Translacional (INNT-UFRJ).