Custom atlases¶
Lacuna ships with several bundled atlases (lacuna info atlases), but you can bring your own. This guide shows how to use custom parcellation atlases with Regional Damage and Structural Network Mapping.
What you'll learn:
- Prepare a custom atlas and labels file
- Run Regional Damage with the XTRACT white matter tract atlas
- Run Structural Network Mapping with the Yeo 2011 functional network atlas
Setup¶
# Install Lacuna from GitHub
!pip install git+https://github.com/m-petersen/lacuna
# Install MRtrix3 via conda (needed for SNM section)
!conda install -c conda-forge -c MRtrix3 mrtrix3 libstdcxx-ng -y
Collecting git+https://github.com/m-petersen/lacuna Cloning https://github.com/m-petersen/lacuna to /tmp/pip-req-build-clf1gxj7 Running command git clone --filter=blob:none --quiet https://github.com/m-petersen/lacuna /tmp/pip-req-build-clf1gxj7 Resolved https://github.com/m-petersen/lacuna to commit a1e5049187637a4216b0f478abc948d826eed8b8 Installing build dependencies ... done Getting requirements to build wheel ... done Preparing metadata (pyproject.toml) ... done Requirement already satisfied: nibabel>=5.0 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from lacuna==0.0.1.dev431) (5.3.2) Requirement already satisfied: numpy>=1.24 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from lacuna==0.0.1.dev431) (2.2.6) Requirement already satisfied: scipy>=1.10 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from lacuna==0.0.1.dev431) (1.15.3) Requirement already satisfied: nilearn>=0.10 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from lacuna==0.0.1.dev431) (0.12.1) Requirement already satisfied: pandas>=2.0 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from lacuna==0.0.1.dev431) (2.3.3) Requirement already satisfied: pooch>=1.8 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from lacuna==0.0.1.dev431) (1.8.2) Requirement already satisfied: h5py>=3.10 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from lacuna==0.0.1.dev431) (3.15.1) Requirement already satisfied: tqdm>=4.66 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from lacuna==0.0.1.dev431) (4.67.1) Requirement already satisfied: joblib>=1.3 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from lacuna==0.0.1.dev431) (1.5.2) Requirement already satisfied: nitransforms>=23.0 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from lacuna==0.0.1.dev431) (25.1.0) Requirement already satisfied: templateflow>=23.0 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from lacuna==0.0.1.dev431) (25.1.1) Requirement already satisfied: pyyaml>=6.0 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from lacuna==0.0.1.dev431) (6.0.3) Requirement already satisfied: requests>=2.31 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from lacuna==0.0.1.dev431) (2.32.5) Requirement already satisfied: importlib-resources>=5.12 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from nibabel>=5.0->lacuna==0.0.1.dev431) (6.5.2) Requirement already satisfied: packaging>=20 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from nibabel>=5.0->lacuna==0.0.1.dev431) (25.0) Requirement already satisfied: typing-extensions>=4.6 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from nibabel>=5.0->lacuna==0.0.1.dev431) (4.15.0) Requirement already satisfied: lxml in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from nilearn>=0.10->lacuna==0.0.1.dev431) (6.0.2) Requirement already satisfied: scikit-learn>=1.4.0 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from nilearn>=0.10->lacuna==0.0.1.dev431) (1.7.2) Requirement already satisfied: python-dateutil>=2.8.2 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from pandas>=2.0->lacuna==0.0.1.dev431) (2.9.0.post0) Requirement already satisfied: pytz>=2020.1 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from pandas>=2.0->lacuna==0.0.1.dev431) (2025.2) Requirement already satisfied: tzdata>=2022.7 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from pandas>=2.0->lacuna==0.0.1.dev431) (2025.2) Requirement already satisfied: platformdirs>=2.5.0 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from pooch>=1.8->lacuna==0.0.1.dev431) (4.5.0) Requirement already satisfied: six>=1.5 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from python-dateutil>=2.8.2->pandas>=2.0->lacuna==0.0.1.dev431) (1.17.0) Requirement already satisfied: charset_normalizer<4,>=2 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from requests>=2.31->lacuna==0.0.1.dev431) (3.4.4) Requirement already satisfied: idna<4,>=2.5 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from requests>=2.31->lacuna==0.0.1.dev431) (3.11) Requirement already satisfied: urllib3<3,>=1.21.1 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from requests>=2.31->lacuna==0.0.1.dev431) (2.5.0) Requirement already satisfied: certifi>=2017.4.17 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from requests>=2.31->lacuna==0.0.1.dev431) (2025.11.12) Requirement already satisfied: threadpoolctl>=3.1.0 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from scikit-learn>=1.4.0->nilearn>=0.10->lacuna==0.0.1.dev431) (3.6.0) Requirement already satisfied: acres>=0.5.0 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from templateflow>=23.0->lacuna==0.0.1.dev431) (0.5.0) Requirement already satisfied: pybids>=0.15.2 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from templateflow>=23.0->lacuna==0.0.1.dev431) (0.21.0) Requirement already satisfied: formulaic>=0.3 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from pybids>=0.15.2->templateflow>=23.0->lacuna==0.0.1.dev431) (1.2.1) Requirement already satisfied: sqlalchemy>=1.4.31 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from pybids>=0.15.2->templateflow>=23.0->lacuna==0.0.1.dev431) (2.0.44) Requirement already satisfied: bids-validator>=1.14.7 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from pybids>=0.15.2->templateflow>=23.0->lacuna==0.0.1.dev431) (1.14.7.post0) Requirement already satisfied: num2words>=0.5.10 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from pybids>=0.15.2->templateflow>=23.0->lacuna==0.0.1.dev431) (0.5.14) Requirement already satisfied: click>=8.0 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from pybids>=0.15.2->templateflow>=23.0->lacuna==0.0.1.dev431) (8.3.0) Requirement already satisfied: universal_pathlib>=0.2.2 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from pybids>=0.15.2->templateflow>=23.0->lacuna==0.0.1.dev431) (0.3.6) Requirement already satisfied: frozendict>=2.3 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from pybids>=0.15.2->templateflow>=23.0->lacuna==0.0.1.dev431) (2.4.7) Requirement already satisfied: bidsschematools>=0.10 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from bids-validator>=1.14.7->pybids>=0.15.2->templateflow>=23.0->lacuna==0.0.1.dev431) (1.1.2) Requirement already satisfied: interface-meta>=1.2.0 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from formulaic>=0.3->pybids>=0.15.2->templateflow>=23.0->lacuna==0.0.1.dev431) (1.3.0) Requirement already satisfied: narwhals>=1.17 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from formulaic>=0.3->pybids>=0.15.2->templateflow>=23.0->lacuna==0.0.1.dev431) (2.11.0) Requirement already satisfied: wrapt>=1.0 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from formulaic>=0.3->pybids>=0.15.2->templateflow>=23.0->lacuna==0.0.1.dev431) (2.0.1) Requirement already satisfied: docopt>=0.6.2 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from num2words>=0.5.10->pybids>=0.15.2->templateflow>=23.0->lacuna==0.0.1.dev431) (0.6.2) Requirement already satisfied: greenlet>=1 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from sqlalchemy>=1.4.31->pybids>=0.15.2->templateflow>=23.0->lacuna==0.0.1.dev431) (3.2.4) Requirement already satisfied: fsspec>=2024.5.0 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from universal_pathlib>=0.2.2->pybids>=0.15.2->templateflow>=23.0->lacuna==0.0.1.dev431) (2025.10.0) Requirement already satisfied: pathlib-abc<0.6.0,>=0.5.1 in /home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages (from universal_pathlib>=0.2.2->pybids>=0.15.2->templateflow>=23.0->lacuna==0.0.1.dev431) (0.5.2)
!lacuna tutorial /tmp/tutorial_bids --force
Setting up tutorial data at: /tmp/tutorial_bids ✓ Tutorial data copied to: /tmp/tutorial_bids The tutorial dataset includes: - 3 synthetic subjects (sub-01, sub-02, sub-03) - Binary lesion masks in MNI152NLin6Asym space - BIDS-style structure
How Lacuna handles custom atlases¶
All lacuna run analyses accept the --custom-parcellation flag:
--custom-parcellation <NAME> <NIFTI> <LABELS> <SPACE>
| Argument | Description |
|---|---|
NAME |
A short name used to label outputs (e.g., XTRACT, Yeo7) |
NIFTI |
Path to the parcellation NIfTI file (3D integer-labeled or 4D probabilistic) |
LABELS |
Path to a text file mapping region IDs to names |
SPACE |
Coordinate space of the atlas (MNI152NLin6Asym or MNI152NLin2009cAsym) |
The flag can be specified multiple times and combined with --parcel-atlases.
Labels file format¶
The labels file is plain text with one region per line. Each line contains a numeric region ID and the region name, separated by a space:
1 Left-Precentral
2 Left-Postcentral
3 Left-SMA
Lines starting with # are treated as comments and ignored.
Download the atlas¶
!wget -q -O /tmp/xtract-maxprob5-1mm.nii.gz \
"https://github.com/SPMIC-UoN/XTRACT_atlases/raw/master/fsleyes_atlases/XTRACT/xtract-tract-atlases-maxprob5-1mm.nii.gz"
!wget -q -O /tmp/xtract-maxprob5-1mm.xml \
"https://github.com/SPMIC-UoN/XTRACT_atlases/raw/master/fsleyes_atlases/XTRACT.xml"
print("Downloaded XTRACT atlas and label file.")
Downloaded XTRACT atlas and label file.
2. Create a labels file¶
The XTRACT atlas ships with an XML label file. Convert it to a plain text labels file that Lacuna can read.
import xml.etree.ElementTree as ET
tree = ET.parse("/tmp/xtract-maxprob5-1mm.xml")
root = tree.getroot()
with open("/tmp/xtract_labels.txt", "w") as f:
for label in root.iter("label"):
# XTRACT XML uses 0-based indices; Lacuna labels are 1-based (0 is background)
index = int(label.get("index")) + 1
name = label.text.strip()
f.write(f"{index} {name}\n")
!cat /tmp/xtract_labels.txt
1 Anterior Commissure 2 Arcuate Fasciculus L 3 Arcuate Fasciculus R 4 Acoustic Radiation L 5 Acoustic Radiation R 6 Anterior Thalamic Radiation L 7 Anterior Thalamic Radiation R 8 Cingulum subsection: Dorsal L 9 Cingulum subsection: Dorsal R 10 Cingulum subsection: Peri-genual L 11 Cingulum subsection: Peri-genual R 12 Cingulum subsection: Temporal L 13 Cingulum subsection: Temporal R 14 Corticospinal Tract L 15 Corticospinal Tract R 16 Frontal Aslant Tract L 17 Frontal Aslant Tract R 18 Forceps Major 19 Forceps Minor 20 Fornix L 21 Fornix R 22 Inferior Fronto-Occipital Fasciculus L 23 Inferior Fronto-Occipital Fasciculus R 24 Inferior Longitudinal Fasciculus L 25 Inferior Longitudinal Fasciculus R 26 Middle Cerebellar Peduncle 27 Middle Longitudinal Fasciculus L 28 Middle Longitudinal Fasciculus R 29 Optic Radiation L 30 Optic Radiation R 31 Superior Longitudinal Fasciculus 1 L 32 Superior Longitudinal Fasciculus 1 R 33 Superior Longitudinal Fasciculus 2 L 34 Superior Longitudinal Fasciculus 2 R 35 Superior Longitudinal Fasciculus 3 L 36 Superior Longitudinal Fasciculus 3 R 37 Superior Thalamic Radiation L 38 Superior Thalamic Radiation R 39 Uncinate Fasciculus L 40 Uncinate Fasciculus R 41 Vertical Occipital Fasciculus L 42 Vertical Occipital Fasciculus R
Run analysis¶
The XTRACT atlas is in MNI152NLin6Asym space (FSL's standard MNI space).
!lacuna run rd \
/tmp/tutorial_bids/ \
/tmp/outputs_rd_xtract/ \
--participant-label 01 \
--mask-space MNI152NLin6Asym \
--custom-parcellation \
XTRACT \
/tmp/xtract-maxprob5-1mm.nii.gz \
/tmp/xtract_labels.txt \
MNI152NLin6Asym
!lacuna run rd --help
usage: lacuna run rd [-h] [--participant-label LABEL [LABEL ...]]
[--session-id SESSION [SESSION ...]] [--pattern GLOB]
[--mask-space SPACE] [--overwrite]
[--on-empty {warn,skip,error}] [--keep-intermediate] [-v]
[--nprocs N] [--batch-size N]
[--parcel-atlases ATLAS [ATLAS ...]]
[--custom-parcellation NAME NIFTI LABELS SPACE]
bids_dir output_dir
RegionalDamage Analysis
Computes lesion overlap with brain parcellations (atlases).
For each parcel, calculates the percentage of voxels overlapping
with the lesion mask.
Use 'lacuna info atlases' to see available atlases.
Examples:
lacuna run rd /bids /output
lacuna run rd /bids /output --parcel-atlases schaefer2018parcels100networks7
lacuna run rd /bids /output --parcel-atlases schaefer2018parcels400networks17 tian2020parcels32
positional arguments:
bids_dir Root folder of BIDS dataset (sub-XXXXX folders at top
level), OR path to a single NIfTI mask file for quick
analysis
output_dir Output directory for derivatives
options:
-h, --help show this help message and exit
BIDS filtering options:
--participant-label LABEL [LABEL ...], --participant_label LABEL [LABEL ...]
Subject IDs to process (without sub- prefix)
--session-id SESSION [SESSION ...], --session_id SESSION [SESSION ...]
Session IDs to process (without ses- prefix)
--pattern GLOB Glob pattern to filter mask files (e.g., '*label-
WMH*')
Mask space options:
--mask-space SPACE Coordinate space of input masks (MNI152NLin6Asym or
MNI152NLin2009cAsym). Required if not detectable from
filename or sidecar JSON.
Other options:
--overwrite Overwrite existing output files
--on-empty {warn,skip,error}
Behavior when a subject has an empty mask (no non-zero
voxels) or no overlap with the analysis network/atlas:
warn (default, process with zero-valued outputs), skip
(exclude from processing), or error (raise error and
halt processing).
--keep-intermediate Keep intermediate results in output
-v, --verbose Increase verbosity (-v=INFO, -vv=DEBUG)
Performance options:
--nprocs N Number of parallel processes for subject processing
(-1 for all CPUs)
--batch-size N Number of subjects to process before writing outputs
(-1 for all). Lower values produce incremental output
and reduce peak memory.
RegionalDamage options:
--parcel-atlases ATLAS [ATLAS ...]
Atlas names to use. Use 'lacuna info atlases' to list
available atlases.
--custom-parcellation NAME NIFTI LABELS SPACE
Custom parcellation: a short name for output
labelling, NIfTI file path, labels file path, and
coordinate space (e.g., MNI152NLin6Asym). Can be
specified multiple times.
!ls /tmp/outputs_rd_xtract/sub-01/ses-01/anat/
sub-01_ses-01_desc-provenance.json sub-01_ses-01_label-acuteinfarct_method-rd_atlas-XTRACT_desc-damagebin_parcelstats.json sub-01_ses-01_label-acuteinfarct_method-rd_atlas-XTRACT_desc-damagebin_parcelstats.tsv sub-01_ses-01_label-acuteinfarct_method-rd_atlas-XTRACT_desc-damagepct_parcelstats.json sub-01_ses-01_label-acuteinfarct_method-rd_atlas-XTRACT_desc-damagepct_parcelstats.tsv sub-01_ses-01_label-acuteinfarct_space-MNI152NLin6Asym_mask.nii.gz
Inspect the results¶
The output shows what percentage of each white matter tract is overlapped by the lesion.
import pandas as pd
from pathlib import Path
output_dir = Path("/tmp/outputs_rd_xtract/sub-01/ses-01/anat/")
tsv_files = list(output_dir.glob("*damagepct*parcelstats.tsv"))
df = pd.read_csv(tsv_files[0], sep="\t")
df.sort_values(by="value", ascending=False).head(10)
| region | value | |
|---|---|---|
| 22 | Inferior Fronto-Occipital Fasciculus R | 5.251906 |
| 17 | Forceps Major | 4.460023 |
| 2 | Arcuate Fasciculus R | 0.000000 |
| 3 | Acoustic Radiation L | 0.000000 |
| 4 | Acoustic Radiation R | 0.000000 |
| 5 | Anterior Thalamic Radiation L | 0.000000 |
| 6 | Anterior Thalamic Radiation R | 0.000000 |
| 7 | Cingulum subsection: Dorsal L | 0.000000 |
| 0 | Anterior Commissure | 0.000000 |
| 1 | Arcuate Fasciculus L | 0.000000 |
Visualize lesion and tract overlap¶
Overlay the lesion with the most affected tract (Inferior Fronto-Occipital Fasciculus R, label 23 in the XTRACT atlas).
import nibabel as nib
import numpy as np
from nilearn import plotting
xtract_img = nib.load("/tmp/xtract-maxprob5-1mm.nii.gz")
xtract_data = xtract_img.get_fdata()
# Extract the Inferior Fronto-Occipital Fasciculus R (label 23)
xtract_ifof = (xtract_data == 23).astype(np.int8)
roi_img = nib.Nifti1Image(xtract_ifof, affine=xtract_img.affine, header=xtract_img.header)
lesion_img = nib.load("/tmp/tutorial_bids/sub-01/ses-01/anat/sub-01_ses-01_space-MNI152NLin6Asym_label-acuteinfarct_mask.nii.gz")
plot = plotting.plot_roi(roi_img,
radiological=True,
title="Inferior Fronto-Occipital Fasciculus R",
display_mode="z",
draw_cross=False,
colorbar=False)
plot.add_overlay(lesion_img, cmap='Reds_r', transparency=0.5)
/home/marvin/miniconda3/envs/lacuna/lib/python3.10/site-packages/numpy/ma/core.py:2892: UserWarning: Warning: converting a masked element to nan. _data = np.array(data, dtype=dtype, copy=copy,
You can also combine custom and bundled atlases in a single run:
!lacuna run rd \
/tmp/tutorial_bids/ \
/tmp/outputs_rd_combined/ \
--participant-label 01 \
--mask-space MNI152NLin6Asym \
--parcel-atlases schaefer2018parcels100networks7 \
--custom-parcellation \
XTRACT \
/tmp/xtract-maxprob5-1mm.nii.gz \
/tmp/xtract_labels.txt \
MNI152NLin6Asym
!ls /tmp/outputs_rd_combined/sub-01/ses-01/anat/
sub-01_ses-01_desc-provenance.json sub-01_ses-01_label-acuteinfarct_method-rd_atlas-schaefer2018parcels100networks7_desc-damagebin_parcelstats.json sub-01_ses-01_label-acuteinfarct_method-rd_atlas-schaefer2018parcels100networks7_desc-damagebin_parcelstats.tsv sub-01_ses-01_label-acuteinfarct_method-rd_atlas-schaefer2018parcels100networks7_desc-damagepct_parcelstats.json sub-01_ses-01_label-acuteinfarct_method-rd_atlas-schaefer2018parcels100networks7_desc-damagepct_parcelstats.tsv sub-01_ses-01_label-acuteinfarct_method-rd_atlas-XTRACT_desc-damagebin_parcelstats.json sub-01_ses-01_label-acuteinfarct_method-rd_atlas-XTRACT_desc-damagebin_parcelstats.tsv sub-01_ses-01_label-acuteinfarct_method-rd_atlas-XTRACT_desc-damagepct_parcelstats.json sub-01_ses-01_label-acuteinfarct_method-rd_atlas-XTRACT_desc-damagepct_parcelstats.tsv sub-01_ses-01_label-acuteinfarct_space-MNI152NLin6Asym_mask.nii.gz
Fetch the atlas with nilearn¶
We use the 7-network thick cortical model and save it as a standalone NIfTI. The Yeo atlas is in MNI152NLin6Asym space.
import nibabel as nib
from nilearn.datasets import fetch_atlas_yeo_2011
yeo = fetch_atlas_yeo_2011()
# Save the 7-network thick cortical model
img = nib.load(yeo["thick_7"])
nib.save(img, "/tmp/yeo_7networks.nii.gz")
print(f"Yeo atlas shape: {img.shape}, voxel size: {img.header.get_zooms()[:3]}")
/tmp/ipykernel_8915/3461622051.py:4: DeprecationWarning: From release >=0.13.0, instead of returning several atlas image accessible via different keys, this fetcher will return the atlas as a dictionary with a single atlas image, accessible through a 'maps' key. To suppress this warning, Please use the parameters 'n_networks' and 'thickness' to specify the exact atlas image you want. yeo = fetch_atlas_yeo_2011()
[fetch_atlas_yeo_2011] Dataset found in /home/marvin/nilearn_data/yeo_2011
Yeo atlas shape: (256, 256, 256, 1), voxel size: (np.float32(1.0), np.float32(1.0), np.float32(1.0))
Create a labels file¶
labels = {
1: "Visual",
2: "Somatomotor",
3: "Dorsal-Attention",
4: "Ventral-Attention",
5: "Limbic",
6: "Frontoparietal",
7: "Default",
}
with open("/tmp/yeo_7networks_labels.txt", "w") as f:
for idx, name in labels.items():
f.write(f"{idx} {name}\n")
!cat /tmp/yeo_7networks_labels.txt
1 Visual 2 Somatomotor 3 Dorsal-Attention 4 Ventral-Attention 5 Limbic 6 Frontoparietal 7 Default
3. Fetch a tractogram¶
SNM requires a structural connectome. We use the lightweight HCP1065 tractogram for this tutorial.
!lacuna fetch hcp1065 \
--output-dir /tmp/hcp1065_data
Fetching HCP1065 structural tractogram... Output: /tmp/hcp1065_data Keep original: True hcp1065_avg_tracts_trk.zip: 100%|████████████| 588M/588M [00:14<00:00, 40.2MB/s] Found 77 tract files (10 excluded) Loading and merging streamlines... Merging tracts: 100%|███████████████████████████| 77/77 [00:19<00:00, 4.03it/s] Processed 77 files, 479804 total streamlines Saving merged tractogram to /tmp/hcp1065_data/hcp1065.tck... Merge complete: /tmp/hcp1065_data/hcp1065.tck ✓ HCP1065 fetch complete! Files: 2 Duration: 42.1s Output: /tmp/hcp1065_data
4. Run Structural Network Mapping¶
!lacuna run snm \
/tmp/tutorial_bids/ \
/tmp/outputs_snm_yeo/ \
--connectome-path /tmp/hcp1065_data/hcp1065.tck \
--participant-label 01 \
--mask-space MNI152NLin6Asym \
--custom-parcellation \
Yeo7 \
/tmp/yeo_7networks.nii.gz \
/tmp/yeo_7networks_labels.txt \
MNI152NLin6Asym \
--compute-disconnectivity-matrix \
--compute-roi-disconnection \
--verbose
2026-04-01 09:56:20 - lacuna.cli.main - INFO - Lacuna CLI starting 2026-04-01 09:56:20 - lacuna.cli.main - INFO - Input: /tmp/tutorial_bids 2026-04-01 09:56:20 - lacuna.cli.main - INFO - Output directory: /tmp/outputs_snm_yeo 2026-04-01 09:56:20 - lacuna.cli.main - INFO - Analysis: snm 2026-04-01 09:56:20 - lacuna.cli.main - INFO - Registering structural connectome: hcp1065.tck 2026-04-01 09:56:20 - lacuna.cli.main - INFO - Registering custom parcellation: Yeo7 (space=MNI152NLin6Asym, res=1mm) 2026-04-01 09:56:20 - lacuna.cli.main - INFO - Running analysis: StructuralNetworkMapping 2026-04-01 09:56:20 - lacuna.cli.main - INFO - 2026-04-01 09:56:20 - lacuna.cli.main - INFO - ============================================================ 2026-04-01 09:56:20 - lacuna.cli.main - INFO - DISCOVERY SUMMARY 2026-04-01 09:56:20 - lacuna.cli.main - INFO - ============================================================ 2026-04-01 09:56:20 - lacuna.cli.main - INFO - Total mask images: 1 2026-04-01 09:56:20 - lacuna.cli.main - INFO - Unique subjects: 1 2026-04-01 09:56:20 - lacuna.cli.main - INFO - Unique sessions: 1 2026-04-01 09:56:20 - lacuna.cli.main - INFO - Labels: acuteinfarct 2026-04-01 09:56:20 - lacuna.cli.main - INFO - Filters: subjects=['01'] 2026-04-01 09:56:20 - lacuna.cli.main - INFO - ============================================================ 2026-04-01 09:56:20 - lacuna.cli.main - INFO - 2026-04-01 09:56:20 - lacuna.cli.main - INFO - Masks to process: 2026-04-01 09:56:20 - lacuna.cli.main - INFO - 1. sub-01/ses-01/acuteinfarct 2026-04-01 09:56:20 - lacuna.cli.main - INFO - Processing subjects: 0%| | 0/1 [00:00<?, ?it/s]2026-04-01 09:56:20 - lacuna.analysis - INFO - 2026-04-01 09:56:20 - lacuna.analysis - INFO - ====================================================================== 2026-04-01 09:56:20 - lacuna.analysis - INFO - Running StructuralNetworkMapping 2026-04-01 09:56:20 - lacuna.analysis - INFO - ====================================================================== 2026-04-01 09:56:23 - lacuna.spatial.transform - INFO - Warping image 'mask' to reference: MNI152NLin6Asym@1.0mm → MNI152NLin2009cAsym@2mm (interpolation: nearest) 2026-04-01 09:56:26 - lacuna.analysis - INFO - Using cached TDI: /home/marvin/.cache/lacuna/tdi/tdi_aabe71d9b59b_2mm.nii.gz 2026-04-01 09:56:26 - lacuna.analysis - INFO - Atlas space (MNI152NLin6Asym) differs from tractogram space (MNI152NLin2009cAsym). Transforming atlas 'Yeo7'... 2026-04-01 09:56:26 - lacuna.spatial.transform - INFO - Warping image 'atlas 'Yeo7'' to reference: MNI152NLin6Asym@1mm → MNI152NLin2009cAsym@2mm (interpolation: nearest) 2026-04-01 09:56:28 - lacuna.analysis - INFO - Atlas transformed and cached to: /home/marvin/.cache/lacuna/atlases/atlas_f624c9d4d89b.nii.gz 2026-04-01 09:56:28 - lacuna.analysis - INFO - Filtering tractogram by mask... 2026-04-01 09:56:38 - lacuna.analysis - INFO - Tractogram filtered 2026-04-01 09:56:38 - lacuna.analysis - INFO - Computing track-density image (TDI)... 2026-04-01 09:56:38 - lacuna.analysis - INFO - Computing disconnection map... 2026-04-01 09:56:39 - lacuna.analysis - INFO - Computing connectivity matrices for 'Yeo7'... 2026-04-01 09:56:39 - lacuna.analysis - INFO - Computing full-brain connectivity matrix for 'Yeo7' (will be cached) 2026-04-01 09:56:41 - lacuna.analysis - INFO - Computing mask connectivity matrix 2026-04-01 09:56:41 - lacuna.analysis - INFO - Computing disconnectivity percentage 2026-04-01 09:56:41 - lacuna.analysis - INFO - Computing intact (post-disconnection) tractogram 2026-04-01 09:56:49 - lacuna.analysis - INFO - Computing intact connectivity matrix 2026-04-01 09:56:52 - lacuna.analysis - INFO - Transforming VoxelMap outputs from MNI152NLin2009cAsym@2mm to MNI152NLin6Asym@2mm 2026-04-01 09:56:52 - lacuna.spatial.transform - INFO - Warping image to reference: MNI152NLin2009cAsym@2mm → MNI152NLin6Asym@2mm (interpolation: linear) 2026-04-01 09:56:53 - lacuna.analysis - INFO - Analysis complete (8 results) Processing subjects: 100%|████████████████████████| 1/1 [00:33<00:00, 33.31s/it] 2026-04-01 09:56:53 - lacuna.cli.main - INFO - Successfully processed 1 subject(s) 2026-04-01 09:56:53 - lacuna.cli.main - INFO - Results saved to: /tmp/outputs_snm_yeo 2026-04-01 09:56:53 - lacuna.cli.main - INFO - Lacuna CLI completed successfully
!ls /tmp/outputs_snm_yeo/sub-01/ses-01/anat/
sub-01_ses-01_desc-provenance.json sub-01_ses-01_label-acuteinfarct_method-snm_atlas-Yeo7_desc-disconnectionpct_connmatrix.json sub-01_ses-01_label-acuteinfarct_method-snm_atlas-Yeo7_desc-disconnectionpct_connmatrix.tsv sub-01_ses-01_label-acuteinfarct_method-snm_atlas-Yeo7_desc-disconnectionpct_parcelstats.json sub-01_ses-01_label-acuteinfarct_method-snm_atlas-Yeo7_desc-disconnectionpct_parcelstats.tsv sub-01_ses-01_label-acuteinfarct_method-snm_atlas-Yeo7_desc-fullconnectivitymatrix_connmatrix.json sub-01_ses-01_label-acuteinfarct_method-snm_atlas-Yeo7_desc-fullconnectivitymatrix_connmatrix.tsv sub-01_ses-01_label-acuteinfarct_method-snm_atlas-Yeo7_desc-intactconnectivitymatrix_connmatrix.json sub-01_ses-01_label-acuteinfarct_method-snm_atlas-Yeo7_desc-intactconnectivitymatrix_connmatrix.tsv sub-01_ses-01_label-acuteinfarct_method-snm_atlas-Yeo7_desc-maskconnectivitymatrix_connmatrix.json sub-01_ses-01_label-acuteinfarct_method-snm_atlas-Yeo7_desc-maskconnectivitymatrix_connmatrix.tsv sub-01_ses-01_label-acuteinfarct_method-snm_atlas-Yeo7_desc-matrixstatistics_stats.json sub-01_ses-01_label-acuteinfarct_method-snm_desc-summarystatistics_stats.json sub-01_ses-01_label-acuteinfarct_method-snm_space-MNI152NLin6Asym_desc-disconnectionpct.json sub-01_ses-01_label-acuteinfarct_method-snm_space-MNI152NLin6Asym_desc-disconnectionpct.nii.gz sub-01_ses-01_label-acuteinfarct_space-MNI152NLin2009cAsym_mask.nii.gz
5. Inspect the results¶
View the ROI-level disconnection values for the 7 Yeo networks.
output_dir = Path("/tmp/outputs_snm_yeo/sub-01/ses-01/anat/")
tsv_files = list(output_dir.glob("*disconnectionpct*parcelstats.tsv"))
df = pd.read_csv(tsv_files[0], sep="\t")
df.sort_values(by="value", ascending=False)
| region | value | |
|---|---|---|
| 0 | Visual | 4.547500 |
| 5 | Frontoparietal | 3.579716 |
| 6 | Default | 1.577464 |
| 4 | Limbic | 1.087839 |
| 3 | Ventral-Attention | 0.255028 |
| 2 | Dorsal-Attention | 0.209569 |
| 1 | Somatomotor | 0.109049 |
View the 7x7 disconnectivity matrix.
import numpy as np
import matplotlib.pyplot as plt
matrix_files = list(output_dir.glob("*disconnectionpct*connmatrix.tsv"))
mat = pd.read_csv(matrix_files[0], sep="\t", header=0, index_col=0)
fig, ax = plt.subplots(figsize=(7, 6))
im = ax.imshow(mat.values, cmap="Reds", vmin=0)
ax.set_xticks(range(len(mat.columns)))
ax.set_xticklabels(mat.columns, rotation=45, ha="right")
ax.set_yticks(range(len(mat.index)))
ax.set_yticklabels(mat.index)
plt.colorbar(im, label="Disconnectivity (%)")
ax.set_title("Yeo 7-network disconnectivity matrix")
plt.tight_layout()
plt.show()
Space handling¶
Lacuna automatically transforms the custom atlas to match the input mask space if they differ. For example, if your masks are in MNI152NLin2009cAsym but the atlas is in MNI152NLin6Asym, the atlas will be warped to match. This requires the corresponding TemplateFlow transform files, which are downloaded automatically.