Skip to content

Commit bac044b

Browse files
committed
Update docstring and addressed @matthew-brett's last comments
1 parent c6117fe commit bac044b

File tree

5 files changed

+115
-52
lines changed

5 files changed

+115
-52
lines changed

nibabel/streamlines/tck.py

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,35 +23,28 @@
2323
MEGABYTE = 1024 * 1024
2424

2525

26-
def create_empty_header():
27-
''' Return an empty compliant TCK header. '''
28-
header = {}
29-
30-
# Default values
31-
header[Field.MAGIC_NUMBER] = TckFile.MAGIC_NUMBER
32-
header[Field.NB_STREAMLINES] = 0
33-
header['datatype'] = "Float32LE"
34-
return header
35-
36-
3726
class TckFile(TractogramFile):
3827
""" Convenience class to encapsulate TCK file format.
3928
4029
Notes
4130
-----
42-
MRtrix (so its file format: TCK) considers the streamline coordinate
43-
(0,0,0) to be in the center of the voxel which is also the case for
44-
NiBabel's streamlines internal representation.
45-
46-
Moreover, streamlines coordinates coming from a TCK file are considered
31+
MRtrix (so its file format: TCK) considers streamlines coordinates
4732
to be in world space (RAS+ and mm space). MRtrix refers to that space
4833
as the "real" or "scanner" space [1]_.
4934
35+
Moreover, when streamlines are mapped back to voxel space [2]_, a
36+
streamline point located at an integer coordinate (i,j,k) is considered
37+
to be at the center of the corresponding voxel. This is in contrast with
38+
TRK's internal convention where it would have referred to a corner.
39+
40+
NiBabel's streamlines internal representation follows the same
41+
convention as MRtrix.
42+
5043
References
5144
----------
5245
[1] http://www.nitrc.org/pipermail/mrtrix-discussion/2014-January/000859.html
46+
[2] http://nipy.org/nibabel/coordinate_systems.html#voxel-coordinates-are-in-voxel-space
5347
"""
54-
5548
# Constants
5649
MAGIC_NUMBER = "mrtrix tracks"
5750
SUPPORTS_DATA_PER_POINT = False # Not yet
@@ -72,12 +65,15 @@ def __init__(self, tractogram, header=None):
7265
7366
Notes
7467
-----
75-
Streamlines of the tractogram are assumed to be in *RAS+*
76-
and *mm* space where coordinate (0,0,0) refers to the center
77-
of the voxel.
68+
Streamlines of the tractogram are assumed to be in *RAS+* and *mm*
69+
space. It is also assumed that when streamlines are mapped back to
70+
voxel space, a streamline point located at an integer coordinate
71+
(i,j,k) is considered to be at the center of the corresponding voxel.
72+
This is in contrast with TRK's internal convention where it would
73+
have referred to a corner.
7874
"""
7975
if header is None:
80-
header = create_empty_header()
76+
header = self.create_empty_header()
8177

8278
super(TckFile, self).__init__(tractogram, header)
8379

@@ -105,6 +101,17 @@ def is_correct_format(cls, fileobj):
105101

106102
return magic_number.strip() == cls.MAGIC_NUMBER
107103

104+
@classmethod
105+
def create_empty_header(cls):
106+
""" Return an empty compliant TCK header. """
107+
header = {}
108+
109+
# Default values
110+
header[Field.MAGIC_NUMBER] = cls.MAGIC_NUMBER
111+
header[Field.NB_STREAMLINES] = 0
112+
header['datatype'] = "Float32LE"
113+
return header
114+
108115
@classmethod
109116
def load(cls, fileobj, lazy_load=False):
110117
""" Loads streamlines from a filename or file-like object.
@@ -128,9 +135,12 @@ def load(cls, fileobj, lazy_load=False):
128135
129136
Notes
130137
-----
131-
Streamlines of the tractogram are assumed to be in *RAS+*
132-
and *mm* space where coordinate (0,0,0) refers to the center
133-
of the voxel.
138+
Streamlines of the tractogram are assumed to be in *RAS+* and *mm*
139+
space. It is also assumed that when streamlines are mapped back to
140+
voxel space, a streamline point located at an integer coordinate
141+
(i,j,k) is considered to be at the center of the corresponding voxel.
142+
This is in contrast with TRK's internal convention where it would
143+
have referred to a corner.
134144
"""
135145
hdr = cls._read_header(fileobj)
136146

@@ -169,7 +179,7 @@ def save(self, fileobj):
169179
"""
170180
# Enforce float32 in little-endian byte order for data.
171181
dtype = np.dtype('<f4')
172-
header = create_empty_header()
182+
header = self.create_empty_header()
173183

174184
# Override hdr's fields by those contained in `header`.
175185
header.update(self.header)
@@ -437,13 +447,13 @@ def _read(cls, fileobj, header, buffer_size=4):
437447
f.seek(start_position, os.SEEK_CUR)
438448

439449
def __str__(self):
440-
''' Gets a formatted string of the header of a TCK file.
450+
""" Gets a formatted string of the header of a TCK file.
441451
442452
Returns
443453
-------
444454
info : string
445455
Header information relevant to the TCK format.
446-
'''
456+
"""
447457
hdr = self.header
448458

449459
info = ""

nibabel/streamlines/tests/test_tractogram_file.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ def is_correct_format(cls, fileobj):
1616
def load(cls, fileobj, lazy_load=True):
1717
return None
1818

19+
@classmethod
20+
def create_empty_header(cls):
21+
return None
22+
1923
assert_raises(TypeError, DummyTractogramFile, Tractogram())
2024

2125
# Missing 'load' method
@@ -27,12 +31,32 @@ def is_correct_format(cls, fileobj):
2731
def save(self, fileobj):
2832
pass
2933

34+
@classmethod
35+
def create_empty_header(cls):
36+
return None
37+
38+
assert_raises(TypeError, DummyTractogramFile, Tractogram())
39+
40+
# Missing 'create_empty_header' method
41+
class DummyTractogramFile(TractogramFile):
42+
@classmethod
43+
def is_correct_format(cls, fileobj):
44+
return False
45+
46+
@classmethod
47+
def load(cls, fileobj, lazy_load=True):
48+
return None
49+
50+
def save(self, fileobj):
51+
pass
52+
3053
assert_raises(TypeError, DummyTractogramFile, Tractogram())
3154

3255

3356
def test_tractogram_file():
3457
assert_raises(NotImplementedError, TractogramFile.is_correct_format, "")
3558
assert_raises(NotImplementedError, TractogramFile.load, "")
59+
assert_raises(NotImplementedError, TractogramFile.create_empty_header)
3660

3761
# Testing calling the 'save' method of `TractogramFile` object.
3862
class DummyTractogramFile(TractogramFile):
@@ -48,6 +72,10 @@ def load(cls, fileobj, lazy_load=True):
4872
def save(self, fileobj):
4973
pass
5074

75+
@classmethod
76+
def create_empty_header(cls):
77+
return None
78+
5179
assert_raises(NotImplementedError,
5280
super(DummyTractogramFile,
5381
DummyTractogramFile(Tractogram)).save, "")

nibabel/streamlines/tractogram.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,14 @@ class Tractogram(object):
261261
262262
Streamlines of a tractogram can be in any coordinate system of your
263263
choice as long as you provide the correct `affine_to_rasmm` matrix, at
264-
construction time, that brings the streamlines back to *RAS+*, *mm* space,
265-
where the coordinates (0,0,0) corresponds to the center of the voxel
266-
(as opposed to the corner of the voxel).
264+
construction time. When applied to streamlines coordinates, that
265+
transformation matrix should bring the streamlines back to world space
266+
(RAS+ and mm space) [1]_.
267+
268+
Moreover, when streamlines are mapped back to voxel space [2]_, a
269+
streamline point located at an integer coordinate (i,j,k) is considered
270+
to be at the center of the corresponding voxel. This is in contrast with
271+
other conventions where it might have referred to a corner.
267272
268273
Attributes
269274
----------
@@ -284,6 +289,11 @@ class Tractogram(object):
284289
ndarrays of shape ($N_t$, $M_i$) where $N_t$ is the number of points
285290
for a particular streamline $t$ and $M_i$ is the number values to store
286291
for that particular piece of information $i$.
292+
293+
References
294+
----------
295+
[1] http://nipy.org/nibabel/coordinate_systems.html#naming-reference-spaces
296+
[2] http://nipy.org/nibabel/coordinate_systems.html#voxel-coordinates-are-in-voxel-space
287297
"""
288298
def __init__(self, streamlines=None,
289299
data_per_streamline=None,
@@ -504,11 +514,16 @@ class LazyTractogram(Tractogram):
504514
streamlines and their data information. This container is thus memory
505515
friendly since it doesn't require having all this data loaded in memory.
506516
507-
Streamlines of a lazy tractogram can be in any coordinate system of your
517+
Streamlines of a tractogram can be in any coordinate system of your
508518
choice as long as you provide the correct `affine_to_rasmm` matrix, at
509-
construction time, that brings the streamlines back to *RAS+*, *mm* space,
510-
where the coordinates (0,0,0) corresponds to the center of the voxel
511-
(as opposed to the corner of the voxel).
519+
construction time. When applied to streamlines coordinates, that
520+
transformation matrix should bring the streamlines back to world space
521+
(RAS+ and mm space) [1]_.
522+
523+
Moreover, when streamlines are mapped back to voxel space [2]_, a
524+
streamline point located at an integer coordinate (i,j,k) is considered
525+
to be at the center of the corresponding voxel. This is in contrast with
526+
other conventions where it might have referred to a corner.
512527
513528
Attributes
514529
----------
@@ -538,6 +553,11 @@ class LazyTractogram(Tractogram):
538553
LazyTractogram objects are suited for operations that can be linearized
539554
such as applying an affine transformation or converting streamlines from
540555
one file format to another.
556+
557+
References
558+
----------
559+
[1] http://nipy.org/nibabel/coordinate_systems.html#naming-reference-spaces
560+
[2] http://nipy.org/nibabel/coordinate_systems.html#voxel-coordinates-are-in-voxel-space
541561
"""
542562
def __init__(self, streamlines=None,
543563
data_per_streamline=None,

nibabel/streamlines/tractogram_file.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ def is_correct_format(cls, fileobj):
7777
"""
7878
raise NotImplementedError()
7979

80+
@abstractclassmethod
81+
def create_empty_header(cls):
82+
""" Returns an empty header for this streamlines file format. """
83+
raise NotImplementedError()
84+
8085
@abstractclassmethod
8186
def load(cls, fileobj, lazy_load=True):
8287
""" Loads streamlines from a filename or file-like object.

nibabel/streamlines/trk.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -199,22 +199,6 @@ def decode_value_from_name(encoded_name):
199199
return name, value
200200

201201

202-
def create_empty_header():
203-
""" Return an empty compliant TRK header. """
204-
header = np.zeros(1, dtype=header_2_dtype)
205-
206-
# Default values
207-
header[Field.MAGIC_NUMBER] = TrkFile.MAGIC_NUMBER
208-
header[Field.VOXEL_SIZES] = np.array((1, 1, 1), dtype="f4")
209-
header[Field.DIMENSIONS] = np.array((1, 1, 1), dtype="h")
210-
header[Field.VOXEL_TO_RASMM] = np.eye(4, dtype="f4")
211-
header[Field.VOXEL_ORDER] = b"RAS"
212-
header['version'] = 2
213-
header['hdr_size'] = TrkFile.HEADER_SIZE
214-
215-
return header
216-
217-
218202
class TrkFile(TractogramFile):
219203
""" Convenience class to encapsulate TRK file format.
220204
@@ -252,7 +236,7 @@ def __init__(self, tractogram, header=None):
252236
of the voxel.
253237
"""
254238
if header is None:
255-
header_rec = create_empty_header()
239+
header_rec = self.create_empty_header()
256240
header = dict(zip(header_rec.dtype.names, header_rec[0]))
257241

258242
super(TrkFile, self).__init__(tractogram, header)
@@ -281,6 +265,22 @@ def is_correct_format(cls, fileobj):
281265
f.seek(-magic_len, os.SEEK_CUR)
282266
return magic_number == cls.MAGIC_NUMBER
283267

268+
@classmethod
269+
def create_empty_header(cls):
270+
""" Return an empty compliant TRK header. """
271+
header = np.zeros(1, dtype=header_2_dtype)
272+
273+
# Default values
274+
header[Field.MAGIC_NUMBER] = cls.MAGIC_NUMBER
275+
header[Field.VOXEL_SIZES] = np.array((1, 1, 1), dtype="f4")
276+
header[Field.DIMENSIONS] = np.array((1, 1, 1), dtype="h")
277+
header[Field.VOXEL_TO_RASMM] = np.eye(4, dtype="f4")
278+
header[Field.VOXEL_ORDER] = b"RAS"
279+
header['version'] = 2
280+
header['hdr_size'] = cls.HEADER_SIZE
281+
282+
return header
283+
284284
@classmethod
285285
def load(cls, fileobj, lazy_load=False):
286286
""" Loads streamlines from a filename or file-like object.
@@ -388,7 +388,7 @@ def save(self, fileobj):
388388
of the TRK header data).
389389
"""
390390
# Enforce little-endian byte order for header
391-
header = create_empty_header().newbyteorder('<')
391+
header = self.create_empty_header().newbyteorder('<')
392392

393393
# Override hdr's fields by those contained in `header`.
394394
for k, v in self.header.items():

0 commit comments

Comments
 (0)