Metadata-Version: 2.5
Name: spectralbrain
Version: 0.1.0
Summary: Spectral shape analysis of brain surface meshes via the Laplace-Beltrami operator and beyond.
Project-URL: Homepage, https://github.com/rdneuro/spectralbrain
Project-URL: Documentation, https://spectralbrain.readthedocs.io
Project-URL: Repository, https://github.com/rdneuro/spectralbrain
Project-URL: Issues, https://github.com/rdneuro/spectralbrain/issues
Project-URL: Changelog, https://github.com/rdneuro/spectralbrain/blob/main/CHANGELOG.md
Author-email: Rodrigo Debona <r.debona@posgrad.ufsc.br>
Maintainer-email: Rodrigo Debona <r.debona@posgrad.ufsc.br>
License-Expression: MIT
License-File: LICENSE
Keywords: HKS,ShapeDNA,WKS,brain,laplace-beltrami,morphometry,neuroimaging,neuroscience,shape-analysis,spectral-analysis
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
Classifier: Topic :: Scientific/Engineering :: Visualization
Classifier: Typing :: Typed
Requires-Python: <3.13,>=3.11
Requires-Dist: h5py>=3.10
Requires-Dist: joblib>=1.3
Requires-Dist: lapy>=1.0
Requires-Dist: matplotlib>=3.8
Requires-Dist: nibabel>=5.2
Requires-Dist: numpy>=1.26
Requires-Dist: pandas>=2.2
Requires-Dist: pyacvd>=0.2.11
Requires-Dist: pymeshfix>=0.17
Requires-Dist: pyvista>=0.44
Requires-Dist: rich>=13.7
Requires-Dist: scikit-image>=0.22
Requires-Dist: scikit-learn>=1.4
Requires-Dist: scipy>=1.13
Requires-Dist: seaborn>=0.13
Requires-Dist: tqdm>=4.66
Requires-Dist: trimesh>=4.0
Requires-Dist: vtk>=9.3
Provides-Extra: bayesian
Requires-Dist: arviz>=0.18; extra == 'bayesian'
Requires-Dist: bambi>=0.14; extra == 'bayesian'
Requires-Dist: blackjax>=1.2; extra == 'bayesian'
Requires-Dist: numpyro>=0.15; extra == 'bayesian'
Requires-Dist: nutpie>=0.13; extra == 'bayesian'
Requires-Dist: pymc>=5.16; extra == 'bayesian'
Provides-Extra: expanded
Requires-Dist: graphriccicurvature>=0.5.3; extra == 'expanded'
Requires-Dist: gudhi>=3.9; extra == 'expanded'
Requires-Dist: networkx>=3.2; extra == 'expanded'
Requires-Dist: persim>=0.3; extra == 'expanded'
Requires-Dist: pot>=0.9; extra == 'expanded'
Requires-Dist: potpourri3d>=1.0; extra == 'expanded'
Requires-Dist: pyefd>=1.6; extra == 'expanded'
Requires-Dist: ripser>=0.6; extra == 'expanded'
Requires-Dist: robust-laplacian>=0.3; extra == 'expanded'
Provides-Extra: full
Requires-Dist: antspyx>=0.4; (platform_system != 'Windows') and extra == 'full'
Requires-Dist: arviz>=0.18; extra == 'full'
Requires-Dist: bambi>=0.14; extra == 'full'
Requires-Dist: blackjax>=1.2; extra == 'full'
Requires-Dist: brainspace; extra == 'full'
Requires-Dist: cmcrameri>=1.7; extra == 'full'
Requires-Dist: cupy-cuda13x; (platform_system != 'Darwin') and extra == 'full'
Requires-Dist: dipy>=1.9; extra == 'full'
Requires-Dist: fury>=0.10; extra == 'full'
Requires-Dist: graphriccicurvature>=0.5.3; extra == 'full'
Requires-Dist: gudhi>=3.9; extra == 'full'
Requires-Dist: hippomaps; extra == 'full'
Requires-Dist: hippunfold-plot; extra == 'full'
Requires-Dist: ipywidgets>=8.1; extra == 'full'
Requires-Dist: jax[cuda13]; extra == 'full'
Requires-Dist: jaxlib; extra == 'full'
Requires-Dist: jupyterlab>=4.2; extra == 'full'
Requires-Dist: mne-connectivity>=0.7; extra == 'full'
Requires-Dist: mne>=1.7; extra == 'full'
Requires-Dist: networkx>=3.2; extra == 'full'
Requires-Dist: nilearn; extra == 'full'
Requires-Dist: numpyro>=0.15; extra == 'full'
Requires-Dist: nutpie>=0.13; extra == 'full'
Requires-Dist: optuna>=3.6; extra == 'full'
Requires-Dist: persim>=0.3; extra == 'full'
Requires-Dist: pot>=0.9; extra == 'full'
Requires-Dist: potpourri3d>=1.0; extra == 'full'
Requires-Dist: pybids>=0.16; extra == 'full'
Requires-Dist: pyefd>=1.6; extra == 'full'
Requires-Dist: pymc>=5.16; extra == 'full'
Requires-Dist: ripser>=0.6; extra == 'full'
Requires-Dist: robust-laplacian>=0.3; extra == 'full'
Requires-Dist: scienceplots>=2.1; extra == 'full'
Requires-Dist: templateflow>=24.0; extra == 'full'
Requires-Dist: torch>=2.4; extra == 'full'
Requires-Dist: torchvision; extra == 'full'
Requires-Dist: trame-vtk; extra == 'full'
Requires-Dist: trame-vuetify; extra == 'full'
Requires-Dist: trame>=3.6; extra == 'full'
Requires-Dist: vedo>=2024.5; extra == 'full'
Requires-Dist: yabplot; (python_version == '3.11.*') and extra == 'full'
Provides-Extra: gpu
Requires-Dist: cupy-cuda13x; (platform_system != 'Darwin') and extra == 'gpu'
Requires-Dist: jax[cuda13]; extra == 'gpu'
Requires-Dist: jaxlib; extra == 'gpu'
Requires-Dist: torch>=2.4; extra == 'gpu'
Requires-Dist: torchvision; extra == 'gpu'
Provides-Extra: neuro
Requires-Dist: antspyx>=0.4; (platform_system != 'Windows') and extra == 'neuro'
Requires-Dist: brainspace; extra == 'neuro'
Requires-Dist: dipy>=1.9; extra == 'neuro'
Requires-Dist: mne-connectivity>=0.7; extra == 'neuro'
Requires-Dist: mne>=1.7; extra == 'neuro'
Requires-Dist: nilearn; extra == 'neuro'
Requires-Dist: pybids>=0.16; extra == 'neuro'
Requires-Dist: robust-laplacian>=0.3; extra == 'neuro'
Requires-Dist: templateflow>=24.0; extra == 'neuro'
Provides-Extra: notebooks
Requires-Dist: ipywidgets>=8.1; extra == 'notebooks'
Requires-Dist: jupyterlab>=4.2; extra == 'notebooks'
Requires-Dist: robust-laplacian>=0.3; extra == 'notebooks'
Requires-Dist: trame-vtk; extra == 'notebooks'
Requires-Dist: trame-vuetify; extra == 'notebooks'
Requires-Dist: trame>=3.6; extra == 'notebooks'
Provides-Extra: operators
Requires-Dist: graphriccicurvature>=0.5.3; extra == 'operators'
Requires-Dist: gudhi>=3.9; extra == 'operators'
Requires-Dist: networkx>=3.2; extra == 'operators'
Requires-Dist: persim>=0.3; extra == 'operators'
Requires-Dist: pot>=0.9; extra == 'operators'
Requires-Dist: potpourri3d>=1.0; extra == 'operators'
Requires-Dist: pyefd>=1.6; extra == 'operators'
Requires-Dist: ripser>=0.6; extra == 'operators'
Requires-Dist: robust-laplacian>=0.3; extra == 'operators'
Provides-Extra: tuning
Requires-Dist: optuna>=3.6; extra == 'tuning'
Provides-Extra: viz
Requires-Dist: brainspace; extra == 'viz'
Requires-Dist: cmcrameri>=1.7; extra == 'viz'
Requires-Dist: fury>=0.10; extra == 'viz'
Requires-Dist: hippomaps; extra == 'viz'
Requires-Dist: hippunfold-plot; extra == 'viz'
Requires-Dist: scienceplots>=2.1; extra == 'viz'
Requires-Dist: vedo>=2024.5; extra == 'viz'
Requires-Dist: yabplot; (python_version == '3.11.*') and extra == 'viz'
Description-Content-Type: text/markdown

