[ENH]: Support Brainnetome 246 parcellation #275

Merged
synchon merged 4 commits from feat/brainnetome-parcellation into main 2024-03-07 08:40:12 +00:00
4 changed files with 215 additions and 0 deletions

View file

@ -365,6 +365,16 @@ Available
| resting-state functional connectivity. | resting-state functional connectivity.
| NeuroImage, Volume 273 (2023). | NeuroImage, Volume 273 (2023).
| https://doi.org/10.1016/j.neuroimage.2023.120010 | https://doi.org/10.1016/j.neuroimage.2023.120010
* - Brainnetome
- ``threshold``
- ``Brainnetome_thr0``, ``Brainnetome_thr25``, ``Brainnetome_thr50``
- ``MNI152NLin6Asym``
- 0.0.4
- | Fan, L., Li, H., Zhuo, J., et al.
| The Human Brainnetome Atlas: A New Brain Atlas Based on Connectional
| Architecture
| Cerebral Cortex, Volume 26(8), Pages 3508–3526 (2016).
| https://doi.org/10.1093/cercor/bhw157
Planned Planned

View file

@ -0,0 +1 @@
Add ``Brainnetome 246`` parcellation to ``junifer.data`` by `Synchon Mandal`_

View file

@ -127,6 +127,13 @@ for n_rois in range(100, 1001, 100):
"kong_networks": 17, "kong_networks": 17,
"space": "MNI152NLin6Asym", "space": "MNI152NLin6Asym",
} }
# Add Brainnetome parcellation info
for threshold in [0, 25, 50]:
_available_parcellations[f"Brainnetome_thr{threshold}"] = {
"family": "Brainnetome",
"threshold": threshold,
"space": "MNI152NLin6Asym",
}
def register_parcellation( def register_parcellation(
@ -523,6 +530,9 @@ def _retrieve_parcellation(
Number of Yeo networks to use (default None). Number of Yeo networks to use (default None).
``kong_networks`` : {17}, optional ``kong_networks`` : {17}, optional
Number of Kong networks to use (default None). Number of Kong networks to use (default None).
* Brainnetome :
``threshold`` : {0, 25, 50}
Threshold for the probabilistic maps of subregion.
Returns Returns
------- -------
@ -586,6 +596,12 @@ def _retrieve_parcellation(
resolution=resolution, resolution=resolution,
**kwargs, **kwargs,
) )
elif family == "Brainnetome":
parcellation_fname, parcellation_labels = _retrieve_brainnetome(
parcellations_dir=parcellations_dir,
resolution=resolution,
**kwargs,
)
else: else:
raise_error( raise_error(
f"The provided parcellation name {family} cannot be retrieved." f"The provided parcellation name {family} cannot be retrieved."
@ -1555,6 +1571,122 @@ def _retrieve_yan(
return parcellation_fname, labels return parcellation_fname, labels
def _retrieve_brainnetome(
parcellations_dir: Path,
resolution: Optional[float] = None,
threshold: Optional[int] = None,
) -> Tuple[Path, List[str]]:
"""Retrieve Brainnetome parcellation.
Parameters
----------
parcellations_dir : pathlib.Path
The path to the parcellation data directory.
resolution : {1.0, 1.25, 2.0}, optional
The desired resolution of the parcellation to load. If it is not
available, the closest resolution will be loaded. Preferably, use a
resolution higher than the desired one. By default, will load the
highest one (default None). Available resolutions for this
parcellation are 1mm, 1.25mm and 2mm.
threshold : {0, 25, 50}, optional
The threshold for the probabilistic maps of subregion (default None).
Returns
-------
pathlib.Path
File path to the parcellation image.
list of str
Parcellation labels.
Raises
------
RuntimeError
If there is a problem fetching files.
ValueError
If invalid value is provided for ``threshold``.
"""
logger.info("Parcellation parameters:")
logger.info(f"\tresolution: {resolution}")
logger.info(f"\tthreshold: {threshold}")
# Check resolution
_valid_resolutions = [1.0, 1.25, 2.0]
resolution = closest_resolution(resolution, _valid_resolutions)
# Check threshold value
_valid_threshold = [0, 25, 50]
if threshold not in _valid_threshold:
raise_error(
f"The parameter `threshold` ({threshold}) needs to be one of the "
f"following: {_valid_threshold}"
)
# Correct resolution for further stuff
if resolution in [1.0, 2.0]:
resolution = int(resolution)
parcellation_fname = (
parcellations_dir
/ "BNA246"
/ f"BNA-maxprob-thr{threshold}-{resolution}mm.nii.gz"
)
# Check for existence of parcellation
if not parcellation_fname.exists():
# Set URL
url = f"http://neurovault.org/media/images/1625/BNA-maxprob-thr{threshold}-{resolution}mm.nii.gz"
logger.info(f"Downloading Brainnetome from {url}")
# Make HTTP request
try:
resp = httpx.get(url, follow_redirects=True)
resp.raise_for_status()
except httpx.HTTPError as exc:
raise_error(
f"Error response {exc.response.status_code} while "
f"requesting {exc.request.url!r}",
klass=RuntimeError,
)
else:
# Create local directory if not present
parcellation_fname.parent.mkdir(parents=True, exist_ok=True)
# Create file if not present
parcellation_fname.touch(exist_ok=True)
# Open file and write bytes
parcellation_fname.write_bytes(resp.content)
# Load labels
labels = (
sorted([f"SFG_L(R)_7_{i}" for i in range(1, 8)] * 2)
+ sorted([f"MFG_L(R)_7_{i}" for i in range(1, 8)] * 2)
+ sorted([f"IFG_L(R)_6_{i}" for i in range(1, 7)] * 2)
+ sorted([f"OrG_L(R)_6_{i}" for i in range(1, 7)] * 2)
+ sorted([f"PrG_L(R)_6_{i}" for i in range(1, 7)] * 2)
+ sorted([f"PCL_L(R)_2_{i}" for i in range(1, 3)] * 2)
+ sorted([f"STG_L(R)_6_{i}" for i in range(1, 7)] * 2)
+ sorted([f"MTG_L(R)_4_{i}" for i in range(1, 5)] * 2)
+ sorted([f"ITG_L(R)_7_{i}" for i in range(1, 8)] * 2)
+ sorted([f"FuG_L(R)_3_{i}" for i in range(1, 4)] * 2)
+ sorted([f"PhG_L(R)_6_{i}" for i in range(1, 7)] * 2)
+ sorted([f"pSTS_L(R)_2_{i}" for i in range(1, 3)] * 2)
+ sorted([f"SPL_L(R)_5_{i}" for i in range(1, 6)] * 2)
+ sorted([f"IPL_L(R)_6_{i}" for i in range(1, 7)] * 2)
+ sorted([f"PCun_L(R)_4_{i}" for i in range(1, 5)] * 2)
+ sorted([f"PoG_L(R)_4_{i}" for i in range(1, 5)] * 2)
+ sorted([f"INS_L(R)_6_{i}" for i in range(1, 7)] * 2)
+ sorted([f"CG_L(R)_7_{i}" for i in range(1, 8)] * 2)
+ sorted([f"MVOcC _L(R)_5_{i}" for i in range(1, 6)] * 2)
+ sorted([f"LOcC_L(R)_4_{i}" for i in range(1, 5)] * 2)
+ sorted([f"LOcC_L(R)_2_{i}" for i in range(1, 3)] * 2)
+ sorted([f"Amyg_L(R)_2_{i}" for i in range(1, 3)] * 2)
+ sorted([f"Hipp_L(R)_2_{i}" for i in range(1, 3)] * 2)
+ sorted([f"BG_L(R)_6_{i}" for i in range(1, 7)] * 2)
+ sorted([f"Tha_L(R)_8_{i}" for i in range(1, 9)] * 2)
)
return parcellation_fname, labels
def merge_parcellations( def merge_parcellations(
parcellations_list: List["Nifti1Image"], parcellations_list: List["Nifti1Image"],
parcellations_names: List[str], parcellations_names: List[str],

View file

@ -16,6 +16,7 @@ from numpy.testing import assert_array_almost_equal, assert_array_equal
from junifer.data.parcellations import ( from junifer.data.parcellations import (
_retrieve_aicha, _retrieve_aicha,
_retrieve_brainnetome,
_retrieve_parcellation, _retrieve_parcellation,
_retrieve_schaefer, _retrieve_schaefer,
_retrieve_shen, _retrieve_shen,
@ -955,6 +956,77 @@ def test_retrieve_yan_incorrect_kong_networks(tmp_path: Path) -> None:
) )
@pytest.mark.parametrize(
"resolution, threshold",
[
(1.0, 0),
(1.0, 25),
(1.0, 50),
(1.25, 0),
(1.25, 25),
(1.25, 50),
(2, 0),
(2, 25),
(2, 50),
],
)
def test_brainnetome(
tmp_path: Path,
resolution: float,
threshold: int,
) -> None:
"""Test Brainnetome parcellation.
Parameters
----------
tmp_path : pathlib.Path
The path to the test directory.
resolution : float
The parametrized resolution values.
threshold : int
The parametrized threshold values.
"""
parcellations = list_parcellations()
parcellation_name = f"Brainnetome_thr{threshold}"
assert parcellation_name in parcellations
# Fix resolution
if resolution in [1.0, 2.0]:
resolution = int(resolution)
parcellation_file = f"BNA-maxprob-thr{threshold}-{resolution}mm.nii.gz"
# Load parcellation
img, label, img_path, space = load_parcellation(
name=parcellation_name,
parcellations_dir=tmp_path,
resolution=resolution,
)
assert img is not None
assert img_path.name == parcellation_file
assert space == "MNI152NLin6Asym"
assert len(label) == 246
assert_array_equal(
img.header["pixdim"][1:4], 3 * [resolution] # type: ignore
)
def test_retrieve_brainnetome_incorrect_threshold(tmp_path: Path) -> None:
"""Test retrieve Brainnetome with incorrect threshold.
Parameters
----------
tmp_path : pathlib.Path
The path to the test directory.
"""
with pytest.raises(ValueError, match="The parameter `threshold`"):
_retrieve_brainnetome(
parcellations_dir=tmp_path,
threshold=100,
)
def test_merge_parcellations() -> None: def test_merge_parcellations() -> None:
"""Test merging parcellations.""" """Test merging parcellations."""
# load some parcellations for testing # load some parcellations for testing