Skip to content

Commit cc26ae6

Browse files
committed
Fixed import of OrderedDict and added support to apply transformation on streamlines.
1 parent 7dfd83e commit cc26ae6

File tree

7 files changed

+134
-57
lines changed

7 files changed

+134
-57
lines changed

nibabel/streamlines/base_format.py

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import numpy as np
12
from warnings import warn
23

34
from nibabel.externals.six.moves import zip_longest
@@ -111,14 +112,56 @@ def __getitem__(self, idx):
111112
def __len__(self):
112113
return len(self.points)
113114

114-
def to_world_space(self, as_generator=False):
115-
affine = self.header.voxel_to_world
116-
new_points = (apply_affine(affine, pts) for pts in self.points)
115+
def copy(self):
116+
""" Returns a copy of this `Streamlines` object. """
117+
streamlines = Streamlines(self.points, self.scalars, self.properties)
118+
streamlines._header = self.header.copy()
119+
return streamlines
117120

118-
if not as_generator:
119-
return list(new_points)
121+
def transform(self, affine, lazy=False):
122+
""" Applies an affine transformation on the points of each streamline.
120123
121-
return new_points
124+
Parameters
125+
----------
126+
affine : 2D array (4,4)
127+
Transformation that will be applied on each streamline.
128+
lazy : bool (optional)
129+
If true output will be a generator of arrays instead of a list.
130+
131+
Returns
132+
-------
133+
streamlines
134+
If `lazy` is true, a `LazyStreamlines` object is returned,
135+
otherwise a `Streamlines` object is returned. In both case,
136+
streamlines are in a space defined by `affine`.
137+
"""
138+
points = lambda: (apply_affine(affine, pts) for pts in self.points)
139+
if not lazy:
140+
points = list(points())
141+
142+
streamlines = self.copy()
143+
streamlines.points = points
144+
streamlines.header.to_world_space = np.dot(streamlines.header.to_world_space,
145+
np.linalg.inv(affine))
146+
147+
return streamlines
148+
149+
def to_world_space(self, lazy=False):
150+
""" Sends the streamlines back into world space.
151+
152+
Parameters
153+
----------
154+
lazy : bool (optional)
155+
If true output will be a generator of arrays instead of a list.
156+
157+
Returns
158+
-------
159+
streamlines
160+
If `lazy` is true, a `LazyStreamlines` object is returned,
161+
otherwise a `Streamlines` object is returned. In both case,
162+
streamlines are in world space.
163+
"""
164+
return self.transform(self.header.to_world_space, lazy)
122165

123166

124167
class LazyStreamlines(Streamlines):
@@ -255,8 +298,36 @@ def __len__(self):
255298

256299
return self.header.nb_streamlines
257300

301+
def copy(self):
302+
""" Returns a copy of this `LazyStreamlines` object. """
303+
streamlines = LazyStreamlines(self._points, self._scalars, self._properties)
304+
streamlines._header = self.header.copy()
305+
return streamlines
306+
307+
def transform(self, affine):
308+
""" Applies an affine transformation on the points of each streamline.
309+
310+
Parameters
311+
----------
312+
affine : 2D array (4,4)
313+
Transformation that will be applied on each streamline.
314+
315+
Returns
316+
-------
317+
streamlines : `LazyStreamlines` object
318+
Streamlines living in a space defined by `affine`.
319+
"""
320+
return super(LazyStreamlines, self).transform(affine, lazy=True)
321+
258322
def to_world_space(self):
259-
return super(LazyStreamlines, self).to_world_space(as_generator=True)
323+
""" Sends the streamlines back into world space.
324+
325+
Returns
326+
-------
327+
streamlines : `LazyStreamlines` object
328+
Streamlines living in world space.
329+
"""
330+
return super(LazyStreamlines, self).to_world_space(lazy=True)
260331

261332

262333
class StreamlinesFile:

nibabel/streamlines/header.py

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import copy
12
import numpy as np
23
from nibabel.orientations import aff2axcodes
3-
from collections import OrderedDict
4+
from nibabel.externals import OrderedDict
45

56