<p align="center">
  <img src="assets/sb_logo_light_nobg.png" alt="SpectralBrain" width="400">
</p>

<p align="center">
  <strong>SpectralBrain</strong><br>
  <em>Spectral Shape Analysis for Brain Structures</em>
</p>

<p align="center">
  <a href="https://pypi.org/project/spectralbrain/"><img src="https://img.shields.io/pypi/v/spectralbrain.svg" alt="PyPI"></a>
  <a href="https://pypi.org/project/spectralbrain/"><img src="https://img.shields.io/pypi/pyversions/spectralbrain.svg" alt="Python"></a>
  <a href="https://github.com/rdneuro/spectralbrain/actions"><img src="https://github.com/rdneuro/spectralbrain/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
  <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
  <a href="https://doi.org/10.5281/zenodo.21090748"><img src="https://zenodo.org/badge/DOI/10.5281/zenodo.21090748.svg" alt="DOI"></a>
</p>

---

**SpectralBrain** computes, analyzes, and visualizes spectral shape descriptors
of brain structures — cortical surfaces, subcortical meshes, hippocampal
subfields and white-matter tract surfaces, all as triangle meshes.
It connects spectral geometry (the Laplace–Beltrami operator) to clinical
neuroimaging, with one pipeline from FreeSurfer / HippUnfold output through
statistically rigorous analysis to publication-ready figures.

<p align="center">
  <img src="assets/spectralbrain_concept.png" alt="SpectralBrain concept: input geometry → Laplace–Beltrami operator → spectral signature → descriptors" width="640">
</p>

<p align="center">
  <em>The core idea: any input geometry — cortical surface, subcortical/hippocampal
  mesh, or tract surface — is passed through the Laplace–Beltrami operator to
  obtain its eigenpairs {λ, φ}, from which pose- and mesh-free spectral descriptors
  (ShapeDNA, HKS, SI-HKS, WKS, GPS, BKS, functional maps, wavelets) are read out.</em>
</p>

## Statement of need

Volumetric and thickness measures collapse a structure's shape to a few scalars
and are sensitive to registration and voxel size. **Intrinsic spectral
descriptors** derived from the Laplace–Beltrami operator (LBO) — ShapeDNA, the
Heat/Wave Kernel Signatures, and relatives — characterize shape *independently
of pose and parameterization*, capturing geometry that volume alone misses. They
are well established in geometry processing but scattered across
research code, rarely packaged with the I/O, multi-site harmonization,
correct multiple-comparison statistics, and rendering that a neuroimaging study
needs end to end. SpectralBrain fills that gap as a single, tested library, with
a primary focus on the hippocampus in mesial temporal lobe epilepsy, while
remaining general to any brain surface mesh.

## Key capabilities

- **Spectral descriptors** — ShapeDNA, Heat Kernel Signature (HKS),
  Scale-Invariant HKS, Wave Kernel Signature (WKS), Global Point Signature
  (GPS), Bates–Kornfeld Signature (BKS) and its inverse, functional maps, and
  more — all from the LBO eigenpairs of a triangle mesh.
- **Beyond the LBO** — a family of complementary operators that produce the same
  `SpectralDecomposition` (Dirac, Steklov, Hamiltonian, Hodge, magnetic,
  connection and sheaf Laplacians, Finsler, biharmonic, persistent Laplacian,
  graph spectra, classical morphometric spectra) in `spectralbrain.spectral.operators`.
