Skip to content

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

Merged
merged 139 commits into from
Jun 7, 2016
Merged
Show file tree
Hide file tree
Changes from 124 commits
Commits
Show all changes
139 commits
Select commit Hold shift + click to select a range
9ee5d66
First draft
MarcCote Feb 21, 2014
9cf22a5
A working prototype of the new streamlines API
MarcCote Jul 19, 2014
0a6599b
Added a LazyStreamlines class and made sure streamlines are in voxel …
MarcCote Mar 4, 2015
c6db239
Fixed bug in TRK count function
MarcCote Mar 4, 2015
b0ab29c
Removed unused function check_integrity
MarcCote Mar 5, 2015
2b7e065
Refactored code and tests
MarcCote Apr 17, 2015
a0f6720
Fixed import of OrderedDict and added support to apply transformation…
MarcCote Apr 17, 2015
db1c182
Added a Streamline class used when indexing/iterating a Streamlines o…
MarcCote Sep 22, 2015
db96c26
Fixed tests to use clear_and_catch_warnings
MarcCote Oct 28, 2015
461d5e4
Added the CompactList data structure to keep points and scalars
MarcCote Oct 29, 2015
7f71534
Old unit tests are all passing
MarcCote Oct 31, 2015
177d01a
Moves some unit tests
MarcCote Oct 31, 2015
ee58ae0
Reduced memory usage.
MarcCote Nov 2, 2015
54a5619
Refactored streamlines->tractogram and points->streamlines
MarcCote Nov 2, 2015
2bd732a
Refactoring StreamingFile
MarcCote Nov 2, 2015
8dd08cc
NF: Tractogram is now more specific and supports multiple keys for sc…
Garyfallidis Nov 2, 2015
c7b7bab
Fixed some unit tests for Tractogram and TractogramItem
MarcCote Nov 2, 2015
bea15be
Updated TractogramFile
Garyfallidis Nov 2, 2015
7399237
minor cleanup
Garyfallidis Nov 2, 2015
0994547
Changed unit tests to reflect modifications made to Tractogram
MarcCote Nov 3, 2015
1872d4f
Refactored LazyTractogram
MarcCote Nov 3, 2015
1a72cbf
Added CompactList to init
MarcCote Nov 8, 2015
a762802
Added get_streamlines method to TractogramFile
MarcCote Nov 8, 2015
91837dd
Added save and load support for compact_list
MarcCote Nov 8, 2015
0b99ae9
DOC: load and save utils functions
MarcCote Nov 8, 2015
cbdc2a3
Refactored streamlines API and added unit tests for LazyTractogram
MarcCote Nov 11, 2015
100cb84
Save scalars and properties name when using the TRK file format
MarcCote Nov 17, 2015
0e84d01
BF: Extend on empty CompactList is now allowed
MarcCote Nov 18, 2015
9ed55b8
BF: Support creating TractogramHeader from dict
MarcCote Nov 18, 2015
d3af0b3
BF: Fixed creating TractogramHeader from another TractogramHeader whe…
MarcCote Nov 18, 2015
aaf2e96
BF: Not all property and scalar name were save in the TRK header
MarcCote Nov 19, 2015
277f211
ENH: CompactList support advance indexing with ndarray of data type bool
MarcCote Nov 19, 2015
b8d47fd
ENH: Tractogram support advance indexing with ndarray of data type bo…
MarcCote Nov 19, 2015
72278b1
Added support for voxel order other than RAS in TRK file format
MarcCote Nov 22, 2015
899f6a6
Fixed LazyTractogram
MarcCote Nov 22, 2015
463ef19
BF: Handle empty LazyTractogram
MarcCote Nov 22, 2015
fe6f601
TRK supports LazyTractogram
MarcCote Nov 22, 2015
be23f76
Fixed some unit tests
MarcCote Nov 23, 2015
7f91755
BF: limit property and scalar names to 18 characters
MarcCote Nov 24, 2015
4d93dde
BF: Only store the nb of values in the property or scalar name if it …
MarcCote Nov 24, 2015
5d51c90
Added a script to generate standard test object.
MarcCote Nov 29, 2015
9670c43
Added the mask used by the standard test object
MarcCote Nov 29, 2015
182f23e
Added support for Python 3
MarcCote Nov 29, 2015
1fa32e1
Revert changes made to __init__.py
MarcCote Nov 29, 2015
d082756
Removed streamlines benchmark for now.
MarcCote Nov 29, 2015
4bc8f04
Python2.6 compatibility fix. Thanks @effigies
MarcCote Nov 29, 2015
c60b348
Added more unit tests to increase coverage.
MarcCote Nov 29, 2015
d4b05f9
Changed function name 'isiterable' to 'check_iteration'
MarcCote Dec 1, 2015
476a93a
Support upper case file extension
MarcCote Dec 1, 2015
cac1f17
Fixed typo
MarcCote Dec 1, 2015
b6591e4
Use isinstance instead of type() whenever possible
MarcCote Dec 1, 2015
f131211
Added the module streamlines to nibabel
MarcCote Dec 2, 2015
49a0462
BF: CompactList.extend with a sliced CompactList was not doing the ri…
MarcCote Dec 2, 2015
2edd67e
Now use custom dictionnaries for data_per_streamline and data_per_point
MarcCote Dec 3, 2015
f99bc32
Removed TractogramHeader class
MarcCote Dec 4, 2015
1ef3f98
Increased test coverage.
MarcCote Dec 4, 2015
d0352ec
Python3 fixes
MarcCote Dec 4, 2015
44c531f
Increased test coverage.
MarcCote Jan 11, 2016
ac1797e
Added streamlines tests folder to the NiBabel packages
MarcCote Jan 11, 2016
688f66c
Made the code numpy1.5 compliant
MarcCote Jan 13, 2016
b16530c
Make code python2.6 compliant
MarcCote Jan 13, 2016
dfb7dec
Use nibabel's InTemporaryDirectory instead of tempfile.NamedTemporary…
MarcCote Jan 13, 2016
be2d6df
Reduced read buffer from 128Mb to 4Mb
MarcCote Jan 14, 2016
62d0681
Increased read buffer from 4Mb to 8Mb
MarcCote Jan 14, 2016
6618700
Decreased read buffer from 8Mb back to 4Mb
MarcCote Jan 14, 2016
565f064
Addressed comments of @samuelstjean and @MrBago. Added a lazy option …
MarcCote Jan 28, 2016
6b8c115
Added property 'affine' to TractogramFile.
MarcCote Jan 29, 2016
b6acfcc
Added check for the vox_to_ras affine.
MarcCote Jan 30, 2016
9d1b5a8
Changed type of CompactList's _offsets and _lengths from list to ndarray
MarcCote Feb 4, 2016
2e2abe7
[ENH] CompactList now support advanced indexing with numpy array of i…
MarcCote Feb 5, 2016
d459e80
In the doc, use :class: for a nicer markup with a link.
MarcCote Feb 9, 2016
a641deb
Fused save and save_tractogram functions.
MarcCote Feb 9, 2016
015ba0f
Fixed DOC
MarcCote Feb 9, 2016
a8b2b6e
Renamed property shape for common_shape
MarcCote Feb 9, 2016
fbef9a5
Moved functions save_compact_list and load_compact_list functions int…
MarcCote Feb 9, 2016
646ac99
Addressed @matthew-brett's comments.
MarcCote Feb 9, 2016
d34c452
RFG: renamed CompactList to ArraySequence
MarcCote Feb 9, 2016
2b95c29
Added test for creating ArraySequence from arrays of different number…
MarcCote Feb 10, 2016
c78d18b
Added validation in nib.streamlines.save
MarcCote Feb 10, 2016
d1f4dbe
Fixed typos + clean code
MarcCote Feb 11, 2016
66fea19
Refactored test_array_sequence following @matthew-brett's suggestions.
MarcCote Feb 11, 2016
6aa70b7
Addressed @matthew-brett's comments.
MarcCote Feb 19, 2016
1e395cc
Refactored DataDict following @matthew-brett's suggestion so Tractogr…
MarcCote Feb 19, 2016
36bff9f
Rebased master
MarcCote Feb 19, 2016
5faf62e
Support numpy 1.5 and make code PEP8 compliant.
MarcCote Feb 20, 2016
7ba65c2
Addressed @matthew-brett's about coroutine and ArraySequence.extend m…
MarcCote Feb 24, 2016
909e0b6
Added Tractogram.to_world and LazyTractogram.to_world methods
MarcCote Feb 24, 2016
111f53a
Clarified Tractogram and LazyTractogram docstring
MarcCote Feb 24, 2016
ebe702c
Removed ref param in nibabel.streamlines.load
MarcCote Feb 29, 2016
a91cb47
RF: refactor trk file object str
matthew-brett Mar 3, 2016
e331486
Rebased and addressed some comments
MarcCote Mar 30, 2016
aadcf57
Fixed affine transformation of LazyTractogram
MarcCote Mar 30, 2016
96a221f
Fixed str method of TrkFile
MarcCote Mar 30, 2016
a6e5026
DOC: small docstring edits for ArraySequence
matthew-brett Apr 4, 2016
fa7a3a2
RF: use self.__class__ for easier subclassing
matthew-brett Apr 4, 2016
7d0f80a
RF: fuse two similar test functions
matthew-brett Apr 4, 2016
c86185b
Use np.allclose to compare the affine_transformation with the identit…
MarcCote Apr 11, 2016
233af1a
WIP: thinking about API for data dict
matthew-brett Apr 4, 2016
1d01c0a
Refactored DataDict following @matthew-brett's advices.
MarcCote Apr 12, 2016
713ef7b
Improved repr of ArraySequence
MarcCote Apr 12, 2016
196761a
Added function to create multiple ArraySequences from a generator.
MarcCote May 22, 2016
3b753dc
Refactored TRK file. Removed unnecessary classmethod in TractogramFile
MarcCote May 22, 2016
40ec4e5
Change how affine_to_rasmm is handled. Can now set affine_to_rasmm to…
MarcCote May 23, 2016
9b08496
Refactored trk.py
MarcCote May 23, 2016
9e2a39c
Be numpy 1.11 compliant
MarcCote May 23, 2016
0b4a375
PEP8
MarcCote May 23, 2016
8348d1e
Addressed @matthew-brett's comments
MarcCote May 24, 2016
c950b71
Merge TrkWriter and TrkReader with TrkFile
MarcCote May 24, 2016
ae62594
Refactored test_trk.py
MarcCote May 26, 2016
17bd66c
Addressed @jchoude's comments.
MarcCote May 28, 2016
be485c6
Improved speed for loading TRK
MarcCote May 31, 2016
d62facc
Fixed typos
MarcCote May 31, 2016
2961bf5
Moved data for tests.
MarcCote Jun 1, 2016
3fc0593
Followed @matthew-brett's suggestion for TRK header hack.
MarcCote Jun 1, 2016
538d4dd
Check we can add new elements to a ArraySequence loaded from disk
MarcCote Jun 1, 2016
075f5e7
Added tests for decode_value function
MarcCote Jun 1, 2016
18d3c32
Supports Python2.6
MarcCote Jun 1, 2016
6d38e8c
Added another test that will be failing until #https://github.com/Mar…
MarcCote Jun 1, 2016
e3b4db5
RF: refactor to used cached append method
matthew-brett Jun 4, 2016
6ff7e64
RF: use new cached append for multi-seq read
matthew-brett Jun 4, 2016
0a1807f
Merge pull request #7 from matthew-brett/cached-arr-seq-append
MarcCote Jun 5, 2016
acfbf39
RF: refactor init + extend for arraysequence
matthew-brett Jun 5, 2016
7fabe9b
Merge pull request #8 from matthew-brett/refactor-aseq-init
MarcCote Jun 6, 2016
a944957
Removed _extend_using_coroutines
MarcCote Jun 6, 2016
649eff3
RF: small streamlines API PR edits
matthew-brett Jun 6, 2016
5fc5dd9
RF: move assert_arr_dict_equal to testing
matthew-brett Jun 6, 2016
bf8b6b5
BF+TST: test and fix header file position reset
matthew-brett Jun 6, 2016
959a1c7
RF: use generic testing function to hdr1=hdr2
matthew-brett Jun 7, 2016
84dd196
RF+TST: add tests for en/decoding value in fields
matthew-brett Jun 7, 2016
a8af94c
DOC: add docstring for function giving affine
matthew-brett Jun 7, 2016
16acde3
DOC: small edits to tractogram_file docstrings
matthew-brett Jun 7, 2016
d910a5e
RF: remove duplicate methods for attribute access
matthew-brett Jun 7, 2016
d231004
DOC: edits to docstrings
matthew-brett Jun 7, 2016
ce9b44a
Merge pull request #9 from matthew-brett/small-streamlines-edits
MarcCote Jun 7, 2016
8952c23
Renamed LazyTractogram.create_from to Lazy_Tractogram.from_data_func
MarcCote Jun 7, 2016
f34b779
RF: simplify constructor for sliceable dicts
matthew-brett Jun 7, 2016
2dd2d4a
DOC: fix hash typo in docstring
matthew-brett Jun 7, 2016
0324bce
Merge pull request #10 from matthew-brett/suggestion-for-per-array-dicts
MarcCote Jun 7, 2016
0a22b93
Renamed nb_elements to total_nb_rows
MarcCote Jun 7, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions Changelog
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ References like "pr/298" refer to github pull request numbers.
are raising a DataError if the track is truncated when ``strict=True``
(the default), rather than a TypeError when trying to create the points
array.
* New API for managing streamlines and their different file formats. This
adds a new module ``nibabel.streamlines`` that will eventually deprecate
the current trackvis reader found in ``nibabel.trackvis``.

