[ENH]: Introduce get_template for getting templates #298
7 changed files with 122 additions and 5 deletions
1
docs/changes/newsfragments/298.feature
Normal file
1
docs/changes/newsfragments/298.feature
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Introduce :func:`.get_template` to fetch template space image tailored to a target data by `Synchon Mandal`_
|
||||||
|
|
@ -44,6 +44,7 @@ def test_get_dependency_information_short() -> None:
|
||||||
"ruamel.yaml",
|
"ruamel.yaml",
|
||||||
"httpx",
|
"httpx",
|
||||||
"tqdm",
|
"tqdm",
|
||||||
|
"templateflow",
|
||||||
]
|
]
|
||||||
if int(pl.python_version_tuple()[1]) < 10:
|
if int(pl.python_version_tuple()[1]) < 10:
|
||||||
dependency_list.append("importlib_metadata")
|
dependency_list.append("importlib_metadata")
|
||||||
|
|
@ -68,6 +69,7 @@ def test_get_dependency_information_long() -> None:
|
||||||
"ruamel.yaml",
|
"ruamel.yaml",
|
||||||
"httpx",
|
"httpx",
|
||||||
"tqdm",
|
"tqdm",
|
||||||
|
"templateflow",
|
||||||
]
|
]
|
||||||
for key in dependency_list:
|
for key in dependency_list:
|
||||||
assert key in dependency_information_keys
|
assert key in dependency_information_keys
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,6 @@ from .masks import (
|
||||||
get_mask,
|
get_mask,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .template_spaces import get_xfm
|
from .template_spaces import get_template, get_xfm
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,15 @@
|
||||||
# License: AGPL
|
# License: AGPL
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Union
|
from typing import Any, Dict, Optional, Union
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
import nibabel as nib
|
||||||
|
import numpy as np
|
||||||
|
from templateflow import api as tflow
|
||||||
|
|
||||||
from ..utils import logger, raise_error
|
from ..utils import logger, raise_error
|
||||||
|
from .utils import closest_resolution
|
||||||
|
|
||||||
|
|
||||||
def get_xfm(
|
def get_xfm(
|
||||||
|
|
@ -89,3 +93,71 @@ def get_xfm(
|
||||||
f.write(chunk)
|
f.write(chunk)
|
||||||
|
|
||||||
return xfm_file_path
|
return xfm_file_path
|
||||||
|
|
||||||
|
|
||||||
|
def get_template(
|
||||||
|
space: str,
|
||||||
|
target_data: Dict[str, Any],
|
||||||
|
extra_input: Optional[Dict[str, Any]] = None,
|
||||||
|
) -> nib.Nifti1Image:
|
||||||
|
"""Get template for the space, tailored for the target image.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
space : str
|
||||||
|
The name of the template space.
|
||||||
|
target_data : dict
|
||||||
|
The corresponding item of the data object for which the template space
|
||||||
|
will be loaded.
|
||||||
|
extra_input : dict, optional
|
||||||
|
The other fields in the data object. Useful for accessing other data
|
||||||
|
types (default None).
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
Nifti1Image
|
||||||
|
The template image.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
ValueError
|
||||||
|
If ``space`` is invalid.
|
||||||
|
RuntimeError
|
||||||
|
If template in the required resolution is not found.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Check for invalid space; early check to raise proper error
|
||||||
|
if space not in tflow.templates():
|
||||||
|
raise_error(f"Unknown template space: {space}")
|
||||||
|
|
||||||
|
# Get the min of the voxels sizes and use it as the resolution
|
||||||
|
target_img = target_data["data"]
|
||||||
|
resolution = np.min(target_img.header.get_zooms()[:3]).astype(int)
|
||||||
|
|
||||||
|
# Fetch available resolutions for the template
|
||||||
|
available_resolutions = [
|
||||||
|
int(min(val["zooms"]))
|
||||||
|
for val in tflow.get_metadata(space)["res"].values()
|
||||||
|
]
|
||||||
|
# Use the closest resolution if desired resolution is not found
|
||||||
|
resolution = closest_resolution(resolution, available_resolutions)
|
||||||
|
|
||||||
|
logger.info(f"Downloading template {space} in resolution {resolution}")
|
||||||
|
# Retrieve template
|
||||||
|
try:
|
||||||
|
template_path = tflow.get(
|
||||||
|
space,
|
||||||
|
raise_empty=True,
|
||||||
|
resolution=resolution,
|
||||||
|
suffix="T1w",
|
||||||
|
desc=None,
|
||||||
|
extension="nii.gz",
|
||||||
|
)
|
||||||
|
except Exception: # noqa: BLE001
|
||||||
|
raise_error(
|
||||||
|
f"Template {space} not found in the required resolution "
|
||||||
|
f"{resolution}",
|
||||||
|
klass=RuntimeError,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return nib.load(template_path) # type: ignore
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,12 @@
|
||||||
import socket
|
import socket
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import nibabel as nib
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from junifer.data import get_xfm
|
from junifer.data import get_template, get_xfm
|
||||||
|
from junifer.datareader import DefaultDataReader
|
||||||
|
from junifer.testing.datagrabbers import OasisVBMTestingDataGrabber
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
|
|
@ -28,3 +31,39 @@ def test_get_xfm(tmp_path: Path) -> None:
|
||||||
src="MNI152NLin6Asym", dst="MNI152NLin2009cAsym", xfms_dir=tmp_path
|
src="MNI152NLin6Asym", dst="MNI152NLin2009cAsym", xfms_dir=tmp_path
|
||||||
)
|
)
|
||||||
assert isinstance(xfm_path, Path)
|
assert isinstance(xfm_path, Path)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_template() -> None:
|
||||||
|
"""Test tailored template image fetch."""
|
||||||
|
with OasisVBMTestingDataGrabber() as dg:
|
||||||
|
element = dg["sub-01"]
|
||||||
|
element_data = DefaultDataReader().fit_transform(element)
|
||||||
|
vbm_gm = element_data["VBM_GM"]
|
||||||
|
# Get tailored parcellation
|
||||||
|
tailored_template = get_template(
|
||||||
|
space=vbm_gm["space"], target_data=vbm_gm
|
||||||
|
)
|
||||||
|
assert isinstance(tailored_template, nib.Nifti1Image)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_template_invalid_space() -> None:
|
||||||
|
"""Test invalid space check for template fetch."""
|
||||||
|
with OasisVBMTestingDataGrabber() as dg:
|
||||||
|
element = dg["sub-01"]
|
||||||
|
element_data = DefaultDataReader().fit_transform(element)
|
||||||
|
vbm_gm = element_data["VBM_GM"]
|
||||||
|
# Get tailored parcellation
|
||||||
|
with pytest.raises(ValueError, match="Unknown template space:"):
|
||||||
|
_ = get_template(space="andromeda", target_data=vbm_gm)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_template_closest_resolution() -> None:
|
||||||
|
"""Test closest resolution check for template fetch."""
|
||||||
|
with OasisVBMTestingDataGrabber() as dg:
|
||||||
|
element = dg["sub-01"]
|
||||||
|
element_data = DefaultDataReader().fit_transform(element)
|
||||||
|
vbm_gm = element_data["VBM_GM"]
|
||||||
|
# Change header resolution to fetch closest resolution
|
||||||
|
element_data["VBM_GM"]["data"].header.set_zooms((3, 3, 3))
|
||||||
|
template = get_template(space=vbm_gm["space"], target_data=vbm_gm)
|
||||||
|
assert isinstance(template, nib.Nifti1Image)
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,14 @@ from ..utils.logging import logger
|
||||||
|
|
||||||
|
|
||||||
def closest_resolution(
|
def closest_resolution(
|
||||||
resolution: Optional[float],
|
resolution: Optional[Union[float, int]],
|
||||||
valid_resolution: Union[List[float], List[int], np.ndarray],
|
valid_resolution: Union[List[float], List[int], np.ndarray],
|
||||||
) -> Union[float, int]:
|
) -> Union[float, int]:
|
||||||
"""Find the closest resolution.
|
"""Find the closest resolution.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
resolution : float, optional
|
resolution : float or int, optional
|
||||||
The given resolution. If None, will return the highest resolution
|
The given resolution. If None, will return the highest resolution
|
||||||
(default None).
|
(default None).
|
||||||
valid_resolution : list of float or int, or np.ndarray
|
valid_resolution : list of float or int, or np.ndarray
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ dependencies = [
|
||||||
"h5py>=3.8.0,<3.10",
|
"h5py>=3.8.0,<3.10",
|
||||||
"httpx[http2]==0.26.0",
|
"httpx[http2]==0.26.0",
|
||||||
"tqdm==4.66.1",
|
"tqdm==4.66.1",
|
||||||
|
"templateflow>=23.0.0",
|
||||||
]
|
]
|
||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
|
|
||||||
|
|
@ -188,8 +189,10 @@ known-third-party =[
|
||||||
"nilearn",
|
"nilearn",
|
||||||
"sqlalchemy",
|
"sqlalchemy",
|
||||||
"yaml",
|
"yaml",
|
||||||
|
"importlib_metadata",
|
||||||
"httpx",
|
"httpx",
|
||||||
"tqdm",
|
"tqdm",
|
||||||
|
"templateflow",
|
||||||
"bct",
|
"bct",
|
||||||
"neurokit2",
|
"neurokit2",
|
||||||
"pytest",
|
"pytest",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue