33import numpy as np
44import getpass
55import time
6+ from collections import OrderedDict
67
78
89from ..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
133188def read_morph_data (filepath ):
134189 """Read a Freesurfer morphometry data file.
0 commit comments