- **Input-agnostic I/O** — FreeSurfer surfaces and morphometry, GIfTI
  (`.surf.gii` / `.func.gii` / `.shape.gii`), NIfTI / MGZ volumes and labels,
  HippUnfold v1 & v2 outputs, `.ply / .obj / .stl / .vtk` and HDF5, with
  automatic format detection; volumes and label maps become LBO-ready meshes
  (`volume_to_mesh`, `raw_to_mesh`).
- **Cohort loading** — BIDS / derivatives, FreeSurfer `SUBJECTS_DIR`, or an
  explicit list, loaded in parallel and stacked for group analysis; FreeSurfer
  measures can be resampled onto a common template; TractSeg bundle masks import
  directly as isosurface meshes.
- **Statistics done right** — vertex-wise tests with genuine family-wise error
  control (max-statistic permutation), FDR, partial correlations with correct
  degrees of freedom, TFCE, the analytic DeLong AUC test, BCa bootstrap, ComBat /
  ComBat-GAM harmonization, and six PyMC Bayesian models.
- **Contiguous clustering & atlas comparison** — a distance-dependent Chinese
  Restaurant Process (ddCRP) with a Normal-Inverse-Wishart collapsed marginal
  likelihood (spatial and fPCA-functional variants), consensus clustering,
  data-driven hyperparameter autotuning, and a cluster-vs-atlas suite (ARI/AMI/
  NMI/VI, Dice/Jaccard, within-parcel homogeneity) reported against **size-matched
  random parcellations**, plus spatially-aware ARI (spARI) and eigenstrapping/
  BrainSMASH spatial nulls for bounded surfaces.
- **Publication figures** — a template-free six-view 3D renderer (vedo), unfolded
  flat-maps, Bayesian-posterior plots, **advanced 3D tractography** (direction-
  encoded/scalar streamlines, bundle surfaces with HKS/WKS overlays, multi-POV
  montages), and **parcellation-vs-clustering grids** (views × labelings) for
  hippocampi, brains, and bundles.

## Installation

```bash
pip install spectralbrain
```

Optional feature sets (extras):

```bash
pip install "spectralbrain[bayesian]"   # PyMC, nutpie, NumPyro, BlackJAX, ArviZ
pip install "spectralbrain[viz]"        # vedo, fury, trimesh, cmcrameri, …
pip install "spectralbrain[gpu]"        # torch, CuPy, JAX (CUDA)
pip install "spectralbrain[neuro]"      # nilearn, dipy, pybids, templateflow, …
pip install "spectralbrain[operators]"  # gudhi, ripser, POT, networkx, … (non-LBO operators)
pip install "spectralbrain[tuning]"     # optuna (ddCRP autotuning; random fallback)
pip install "spectralbrain[full]"       # everything above
```

Requires Python 3.11–3.12.

## API at a glance

The core API is on the top-level package; heavier statistics and visualization
live in submodules you import explicitly (mirroring `scipy.stats`):

```python
import spectralbrain as sb               # meshes, descriptors, I/O
import spectralbrain.statistics as sbstats   # frequentist + Bayesian
import spectralbrain.viz as sbviz             # 3D / 2D figures
```

## Quick start

### 1 — Mesh → eigenpairs → descriptors

```python
import spectralbrain as sb

# A BrainMesh from vertices (N, 3) and faces (M, 3).
vertices, faces = sb.io.load_gifti_surface("path/to/surf/gii")
mesh   = sb.BrainMesh(vertices, faces)
decomp = mesh.decompose(k=100)                       # 100 LBO eigenpairs

hks = sb.compute_hks(decomp, t_values=[1.0, 10.0, 100.0])   # (N, 3)
wks = sb.compute_wks(decomp, n_energies=50)                 # (N, 50)
dna = sb.compute_shapedna(decomp)                           # (k-1,) global
```

SpectralBrain is mesh-only. Point clouds live in the sibling library
**pointsbrain**, which builds a point-cloud `SpectralDecomposition` and then reuses
every descriptor, statistic and figure of this library unchanged.

