1
1
from __future__ import division , print_function , absolute_import
2
2
3
+ import warnings
3
4
import numpy as np
4
5
import getpass
5
6
import time
6
7
7
-
8
+ from .. externals import OrderedDict
8
9
from ..externals .six .moves import xrange
9
10
from ..openers import Opener
10
11
@@ -44,24 +45,70 @@ 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_volume_info (fobj ):
49
+ volume_info = OrderedDict ()
50
+ head = np .fromfile (fobj , '>i4' , 1 )
51
+ if not np .array_equal (head , [20 ]): # Read two bytes more
52
+ head = np .concatenate ([head , np .fromfile (fobj , '>i4' , 2 )])
53
+ if not np .array_equal (head , [2 , 0 , 20 ]):
54
+ warnings .warn ("Unknown extension code." )
55
+ return volume_info
56
+
57
+ volume_info ['head' ] = head
58
+ for key in ['valid' , 'filename' , 'volume' , 'voxelsize' , 'xras' , 'yras' ,
59
+ 'zras' , 'cras' ]:
60
+ pair = fobj .readline ().decode ('utf-8' ).split ('=' )
61
+ if pair [0 ].strip () != key or len (pair ) != 2 :
62
+ raise IOError ('Error parsing volume info.' )
63
+ if key in ('valid' , 'filename' ):
64
+ volume_info [key ] = pair [1 ].strip ()
65
+ elif key == 'volume' :
66
+ volume_info [key ] = np .array (pair [1 ].split ()).astype (int )
67
+ else :
68
+ volume_info [key ] = np .array (pair [1 ].split ()).astype (float )
69
+ # Ignore the rest
70
+ return volume_info
71
+
72
+
73
+ def read_geometry (filepath , read_metadata = False , read_stamp = False ):
48
74
"""Read a triangular format Freesurfer surface mesh.
49
75
50
76
Parameters
51
77
----------
52
78
filepath : str
53
79
Path to surface file
80
+ read_metadata : bool
81
+ Read metadata as key-value pairs.
82
+ Valid keys:
83
+ * 'head' : array of int
84
+ * 'valid' : str
85
+ * 'filename' : str
86
+ * 'volume' : array of int, shape (3,)
87
+ * 'voxelsize' : array of float, shape (3,)
88
+ * 'xras' : array of float, shape (3,)
89
+ * 'yras' : array of float, shape (3,)
90
+ * 'zras' : array of float, shape (3,)
91
+ * 'cras' : array of float, shape (3,)
92
+ read_stamp : bool
93
+ Return the comment from the file
54
94
55
95
Returns
56
96
-------
57
97
coords : numpy array
58
98
nvtx x 3 array of vertex (x, y, z) coordinates
59
99
faces : numpy array
60
100
nfaces x 3 array of defining mesh triangles
101
+ volume_info : OrderedDict
102
+ If read_metadata is true, key-value pairs found in the geometry file
103
+ create_stamp : str
104
+ If read_stamp is true, the comment added by the program that saved
105
+ the file
61
106
"""
107
+ volume_info = OrderedDict ()
108
+
62
109
with open (filepath , "rb" ) as fobj :
63
110
magic = _fread3 (fobj )
64
- if magic == 16777215 : # Quad file
111
+ if magic in ( 16777215 , 16777213 ) : # Quad file
65
112
nvert = _fread3 (fobj )
66
113
nquad = _fread3 (fobj )
67
114
coords = np .fromfile (fobj , ">i2" , nvert * 3 ).astype (np .float )
@@ -86,20 +133,33 @@ def read_geometry(filepath):
86
133
nface += 1
87
134
88
135
elif magic == 16777214 : # Triangle file
89
- fobj .readline () # create_stamp
136
+ create_stamp = fobj .readline (). rstrip ( b' \n ' ). decode ( 'utf-8' )
90
137
fobj .readline ()
91
138
vnum = np .fromfile (fobj , ">i4" , 1 )[0 ]
92
139
fnum = np .fromfile (fobj , ">i4" , 1 )[0 ]
93
140
coords = np .fromfile (fobj , ">f4" , vnum * 3 ).reshape (vnum , 3 )
94
141
faces = np .fromfile (fobj , ">i4" , fnum * 3 ).reshape (fnum , 3 )
142
+
143
+ if read_metadata :
144
+ volume_info = _read_volume_info (fobj )
95
145
else :
96
146
raise ValueError ("File does not appear to be a Freesurfer surface" )
97
147
98
148
coords = coords .astype (np .float ) # XXX: due to mayavi bug on mac 32bits
99
- return coords , faces
100
149
150
+ ret = (coords , faces )
151
+ if read_metadata :
152
+ if len (volume_info ) == 0 :
153
+ warnings .warn ('No volume information contained in the file' )
154
+ ret += (volume_info ,)
155
+ if read_stamp :
156
+ ret += (create_stamp ,)
101
157
102
- def write_geometry (filepath , coords , faces , create_stamp = None ):
158
+ return ret
159
+
160
+
161
+ def write_geometry (filepath , coords , faces , create_stamp = None ,
162
+ volume_info = None ):
103
163
"""Write a triangular format Freesurfer surface mesh.
104
164
105
165
Parameters
@@ -112,6 +172,19 @@ def write_geometry(filepath, coords, faces, create_stamp=None):
112
172
nfaces x 3 array of defining mesh triangles
113
173
create_stamp : str
114
174
User/time stamp (default: "created by <user> on <ctime>")
175
+ volume_info : dict-like or None
176
+ Key-value pairs to encode at the end of the file.
177
+ Valid keys:
178
+ * 'head' : array of int
179
+ * 'valid' : str
180
+ * 'filename' : str
181
+ * 'volume' : array of int, shape (3,)
182
+ * 'voxelsize' : array of float, shape (3,)
183
+ * 'xras' : array of float, shape (3,)
184
+ * 'yras' : array of float, shape (3,)
185
+ * 'zras' : array of float, shape (3,)
186
+ * 'cras' : array of float, shape (3,)
187
+
115
188
"""
116
189
magic_bytes = np .array ([255 , 255 , 254 ], dtype = np .uint8 )
117
190
@@ -129,6 +202,10 @@ def write_geometry(filepath, coords, faces, create_stamp=None):
129
202
coords .astype ('>f4' ).reshape (- 1 ).tofile (fobj )
130
203
faces .astype ('>i4' ).reshape (- 1 ).tofile (fobj )
131
204
205
+ # Add volume info, if given
206
+ if volume_info is not None and len (volume_info ) > 0 :
207
+ fobj .write (_serialize_volume_info (volume_info ))
208
+
132
209
133
210
def read_morph_data (filepath ):
134
211
"""Read a Freesurfer morphometry data file.
@@ -369,3 +446,32 @@ def read_label(filepath, read_scalars=False):
369
446
scalar_array = np .loadtxt (filepath , skiprows = 2 , usecols = [- 1 ])
370
447
return label_array , scalar_array
371
448
return label_array
449
+
450
+
451
+ def _serialize_volume_info (volume_info ):
452
+ """Helper for serializing the volume info."""
453
+ keys = ['head' , 'valid' , 'filename' , 'volume' , 'voxelsize' , 'xras' , 'yras' ,
454
+ 'zras' , 'cras' ]
455
+ diff = set (volume_info .keys ()).difference (keys )
456
+ if len (diff ) > 0 :
457
+ raise ValueError ('Invalid volume info: %s.' % diff .pop ())
458
+
459
+ strings = list ()
460
+ for key in keys :
461
+ if key == 'head' :
462
+ if not (np .array_equal (volume_info [key ], [20 ]) or np .array_equal (
463
+ volume_info [key ], [2 , 0 , 20 ])):
464
+ warnings .warn ("Unknown extension code." )
465
+ strings .append (np .array (volume_info [key ], dtype = '>i4' ).tostring ())
466
+ elif key in ('valid' , 'filename' ):
467
+ val = volume_info [key ]
468
+ strings .append ('{0} = {1}\n ' .format (key , val ).encode ('utf-8' ))
469
+ elif key == 'volume' :
470
+ val = volume_info [key ]
471
+ strings .append ('{0} = {1} {2} {3}\n ' .format (
472
+ key , val [0 ], val [1 ], val [2 ]).encode ('utf-8' ))
473
+ else :
474
+ val = volume_info [key ]
475
+ strings .append ('{0} = {1:0.10g} {2:0.10g} {3:0.10g}\n ' .format (
476
+ key .ljust (6 ), val [0 ], val [1 ], val [2 ]).encode ('utf-8' ))
477
+ return b'' .join (strings )
0 commit comments