-
Notifications
You must be signed in to change notification settings - Fork 261
Streamlines API #391
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Streamlines API #391
Changes from 124 commits
9ee5d66
9cf22a5
0a6599b
c6db239
b0ab29c
2b7e065
a0f6720
db1c182
db96c26
461d5e4
7f71534
177d01a
ee58ae0
54a5619
2bd732a
8dd08cc
c7b7bab
bea15be
7399237
0994547
1872d4f
1a72cbf
a762802
91837dd
0b99ae9
cbdc2a3
100cb84
0e84d01
9ed55b8
d3af0b3
aaf2e96
277f211
b8d47fd
72278b1
899f6a6
463ef19
fe6f601
be23f76
7f91755
4d93dde
5d51c90
9670c43
182f23e
1fa32e1
d082756
4bc8f04
c60b348
d4b05f9
476a93a
cac1f17
b6591e4
f131211
49a0462
2edd67e
f99bc32
1ef3f98
d0352ec
44c531f
ac1797e
688f66c
b16530c
dfb7dec
be2d6df
62d0681
6618700
565f064
6b8c115
b6acfcc
9d1b5a8
2e2abe7
d459e80
a641deb
015ba0f
a8b2b6e
fbef9a5
646ac99
d34c452
2b95c29
c78d18b
d1f4dbe
66fea19
6aa70b7
1e395cc
36bff9f
5faf62e
7ba65c2
909e0b6
111f53a
ebe702c
a91cb47
e331486
aadcf57
96a221f
a6e5026
fa7a3a2
7d0f80a
c86185b
233af1a
1d01c0a
713ef7b
196761a
3b753dc
40ec4e5
9b08496
9e2a39c
0b4a375
8348d1e
c950b71
ae62594
17bd66c
be485c6
d62facc
2961bf5
3fc0593
538d4dd
075f5e7
18d3c32
6d38e8c
e3b4db5
6ff7e64
0a1807f
acfbf39
7fabe9b
a944957
649eff3
5fc5dd9
bf8b6b5
959a1c7
84dd196
a8af94c
16acde3
d910a5e
d231004
ce9b44a
8952c23
f34b779
2dd2d4a
0324bce
0a22b93
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
""" Benchmarks for load and save of streamlines | ||
|
||
Run benchmarks with:: | ||
|
||
import nibabel as nib | ||
nib.bench() | ||
|
||
If you have doctests enabled by default in nose (with a noserc file or | ||
environment variable), and you have a numpy version <= 1.6.1, this will also run | ||
the doctests, let's hope they pass. | ||
|
||
Run this benchmark with: | ||
|
||
nosetests -s --match '(?:^|[\\b_\\.//-])[Bb]ench' /path/to/bench_streamlines.py | ||
""" | ||
from __future__ import division, print_function | ||
|
||
import numpy as np | ||
|
||
from nibabel.externals.six.moves import zip | ||
from nibabel.tmpdirs import InTemporaryDirectory | ||
|
||
from numpy.testing import assert_array_equal | ||
from nibabel.streamlines import Tractogram | ||
from nibabel.streamlines import TrkFile | ||
|
||
import nibabel as nib | ||
import nibabel.trackvis as tv | ||
|
||
from numpy.testing import measure | ||
|
||
|
||
def bench_load_trk(): | ||
rng = np.random.RandomState(42) | ||
dtype = 'float32' | ||
NB_STREAMLINES = 5000 | ||
NB_POINTS = 1000 | ||
points = [rng.rand(NB_POINTS, 3).astype(dtype) | ||
for i in range(NB_STREAMLINES)] | ||
scalars = [rng.rand(NB_POINTS, 10).astype(dtype) | ||
for i in range(NB_STREAMLINES)] | ||
|
||
repeat = 10 | ||
|
||
with InTemporaryDirectory(): | ||
trk_file = "tmp.trk" | ||
tractogram = Tractogram(points, affine_to_rasmm=np.eye(4)) | ||
TrkFile(tractogram).save(trk_file) | ||
|
||
streamlines_old = [d[0] - 0.5 | ||
for d in tv.read(trk_file, points_space="rasmm")[0]] | ||
mtime_old = measure('tv.read(trk_file, points_space="rasmm")', repeat) | ||
print("Old: Loaded {:,} streamlines in {:6.2f}".format(NB_STREAMLINES, | ||
mtime_old)) | ||
|
||
trk = nib.streamlines.load(trk_file, lazy_load=False) | ||
streamlines_new = trk.streamlines | ||
mtime_new = measure('nib.streamlines.load(trk_file, lazy_load=False)', | ||
repeat) | ||
print("\nNew: Loaded {:,} streamlines in {:6.2}".format(NB_STREAMLINES, | ||
mtime_new)) | ||
print("Speedup of {:.2f}".format(mtime_old / mtime_new)) | ||
for s1, s2 in zip(streamlines_new, streamlines_old): | ||
assert_array_equal(s1, s2) | ||
|
||
# Points and scalars | ||
with InTemporaryDirectory(): | ||
|
||
trk_file = "tmp.trk" | ||
tractogram = Tractogram(points, | ||
data_per_point={'scalars': scalars}, | ||
affine_to_rasmm=np.eye(4)) | ||
TrkFile(tractogram).save(trk_file) | ||
|
||
streamlines_old = [d[0] - 0.5 | ||
for d in tv.read(trk_file, points_space="rasmm")[0]] | ||
|
||
scalars_old = [d[1] | ||
for d in tv.read(trk_file, points_space="rasmm")[0]] | ||
mtime_old = measure('tv.read(trk_file, points_space="rasmm")', repeat) | ||
msg = "Old: Loaded {:,} streamlines with scalars in {:6.2f}" | ||
print(msg.format(NB_STREAMLINES, mtime_old)) | ||
|
||
trk = nib.streamlines.load(trk_file, lazy_load=False) | ||
scalars_new = trk.tractogram.data_per_point['scalars'] | ||
mtime_new = measure('nib.streamlines.load(trk_file, lazy_load=False)', | ||
repeat) | ||
msg = "New: Loaded {:,} streamlines with scalars in {:6.2f}" | ||
print(msg.format(NB_STREAMLINES, mtime_new)) | ||
print("Speedup of {:2f}".format(mtime_old / mtime_new)) | ||
for s1, s2 in zip(scalars_new, scalars_old): | ||
assert_array_equal(s1, s2) | ||
|
||
if __name__ == '__main__': | ||
bench_load_trk() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import os | ||
import warnings | ||
from ..externals.six import string_types | ||
|
||
from .header import Field | ||
from .array_sequence import ArraySequence | ||
from .tractogram import Tractogram, LazyTractogram | ||
from .tractogram_file import ExtensionWarning | ||
|
||
from .trk import TrkFile | ||
|
||
# List of all supported formats | ||
FORMATS = {".trk": TrkFile} | ||
|
||
|
||
def is_supported(fileobj): | ||
""" Checks if the file-like object if supported by NiBabel. | ||
|
||
Parameters | ||
---------- | ||
fileobj : string or file-like object | ||
If string, a filename; otherwise an open file-like object pointing | ||
to a streamlines file (and ready to read from the beginning of the | ||
header) | ||
|
||
Returns | ||
------- | ||
is_supported : boolean | ||
""" | ||
return detect_format(fileobj) is not None | ||
|
||
|
||
def detect_format(fileobj): | ||
""" Returns the StreamlinesFile object guessed from the file-like object. | ||
|
||
Parameters | ||
---------- | ||
fileobj : string or file-like object | ||
If string, a filename; otherwise an open file-like object pointing | ||
to a tractogram file (and ready to read from the beginning of the | ||
header) | ||
|
||
Returns | ||
------- | ||
tractogram_file : :class:`TractogramFile` class | ||
The class type guessed from the content of `fileobj`. | ||
""" | ||
for format in FORMATS.values(): | ||
try: | ||
if format.is_correct_format(fileobj): | ||
return format | ||
except IOError: | ||
pass | ||
|
||
if isinstance(fileobj, string_types): | ||
_, ext = os.path.splitext(fileobj) | ||
return FORMATS.get(ext.lower()) | ||
|
||
return None | ||
|
||
|
||
def load(fileobj, lazy_load=False): | ||
""" Loads streamlines in *RAS+* and *mm* space from a file-like object. | ||
|
||
Parameters | ||
---------- | ||
fileobj : string or file-like object | ||
If string, a filename; otherwise an open file-like object | ||
pointing to a streamlines file (and ready to read from the beginning | ||
of the streamlines file's header). | ||
lazy_load : {False, True}, optional | ||
If True, load streamlines in a lazy manner i.e. they will not be kept | ||
in memory and only be loaded when needed. | ||
Otherwise, load all streamlines in memory. | ||
|
||
Returns | ||
------- | ||
tractogram_file : :class:`TractogramFile` object | ||
Returns an instance of a :class:`TractogramFile` containing data and | ||
metadata of the tractogram loaded from `fileobj`. | ||
|
||
Notes | ||
----- | ||
The streamline coordinate (0,0,0) refers to the center of the voxel. | ||
""" | ||
tractogram_file = detect_format(fileobj) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In general should be sparse with blank lines within code - only to signal start / end of a new section - see https://www.python.org/dev/peps/pep-0008/#blank-lines |
||
if tractogram_file is None: | ||
raise ValueError("Unknown format for 'fileobj': {}".format(fileobj)) | ||
|
||
return tractogram_file.load(fileobj, lazy_load=lazy_load) | ||
|
||
|
||
def save(tractogram, filename, **kwargs): | ||
""" Saves a tractogram to a file. | ||
|
||
Parameters | ||
---------- | ||
tractogram : :class:`Tractogram` object or :class:`TractogramFile` object | ||
If :class:`Tractogram` object, the file format will be guessed from | ||
`filename` and a :class:`TractogramFile` object will be created using | ||
provided keyword arguments. | ||
If :class:`TractogramFile` object, the file format is known and will | ||
be used to save its content to `filename`. | ||
filename : str | ||
Name of the file where the tractogram will be saved. | ||
\*\*kwargs : keyword arguments | ||
Keyword arguments passed to :class:`TractogramFile` constructor. | ||
Should not be specified if `tractogram` is already an instance of | ||
:class:`TractogramFile`. | ||
""" | ||
tractogram_file_class = detect_format(filename) | ||
if isinstance(tractogram, Tractogram): | ||
if tractogram_file_class is None: | ||
msg = "Unknown tractogram file format: '{}'".format(filename) | ||
raise ValueError(msg) | ||
|
||
tractogram_file = tractogram_file_class(tractogram, **kwargs) | ||
|
||
else: # Assume it's a TractogramFile object. | ||
tractogram_file = tractogram | ||
if (tractogram_file_class is None or | ||
not isinstance(tractogram_file, tractogram_file_class)): | ||
msg = ("The extension you specified is unusual for the provided" | ||
" 'TractogramFile' object.") | ||
warnings.warn(msg, ExtensionWarning) | ||
|
||
if len(kwargs) > 0: | ||
msg = ("A 'TractogramFile' object was provided, no need for" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Philosophical question: why do you want to raise here? To make sure that the user doesn't expect that his kwargs will be used for the tractogram_file when they are not used? I guess you want to avoid that a user gives a tractogram_file with some kwargs that might not be in accordance with the pre-created tractogram_file. Just want to make sure. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are correct, it is a way to make sure users are aware that kwargs won't be used if a That said, I guess I could have warn instead of raising an error as I did just above when the extension doesn't correspond to the type of |
||
" keyword arguments.") | ||
raise ValueError(msg) | ||
|
||
tractogram_file.save(filename) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So no validation done here, we just guess from the extension, and then the loading code will fail if the file is still not valid, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What you said is correct. Note also that this part of the function is mainly used when saving a
Tractogram
object (rather than aTractogramFile
object) usingnib.streamlines.save
since we don't have any other way of guessing the file format.