Skip to content

Commit 97942fa

Browse files
committed
fix: resolved conflict
2 parents 474d331 + 9959c74 commit 97942fa

33 files changed

+4310
-14
lines changed

Changelog

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ References like "pr/298" refer to github pull request numbers.
3636
are raising a DataError if the track is truncated when ``strict=True``
3737
(the default), rather than a TypeError when trying to create the points
3838
array.
39+
* New API for managing streamlines and their different file formats. This
40+
adds a new module ``nibabel.streamlines`` that will eventually deprecate
41+
the current trackvis reader found in ``nibabel.trackvis``.
3942

4043
* 2.0.2 (Monday 23 November 2015)
4144

@@ -251,7 +254,7 @@ References like "pr/298" refer to github pull request numbers.
251254
the ability to transform to the image with data closest to the cononical
252255
image orientation (first axis left-to-right, second back-to-front, third
253256
down-to-up) (MB, Jonathan Taylor)
254-
* Gifti format read and write support (preliminary) (Stephen Gerhard)
257+
* Gifti format read and write support (preliminary) (Stephen Gerhard)
255258
* Added utilities to use nipy-style data packages, by rip then edit of nipy
256259
data package code (MB)
257260
* Some improvements to release support (Jarrod Millman, MB, Fernando Perez)
@@ -469,7 +472,7 @@ visiting the URL::
469472

470473
* Removed functionality for "NiftiImage.save() raises an IOError
471474
exception when writing the image file fails." (Yaroslav Halchenko)
472-
* Added ability to force a filetype when setting the filename or saving
475+
* Added ability to force a filetype when setting the filename or saving
473476
a file.
474477
* Reverse the order of the 'header' and 'load' argument in the NiftiImage
475478
constructor. 'header' is now first as it seems to be used more often.
@@ -481,7 +484,7 @@ visiting the URL::
481484

482485
* 0.20070301.2 (Thu, 1 Mar 2007)
483486

484-
* Fixed wrong link to the source tarball in README.html.
487+
* Fixed wrong link to the source tarball in README.html.
485488

486489

487490
* 0.20070301.1 (Thu, 1 Mar 2007)

nibabel/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
from .imageclasses import class_map, ext_map, all_image_classes
6565
from . import trackvis
6666
from . import mriutils
67+
from . import streamlines
6768
from . import viewers
6869

6970
# be friendly on systems with ancient numpy -- no tests, but at least
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
""" Benchmarks for load and save of streamlines
2+
3+
Run benchmarks with::
4+
5+
import nibabel as nib
6+
nib.bench()
7+
8+
If you have doctests enabled by default in nose (with a noserc file or
9+
environment variable), and you have a numpy version <= 1.6.1, this will also run
10+
the doctests, let's hope they pass.
11+
12+
Run this benchmark with:
13+
14+
nosetests -s --match '(?:^|[\\b_\\.//-])[Bb]ench' /path/to/bench_streamlines.py
15+
"""
16+
from __future__ import division, print_function
17+
18+
import numpy as np
19+
20+
from nibabel.externals.six.moves import zip
21+
from nibabel.tmpdirs import InTemporaryDirectory
22+
23+
from numpy.testing import assert_array_equal
24+
from nibabel.streamlines import Tractogram
25+
from nibabel.streamlines import TrkFile
26+
27+
import nibabel as nib
28+
import nibabel.trackvis as tv
29+
30+
from numpy.testing import measure
31+
32+
33+
def bench_load_trk():
34+
rng = np.random.RandomState(42)
35+
dtype = 'float32'
36+
NB_STREAMLINES = 5000
37+
NB_POINTS = 1000
38+
points = [rng.rand(NB_POINTS, 3).astype(dtype)
39+
for i in range(NB_STREAMLINES)]
40+
scalars = [rng.rand(NB_POINTS, 10).astype(dtype)
41+
for i in range(NB_STREAMLINES)]
42+
43+
repeat = 10
44+
45+
with InTemporaryDirectory():
46+
trk_file = "tmp.trk"
47+
tractogram = Tractogram(points, affine_to_rasmm=np.eye(4))
48+
TrkFile(tractogram).save(trk_file)
49+
50+
streamlines_old = [d[0] - 0.5
51+
for d in tv.read(trk_file, points_space="rasmm")[0]]
52+
mtime_old = measure('tv.read(trk_file, points_space="rasmm")', repeat)
53+
print("Old: Loaded {:,} streamlines in {:6.2f}".format(NB_STREAMLINES,
54+
mtime_old))
55+
56+
trk = nib.streamlines.load(trk_file, lazy_load=False)
57+
streamlines_new = trk.streamlines
58+
mtime_new = measure('nib.streamlines.load(trk_file, lazy_load=False)',
59+
repeat)
60+
print("\nNew: Loaded {:,} streamlines in {:6.2}".format(NB_STREAMLINES,
61+
mtime_new))
62+
print("Speedup of {:.2f}".format(mtime_old / mtime_new))
63+
for s1, s2 in zip(streamlines_new, streamlines_old):
64+
assert_array_equal(s1, s2)
65+
66+
# Points and scalars
67+
with InTemporaryDirectory():
68+
69+
trk_file = "tmp.trk"
70+
tractogram = Tractogram(points,
71+
data_per_point={'scalars': scalars},
72+
affine_to_rasmm=np.eye(4))
73+
TrkFile(tractogram).save(trk_file)
74+
75+
streamlines_old = [d[0] - 0.5
76+
for d in tv.read(trk_file, points_space="rasmm")[0]]
77+
78+
scalars_old = [d[1]
79+
for d in tv.read(trk_file, points_space="rasmm")[0]]
80+
mtime_old = measure('tv.read(trk_file, points_space="rasmm")', repeat)
81+
msg = "Old: Loaded {:,} streamlines with scalars in {:6.2f}"
82+
print(msg.format(NB_STREAMLINES, mtime_old))
83+
84+
trk = nib.streamlines.load(trk_file, lazy_load=False)
85+
scalars_new = trk.tractogram.data_per_point['scalars']
86+
mtime_new = measure('nib.streamlines.load(trk_file, lazy_load=False)',
87+
repeat)
88+
msg = "New: Loaded {:,} streamlines with scalars in {:6.2f}"
89+
print(msg.format(NB_STREAMLINES, mtime_new))
90+
print("Speedup of {:2f}".format(mtime_old / mtime_new))
91+
for s1, s2 in zip(scalars_new, scalars_old):
92+
assert_array_equal(s1, s2)
93+
94+
if __name__ == '__main__':
95+
bench_load_trk()

nibabel/externals/six.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ class _MovedItems(types.ModuleType):
143143
MovedAttribute("StringIO", "StringIO", "io"),
144144
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
145145
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
146+
MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
146147

147148
MovedModule("builtins", "__builtin__"),
148149
MovedModule("configparser", "ConfigParser"),

nibabel/loadsave.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
# module imports
1010
""" Utilities to load and save image objects """
1111

12+
import os.path as op
1213
import numpy as np
1314
import warnings
1415

@@ -17,6 +18,7 @@
1718
from .filebasedimages import ImageFileError
1819
from .imageclasses import all_image_classes
1920
from .arrayproxy import is_proxy
21+
from .py3k import FileNotFoundError
2022

2123

2224
def load(filename, **kwargs):
@@ -34,6 +36,8 @@ def load(filename, **kwargs):
3436
img : ``SpatialImage``
3537
Image of guessed type
3638
'''
39+
if not op.exists(filename):
40+
raise FileNotFoundError("No such file: '%s'" % filename)
3741
sniff = None
3842
for image_klass in all_image_classes:
3943
is_valid, sniff = image_klass.path_maybe_image(filename, sniff)

nibabel/nifti1.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -835,9 +835,9 @@ def set_data_shape(self, shape):
835835
836836
.. _issue 100: https://github.com/nipy/nibabel/issues/100
837837
.. _issue 309: https://github.com/nipy/nibabel/issues/309
838-
.. _save77: https://code.google.com/p/fieldtrip/source/browse/trunk/external/freesurfer/save_nifti.m?spec=svn8776&r=8776#77
839-
.. _save50: https://code.google.com/p/fieldtrip/source/browse/trunk/external/freesurfer/save_nifti.m?spec=svn8776&r=8776#50
840-
.. _load_nifti.m: https://code.google.com/p/fieldtrip/source/browse/trunk/external/freesurfer/load_nifti.m?spec=svn8776&r=8776#86
838+
.. _save77: https://github.com/fieldtrip/fieldtrip/blob/428798b/external/freesurfer/save_nifti.m#L77-L82
839+
.. _save50: https://github.com/fieldtrip/fieldtrip/blob/428798b/external/freesurfer/save_nifti.m#L50-L56
840+
.. _load_nifti.m: https://github.com/fieldtrip/fieldtrip/blob/428798b/external/freesurfer/load_nifti.m#L86-L89
841841
.. _standard header: http://nifti.nimh.nih.gov/pub/dist/src/niftilib/nifti1.h
842842
'''
843843
hdr = self._structarr

nibabel/py3k.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def open_latin1(filename, mode='r'):
4040
strchar = 'U'
4141
ints2bytes = lambda seq: bytes(seq)
4242
ZEROB = bytes([0])
43+
FileNotFoundError = FileNotFoundError
4344
else:
4445
import StringIO
4546
StringIO = BytesIO = StringIO.StringIO
@@ -62,6 +63,9 @@ def open_latin1(filename, mode='r'):
6263
ints2bytes = lambda seq: ''.join(chr(i) for i in seq)
6364
ZEROB = chr(0)
6465