### 2 — Compare two shapes

```python
d = sb.shapedna_distance(dna_a, dna_b)   # pose-invariant spectral distance
```

### 3 — Vertex-wise group statistics with FWER control

```python
import spectralbrain.statistics as sbstats

# controls, patients : (n_subjects, n_vertices) descriptor fields
res = sbstats.vertexwise_permutation(
    controls, patients,
    n_permutations=5000,
    correction="max",      # family-wise error via the max-statistic null
    seed=0,
)
significant = res.significant          # boolean mask, FWER-controlled
```

`correction="fdr"` and `"none"` are also available; `vertexwise_ttest`
defaults to Welch's t-test.

### 4 — Compare two classifiers (analytic DeLong)

```python
auc_new, auc_ref, p = sbstats.auc_comparison_delong(y_true, scores_new, scores_ref)
```

### 5 — Six-view 3D render (template-free)

```python
import spectralbrain.viz as sbviz

fig = sbviz.plot_hippocampus_sixview(
    mesh, scalars=hks[:, 1],
    cmap="plasma", scalar_bar_title="HKS(t=10)",
    save="hipp_sixview.png",
)
# Pick any subset/order of the six canonical views:
fig = sbviz.plot_hippocampus_sixview(mesh, scalars=hks[:, 1],
                                     views=("superior", "left_lateral"))
```

Views: `anterior, posterior, inferior, superior, left_lateral, right_lateral`.
It renders *any* surface — HippUnfold v2 `den-8k`, an `aseg` ROI mesh, or a whole
cortical hemisphere — with no bundled template, so scalar↔vertex correspondence
is guaranteed.

### 6 — Bayesian sparse regression (extra: `[bayesian]`)

```python
from spectralbrain.statistics import HorseshoeRegression

model = HorseshoeRegression(tau_prior=0.5).fit(X, y, sampler="nuts")
importance = model.feature_importance()   # sparse posterior shrinkage
```

### 7 — Contiguous ddCRP clustering + atlas comparison

The sampler maintains **incremental per-component sufficient statistics** (the cluster scatter is never recomputed inside the candidate loop) and caches the NIW marginal per cluster, so it scales to dense surfaces (~1-3 s/draw at ~7k vertices). Pass `n_components` to PCA-whiten the descriptors for a further speedup and better conditioning.

```python
import spectralbrain.statistics as sbstats

# H : (V, d) per-vertex descriptors (e.g. fused HKS/WKS); vertices/faces from the mesh.
res = sbstats.cluster_ddcrp(H, faces=faces, vertices=vertices,
                            decay_kind="exponential")   # decay uses real edge distances
print(res)                                              # ClusterResult(method='ddcrp', n_clusters=…)

# Tune hyperparameters from the data (optuna if installed, else random search):
tuned = sbstats.autotune_ddcrp(H, faces=faces, vertices=vertices, n_trials=40)
final = tuned.cluster_result                            # refit ClusterResult

# Is the partition more atlas-like than a size-matched random parcellation?
report = sbstats.cluster_atlas_concordance(final.labels, atlas_labels,
                                           faces=faces, coords=vertices, n_null=1000)
print(report["metrics"]["ari"], report["ari_null"]["z"], report["spARI"]["spARI"])
```

### 8 — Tracts in 3D and parcellation-vs-clustering grids (extra: `[viz]`)

```python
import spectralbrain.viz as sbviz

# (a) advanced 3D tractography from several POVs (FURY; DEC orientation colours)
sl, _ = sbviz.load_streamlines("CST_left.trk", to_space="world")   # needs dipy
fig, _ = sbviz.streamlines_multiview(sl, views=("left", "anterior", "superior", "oblique"),
                                     out_path="cst_multiview.pdf")

# bundle surface from a TractSeg mask, with an HKS overlay
V, F = sbviz.mask_to_mesh(mask, affine=affine)
hks = sbviz.spectral_overlay(V, F, kind="hks")
sbviz.render_bundle_surface(V, F, scalars=hks, view="oblique", out_path="cst_hks.png")

# (b) 3D grid: columns = views, rows = a reference parcellation then each clustering
fig, meta = sbviz.plot_parcellation_vs_clusters(
    vertices, faces, parcellation=atlas_labels,
    clusterings={"ddCRP": res, "Leiden": leiden_res},   # ClusterResult or label arrays
    views=["left_lateral", "anterior", "superior"],
    save="parcellation_vs_clusters.png",
)
```

