1- from __future__ import division
1+ """ Read / write access to TCK streamlines format.
22
3- # Documentation available here:
4- # http://mrtrix.readthedocs.io/en/latest/getting_started/image_data.html?highlight=format#tracks-file-format-tck
3+ TCK format is defined at
4+ http://mrtrix.readthedocs.io/en/latest/getting_started/image_data.html?highlight=format#tracks-file-format-tck
5+ """
6+ from __future__ import division
57
68import os
79import warnings
@@ -52,7 +54,7 @@ class TckFile(TractogramFile):
5254 """
5355
5456 # Contants
55- MAGIC_NUMBER = b "mrtrix tracks"
57+ MAGIC_NUMBER = "mrtrix tracks"
5658 SUPPORTS_DATA_PER_POINT = False # Not yet
5759 SUPPORTS_DATA_PER_STREAMLINE = False # Not yet
5860
@@ -98,7 +100,7 @@ def is_correct_format(cls, fileobj):
98100 otherwise returns False.
99101 """
100102 with Opener (fileobj ) as f :
101- magic_number = f .fobj .readline ()
103+ magic_number = asstr ( f .fobj .readline () )
102104 f .seek (- len (magic_number ), os .SEEK_CUR )
103105
104106 return magic_number .strip () == cls .MAGIC_NUMBER
@@ -150,6 +152,11 @@ def _read():
150152
151153 return cls (tractogram , header = hdr )
152154
155+ def _finalize_header (self , f , header , offset = 0 ):
156+ # Overwrite header with updated one.
157+ f .seek (offset , os .SEEK_SET )
158+ self ._write_header (f , header )
159+
153160 def save (self , fileobj ):
154161 """ Save tractogram to a filename or file-like object using TCK format.
155162
@@ -182,9 +189,7 @@ def save(self, fileobj):
182189 except StopIteration :
183190 # Empty tractogram
184191 header [Field .NB_STREAMLINES ] = 0
185- # Overwrite header with updated one.
186- f .seek (beginning , os .SEEK_SET )
187- self ._write_header (f , header )
192+ self ._finalize_header (f , header , offset = beginning )
188193
189194 # Add the EOF_DELIMITER.
190195 f .write (asbytes (self .EOF_DELIMITER .tostring ()))
@@ -217,10 +222,7 @@ def save(self, fileobj):
217222
218223 # Add the EOF_DELIMITER.
219224 f .write (asbytes (self .EOF_DELIMITER .tostring ()))
220-
221- # Overwrite header with updated one.
222- f .seek (beginning , os .SEEK_SET )
223- self ._write_header (f , header )
225+ self ._finalize_header (f , header , offset = beginning )
224226
225227 @staticmethod
226228 def _write_header (fileobj , header ):
@@ -247,24 +249,33 @@ def _write_header(fileobj, header):
247249 for k , v in header .items ()
248250 if k not in exclude and not k .startswith ("_" )])
249251 lines .append ("file: . " ) # Manually add this last field.
250- out = "\n " .join ((asstr (line ).replace ('\n ' , '\t ' ) for line in lines ))
251- fileobj .write (asbytes (out ))
252+ out = "\n " .join (lines )
252253
253- # Compute offset to the beginning of the binary data.
254- # We add 5 for "\nEND\n" that will be added just before the data.
255- tentative_offset = len (out ) + 5
254+ # Check the header is well formatted.
255+ if out .count ("\n " ) > len (lines ) - 1 : # \n only allowed between lines.
256+ msg = "Key-value pairs cannot contain '\\ n':\n {}" .format (out )
257+ raise HeaderError (msg )
256258
257- # Count the number of characters needed to write the offset in ASCII.
258- offset = tentative_offset + len (str (tentative_offset ))
259+ if out .count (":" ) > len (lines ): # : only one per line.
260+ msg = "Key-value pairs cannot contain ':':\n {}" .format (out )
261+ raise HeaderError (msg )
262+
263+ # Write header to file.
264+ fileobj .write (asbytes (out ))
259265
260- # The new offset might need one more character to write it in ASCII.
261- # e.g. offset = 98 (i.e. 2 char.), so offset += 2 = 100 (i.e. 3 char.)
262- # thus the final offset = 101.
263- if len (str (tentative_offset )) != len (str (offset )):
264- offset += 1 # +1, we need one more character for that new digit.
266+ hdr_len_no_offset = len (out ) + 5
267+ # Need to add number of bytes to store offset as decimal string. We
268+ # start with estimate without string, then update if the
269+ # offset-as-decimal-string got longer after adding length of the
270+ # offset string.
271+ new_offset = - 1
272+ old_offset = hdr_len_no_offset
273+ while new_offset != old_offset :
274+ old_offset = new_offset
275+ new_offset = hdr_len_no_offset + len (str (old_offset ))
265276
266- fileobj .write (asbytes (str (offset ) + "\n " ))
267- fileobj .write (asbytes (b "END\n " ))
277+ fileobj .write (asbytes (str (new_offset ) + "\n " ))
278+ fileobj .write (asbytes ("END\n " ))
268279
269280 @staticmethod
270281 def _read_header (fileobj ):
@@ -318,9 +329,7 @@ def _read_header(fileobj):
318329 raise HeaderError ("Missing 'file' attribute in TCK header." )
319330
320331 # Set endianness and _dtype attributes in the header.
321- hdr [Field .ENDIANNESS ] = '<'
322- if hdr ['datatype' ].endswith ('BE' ):
323- hdr [Field .ENDIANNESS ] = '>'
332+ hdr [Field .ENDIANNESS ] = '>' if hdr ['datatype' ].endswith ('BE' ) else '<'
324333
325334 hdr ['_dtype' ] = np .dtype (hdr [Field .ENDIANNESS ] + 'f4' )
326335
0 commit comments