Add retrieval of Tian atlases #5
2 changed files with 216 additions and 3 deletions
|
|
@ -3,7 +3,10 @@
|
||||||
# License: AGPL
|
# License: AGPL
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import io
|
import io
|
||||||
|
import tempfile
|
||||||
import requests
|
import requests
|
||||||
|
import shutil
|
||||||
|
import zipfile
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
|
|
@ -44,6 +47,29 @@ for n_rois in range(100, 1001, 100):
|
||||||
'yeo_networks': t_net,
|
'yeo_networks': t_net,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for scale in range(1, 5):
|
||||||
|
t_name = f'TianxS{scale}x7TxMNI6thgeneration'
|
||||||
|
_available_atlases[t_name] = {
|
||||||
|
'family': 'Tian',
|
||||||
|
'scale': scale,
|
||||||
|
'magneticfield': '7T',
|
||||||
|
'space': 'MNI6thgeneration'
|
||||||
|
}
|
||||||
|
t_name = f'TianxS{scale}x3TxMNI6thgeneration'
|
||||||
|
_available_atlases[t_name] = {
|
||||||
|
'family': 'Tian',
|
||||||
|
'scale': scale,
|
||||||
|
'magneticfield': '3T',
|
||||||
|
'space': 'MNI6thgeneration'
|
||||||
|
}
|
||||||
|
t_name = f'TianxS{scale}x3TxMNInonlinear2009cAsym'
|
||||||
|
_available_atlases[t_name] = {
|
||||||
|
'family': 'Tian',
|
||||||
|
'scale': scale,
|
||||||
|
'magneticfield': '3T',
|
||||||
|
'space': 'MNInonlinear2009cAsym'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def register_atlas(name, atlas_path, atl_labels, overwrite=False):
|
def register_atlas(name, atlas_path, atl_labels, overwrite=False):
|
||||||
"""Register a custom user atlas.
|
"""Register a custom user atlas.
|
||||||
|
|
@ -136,7 +162,15 @@ def load_atlas(name, atlas_dir=None, resolution=None, path_only=False):
|
||||||
Number of yeo networks to use. Valid values: 7, 17. Defaults to 7.
|
Number of yeo networks to use. Valid values: 7, 17. Defaults to 7.
|
||||||
|
|
||||||
Tian :
|
Tian :
|
||||||
# TODO add
|
scale (required) : int
|
||||||
|
Scale of atlas between 1 and 4 (defines granularity)
|
||||||
|
space (optional) : str
|
||||||
|
Space of atlas can be either 'MNI6thgeneration' or
|
||||||
|
'MNInonlinear2009cAsym' (for some cases).
|
||||||
|
Defaults to 'MNI6thgeneration'. (For more information see
|
||||||
|
https://github.com/yetianmed/subcortex)
|
||||||
|
magneticfield (optional) : str
|
||||||
|
Options are 3T and 7T, defaults to 3T.
|
||||||
|
|
||||||
SUIT :
|
SUIT :
|
||||||
space (optional) : str
|
space (optional) : str
|
||||||
|
|
@ -207,6 +241,16 @@ def _retrieve_atlas(family, atlas_dir=None, resolution=None, **kwargs):
|
||||||
(included) in steps of 100.
|
(included) in steps of 100.
|
||||||
yeo_network (optional) : int
|
yeo_network (optional) : int
|
||||||
Number of yeo networks to use [7 or 17]. Defaults to 7.
|
Number of yeo networks to use [7 or 17]. Defaults to 7.
|
||||||
|
Tian :
|
||||||
|
scale (required) : int
|
||||||
|
Scale of atlas between 1 and 4 (defines granularity)
|
||||||
|
space (optional) : str
|
||||||
|
Space of atlas can be either 'MNI6thgeneration' or
|
||||||
|
'MNInonlinear2009cAsym' (for some cases).
|
||||||
|
Defaults to 'MNI6thgeneration'. (For more information see
|
||||||
|
https://github.com/yetianmed/subcortex)
|
||||||
|
magneticfield (optional) : str
|
||||||
|
Options are 3T and 7T, defaults to 3T.
|
||||||
SUIT :
|
SUIT :
|
||||||
space (optional) : str
|
space (optional) : str
|
||||||
Space of atlas can be either 'MNI' or 'SUIT' (for more information
|
Space of atlas can be either 'MNI' or 'SUIT' (for more information
|
||||||
|
|
@ -235,6 +279,9 @@ def _retrieve_atlas(family, atlas_dir=None, resolution=None, **kwargs):
|
||||||
elif family == 'SUIT':
|
elif family == 'SUIT':
|
||||||
atlas_fname, atl_labels = \
|
atlas_fname, atl_labels = \
|
||||||
_retrieve_suit(atlas_dir, resolution=resolution, **kwargs)
|
_retrieve_suit(atlas_dir, resolution=resolution, **kwargs)
|
||||||
|
elif family == 'Tian':
|
||||||
|
atlas_fname, atl_labels = \
|
||||||
|
_retrieve_tian(atlas_dir, resolution=resolution, **kwargs)
|
||||||
else:
|
else:
|
||||||
raise_error(
|
raise_error(
|
||||||
f"The provided atlas name {family} cannot be retrieved. ")
|
f"The provided atlas name {family} cannot be retrieved. ")
|
||||||
|
|
@ -312,6 +359,108 @@ def _retrieve_schaefer(atlas_dir, resolution, n_rois=None, yeo_networks=7):
|
||||||
return atlas_fname, labels
|
return atlas_fname, labels
|
||||||
|
|
||||||
|
|
||||||
|
def _retrieve_tian(
|
||||||
|
atlas_dir, resolution, scale=None, space='MNI6thgeneration',
|
||||||
|
magneticfield='3T'):
|
||||||
|
# show atlas parameters to user
|
||||||
|
logger.info('Atlas parameters:')
|
||||||
|
logger.info(f'\tscale: {scale}')
|
||||||
|
logger.info(f'\tspace: {space}')
|
||||||
|
logger.info(f'\tmagneticfield: {magneticfield}')
|
||||||
|
logger.info(f'\tresolution: {resolution}')
|
||||||
|
# check validity of atlas parameters
|
||||||
|
_valid_scales = [1, 2, 3, 4]
|
||||||
|
_valid_fields = ['3T', '7T']
|
||||||
|
if scale not in _valid_scales:
|
||||||
|
raise_error(
|
||||||
|
f'The parameter `scale` ({scale}) needs to be one of the '
|
||||||
|
f'following: {_valid_scales}')
|
||||||
|
if magneticfield not in _valid_fields:
|
||||||
|
raise_error(
|
||||||
|
f'The parameter `magneticfield` ({magneticfield}) needs to be '
|
||||||
|
f'one of the following: {_valid_fields}')
|
||||||
|
|
||||||
|
if magneticfield == '3T':
|
||||||
|
_valid_spaces = ['MNI6thgeneration', 'MNInonlinear2009cAsym']
|
||||||
|
if space == 'MNI6thgeneration':
|
||||||
|
_valid_resolutions = [1, 2]
|
||||||
|
else: # space == 'MNInonlinear2009cAsym':
|
||||||
|
_valid_resolutions = [2]
|
||||||
|
else: # magneticfield == '7T':
|
||||||
|
_valid_spaces = ['MNI6thgeneration']
|
||||||
|
_valid_resolutions = [1.6]
|
||||||
|
if space not in _valid_spaces:
|
||||||
|
raise_error(
|
||||||
|
f'The parameter `space` ({space}) needs to be one of '
|
||||||
|
f'the following: {_valid_spaces}')
|
||||||
|
|
||||||
|
resolution = _closest_resolution(resolution, _valid_resolutions)
|
||||||
|
|
||||||
|
# define file names
|
||||||
|
if magneticfield == '3T':
|
||||||
|
atlas_fname_base_3T = (
|
||||||
|
atlas_dir / 'Tian2020MSA_v1.1' / '3T' / 'Subcortex-Only')
|
||||||
|
atlas_lname = atlas_fname_base_3T / (
|
||||||
|
f'Tian_Subcortex_S{scale}_3T_label.txt')
|
||||||
|
if space == 'MNI6thgeneration':
|
||||||
|
atlas_fname = atlas_fname_base_3T / (
|
||||||
|
f'Tian_Subcortex_S{scale}_{magneticfield}.nii.gz')
|
||||||
|
if resolution == 1:
|
||||||
|
atlas_fname = atlas_fname_base_3T / \
|
||||||
|
f'Tian_Subcortex_S{scale}_{magneticfield}_1mm.nii.gz'
|
||||||
|
else: # space == 'MNInonlinear2009cAsym':
|
||||||
|
space = '2009cAsym'
|
||||||
|
atlas_fname = atlas_fname_base_3T / (
|
||||||
|
f'Tian_Subcortex_S{scale}_{magneticfield}_{space}.nii.gz')
|
||||||
|
else: # magneticfield == '7T':
|
||||||
|
atlas_fname_base_7T = (
|
||||||
|
atlas_dir / 'Tian2020MSA_v1.1' / '7T')
|
||||||
|
atlas_fname = atlas_dir / 'Tian2020MSA_v1.1' / f'{magneticfield}' / (
|
||||||
|
f'Tian_Subcortex_S{scale}_{magneticfield}.nii.gz')
|
||||||
|
# define 7T labels (b/c currently no labels file available for 7T)
|
||||||
|
scale7Trois = {1: 16, 2: 34, 3: 54, 4: 62}
|
||||||
|
labels = [
|
||||||
|
('parcel_' + str(x)) for x in np.arange(1, scale7Trois[scale] + 1)]
|
||||||
|
atlas_lname = atlas_fname_base_7T / (
|
||||||
|
f'Tian_Subcortex_S{scale}_7T_labelnumbering.txt')
|
||||||
|
with open(atlas_lname, 'w') as filehandle:
|
||||||
|
for listitem in labels:
|
||||||
|
filehandle.write('%s\n' % listitem)
|
||||||
|
logger.info(
|
||||||
|
'Currently there are no labels provided for the 7T Tian atlas. A '
|
||||||
|
'simple numbering scheme for distinction was therefore used.')
|
||||||
|
|
||||||
|
# check existance of atlas
|
||||||
|
if not (atlas_fname.exists() and atlas_lname.exists()):
|
||||||
|
logger.info(
|
||||||
|
'At least one of the atlas files is missing. '
|
||||||
|
'Fetching.')
|
||||||
|
|
||||||
|
url_basis = \
|
||||||
|
'https://www.nitrc.org/frs/download.php/12012/Tian2020MSA_v1.1.zip'
|
||||||
|
|
||||||
|
logger.info(f'Downloading TIAN from {url_basis}')
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
atlas_download = requests.get(url_basis)
|
||||||
|
atlas_zip_fname = Path(tmpdir) / 'Tian2020MSA_v1.1.zip'
|
||||||
|
with open(atlas_zip_fname, 'wb') as f:
|
||||||
|
f.write(atlas_download.content)
|
||||||
|
with zipfile.ZipFile(atlas_zip_fname, 'r') as zip_ref:
|
||||||
|
zip_ref.extractall(atlas_dir.as_posix())
|
||||||
|
# clean after unzipping
|
||||||
|
if (atlas_dir / '__MACOSX').exists():
|
||||||
|
shutil.rmtree((atlas_dir / '__MACOSX').as_posix())
|
||||||
|
|
||||||
|
labels = pd.read_csv(atlas_lname, sep=" ", header=None)[0].to_list()
|
||||||
|
|
||||||
|
if not (atlas_fname.exists() and atlas_lname.exists()):
|
||||||
|
raise_error('There was a problem fetching the atlases.')
|
||||||
|
|
||||||
|
labels = pd.read_csv(atlas_lname, sep=" ", header=None)[0].to_list()
|
||||||
|
|
||||||
|
return atlas_fname, labels
|
||||||
|
|
||||||
|
|
||||||
def _retrieve_suit(atlas_path, resolution, space='MNI'):
|
def _retrieve_suit(atlas_path, resolution, space='MNI'):
|
||||||
logger.info('Atlas parameters:')
|
logger.info('Atlas parameters:')
|
||||||
logger.info(f'\tspace: {space}')
|
logger.info(f'\tspace: {space}')
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import tempfile
|
import tempfile
|
||||||
import pytest
|
import pytest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from numpy.testing import assert_array_equal
|
from numpy.testing import assert_array_equal, assert_array_almost_equal
|
||||||
|
|
||||||
from junifer.data.atlases import (register_atlas, list_atlases, load_atlas,
|
from junifer.data.atlases import (register_atlas, list_atlases, load_atlas,
|
||||||
_retrieve_schaefer, _retrieve_suit,
|
_retrieve_schaefer, _retrieve_suit,
|
||||||
_retrieve_atlas)
|
_retrieve_atlas, _retrieve_tian)
|
||||||
|
|
||||||
|
|
||||||
def test_register_atlas():
|
def test_register_atlas():
|
||||||
|
|
@ -150,3 +150,67 @@ def test_suit():
|
||||||
|
|
||||||
with pytest.raises(ValueError, match=r"The parameter `space`"):
|
with pytest.raises(ValueError, match=r"The parameter `space`"):
|
||||||
_retrieve_suit(tmpdir, 1, space='wrong')
|
_retrieve_suit(tmpdir, 1, space='wrong')
|
||||||
|
|
||||||
|
|
||||||
|
def test_tian():
|
||||||
|
"""Test TIAN atlas"""
|
||||||
|
|
||||||
|
atlases = list_atlases()
|
||||||
|
assert 'TianxS1x3TxMNI6thgeneration' in atlases
|
||||||
|
assert 'TianxS2x3TxMNI6thgeneration' in atlases
|
||||||
|
assert 'TianxS3x3TxMNI6thgeneration' in atlases
|
||||||
|
assert 'TianxS4x3TxMNI6thgeneration' in atlases
|
||||||
|
|
||||||
|
assert 'TianxS1x3TxMNInonlinear2009cAsym' in atlases
|
||||||
|
assert 'TianxS2x3TxMNInonlinear2009cAsym' in atlases
|
||||||
|
assert 'TianxS3x3TxMNInonlinear2009cAsym' in atlases
|
||||||
|
assert 'TianxS4x3TxMNInonlinear2009cAsym' in atlases
|
||||||
|
|
||||||
|
assert 'TianxS1x7TxMNI6thgeneration' in atlases
|
||||||
|
assert 'TianxS2x7TxMNI6thgeneration' in atlases
|
||||||
|
assert 'TianxS3x7TxMNI6thgeneration' in atlases
|
||||||
|
assert 'TianxS4x7TxMNI6thgeneration' in atlases
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
for scale, n_lbl in zip([1, 2, 3, 4], [16, 32, 50, 54]):
|
||||||
|
img, lbl, fname = load_atlas(
|
||||||
|
f'TianxS{scale}x3TxMNI6thgeneration', atlas_dir=tmpdir)
|
||||||
|
fname1 = f'Tian_Subcortex_S{scale}_3T_1mm.nii.gz'
|
||||||
|
assert img is not None
|
||||||
|
assert fname.name == fname1 # type: ignore
|
||||||
|
assert len(lbl) == n_lbl
|
||||||
|
assert_array_equal(img.header['pixdim'][1:4], [1, 1, 1])
|
||||||
|
|
||||||
|
img, lbl, fname = load_atlas(
|
||||||
|
f'TianxS{scale}x3TxMNI6thgeneration', atlas_dir=tmpdir,
|
||||||
|
resolution=2)
|
||||||
|
fname1 = f'Tian_Subcortex_S{scale}_3T.nii.gz'
|
||||||
|
assert img is not None
|
||||||
|
assert fname.name == fname1 # type: ignore
|
||||||
|
assert len(lbl) == n_lbl
|
||||||
|
assert_array_equal(img.header['pixdim'][1:4], [2, 2, 2])
|
||||||
|
|
||||||
|
img, lbl, fname = load_atlas(
|
||||||
|
f'TianxS{scale}x3TxMNInonlinear2009cAsym', atlas_dir=tmpdir)
|
||||||
|
fname1 = f'Tian_Subcortex_S{scale}_3T_2009cAsym.nii.gz'
|
||||||
|
assert img is not None
|
||||||
|
assert fname.name == fname1 # type: ignore
|
||||||
|
assert len(lbl) == n_lbl
|
||||||
|
assert_array_equal(img.header['pixdim'][1:4], [2, 2, 2])
|
||||||
|
|
||||||
|
for scale, n_lbl in zip([1, 2, 3, 4], [16, 34, 54, 62]):
|
||||||
|
img, lbl, fname = load_atlas(
|
||||||
|
f'TianxS{scale}x7TxMNI6thgeneration', atlas_dir=tmpdir)
|
||||||
|
fname1 = f'Tian_Subcortex_S{scale}_7T.nii.gz'
|
||||||
|
assert img is not None
|
||||||
|
assert fname.name == fname1 # type: ignore
|
||||||
|
assert len(lbl) == n_lbl
|
||||||
|
assert_array_almost_equal(
|
||||||
|
img.header['pixdim'][1:4], [1.6, 1.6, 1.6])
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match=r"The parameter `space`"):
|
||||||
|
_retrieve_tian(tmpdir, resolution=1, scale=1, space='wrong')
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match=r"The parameter `magneticfield`"):
|
||||||
|
_retrieve_tian(
|
||||||
|
tmpdir, resolution=1, scale=1, magneticfield='wrong')
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue