Skip to content

Commit 79f968f

Browse files
authored
Merge pull request #1175 from effigies/fix-read-tckedit
ENH: Support multiline header fields in TCK
2 parents a60d2b7 + 95df42e commit 79f968f

File tree

3 files changed

+22
-12
lines changed

3 files changed

+22
-12
lines changed

nibabel/streamlines/tck.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import os
88
import warnings
9+
from contextlib import suppress
910

1011
import numpy as np
1112

@@ -266,11 +267,6 @@ def _write_header(fileobj, header):
266267
)
267268
out = '\n'.join(lines)
268269

269-
# Check the header is well formatted.
270-
if out.count('\n') > len(lines) - 1: # \n only allowed between lines.
271-
msg = f"Key-value pairs cannot contain '\\n':\n{out}"
272-
raise HeaderError(msg)
273-
274270
if out.count(':') > len(lines):
275271
# : only one per line (except the last one which contains END).
276272
msg = f"Key-value pairs cannot contain ':':\n{out}"
@@ -331,6 +327,8 @@ def _read_header(cls, fileobj):
331327
f.seek(1, os.SEEK_CUR) # Skip \n
332328

333329
found_end = False
330+
key = None
331+
tmp_hdr = {}
334332

335333
# Read all key-value pairs contained in the header, stop at EOF
336334
for n_line, line in enumerate(f, 1):
@@ -343,15 +341,22 @@ def _read_header(cls, fileobj):
343341
found_end = True
344342
break
345343

346-
if ':' not in line: # Invalid header line
344+
# Set new key if available, otherwise append to last known key
345+
with suppress(ValueError):
346+
key, line = line.split(':', 1)
347+
key = key.strip()
348+
349+
# Apparent continuation line before any keys are found
350+
if key is None:
347351
raise HeaderError(f'Invalid header (line {n_line}): {line}')
348352

349-
key, value = line.split(':', 1)
350-
hdr[key.strip()] = value.strip()
353+
tmp_hdr.setdefault(key, []).append(line.strip())
351354

352355
if not found_end:
353356
raise HeaderError('Missing END in the header.')
354357

358+
hdr.update({key: '\n'.join(val) for key, val in tmp_hdr.items()})
359+
355360
offset_data = f.tell()
356361

357362
# Set the file position where it was, in case it was previously open

nibabel/streamlines/tests/test_tck.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def setup_module():
3131
# standard.tck contains only streamlines
3232
DATA['standard_tck_fname'] = pjoin(data_path, 'standard.tck')
3333
DATA['matlab_nan_tck_fname'] = pjoin(data_path, 'matlab_nan.tck')
34+
DATA['multiline_header_fname'] = pjoin(data_path, 'multiline_header_field.tck')
3435

3536
DATA['streamlines'] = [
3637
np.arange(1 * 3, dtype='f4').reshape((1, 3)),
@@ -87,6 +88,14 @@ def test_load_matlab_nan_file(self):
8788
assert len(streamlines) == 1
8889
assert streamlines[0].shape == (108, 3)
8990

91+
def test_load_multiline_header_file(self):
92+
for lazy_load in [False, True]:
93+
tck = TckFile.load(DATA['multiline_header_fname'], lazy_load=lazy_load)
94+
streamlines = list(tck.tractogram.streamlines)
95+
assert len(tck.header['command_history'].splitlines()) == 3
96+
assert len(streamlines) == 1
97+
assert streamlines[0].shape == (253, 3)
98+
9099
def test_writeable_data(self):
91100
data = DATA['simple_tractogram']
92101
for key in ('simple_tck_fname', 'simple_tck_big_endian_fname'):
@@ -192,10 +201,6 @@ def test_write_simple_file(self):
192201
# TCK file containing not well formatted entries in its header.
193202
tck_file = BytesIO()
194203
tck = TckFile(tractogram)
195-
tck.header['new_entry'] = 'value\n' # \n not allowed
196-
with pytest.raises(HeaderError):
197-
tck.save(tck_file)
198-
199204
tck.header['new_entry'] = 'val:ue' # : not allowed
200205
with pytest.raises(HeaderError):
201206
tck.save(tck_file)
4.31 KB
Binary file not shown.

0 commit comments

Comments
 (0)