The grid works identically for hippocampi, whole brains, and bundle surfaces —
it operates on any `(vertices, faces)` mesh plus a dict of per-vertex labelings.

## Loading a cohort

```python
import spectralbrain as sb

# BIDS / derivatives (one file per subject):
files = sb.discover_bids("/data/derivatives/hippunfold",
                         "sub-{sub}/surf/sub-{sub}_hemi-L_*thickness.shape.gii")
group = sb.load_group(files, mode="maps", n_jobs=8)
res   = sb.group_comparison(group, group.covariate("group"), test="ttest")

# FreeSurfer SUBJECTS_DIR, resampled to a common template:
group = sb.load_group_freesurfer("/data/fs", measure="thickness",
                                 template="fsaverage", n_jobs=8)

# TractSeg bundle masks → meshes ready for .decompose():
bundles = sb.load_tractseg("/data/sub-01/tractseg_output", output="mesh")
decomp  = bundles["CST_left"].decompose(k=80)
```

`mode="pipeline"` runs load → decompose → descriptor per subject (with an
optional GPU `backend=`); `mode="maps"` stacks vertex-corresponded fields.

## Compute backends

Eigen-decomposition and Bayesian sampling run on pluggable backends:

```python
from spectralbrain.core.backends import TorchBackend       # or CupyBackend, JaxBackend
decomp = mesh.decompose(k=200, backend=TorchBackend())  # GPU eigsolve
```

Bayesian models accept `sampler="auto" | "nuts" | "nutpie" | "numpyro" |
"blackjax"`.

## Structure & workflow

From inputs to inference, SpectralBrain is one coherent pipeline —
**load geometry → build the LBO & decompose → compute descriptors → group
statistics → assess & visualize** — assembled from a small set of focused,
independently usable subpackages.

<p align="center">
  <img src="assets/spectralbrain_workflow.png" alt="SpectralBrain workflow: inputs, main five-step workflow, and modular subpackage structure" width="880">
</p>

<p align="center">
  <em>(a) inputs — geometry, operator, cohort, atlas, spectral descriptors, and
  inference; (b) the five-step main workflow, ending in a lateralization
  effect-size read-out; (c) the modular structure —
  <code>core · spectral · io · statistics · viz</code> (compute backends now live in
  <code>core</code>).</em>
</p>

## The pipeline in practice

The five stages above are not just a diagram — they run end to end on real
study designs. The figure below carries two analyses through the whole pipeline
side by side and adds a parallel **Bayesian lane**, so the same data are assessed
with both frequentist tools (max-statistic permutation, TFCE, DeLong AUC) and
Bayesian ones (hierarchical models, horseshoe priors, HDI + ROPE, LOO).

<p align="center">
  <img src="assets/spectralbrain_demo.png" alt="SpectralBrain end-to-end demonstration: hippocampal lateralization, cortical morphometry, and a Bayesian inference lane carried across the five pipeline stages" width="900">
</p>

<p align="center">
  <em>An end-to-end walkthrough. <strong>Columns</strong> are worked analyses —
  hippocampal lateralization in MTLE-HS (HippUnfold <code>den-8k</code> surfaces,
  L vs R), cortical morphometry (MTLE-HS vs. controls, FreeSurfer / Schaefer-200),
  and a Bayesian lane (hierarchical model → horseshoe priors → variable selection
  → posterior effect size with HDI + ROPE → posterior-predictive check and LOO
  model comparison). <strong>Rows</strong> are the five pipeline stages — load
  geometry → decompose → descriptors → group statistics → assess — tied on the
  left to the subpackages that implement them
  (<code>io · core · spectral · statistics · viz</code>). Panels are illustrative,
  generated from synthetic example data to show the shape and flow of an analysis,
  not empirical results.</em>
