Skip to content

Commit ce9b44a

Browse files
committed
Merge pull request #9 from matthew-brett/small-streamlines-edits
RF: small streamlines API PR edits
2 parents a944957 + d231004 commit ce9b44a

File tree

7 files changed

+174
-119
lines changed

7 files changed

+174
-119
lines changed

nibabel/streamlines/tests/test_trk.py

+49-16
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from nibabel.externals.six import BytesIO
88

99
from nibabel.testing import data_path
10-
from nibabel.testing import clear_and_catch_warnings
10+
from nibabel.testing import clear_and_catch_warnings, assert_arr_dict_equal
1111
from nose.tools import assert_equal, assert_raises, assert_true
1212
from numpy.testing import assert_array_equal
1313

@@ -16,7 +16,7 @@
1616
from ..tractogram_file import HeaderError, HeaderWarning
1717

1818
from .. import trk as trk_module
19-
from ..trk import TrkFile
19+
from ..trk import TrkFile, encode_value_in_name, decode_value_from_name
2020
from ..header import Field
2121

2222
DATA = {}
@@ -82,14 +82,6 @@ def setup():
8282
affine_to_rasmm=np.eye(4))
8383

8484

85-
def assert_header_equal(h1, h2):
86-
for k in h1.keys():
87-
assert_array_equal(h2[k], h1[k])
88-
89-
for k in h2.keys():
90-
assert_array_equal(h1[k], h2[k])
91-
92-
9385
class TestTRK(unittest.TestCase):
9486

9587
def test_load_empty_file(self):
@@ -178,10 +170,7 @@ def test_load_complex_file_in_big_endian(self):
178170
def test_tractogram_file_properties(self):
179171
trk = TrkFile.load(DATA['simple_trk_fname'])
180172
assert_equal(trk.streamlines, trk.tractogram.streamlines)
181-
assert_equal(trk.get_streamlines(), trk.streamlines)
182-
assert_equal(trk.get_tractogram(), trk.tractogram)
183-
assert_equal(trk.get_header(), trk.header)
184-
assert_array_equal(trk.get_affine(), trk.header[Field.VOXEL_TO_RASMM])
173+
assert_array_equal(trk.affine, trk.header[Field.VOXEL_TO_RASMM])
185174

186175
def test_write_empty_file(self):
187176
tractogram = Tractogram(affine_to_rasmm=np.eye(4))
@@ -298,7 +287,7 @@ def test_load_write_LPS_file(self):
298287

299288
new_trk = TrkFile.load(trk_file)
300289

301-
assert_header_equal(new_trk.header, trk.header)
290+
assert_arr_dict_equal(new_trk.header, trk.header)
302291
assert_tractogram_equal(new_trk.tractogram, trk.tractogram)
303292

304293
new_trk_orig = TrkFile.load(DATA['standard_LPS_trk_fname'])
@@ -322,7 +311,7 @@ def test_load_write_LPS_file(self):
322311

323312
new_trk = TrkFile.load(trk_file)
324313

325-
assert_header_equal(new_trk.header, trk_LPS.header)
314+
assert_arr_dict_equal(new_trk.header, trk_LPS.header)
326315
assert_tractogram_equal(new_trk.tractogram, trk.tractogram)
327316

