Skip to content

Commit 8926c32

Browse files
committed
ENH: Add test implementations of pointsets in test_pointset.py
1 parent f0f238d commit 8926c32

File tree

1 file changed

+174
-0
lines changed

1 file changed

+174
-0
lines changed

nibabel/tests/test_pointset.py

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
from pathlib import Path
2+
3+
import numpy as np
4+
5+
from nibabel import pointset as ps
6+
from nibabel.arrayproxy import ArrayProxy
7+
from nibabel.optpkg import optional_package
8+
9+
h5, has_h5py, _ = optional_package('h5py')
10+
11+
12+
class H5ArrayProxy:
13+
def __init__(self, file_like, dataset_name):
14+
self.file_like = file_like
15+
self.dataset_name = dataset_name
16+
with h5.File(file_like, "r") as h5f:
17+
arr = h5f[dataset_name]
18+
self._shape = arr.shape
19+
self._dtype = arr.dtype
20+
21+
@property
22+
def is_proxy(self):
23+
return True
24+
25+
@property
26+
def shape(self):
27+
return self._shape
28+
29+
@property
30+
def ndim(self):
31+
return len(self.shape)
32+
33+
@property
34+
def dtype(self):
35+
return self._dtype
36+
37+
def __array__(self, dtype=None):
38+
with h5.File(self.file_like, "r") as h5f:
39+
return np.asanyarray(h5f[self.dataset_name], dtype)
40+
41+
def __slicer__(self, slicer):
42+
with h5.File(self.file_like, "r") as h5f:
43+
return h5f[self.dataset_name][slicer]
44+
45+
46+
class H5Geometry(ps.TriangularMesh):
47+
"""Simple Geometry file structure that combines a single topology
48+
with one or more coordinate sets
49+
"""
50+
@classmethod
51+
def from_filename(klass, pathlike):
52+
meshes = {}
53+
with h5.File(pathlike, "r") as h5f:
54+
triangles = h5f['topology']
55+
for name, coords in h5f['coordinates'].items():
56+
meshes[name] = (coords, triangles)
57+
return klass(meshes)
58+
59+
def to_filename(self, pathlike):
60+
topology = None
61+
coordinates = {}
62+
for name, mesh in self.meshes.items():
63+
coords, faces = mesh
64+
if topology is None:
65+
topology = faces
66+
elif not np.array_equal(faces, topology):
67+
raise ValueError("Inconsistent topology")
68+
coordinates[name] = coords
69+
70+
with h5.File(pathlike, "w") as h5f:
71+
h5f.create_dataset("/topology", topology)
72+
for name, coord in coordinates.items():
73+
h5f.create_dataset(f"/coordinates/{name}", coord)
74+
75+
def get_coords(self, name=None):
76+
if name is None:
77+
name = next(iter(self._meshes))
78+
coords, _ = self._meshes[name]
79+
return coords
80+
81+
def get_triangles(self, name=None):
82+
if name is None:
83+
name = next(iter(self._meshes))
84+
_, triangles = self._meshes[name]
85+
return triangles
86+
87+
88+
class FSGeometryProxy:
89+
def __init__(self, pathlike):
90+
self._file_like = str(Path(pathlike))
91+
self._offset = None
92+
self._vnum = None
93+
self._fnum = None
94+
95+
def _peek(self):
96+
from nibabel.freesurfer.io import _fread3
97+
with open(self._file_like, "rb") as fobj:
98+
magic = _fread3(fobj)
99+
if magic != 16777214:
100+
raise NotImplementedError("Triangle files only!")
101+
fobj.readline()
102+
fobj.readline()
103+
self._vnum = np.fromfile(fobj, ">i4", 1)[0]
104+
self._fnum = np.fromfile(fobj, ">i4", 1)[0]
105+
self._offset = fobj.tell()
106+
107+
@property
108+
def vnum(self):
109+
if self._vnum is None:
110+
self._peek()
111+
return self._vnum
112+
113+
@property
114+
def fnum(self):
115+
if self._fnum is None:
116+
self._peek()
117+
return self._fnum
118+
119+
@property
120+
def offset(self):
121+
if self._offset is None:
122+
self._peek()
123+
return self._offset
124+
125+
@property
126+
def coords(self):
127+
ap = ArrayProxy(self._file_like, ((self.vnum, 3), ">f4", self.offset))
128+
ap.order = 'C'
129+
return ap
130+
131+
@property
132+
def triangles(self):
133+
offset = self.offset + 12 * self.vnum
134+
ap = ArrayProxy(self._file_like, ((self.fnum, 3), ">i4", offset))
135+
ap.order = 'C'
136+
return ap
137+
138+
139+
class FreeSurferHemisphere(ps.TriangularMesh):
140+
@classmethod
141+
def from_filename(klass, pathlike):
142+
path = Path(pathlike)
143+
hemi, default = path.name.split(".")
144+
mesh_names = ('orig', 'white', 'smoothwm',
145+
'pial', 'inflated', 'sphere',
146+
'midthickness', 'graymid') # Often created
147+
if default not in mesh_names:
148+
mesh_names.append(default)
149+
meshes = {}
150+
for mesh in mesh_names:
151+
fpath = path.parent / f"{hemi}.{mesh}"
152+
if fpath.exists():
153+
meshes[mesh] = FSGeometryProxy(fpath)
154+
hemi = klass(meshes)
155+
hemi._default = default
156+
return hemi
157+
158+
def get_coords(self, name=None):
159+
if name is None:
160+
name = self._default
161+
return self._meshes[name].coords
162+
163+
def get_triangles(self, name=None):
164+
if name is None:
165+
name = self._default
166+
return self._meshes[name].triangles
167+
168+
@property
169+
def n_coords(self):
170+
return self.meshes[self._default].vnum
171+
172+
@property
173+
def n_triangles(self):
174+
return self.meshes[self._default].fnum

0 commit comments

Comments
 (0)