</p>

## Documentation map

```
spectralbrain/
├── runtime.py            types, logging, seeds, progress, containers, joblib, RAM/VRAM guards
├── core/                 BrainMesh, SpectralDecomposition, GeometricObject, compute backends
├── io/                   loaders/savers, meshing, parcellation, cohorts, TractSeg, preprocessing
├── spectral/
│   ├── lbo/              descriptors, distances, wavelets, collections, anisotropic, eigenmodes
│   └── operators/        Dirac/Steklov, Hamiltonian, Hodge/Ricci/sheaf, persistent, Finsler, graphs, …
├── statistics/
│   ├── analysis.py       vertex-wise inference, effect sizes, RSA, networks, EDA/QC, recommendations
│   ├── bayesian.py       Bayesian models + MCMC samplers
│   ├── normative.py      harmonisation, normative models, method comparison
│   ├── surrogates.py     bootstrap, null models, synthetic data
│   └── clustering/       clustering methods, ddCRP, cluster-vs-atlas statistics
├── utils/                atlases, example data, helpers
└── viz/                  graphics · bayes · clusters · spectral · hipp · render3d
```

| Subpackage | What it provides |
|---|---|
| `spectralbrain` (top level) | `BrainMesh`, `decompose`, all `compute_*` LBO descriptors, distances, I/O, cohort loading |
| `spectralbrain.core` | `BrainMesh`, `SpectralDecomposition`, the `GeometricObject` protocol, CPU / Torch / CuPy / JAX eigensolvers |
| `spectralbrain.io` | loaders/savers, BIDS & FreeSurfer discovery, `load_group`, template resampling, volume → mesh, TractSeg import, parcellation |
| `spectralbrain.spectral.lbo` | Laplace–Beltrami descriptors, distances, wavelets, functional maps, spectral deformation, eigenmodes |
| `spectralbrain.spectral.operators` | non-LBO spectral operators (lazy-loaded; extra `[operators]`) |
| `spectralbrain.statistics` | vertex-wise tests, TFCE, effect sizes, RSA, classification, EDA/QC, ComBat(-GAM), normative models, bootstrap & null models, Bayesian models and samplers, clustering |
| `spectralbrain.viz` | one module per analysis module (`graphics`, `bayes`, `clusters`, `spectral`, `hipp`) on a shared 3D engine (`render3d`) |

Upgrading from 0.0.x? The old → new import table is in [`CHANGELOG.md`](CHANGELOG.md).

## Validation

`examples/example_clustering_tracts.py` is a runnable end-to-end pipeline (HippUnfold hippocampus -> HKS/WKS -> ddCRP -> atlas stats -> 3D figures; synthetic fallback if no data). `validation/validate_spari.py` checks the spatially-aware Rand index: analytical properties (identity, label invariance, symmetry, **reduction to the exact ARI as the spatial scale vanishes**, chance level, locality monotonicity) run anywhere, plus an R cell-for-cell bridge (`--with-r`) against the published `spARI` package that justifies flipping `validated_against_R=True`.

## Development

```bash
git clone https://github.com/rdneuro/spectralbrain
cd spectralbrain
uv sync --group dev          # or: pip install -e ".[full]" + dev tools
uv run pytest                # run the test suite
uv run ruff check src/ tests/
```

## Citing

If SpectralBrain contributes to your work, please cite it. Someday, maybe, if we feel lucky, a JOSS paper will be submited; until then, cite the archived release on Zenodo:

> Debona, R. *SpectralBrain: Spectral Shape Analysis for Brain Structures*.
> Zenodo. https://doi.org/10.5281/zenodo.21090748

The DOI [10.5281/zenodo.21090748](https://doi.org/10.5281/zenodo.21090748)
always resolves to the latest release. See [`CITATION.cff`](CITATION.cff) for a
machine-readable citation — GitHub's **"Cite this repository"** button reads it.

## License

MIT — see [`LICENSE`](LICENSE).
