Skip to content

Commit ef86b1a

Browse files
committed
ENH: Add test implementations of pointsets in test_pointset.py
1 parent 7350b8e commit ef86b1a

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
@@ -182,3 +182,177 @@ def test_to_mask(self):
182182
],
183183
)
184184
assert np.array_equal(mask_img.affine, np.eye(4))
185+
186+
187+
class H5ArrayProxy:
188+
def __init__(self, file_like, dataset_name):
189+
self.file_like = file_like
190+
self.dataset_name = dataset_name
191+
with h5.File(file_like, 'r') as h5f:
192+
arr = h5f[dataset_name]
193+
self._shape = arr.shape
194+
self._dtype = arr.dtype
195+
196+
@property
197+
def is_proxy(self):
198+
return True
199+
200+
@property
201+
def shape(self):
202+
return self._shape
203+
204+
@property
205+
def ndim(self):
206+
return len(self.shape)
207+
208+
@property
209+
def dtype(self):
210+
return self._dtype
211+
212+
def __array__(self, dtype=None):
213+
with h5.File(self.file_like, 'r') as h5f:
214+
return np.asanyarray(h5f[self.dataset_name], dtype)
215+
216+
def __slicer__(self, slicer):
217+
with h5.File(self.file_like, 'r') as h5f:
218+
return h5f[self.dataset_name][slicer]
219+
220+
221+
class H5Geometry(ps.TriangularMesh):
222+
"""Simple Geometry file structure that combines a single topology
223+
with one or more coordinate sets
224+
"""
225+
226+
@classmethod
227+
def from_filename(klass, pathlike):
228+
meshes = {}
229+
with h5.File(pathlike, 'r') as h5f:
230+
triangles = h5f['topology']
231+
for name, coords in h5f['coordinates'].items():
232+
meshes[name] = (coords, triangles)
233+
return klass(meshes)
234+
235+
def to_filename(self, pathlike):
236+
topology = None
237+
coordinates = {}
238+
for name, mesh in self.meshes.items():
239+
coords, faces = mesh
240+
if topology is None:
241+
topology = faces
242+
elif not np.array_equal(faces, topology):
243+
raise ValueError('Inconsistent topology')
244+
coordinates[name] = coords
245+
246+
with h5.File(pathlike, 'w') as h5f:
247+
h5f.create_dataset('/topology', topology)
248+
for name, coord in coordinates.items():
249+
h5f.create_dataset(f'/coordinates/{name}', coord)
250+
251+
def get_coords(self, name=None):
252+
if name is None:
253+
name = next(iter(self._meshes))
254+
coords, _ = self._meshes[name]
255+
return coords
256+
257+
def get_triangles(self, name=None):
258+
if name is None:
259+
name = next(iter(self._meshes))
260+
_, triangles = self._meshes[name]
261+
return triangles
262+
263+
264+
class FSGeometryProxy:
265+
def __init__(self, pathlike):
266+
self._file_like = str(Path(pathlike))
267+
self._offset = None
268+
self._vnum = None
269+
self._fnum = None
270+
271+
def _peek(self):
272+
from nibabel.freesurfer.io import _fread3
273+
274+
with open(self._file_like, 'rb') as fobj:
275+
magic = _fread3(fobj)
276+
if magic != 16777214:
277+
raise NotImplementedError('Triangle files only!')
278+
fobj.readline()
279+
fobj.readline()
280+
self._vnum = np.fromfile(fobj, '>i4', 1)[0]
281+
self._fnum = np.fromfile(fobj, '>i4', 1)[0]
282+
self._offset = fobj.tell()
283+
284+
@property
285+
def vnum(self):
286+
if self._vnum is None:
287+
self._peek()
288+
return self._vnum
289+
290+
@property
291+
def fnum(self):
292+
if self._fnum is None:
293+
self._peek()
294+
return self._fnum
295+
296+
@property
297+
def offset(self):
298+
if self._offset is None:
299+
self._peek()
300+
return self._offset
301+
302+
@property
303+
def coords(self):
304+
ap = ArrayProxy(self._file_like, ((self.vnum, 3), '>f4', self.offset))
305+
ap.order = 'C'
306+
return ap
307+
308+
@property
309+
def triangles(self):
310+
offset = self.offset + 12 * self.vnum
311+
ap = ArrayProxy(self._file_like, ((self.fnum, 3), '>i4', offset))
312+
ap.order = 'C'
313+
return ap
314+
315+
316+
class FreeSurferHemisphere(ps.TriangularMesh):
317+
@classmethod
318+
def from_filename(klass, pathlike):
319+
path = Path(pathlike)
320+
hemi, default = path.name.split('.')
321+
mesh_names = (
322+
'orig',
323+
'white',
324+
'smoothwm',
325+
'pial',
326+
'inflated',
327+
'sphere',
328+
'midthickness',
329+
'graymid',
330+
) # Often created
331+
if default not in mesh_names:
332+
mesh_names.append(default)
333+
meshes = {}
334+
for mesh in mesh_names:
335+
fpath = path.parent / f'{hemi}.{mesh}'
336+
if fpath.exists():
337+
meshes[mesh] = FSGeometryProxy(fpath)
338+
hemi = klass(meshes)
339+
hemi._default = default
340+
return hemi
341+
342+
def get_coords(self, name=None):
343+
if name is None:
344+
name = self._default
345+
return self._meshes[name].coords
346+
347+
def get_triangles(self, name=None):
348+
if name is None:
349+
name = self._default
350+
return self._meshes[name].triangles
351+
352+
@property
353+
def n_coords(self):
354+
return self.meshes[self._default].vnum
355+
356+
@property
357+
def n_triangles(self):
358+
return self.meshes[self._default].fnum

0 commit comments

Comments
 (0)