Skip to content

Commit 5685395

Browse files
committed
[ENH] Adds CIVET reader + FS mapper
1 parent cd0fef8 commit 5685395

File tree

1 file changed

+133
-0
lines changed

1 file changed

+133
-0
lines changed

netneurotools/civet.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Functions for working with CIVET data (ugh)
4+
"""
5+
6+
import nibabel as nib
7+
import numpy as np
8+
from scipy.spatial import cKDTree
9+
10+
from .datasets import fetch_civet, fetch_fsaverage
11+
12+
_MNI305to152 = np.array([[0.9975, -0.0073, 0.0176, -0.0429],
13+
[0.0146, 1.0009, -0.0024, 1.5496],
14+
[-0.0130, -0.0093, 0.9971, 1.1840]])
15+
16+
17+
def read_civet(fname):
18+
"""
19+
Reads a CIVET-style .obj geometry file
20+
21+
Parameters
22+
----------
23+
fname : str or os.PathLike
24+
Filepath to .obj file
25+
26+
Returns
27+
-------
28+
vertices : (N, 3)
29+
triangles : (T, 3)
30+
"""
31+
32+
k, polygons = 0, []
33+
with open(fname, 'r') as src:
34+
n_vert = int(src.readline().split()[6])
35+
vertices = np.zeros((n_vert, 3))
36+
for i, line in enumerate(src):
37+
if i < n_vert:
38+
vertices[i] = [float(i) for i in line.split()]
39+
elif i >= (2 * n_vert) + 5:
40+
if not line.strip():
41+
k = 1
42+
elif k == 1:
43+
polygons.extend([int(i) for i in line.split()])
44+
45+
triangles = np.reshape(np.asarray(polygons), (-1, 3))
46+
47+
return vertices, triangles
48+
49+
50+
def _get_civet_to_fs_mapping(obj, fs):
51+
"""
52+
Returns a mapping between `obj` and `fs` geometry files
53+
54+
Parameters
55+
----------
56+
obj : str or os.PathLike
57+
Path to CIVET geometry file
58+
fs : str or os.PathLike
59+
Path to FreeSurfer geometry file
60+
61+
Returns
62+
-------
63+
idx : (N,) np.ndarray
64+
Mapping from CIVET to FreeSurfer space
65+
"""
66+
67+
vert_cv, _ = read_civet(obj)
68+
vert_fs, _ = nib.freesurfer.read_geometry(fs)
69+
70+
vert_fs = np.c_[vert_fs, np.ones(len(vert_fs))] @ _MNI305to152.T
71+
_, idx = cKDTree(vert_cv).query(vert_fs, k=1, distance_upper_bound=10)
72+
73+
return idx
74+
75+
76+
def civet_to_freesurfer(brainmap, surface='mid', version='v1',
77+
freesurfer='fsaverage6', mapping=None, data_dir=None):
78+
"""
79+
Projects `brainmap` in CIVET space to `freesurfer` fsaverage space
80+
81+
Uses a nearest-neighbor projection based on the geometry of the vertices
82+
83+
Parameters
84+
----------
85+
brainmap : array_like
86+
CIVET brainmap to be converted to freesurfer space
87+
surface : {'white', 'mid'}, optional
88+
Which CIVET surface to use for geometry of `brainmap`. Default: 'mid'
89+
version : {'v1', 'v2'}, optional
90+
Which CIVET version to use for geometry of `brainmap`. Default: 'v1'
91+
freesurfer : str, optional
92+
Which version of FreeSurfer space to project data to. Must be one of
93+
{'fsaverage', 'fsaverage3', 'fsaverage4', 'fsaverage5', 'fsaverage6'}.
94+
Default: 'fsaverage6'
95+
mapping : array_like, optional
96+
If mapping has been pre-computed for `surface` --> `version` and is
97+
provided, this will be used instead of recomputing. Default: None
98+
data_dir : str, optional
99+
Path to use as data directory. If not specified, will check for
100+
environmental variable 'NNT_DATA'; if that is not set, will use
101+
`~/nnt-data` instead. Default: None
102+
103+
Returns
104+
-------
105+
data : np.ndarray
106+
Provided `brainmap` mapped to FreeSurfer
107+
"""
108+
109+
densities = (81924, 327684)
110+
n_vert = len(brainmap)
111+
if n_vert not in densities:
112+
raise ValueError('Unable to interpret `brainmap` space; provided '
113+
'array must have length in {}. Received: {}'
114+
.format(densities, n_vert))
115+
116+
n_vert = n_vert // 2
117+
icbm = fetch_civet(density='41k' if n_vert == 40962 else '164k',
118+
version=version, data_dir=data_dir, verbose=0)[surface]
119+
fsavg = fetch_fsaverage(version=freesurfer, data_dir=data_dir, verbose=0)
120+
fsavg = fsavg['pial' if surface == 'mid' else 'white']
121+
122+
data = []
123+
for n, hemi in enumerate(('lh', 'rh')):
124+
sl = slice(n_vert * n, n_vert * (n + 1))
125+
if mapping is None:
126+
idx = _get_civet_to_fs_mapping(getattr(icbm, hemi),
127+
getattr(fsavg, hemi))
128+
else:
129+
idx = mapping[sl]
130+
131+
data.append(brainmap[sl][idx])
132+
133+
return np.hstack(data)

0 commit comments

Comments
 (0)