[ENH]: Support Brainnetome 246 parcellation #275
4 changed files with 215 additions and 0 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
1
docs/changes/newsfragments/275.feature
Normal file
1
docs/changes/newsfragments/275.feature
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Add ``Brainnetome 246`` parcellation to ``junifer.data`` by `Synchon Mandal`_
|
||||||
|
|
@ -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],
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue