1
- from __future__ import division
1
+ """ Read / write access to TCK streamlines format.
2
2
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
5
7
6
8
import os
7
9
import warnings
@@ -52,7 +54,7 @@ class TckFile(TractogramFile):
52
54
"""
53
55
54
56
# Contants
55
- MAGIC_NUMBER = b "mrtrix tracks"
57
+ MAGIC_NUMBER = "mrtrix tracks"
56
58
SUPPORTS_DATA_PER_POINT = False # Not yet
57
59
SUPPORTS_DATA_PER_STREAMLINE = False # Not yet
58
60
@@ -98,7 +100,7 @@ def is_correct_format(cls, fileobj):
98
100
otherwise returns False.
99
101
"""
100
102
with Opener (fileobj ) as f :
101
- magic_number = f .fobj .readline ()
103
+ magic_number = asstr ( f .fobj .readline () )
102
104
f .seek (- len (magic_number ), os .SEEK_CUR )
103
105
104
106
return magic_number .strip () == cls .MAGIC_NUMBER
@@ -150,6 +152,11 @@ def _read():
150
152
151
153
return cls (tractogram , header = hdr )
152
154
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
+
153
160
def save (self , fileobj ):
154
161
""" Save tractogram to a filename or file-like object using TCK format.
155
162
@@ -182,9 +189,7 @@ def save(self, fileobj):
182
189
except StopIteration :
183
190
# Empty tractogram
184
191
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 )
188
193
189
194
# Add the EOF_DELIMITER.
190
195
f .write (asbytes (self .EOF_DELIMITER .tostring ()))
@@ -217,10 +222,7 @@ def save(self, fileobj):
217
222
218
223
# Add the EOF_DELIMITER.
219
224
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 )
224
226
225
227
@staticmethod
226
228
def _write_header (fileobj , header ):
@@ -247,24 +249,33 @@ def _write_header(fileobj, header):
247
249
for k , v in header .items ()
248
250
if k not in exclude and not k .startswith ("_" )])
249
251
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 )
252
253
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 )
256
258
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 ))
259
265
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 ))
265
276
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 " ))
268
279
269
280
@staticmethod
270
281
def _read_header (fileobj ):
@@ -318,9 +329,7 @@ def _read_header(fileobj):
318
329
raise HeaderError ("Missing 'file' attribute in TCK header." )
319
330
320
331
# 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 '<'
324
333
325
334
hdr ['_dtype' ] = np .dtype (hdr [Field .ENDIANNESS ] + 'f4' )
326
335
0 commit comments