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