11from __future__ import division , print_function , absolute_import
22
3+ import warnings
34import numpy as np
45import getpass
56import time
67
7-
8+ from .. externals import OrderedDict
89from ..externals .six .moves import xrange
910from ..openers import Opener
1011
@@ -44,24 +45,70 @@ def _fread3_many(fobj, n):
4445 return (b1 << 16 ) + (b2 << 8 ) + b3
4546
4647
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 ):
4874 """Read a triangular format Freesurfer surface mesh.
4975
5076 Parameters
5177 ----------
5278 filepath : str
5379 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
5494
5595 Returns
5696 -------
5797 coords : numpy array
5898 nvtx x 3 array of vertex (x, y, z) coordinates
5999 faces : numpy array
60100 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
61106 """
107+ volume_info = OrderedDict ()
108+
62109 with open (filepath , "rb" ) as fobj :
63110 magic = _fread3 (fobj )
64- if magic == 16777215 : # Quad file
111+ if magic in ( 16777215 , 16777213 ) : # Quad file
65112 nvert = _fread3 (fobj )
66113 nquad = _fread3 (fobj )
67114 coords = np .fromfile (fobj , ">i2" , nvert * 3 ).astype (np .float )
@@ -86,20 +133,33 @@ def read_geometry(filepath):
86133 nface += 1
87134
88135 elif magic == 16777214 : # Triangle file
89- fobj .readline () # create_stamp
136+ create_stamp = fobj .readline (). rstrip ( b' \n ' ). decode ( 'utf-8' )
90137 fobj .readline ()
91138 vnum = np .fromfile (fobj , ">i4" , 1 )[0 ]
92139 fnum = np .fromfile (fobj , ">i4" , 1 )[0 ]
93140 coords = np .fromfile (fobj , ">f4" , vnum * 3 ).reshape (vnum , 3 )
94141 faces = np .fromfile (fobj , ">i4" , fnum * 3 ).reshape (fnum , 3 )
142+
143+ if read_metadata :
144+ volume_info = _read_volume_info (fobj )
95145 else :
96146 raise ValueError ("File does not appear to be a Freesurfer surface" )
97147
98148 coords = coords .astype (np .float ) # XXX: due to mayavi bug on mac 32bits
99- return coords , faces
100149
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 ,)
101157
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 ):
103163 """Write a triangular format Freesurfer surface mesh.
104164
105165 Parameters
@@ -112,6 +172,19 @@ def write_geometry(filepath, coords, faces, create_stamp=None):
112172 nfaces x 3 array of defining mesh triangles
113173 create_stamp : str
114174 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+
115188 """
116189 magic_bytes = np .array ([255 , 255 , 254 ], dtype = np .uint8 )
117190
@@ -129,6 +202,10 @@ def write_geometry(filepath, coords, faces, create_stamp=None):
129202 coords .astype ('>f4' ).reshape (- 1 ).tofile (fobj )
130203 faces .astype ('>i4' ).reshape (- 1 ).tofile (fobj )
131204
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+
132209
133210def read_morph_data (filepath ):
134211 """Read a Freesurfer morphometry data file.
@@ -369,3 +446,32 @@ def read_label(filepath, read_scalars=False):
369446 scalar_array = np .loadtxt (filepath , skiprows = 2 , usecols = [- 1 ])
370447 return label_array , scalar_array
371448 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