3
3
import numpy as np
4
4
import getpass
5
5
import time
6
+ from collections import OrderedDict
6
7
7
8
8
9
from ..externals .six .moves import xrange
@@ -44,21 +45,32 @@ def _fread3_many(fobj, n):
44
45
return (b1 << 16 ) + (b2 << 8 ) + b3
45
46
46
47
47
- def read_geometry (filepath ):
48
+ def read_geometry (filepath , read_metadata = False , read_stamp = False ):
48
49
"""Read a triangular format Freesurfer surface mesh.
49
50
50
51
Parameters
51
52
----------
52
53
filepath : str
53
54
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
54
59
55
60
Returns
56
61
-------
57
62
coords : numpy array
58
63
nvtx x 3 array of vertex (x, y, z) coordinates
59
64
faces : numpy array
60
65
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
61
71
"""
72
+ volume_info = None
73
+
62
74
with open (filepath , "rb" ) as fobj :
63
75
magic = _fread3 (fobj )
64
76
if magic == 16777215 : # Quad file
@@ -86,20 +98,46 @@ def read_geometry(filepath):
86
98
nface += 1
87
99
88
100
elif magic == 16777214 : # Triangle file
89
- fobj .readline () # create_stamp
101
+ create_stamp = fobj .readline (). rstrip ( b' \n ' ). decode ( 'utf-8' )
90
102
fobj .readline ()
91
103
vnum = np .fromfile (fobj , ">i4" , 1 )[0 ]
92
104
fnum = np .fromfile (fobj , ">i4" , 1 )[0 ]
93
105
coords = np .fromfile (fobj , ">f4" , vnum * 3 ).reshape (vnum , 3 )
94
106
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
+
95
125
else :
96
126
raise ValueError ("File does not appear to be a Freesurfer surface" )
97
127
98
128
coords = coords .astype (np .float ) # XXX: due to mayavi bug on mac 32bits
99
- return coords , faces
100
129
130
+ ret = (coords , faces )
131
+ if read_metadata :
132
+ ret += (volume_info ,)
133
+ if read_stamp :
134
+ ret += (create_stamp ,)
101
135
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 ):
103
141
"""Write a triangular format Freesurfer surface mesh.
104
142
105
143
Parameters
@@ -112,13 +150,27 @@ def write_geometry(filepath, coords, faces, create_stamp=None):
112
150
nfaces x 3 array of defining mesh triangles
113
151
create_stamp : str
114
152
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
115
155
"""
116
156
magic_bytes = np .array ([255 , 255 , 254 ], dtype = np .uint8 )
117
157
118
158
if create_stamp is None :
119
159
create_stamp = "created by %s on %s" % (getpass .getuser (),
120
160
time .ctime ())
121
161
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
+
122
174
with open (filepath , 'wb' ) as fobj :
123
175
magic_bytes .tofile (fobj )
124
176
fobj .write (("%s\n \n " % create_stamp ).encode ('utf-8' ))
@@ -129,6 +181,9 @@ def write_geometry(filepath, coords, faces, create_stamp=None):
129
181
coords .astype ('>f4' ).reshape (- 1 ).tofile (fobj )
130
182
faces .astype ('>i4' ).reshape (- 1 ).tofile (fobj )
131
183
184
+ # Add volume info, if given
185
+ fobj .write (postlude )
186
+
132
187
133
188
def read_morph_data (filepath ):
134
189
"""Read a Freesurfer morphometry data file.
0 commit comments