@@ -182,3 +182,177 @@ def test_to_mask(self):
182
182
],
183
183
)
184
184
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