328317
new_trk_orig = TrkFile.load(DATA['standard_LPS_trk_fname'])
@@ -460,3 +449,47 @@ def test_write_scalars_and_properties_name_too_long(self):
460449
def test_str(self):
461450
trk = TrkFile.load(DATA['complex_trk_fname'])
462451
str(trk) # Simply test it's not failing when called.
452+
453+
def test_header_read_restore(self):
454+
# Test that reading a header restores the file position
455+
trk_fname = DATA['simple_trk_fname']
456+
bio = BytesIO()
457+
bio.write(b'Along my very merry way')
458+
hdr_pos = bio.tell()
459+
hdr_from_fname = TrkFile._read_header(trk_fname)
460+
with open(trk_fname, 'rb') as fobj:
461+
bio.write(fobj.read())
462+
bio.seek(hdr_pos)
463+
# Check header is as expected
464+
hdr_from_fname['_offset_data'] += hdr_pos # Correct for start position
465+
assert_arr_dict_equal(TrkFile._read_header(bio), hdr_from_fname)
466+
# Check fileobject file position has not changed
467+
assert_equal(bio.tell(), hdr_pos)
468+
469+
470+
def test_encode_names():
471+
# Test function for encoding numbers into property names
472+
b0 = b'\x00'
473+
assert_equal(encode_value_in_name(0, 'foo', 10),
474+
b'foo' + b0 * 7)
475+
assert_equal(encode_value_in_name(1, 'foo', 10),
476+
b'foo' + b0 * 7)
477+
assert_equal(encode_value_in_name(8, 'foo', 10),
478+
b'foo' + b0 + b'8' + b0 * 5)
479+
assert_equal(encode_value_in_name(40, 'foobar', 10),
480+
b'foobar' + b0 + b'40' + b0)
481+
assert_equal(encode_value_in_name(1, 'foobarbazz', 10), b'foobarbazz')
482+
assert_raises(ValueError, encode_value_in_name, 1, 'foobarbazzz', 10)
483+
assert_raises(ValueError, encode_value_in_name, 2, 'foobarbaz', 10)
484+
assert_equal(encode_value_in_name(2, 'foobarba', 10), b'foobarba\x002')
485+
486+
487+
def test_decode_names():
488+
# Test function for decoding name string into name, number
489+
b0 = b'\x00'
490+
assert_equal(decode_value_from_name(b''), ('', 0))
491+
assert_equal(decode_value_from_name(b'foo' + b0 * 7), ('foo', 1))
492+
assert_equal(decode_value_from_name(b'foo\x008' + b0 * 5), ('foo', 8))
493+
assert_equal(decode_value_from_name(b'foobar\x0010\x00'), ('foobar', 10))
494+
assert_raises(ValueError, decode_value_from_name, b'foobar\x0010\x01')
495+
assert_raises(HeaderError, decode_value_from_name, b'foo\x0010\x00111')

nibabel/streamlines/tractogram.py

