Skip to content

Commit 72d146a

Browse files
committed
DOC+PEP8: Finish addressing @matthew-brett's comments.
1 parent 0f63e7c commit 72d146a

File tree

3 files changed

+50
-31
lines changed

3 files changed

+50
-31
lines changed

nibabel/streamlines/tck.py

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
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

68
import os
79
import 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

nibabel/streamlines/tests/test_streamlines.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import nibabel as nib
99
from six import BytesIO
1010
from nibabel.tmpdirs import InTemporaryDirectory
11+
from nibabel.py3k import asbytes
1112

1213
from nibabel.testing import data_path
1314
from nibabel.testing import clear_and_catch_warnings
@@ -89,15 +90,15 @@ def test_is_supported_detect_format():
8990
# Valid file without extension
9091
for tfile_cls in FORMATS.values():
9192
f = BytesIO()
92-
f.write(tfile_cls.MAGIC_NUMBER)
93+
f.write(asbytes(tfile_cls.MAGIC_NUMBER))
9394
f.seek(0, os.SEEK_SET)
9495
assert_true(nib.streamlines.is_supported(f))
9596
assert_true(nib.streamlines.detect_format(f) is tfile_cls)
9697

9798
# Wrong extension but right magic number
9899
for tfile_cls in FORMATS.values():
99100
with tempfile.TemporaryFile(mode="w+b", suffix=".txt") as f:
100-
f.write(tfile_cls.MAGIC_NUMBER)
101+
f.write(asbytes(tfile_cls.MAGIC_NUMBER))
101102
f.seek(0, os.SEEK_SET)
102103
assert_true(nib.streamlines.is_supported(f))
103104
assert_true(nib.streamlines.detect_format(f) is tfile_cls)

nibabel/streamlines/tests/test_tck.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,15 @@ def test_write_simple_file(self):
114114
assert_equal(tck_file.read(),
115115
open(DATA['simple_tck_fname'], 'rb').read())
116116

117+
# # Add custom header fields.
118+
# tck_file = BytesIO()
119+
# tck = TckFile(tractogram)
120+
# # tck.header['Custom_field'] = "Some_value"
121+
# tck.save(tck_file)
122+
# tck_file.seek(0, os.SEEK_SET)
123+
# new_tck = TckFile.load(tck_file)
124+
# assert_equal(tck.header, new_tck.header)
125+
117126
def test_load_write_file(self):
118127
for fname in [DATA['empty_tck_fname'],
119128
DATA['simple_tck_fname']]:

0 commit comments

Comments
 (0)