66+
class FileNotFoundError(IOError):
67+
pass
68+
6569

6670
def getexception():
6771
return sys.exc_info()[1]

nibabel/streamlines/__init__.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import os
2+
import warnings
3+
from ..externals.six import string_types
4+
5+
from .header import Field
6+
from .array_sequence import ArraySequence
7+
from .tractogram import Tractogram, LazyTractogram
8+
from .tractogram_file import ExtensionWarning
9+
10+
from .trk import TrkFile
11+
12+
# List of all supported formats
13+
FORMATS = {".trk": TrkFile}
14+
15+
16+
def is_supported(fileobj):
17+
""" Checks if the file-like object if supported by NiBabel.
18+
19+
Parameters
20+
----------
21+
fileobj : string or file-like object
22+
If string, a filename; otherwise an open file-like object pointing
23+
to a streamlines file (and ready to read from the beginning of the
24+
header)
25+
26+
Returns
27+
-------
28+
is_supported : boolean
29+
"""
30+
return detect_format(fileobj) is not None
31+
32+
33+
def detect_format(fileobj):
34+
""" Returns the StreamlinesFile object guessed from the file-like object.
35+
36+
Parameters
37+
----------
38+
fileobj : string or file-like object
39+
If string, a filename; otherwise an open file-like object pointing
40+
to a tractogram file (and ready to read from the beginning of the
41+
header)
42+
43+
Returns
44+
-------
45+
tractogram_file : :class:`TractogramFile` class
46+
The class type guessed from the content of `fileobj`.
47+
"""
48+
for format in FORMATS.values():
49+
try:
50+
if format.is_correct_format(fileobj):
51+
return format
52+
except IOError:
53+
pass
54+
55+
if isinstance(fileobj, string_types):
56+
_, ext = os.path.splitext(fileobj)
57+
return FORMATS.get(ext.lower())
58+
59+
return None
60+
61+
62+
def load(fileobj, lazy_load=False):
63+
""" Loads streamlines in *RAS+* and *mm* space from a file-like object.
64+
65+
Parameters
66+
----------
67+
fileobj : string or file-like object
68+
If string, a filename; otherwise an open file-like object
69+
pointing to a streamlines file (and ready to read from the beginning
70+
of the streamlines file's header).
71+
lazy_load : {False, True}, optional
72+
If True, load streamlines in a lazy manner i.e. they will not be kept
73+
in memory and only be loaded when needed.
74+
Otherwise, load all streamlines in memory.
75+
76+
Returns
77+
-------
78+
tractogram_file : :class:`TractogramFile` object
79+
Returns an instance of a :class:`TractogramFile` containing data and
80+
metadata of the tractogram loaded from `fileobj`.
81+
82+
Notes
83+
-----
84+
The streamline coordinate (0,0,0) refers to the center of the voxel.
85+
"""
86+
tractogram_file = detect_format(fileobj)
87+
88+
if tractogram_file is None:
89+
raise ValueError("Unknown format for 'fileobj': {}".format(fileobj))
90+
91+
return tractogram_file.load(fileobj, lazy_load=lazy_load)
92+
93+
94+
def save(tractogram, filename, **kwargs):
95+
""" Saves a tractogram to a file.
96+
97+
Parameters
98+
----------
99+
tractogram : :class:`Tractogram` object or :class:`TractogramFile` object
100+
If :class:`Tractogram` object, the file format will be guessed from
101+
`filename` and a :class:`TractogramFile` object will be created using
102+
provided keyword arguments.
103+
If :class:`TractogramFile` object, the file format is known and will
104+
be used to save its content to `filename`.
105+
filename : str
106+
Name of the file where the tractogram will be saved.
107+
\*\*kwargs : keyword arguments
108+
Keyword arguments passed to :class:`TractogramFile` constructor.
109+
Should not be specified if `tractogram` is already an instance of
110+
:class:`TractogramFile`.
111+
"""
112+
tractogram_file_class = detect_format(filename)
113+
if isinstance(tractogram, Tractogram):
114+
if tractogram_file_class is None:
115+
msg = "Unknown tractogram file format: '{}'".format(filename)
116+
raise ValueError(msg)
117+
118+
tractogram_file = tractogram_file_class(tractogram, **kwargs)
119+
120+
else: # Assume it's a TractogramFile object.
121+
tractogram_file = tractogram
122+
if (tractogram_file_class is None or
123+
not isinstance(tractogram_file, tractogram_file_class)):
124+
msg = ("The extension you specified is unusual for the provided"
125+
" 'TractogramFile' object.")
126+
warnings.warn(msg, ExtensionWarning)
127+
128+
if len(kwargs) > 0:
129+
msg = ("A 'TractogramFile' object was provided, no need for"
130+
" keyword arguments.")
131+
raise ValueError(msg)
132+
133+
tractogram_file.save(filename)

0 commit comments

Comments
 (0)