67
class Field:
@@ -18,7 +19,7 @@ class Field:
1819
DIMENSIONS = "dimensions"
1920
MAGIC_NUMBER = "magic_number"
2021
ORIGIN = "origin"
21-
VOXEL_TO_WORLD = "voxel_to_world"
22+
to_world_space = "to_world_space"
2223
VOXEL_ORDER = "voxel_order"
2324
ENDIAN = "endian"
2425

@@ -28,33 +29,33 @@ def __init__(self):
2829
self._nb_streamlines = None
2930
self._nb_scalars_per_point = None
3031
self._nb_properties_per_streamline = None
31-
self._voxel_to_world = np.eye(4)
32+
self._to_world_space = np.eye(4)
3233
self.extra = OrderedDict()
3334

3435
@property
35-
def voxel_to_world(self):
36-
return self._voxel_to_world
36+
def to_world_space(self):
37+
return self._to_world_space
3738

38-
@voxel_to_world.setter
39-
def voxel_to_world(self, value):
40-
self._voxel_to_world = np.array(value, dtype=np.float32)
39+
@to_world_space.setter
40+
def to_world_space(self, value):
41+
self._to_world_space = np.array(value, dtype=np.float32)
4142

4243
@property
4344
def voxel_sizes(self):
44-
""" Get voxel sizes from voxel_to_world. """
45-
return np.sqrt(np.sum(self.voxel_to_world[:3, :3]**2, axis=0))
45+
""" Get voxel sizes from to_world_space. """
46+
return np.sqrt(np.sum(self.to_world_space[:3, :3]**2, axis=0))
4647

4748
@voxel_sizes.setter
4849
def voxel_sizes(self, value):
4950
scaling = np.r_[np.array(value), [1]]
5051
old_scaling = np.r_[np.array(self.voxel_sizes), [1]]
5152
# Remove old scaling and apply new one
52-
self.voxel_to_world = np.dot(np.diag(scaling/old_scaling), self.voxel_to_world)
53+
self.to_world_space = np.dot(np.diag(scaling/old_scaling), self.to_world_space)
5354

5455
@property
5556
def voxel_order(self):
56-
""" Get voxel order from voxel_to_world. """
57-
return "".join(aff2axcodes(self.voxel_to_world))
57+
""" Get voxel order from to_world_space. """
58+
return "".join(aff2axcodes(self.to_world_space))
5859

5960
@property
6061
def nb_streamlines(self):
@@ -88,8 +89,17 @@ def extra(self):
8889
def extra(self, value):
8990
self._extra = OrderedDict(value)
9091

