Skip to content

Commit 3fc0593

Browse files
committed
Followed @matthew-brett's suggestion for TRK header hack.
1 parent 2961bf5 commit 3fc0593

File tree

4 files changed

+59
-30
lines changed

4 files changed

+59
-30
lines changed

nibabel/streamlines/tests/test_array_sequence.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,11 @@ def test_arraysequence_getitem(self):
280280
check_arr_seq_view(seq_view, SEQ_DATA['seq'])
281281
check_arr_seq(seq_view, [d[:, 2] for d in SEQ_DATA['data']])
282282

283+
# Combining multiple slicing and indexing operations.
284+
seq_view = SEQ_DATA['seq'][::-2][:, 2]
285+
check_arr_seq_view(seq_view, SEQ_DATA['seq'])
286+
check_arr_seq(seq_view, [d[:, 2] for d in SEQ_DATA['data'][::-2]])
287+
283288
def test_arraysequence_repr(self):
284289
# Test that calling repr on a ArraySequence object is not falling.
285290
repr(SEQ_DATA['seq'])

nibabel/streamlines/trk.py

Lines changed: 54 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,11 @@ def get_affine_rasmm_to_trackvis(header):
129129

130130

131131
def encode_value_in_name(value, name, max_name_len=20):
132-
""" Encodes a value in the last two bytes of a string.
132+
""" Encodes a value in the last bytes of a string.
133133
134-
If `value` is one, then there is no encoding and the last two bytes
135-
are left untouched. Otherwise, the byte before the last will be
136-
set to \x00 and the last byte will correspond to the value.
134+
If `value` is one, then there is no encoding and the last bytes
135+
are left untouched. Otherwise, a \x00 byte is added after `name`
136+
and followed by the ascii represensation of the value.
137137
138138
This function also verifies that the length of name is less
139139
than `max_name_len`.
@@ -157,20 +157,56 @@ def encode_value_in_name(value, name, max_name_len=20):
157157
msg = ("Data information named '{0}' is too long"
158158
" (max {1} characters.)").format(name, max_name_len)
159159
raise ValueError(msg)
160-
elif len(name) > max_name_len - 2 and value > 1:
160+
elif value > 1 and len(name) + len(str(value)) + 1 > max_name_len:
161161
msg = ("Data information named '{0}' is too long (need to be less"
162162
" than {1} characters when storing more than one value"
163163
" for a given data information."
164-
).format(name, max_name_len - 2)
164+
).format(name, max_name_len - (len(str(value)) + 1))
165165
raise ValueError(msg)
166166

167-
name = name.ljust(max_name_len, '\x00')
167+
encoded_name = name
168168
if value > 1:
169-
# Use the last two bytes of `name` to store `value`.
170-
name = (asbytes(name[:max_name_len - 2]) + b'\x00' +
171-
np.array(value, dtype=np.int8).tostring())
169+
# Store the name followed by \x00 and the `value` (in ascii).
170+
encoded_name += '\x00' + str(value)
172171

173-
return name
172+
encoded_name = encoded_name.ljust(max_name_len, '\x00')
173+
return encoded_name
174+
175+
176+
def decode_value_from_name(encoded_name):
177+
""" Decodes a value that has been encoded in the last bytes of a string.
178+
179+
Check :func:`encode_value_in_name` to see how the value has been encoded.
180+
181+
Parameters
182+
----------
183+
encoded_name : bytes
184+
Name in which a value has been encoded or not.
185+
186+
Returns
187+
-------
188+
name : bytes
189+
Name without the encoded value.
190+
value : int
191+
Value decoded from the name.
192+
"""
193+
encoded_name = asstr(encoded_name)
194+
if len(encoded_name) == 0:
195+
return encoded_name, 0
196+
197+
splits = encoded_name.rstrip('\x00').split('\x00')
198+
name = splits[0]
199+
value = 1
200+
201+
if len(splits) == 2:
202+
value = int(splits[1]) # Decode value.
203+
elif len(splits) > 2:
204+
# The remaining bytes are not \x00, raising.
205+
msg = ("Wrong scalar_name or property_name: '{}'."
206+
" Unused characters should be \\x00.").format(name)
207+
raise HeaderError(msg)
208+
209+
return name, value
174210

175211

176212
def create_empty_header():
@@ -289,17 +325,11 @@ def load(cls, fileobj, lazy_load=False):
289325
if hdr[Field.NB_SCALARS_PER_POINT] > 0:
290326
cpt = 0
291327
for scalar_name in hdr['scalar_name']:
292-
scalar_name = asstr(scalar_name)
293-
if len(scalar_name) == 0:
294-
continue
328+
scalar_name, nb_scalars = decode_value_from_name(scalar_name)
295329

296-
# Check if we encoded the number of values we stocked for this
297-
# scalar name.
298-
nb_scalars = 1
299-
if scalar_name[-2] == '\x00' and scalar_name[-1] != '\x00':
300-
nb_scalars = int(np.fromstring(scalar_name[-1], np.int8))
330+
if nb_scalars == 0:
331+
continue
301332

302-
scalar_name = scalar_name.split('\x00')[0]
303333
slice_obj = slice(cpt, cpt + nb_scalars)
304334
data_per_point_slice[scalar_name] = slice_obj
305335
cpt += nb_scalars
@@ -312,18 +342,12 @@ def load(cls, fileobj, lazy_load=False):
312342
if hdr[Field.NB_PROPERTIES_PER_STREAMLINE] > 0:
313343
cpt = 0
314344
for property_name in hdr['property_name']:
315-
property_name = asstr(property_name)
316-
if len(property_name) == 0:
317-
continue
345+
results = decode_value_from_name(property_name)
346+
property_name, nb_properties = results
318347

319-
# Check if we encoded the number of values we stocked for this
320-
# property name.
321-
nb_properties = 1
322-
if property_name[-2] == '\x00' and property_name[-1] != '\x00':
323-
nb_properties = int(np.fromstring(property_name[-1],
324-
np.int8))
348+
if nb_properties == 0:
349+
continue
325350

326-
property_name = property_name.split('\x00')[0]
327351
slice_obj = slice(cpt, cpt + nb_properties)
328352
data_per_streamline_slice[property_name] = slice_obj
329353
cpt += nb_properties

nibabel/tests/data/complex.trk

0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)