Skip to content

Commit ddf9db1

Browse files
effigiesagramfort
authored andcommitted
NF: Handle metadata in read/write_geometry
1 parent 791b10f commit ddf9db1

File tree

1 file changed

+59
-4
lines changed

1 file changed

+59
-4
lines changed

nibabel/freesurfer/io.py

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import numpy as np
44
import getpass
55
import time
6+
from collections import OrderedDict
67

78

89
from ..externals.six.moves import xrange
@@ -44,21 +45,32 @@ def _fread3_many(fobj, n):
4445
return (b1 << 16) + (b2 << 8) + b3
4546

4647

47-
def read_geometry(filepath):
48+
def read_geometry(filepath, read_metadata=False, read_stamp=False):
4849
"""Read a triangular format Freesurfer surface mesh.
4950
5051
Parameters
5152
----------
5253
filepath : str
5354
Path to surface file
55+
read_metadata : bool
56+
Read metadata as key-value pairs
57+
read_stamp : bool
58+
Return the comment from the file
5459
5560
Returns
5661
-------
5762
coords : numpy array
5863
nvtx x 3 array of vertex (x, y, z) coordinates
5964
faces : numpy array
6065
nfaces x 3 array of defining mesh triangles
66+
volume_info : dict-like
67+
If read_metadata is true, key-value pairs found in the geometry file
68+
create_stamp : str
69+
If read_stamp is true, the comment added by the program that saved
70+
the file
6171
"""
72+
volume_info = None
73+
6274
with open(filepath, "rb") as fobj:
6375
magic = _fread3(fobj)
6476
if magic == 16777215: # Quad file
@@ -86,20 +98,46 @@ def read_geometry(filepath):
8698
nface += 1
8799

88100
elif magic == 16777214: # Triangle file
89-
fobj.readline() # create_stamp
101+
create_stamp = fobj.readline().rstrip(b'\n').decode('utf-8')
90102
fobj.readline()
91103
vnum = np.fromfile(fobj, ">i4", 1)[0]
92104
fnum = np.fromfile(fobj, ">i4", 1)[0]
93105
coords = np.fromfile(fobj, ">f4", vnum * 3).reshape(vnum, 3)
94106
faces = np.fromfile(fobj, ">i4", fnum * 3).reshape(fnum, 3)
107+
108+
extra = fobj.read() if read_metadata else b''
109+
if extra:
110+
if extra[:4] != b'\x00\x00\x00\x14':
111+
warnings.warn("Unknown extension code.")
112+
volume_info = OrderedDict()
113+
try:
114+
for line in extra[4:].split(b'\n'):
115+
key, val = map(bytes.strip, line.split(b'=', 1))
116+
key = key.decode('utf-8')
117+
if key in ('voxelsize', 'xras', 'yras', 'zras'):
118+
val = np.fromstring(val, sep=' ')
119+
elif key == 'volume':
120+
val = np.fromstring(val, sep=' ', dtype=np.uint)
121+
volume_info[key] = val
122+
except ValueError:
123+
raise ValueError("Error parsing volume info")
124+
95125
else:
96126
raise ValueError("File does not appear to be a Freesurfer surface")
97127

98128
coords = coords.astype(np.float) # XXX: due to mayavi bug on mac 32bits
99-
return coords, faces
100129

130+
ret = (coords, faces)
131+
if read_metadata:
132+
ret += (volume_info,)
133+
if read_stamp:
134+
ret += (create_stamp,)
101135

102-
def write_geometry(filepath, coords, faces, create_stamp=None):
136+
return ret
137+
138+
139+
def write_geometry(filepath, coords, faces, create_stamp=None,
140+
volume_info=None):
103141
"""Write a triangular format Freesurfer surface mesh.
104142
105143
Parameters
@@ -112,13 +150,27 @@ def write_geometry(filepath, coords, faces, create_stamp=None):
112150
nfaces x 3 array of defining mesh triangles
113151
create_stamp : str
114152
User/time stamp (default: "created by <user> on <ctime>")
153+
volume_info : dict-like or None
154+
Key-value pairs to encode at the end of the file
115155
"""
116156
magic_bytes = np.array([255, 255, 254], dtype=np.uint8)
117157

118158
if create_stamp is None:
119159
create_stamp = "created by %s on %s" % (getpass.getuser(),
120160
time.ctime())
121161

162+
postlude = b''
163+
if volume_info is not None:
164+
postlude = [b'\x00\x00\x00\x14']
165+
for key, val in volume_info.items():
166+
if key in ('voxelsize', 'xras', 'yras', 'zras'):
167+
val = '{:.3f} {:.3f} {:.3f}'.format(*val)
168+
elif key == 'volume':
169+
val = '{:d} {:d} {:d}'.format(*val)
170+
key = key.ljust(6)
171+
postlude.append('{} = {}'.format(key, val).encode('utf-8'))
172+
postlude = b'\n'.join(postlude)
173+
122174
with open(filepath, 'wb') as fobj:
123175
magic_bytes.tofile(fobj)
124176
fobj.write(("%s\n\n" % create_stamp).encode('utf-8'))
@@ -129,6 +181,9 @@ def write_geometry(filepath, coords, faces, create_stamp=None):
129181
coords.astype('>f4').reshape(-1).tofile(fobj)
130182
faces.astype('>i4').reshape(-1).tofile(fobj)
131183

184+
# Add volume info, if given
185+
fobj.write(postlude)
186+
132187

133188
def read_morph_data(filepath):
134189
"""Read a Freesurfer morphometry data file.

0 commit comments

Comments
 (0)