* 2.0.2 (Monday 23 November 2015)

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

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

* 0.20070301.2 (Thu, 1 Mar 2007)

* Fixed wrong link to the source tarball in README.html.
* Fixed wrong link to the source tarball in README.html.


* 0.20070301.1 (Thu, 1 Mar 2007)
Expand Down
1 change: 1 addition & 0 deletions nibabel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
from .imageclasses import class_map, ext_map, all_image_classes
from . import trackvis
from . import mriutils
from . import streamlines
from . import viewers

# be friendly on systems with ancient numpy -- no tests, but at least
Expand Down
95 changes: 95 additions & 0 deletions nibabel/benchmarks/bench_streamlines.py
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()
1 change: 1 addition & 0 deletions nibabel/externals/six.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ class _MovedItems(types.ModuleType):
MovedAttribute("StringIO", "StringIO", "io"),
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),

MovedModule("builtins", "__builtin__"),
MovedModule("configparser", "ConfigParser"),
Expand Down
133 changes: 133 additions & 0 deletions nibabel/streamlines/__init__.py
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())
Copy link

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?

Copy link
Contributor Author

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 a TractogramFile object) using nib.streamlines.save since we don't have any other way of guessing the file format.


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)

Copy link
Member

Choose a reason for hiding this comment

The 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"
Copy link

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 TractogramFile object is provided. I added the option of saving a TractogramFile object only for convenience as one could easily have done tractogram_file.save(filename) instead of calling nib.streamlines.save(tractogram_file, filename).

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 tractogram_file.

" keyword arguments.")
raise ValueError(msg)

tractogram_file.save(filename)
Loading