+38-39
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class PerArrayDict(SliceableDataDict):
8080
8181
In addition, it makes sure the amount of data contained in those ndarrays
8282
matches the number of streamlines given at the instantiation of this
83-
dictionary.
83+
instance.
8484
"""
8585
def __init__(self, nb_elements, *args, **kwargs):
8686
self.nb_elements = nb_elements
@@ -114,7 +114,7 @@ class PerArraySequenceDict(SliceableDataDict):
114114
115115
In addition, it makes sure the amount of data contained in those array
116116
sequences matches the number of elements given at the instantiation
117-
of this dictionary.
117+
of the instance.
118118
"""
119119
def __init__(self, nb_elements, *args, **kwargs):
120120
self.nb_elements = nb_elements
@@ -136,9 +136,9 @@ def __setitem__(self, key, value):
136136
class LazyDict(collections.MutableMapping):
137137
""" Dictionary of generator functions.
138138
139-
This container behaves like an dictionary but it makes sure its elements
140-
are callable objects and assumed to be generator function yielding values.
141-
When getting the element associated to a given key, the element (i.e. a
139+
This container behaves like a dictionary but it makes sure its elements are
140+
callable objects that it assumes are generator functions yielding values.
141+
When getting the element associated with a given key, the element (i.e. a
142142
generator function) is first called before being returned.
143143
"""
144144
def __init__(self, *args, **kwargs):
@@ -178,7 +178,7 @@ def __len__(self):
178178
class TractogramItem(object):
179179
""" Class containing information about one streamline.
180180
181-
:class:`TractogramItem` objects have three main properties: `streamline`,
181+
:class:`TractogramItem` objects have three public attributes: `streamline`,
182182
`data_for_streamline`, and `data_for_points`.
183183
184184
Parameters
@@ -187,14 +187,14 @@ class TractogramItem(object):
187187
Points of this streamline represented as an ndarray of shape (N, 3)
188188
where N is the number of points.
189189
data_for_streamline : dict
190-
Dictionary containing some data associated to this particular
191-
streamline. Each key `k` is mapped to a ndarray of shape (Pt,), where
192-
`Pt` is the dimension of the data associated with key `k`.
190+
Dictionary containing some data associated with this particular
191+
streamline. Each key ``k`` is mapped to a ndarray of shape (Pt,), where
192+
``Pt`` is the dimension of the data associated with key ``k``.
193193
data_for_points : dict
194194
Dictionary containing some data associated to each point of this
195-
particular streamline. Each key `k` is mapped to a ndarray of
196-
shape (Nt, Mk), where `Nt` is the number of points of this streamline
197-
and `Mk` is the dimension of the data associated with key `k`.
195+
particular streamline. Each key ``k`` is mapped to a ndarray of shape
196+
(Nt, Mk), where ``Nt`` is the number of points of this streamline and
197+
``Mk`` is the dimension of the data associated with key ``k``.
198198
"""
199199
def __init__(self, streamline, data_for_streamline, data_for_points):
200200
self.streamline = np.asarray(streamline)
@@ -215,7 +215,7 @@ class Tractogram(object):
215215
choice as long as you provide the correct `affine_to_rasmm` matrix, at
216216
construction time, that brings the streamlines back to *RAS+*, *mm* space,
217217
where the coordinates (0,0,0) corresponds to the center of the voxel
218-
(opposed to a corner).
218+
(as opposed to the corner of the voxel).
219219
220220
Attributes
221221
----------
@@ -224,18 +224,18 @@ class Tractogram(object):
224224
shape ($N_t$, 3) where $N_t$ is the number of points of
225225
streamline $t$.
226226
data_per_streamline : :class:`PerArrayDict` object
227-
Dictionary where the items are (str, 2D array).
228-
Each key represents an information $i$ to be kept alongside every
229-
streamline, and its associated value is a 2D array of shape
230-
($T$, $P_i$) where $T$ is the number of streamlines and $P_i$ is
231-
the number of values to store for that particular information $i$.
227+
Dictionary where the items are (str, 2D array). Each key represents a
228+
piece of information $i$ to be kept alongside every streamline, and its
229+
associated value is a 2D array of shape ($T$, $P_i$) where $T$ is the
230+
number of streamlines and $P_i$ is the number of values to store for
231+
that particular piece of information $i$.
232232
data_per_point : :class:`PerArraySequenceDict` object
233-
Dictionary where the items are (str, :class:`ArraySequence`).
234-
Each key represents an information $i$ to be kept alongside every
235-
point of every streamline, and its associated value is an iterable
236-
of ndarrays of shape ($N_t$, $M_i$) where $N_t$ is the number of
237-
points for a particular streamline $t$ and $M_i$ is the number
238-
values to store for that particular information $i$.
233+
Dictionary where the items are (str, :class:`ArraySequence`). Each key
234+
represents a piece of information $i$ to be kept alongside every point
235+
of every streamline, and its associated value is an iterable of
236+
ndarrays of shape ($N_t$, $M_i$) where $N_t$ is the number of points
237+
for a particular streamline $t$ and $M_i$ is the number values to store
238+
for that particular piece of information $i$.
239239
"""
240240
def __init__(self, streamlines=None,
241241
data_per_streamline=None,
@@ -424,29 +424,29 @@ class LazyTractogram(Tractogram):
424424
choice as long as you provide the correct `affine_to_rasmm` matrix, at
425425
construction time, that brings the streamlines back to *RAS+*, *mm* space,
426426
where the coordinates (0,0,0) corresponds to the center of the voxel
427-
(opposed to a corner).
427+
(as opposed to the corner of the voxel).
428428
429429
Attributes
430430
----------
431431
streamlines : generator function
432432
Generator function yielding streamlines. Each streamline is an
433433
ndarray of shape ($N_t$, 3) where $N_t$ is the number of points of
434434
streamline $t$.
435-
data_per_streamline : :class:`LazyDict` object
435+
data_per_streamline : instance of :class:`LazyDict`
436436
Dictionary where the items are (str, instantiated generator).
437-
Each key represents an information $i$ to be kept alongside every
438-
streamline, and its associated value is a generator function
439-
yielding that information via ndarrays of shape ($P_i$,) where
440-
$P_i$ is the number of values to store for that particular
441-
information $i$.
437+
Each key represents a piece of information $i$ to be kept alongside
438+
every streamline, and its associated value is a generator function
439+
yielding that information via ndarrays of shape ($P_i$,) where $P_i$ is
440+
the number of values to store for that particular piece of information
441+
$i$.
442442
data_per_point : :class:`LazyDict` object
443-
Dictionary where the items are (str, instantiated generator).
444-
Each key represents an information $i$ to be kept alongside every
445-
point of every streamline, and its associated value is a generator
446-
function yielding that information via ndarrays of shape
447-
($N_t$, $M_i$) where $N_t$ is the number of points for a particular
448-
streamline $t$ and $M_i$ is the number of values to store for
449-
that particular information $i$.
443+
Dictionary where the items are (str, instantiated generator). Each key
444+
represents a piece of information $i$ to be kept alongside every point
445+
of every streamline, and its associated value is a generator function
446+
yielding that information via ndarrays of shape ($N_t$, $M_i$) where
447+
$N_t$ is the number of points for a particular streamline $t$ and $M_i$
448+
is the number of values to store for that particular piece of
449+
information $i$.
450450
451451
Notes
452452
-----
@@ -599,7 +599,6 @@ def _apply_affine():
599599
def _set_streamlines(self, value):
600600
if value is not None and not callable(value):
601601
raise TypeError("`streamlines` must be a generator function.")
602-
603602
self._streamlines = value
604603

605604
@property

nibabel/streamlines/tractogram_file.py

+5-15
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
""" Define abstract interface for Tractogram file classes
2+
"""
13
from abc import ABCMeta, abstractmethod
24
from nibabel.externals.six import with_metaclass
35

@@ -49,21 +51,9 @@ def header(self):
4951

5052
@property
5153
def affine(self):
54+
""" voxmm -> rasmm affine. """
5255
return self.header.get(Field.VOXEL_TO_RASMM)
5356

54-
def get_tractogram(self):
55-
return self.tractogram
56-
57-
def get_streamlines(self):
58-
return self.streamlines
59-
60-
def get_header(self):
61-
return self.header
62-
63-
def get_affine(self):
64-
""" Returns vox -> rasmm affine. """
65-
return self.affine
66-
6757
@abstractclassmethod
6858
def is_correct_format(cls, fileobj):
6959
""" Checks if the file has the right streamlines file format.
@@ -85,7 +75,7 @@ def is_correct_format(cls, fileobj):
8575

8676
@abstractclassmethod
8777
def load(cls, fileobj, lazy_load=True):
88-
""" Loads streamlines from a file-like object.
78+
""" Loads streamlines from a filename or file-like object.
8979
9080
Parameters
9181
----------
@@ -107,7 +97,7 @@ def load(cls, fileobj, lazy_load=True):
10797

10898
@abstractmethod
10999
def save(self, fileobj):
110-
""" Saves streamlines to a file-like object.
100+
""" Saves streamlines to a filename or file-like object.
111101
112102
Parameters
113103
----------

0 commit comments

Comments
 (0)