92+
def copy(self):
93+
header = StreamlinesHeader()
94+
header._nb_streamlines = self.nb_streamlines
95+
header.nb_scalars_per_point = self.nb_scalars_per_point
96+
header.nb_properties_per_streamline = self.nb_properties_per_streamline
97+
header.to_world_space = self.to_world_space.copy()
98+
header.extra = copy.deepcopy(self.extra)
99+
return header
100+
91101
def __eq__(self, other):
92-
return (np.allclose(self.voxel_to_world, other.voxel_to_world) and
102+
return (np.allclose(self.to_world_space, other.to_world_space) and
93103
self.nb_streamlines == other.nb_streamlines and
94104
self.nb_scalars_per_point == other.nb_scalars_per_point and
95105
self.nb_properties_per_streamline == other.nb_properties_per_streamline and
@@ -100,7 +110,7 @@ def __repr__(self):
100110
txt += "nb_streamlines: " + repr(self.nb_streamlines) + '\n'
101111
txt += "nb_scalars_per_point: " + repr(self.nb_scalars_per_point) + '\n'
102112
txt += "nb_properties_per_streamline: " + repr(self.nb_properties_per_streamline) + '\n'
103-
txt += "voxel_to_world: " + repr(self.voxel_to_world) + '\n'
113+
txt += "to_world_space: " + repr(self.to_world_space) + '\n'
104114
txt += "voxel_sizes: " + repr(self.voxel_sizes) + '\n'
105115

106116
txt += "Extra fields: {\n"

nibabel/streamlines/tests/test_base_format.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ def test_to_world_space(self):
117117
# World space is (RAS+) with voxel size of 2x3x4mm.
118118
streamlines.header.voxel_sizes = (2, 3, 4)
119119

120-
new_points = streamlines.to_world_space()
121-
for new_pts, pts in zip(new_points, self.points):
120+
new_streamlines = streamlines.to_world_space()
121+
for new_pts, pts in zip(new_streamlines.points, self.points):
122122
for dim, size in enumerate(streamlines.header.voxel_sizes):
123123
assert_array_almost_equal(new_pts[:, dim], size*pts[:, dim])
124124

@@ -129,7 +129,7 @@ def test_header(self):
129129
assert_equal(streamlines.header.nb_scalars_per_point, 0)
130130
assert_equal(streamlines.header.nb_properties_per_streamline, 0)
131131
assert_array_equal(streamlines.header.voxel_sizes, (1, 1, 1))
132-
assert_array_equal(streamlines.header.voxel_to_world, np.eye(4))
132+
assert_array_equal(streamlines.header.to_world_space, np.eye(4))
133133
assert_equal(streamlines.header.extra, {})
134134

135135
streamlines = Streamlines(self.points, self.colors, self.mean_curvature_torsion)
@@ -138,15 +138,15 @@ def test_header(self):
138138
assert_equal(streamlines.header.nb_scalars_per_point, self.colors[0].shape[1])
139139
assert_equal(streamlines.header.nb_properties_per_streamline, self.mean_curvature_torsion[0].shape[0])
140140

141-
# Modifying voxel_sizes should be reflected in voxel_to_world
141+
# Modifying voxel_sizes should be reflected in to_world_space
142142
streamlines.header.voxel_sizes = (2, 3, 4)
143143
assert_array_equal(streamlines.header.voxel_sizes, (2, 3, 4))
144-
assert_array_equal(np.diag(streamlines.header.voxel_to_world), (2, 3, 4, 1))
144+
assert_array_equal(np.diag(streamlines.header.to_world_space), (2, 3, 4, 1))
145145

146-
# Modifying scaling of voxel_to_world should be reflected in voxel_sizes
147-
streamlines.header.voxel_to_world = np.diag([4, 3, 2, 1])
146+
# Modifying scaling of to_world_space should be reflected in voxel_sizes
147+
streamlines.header.to_world_space = np.diag([4, 3, 2, 1])
148148
assert_array_equal(streamlines.header.voxel_sizes, (4, 3, 2))
149-
assert_array_equal(streamlines.header.voxel_to_world, np.diag([4, 3, 2, 1]))
149+
assert_array_equal(streamlines.header.to_world_space, np.diag([4, 3, 2, 1]))
150150

151151
# Test that we can run __repr__ without error.
152152
repr(streamlines.header)
@@ -313,7 +313,7 @@ def test_lazy_streamlines_header(self):
313313
assert_equal(streamlines.header.nb_scalars_per_point, 0)
314314
assert_equal(streamlines.header.nb_properties_per_streamline, 0)
315315
assert_array_equal(streamlines.header.voxel_sizes, (1, 1, 1))
316-
assert_array_equal(streamlines.header.voxel_to_world, np.eye(4))
316+
assert_array_equal(streamlines.header.to_world_space, np.eye(4))
317317
assert_equal(streamlines.header.extra, {})
318318

319319
points = lambda: (x for x in self.points)

nibabel/streamlines/tests/test_header.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def test_streamlines_header():
1212
assert_true(header.nb_scalars_per_point is None)
1313
assert_true(header.nb_properties_per_streamline is None)
1414
assert_array_equal(header.voxel_sizes, (1, 1, 1))
15-
assert_array_equal(header.voxel_to_world, np.eye(4))
15+
assert_array_equal(header.to_world_space, np.eye(4))
1616
assert_equal(header.extra, {})
1717

1818
# Modify simple attributes
@@ -23,15 +23,15 @@ def test_streamlines_header():
2323
assert_equal(header.nb_scalars_per_point, 2)
2424
assert_equal(header.nb_properties_per_streamline, 3)
2525

26-
# Modifying voxel_sizes should be reflected in voxel_to_world
26+
# Modifying voxel_sizes should be reflected in to_world_space
2727
header.voxel_sizes = (2, 3, 4)
2828
assert_array_equal(header.voxel_sizes, (2, 3, 4))
29-
assert_array_equal(np.diag(header.voxel_to_world), (2, 3, 4, 1))
29+
assert_array_equal(np.diag(header.to_world_space), (2, 3, 4, 1))
3030

31-
# Modifying scaling of voxel_to_world should be reflected in voxel_sizes
32-
header.voxel_to_world = np.diag([4, 3, 2, 1])
31+
# Modifying scaling of to_world_space should be reflected in voxel_sizes
32+
header.to_world_space = np.diag([4, 3, 2, 1])
3333
assert_array_equal(header.voxel_sizes, (4, 3, 2))
34-
assert_array_equal(header.voxel_to_world, np.diag([4, 3, 2, 1]))
34+
assert_array_equal(header.to_world_space, np.diag([4, 3, 2, 1]))
3535

3636
# Test that we can run __repr__ without error.
3737
repr(header)

nibabel/streamlines/tests/test_streamlines.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -140,28 +140,28 @@ def setUp(self):
140140
self.nb_streamlines = len(self.points)
141141
self.nb_scalars_per_point = self.colors[0].shape[1]
142142
self.nb_properties_per_streamline = len(self.mean_curvature_torsion[0])
143-
self.voxel_to_world = np.eye(4)
143+
self.to_world_space = np.eye(4)
144144

145145
def test_load_empty_file(self):
146146
for empty_filename in self.empty_filenames:
147147
streamlines = nib.streamlines.load(empty_filename,
148-
ref=self.voxel_to_world,
148+
ref=self.to_world_space,
149149
lazy_load=False)
150150
assert_true(type(streamlines), Streamlines)
151151
check_streamlines(streamlines, 0, [], [], [])
152152

153153
def test_load_simple_file(self):
154154
for simple_filename in self.simple_filenames:
155155
streamlines = nib.streamlines.load(simple_filename,
156-
ref=self.voxel_to_world,
156+
ref=self.to_world_space,
157157
lazy_load=False)
158158
assert_true(type(streamlines), Streamlines)
159159
check_streamlines(streamlines, self.nb_streamlines,
160160
self.points, [], [])
161161

162162
# Test lazy_load
163163
streamlines = nib.streamlines.load(simple_filename,
164-
ref=self.voxel_to_world,
164+
ref=self.to_world_space,
165165
lazy_load=True)
166166
assert_true(type(streamlines), LazyStreamlines)
167167
check_streamlines(streamlines, self.nb_streamlines,
@@ -180,15 +180,15 @@ def test_load_complex_file(self):
180180
properties = self.mean_curvature_torsion
181181

182182
streamlines = nib.streamlines.load(complex_filename,
183-
ref=self.voxel_to_world,
183+
ref=self.to_world_space,
184184
lazy_load=False)
185185
assert_true(type(streamlines), Streamlines)
186186
check_streamlines(streamlines, self.nb_streamlines,
187187
self.points, scalars, properties)
188188

189189
# Test lazy_load
190190
streamlines = nib.streamlines.load(complex_filename,
191-
ref=self.voxel_to_world,
191+
ref=self.to_world_space,
192192
lazy_load=True)
193193
assert_true(type(streamlines), LazyStreamlines)
194194
check_streamlines(streamlines, self.nb_streamlines,
@@ -199,7 +199,7 @@ def test_save_simple_file(self):
199199
for ext in nib.streamlines.FORMATS.keys():
200200
with tempfile.NamedTemporaryFile(mode="w+b", suffix=ext) as f:
201201
nib.streamlines.save(streamlines, f.name)
202-
loaded_streamlines = nib.streamlines.load(f, ref=self.voxel_to_world, lazy_load=False)
202+
loaded_streamlines = nib.streamlines.load(f, ref=self.to_world_space, lazy_load=False)
203203
check_streamlines(loaded_streamlines, self.nb_streamlines,
204204
self.points, [], [])
205205

@@ -224,6 +224,6 @@ def test_save_complex_file(self):
224224
if cls.can_save_properties():
225225
properties = self.mean_curvature_torsion
226226

227-
loaded_streamlines = nib.streamlines.load(f, ref=self.voxel_to_world, lazy_load=False)
227+
loaded_streamlines = nib.streamlines.load(f, ref=self.to_world_space, lazy_load=False)
228228
check_streamlines(loaded_streamlines, self.nb_streamlines,
229229
self.points, scalars, properties)

0 commit comments

Comments
 (0)