Skip to content
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

[MRG] Cortex surface projections #1516

Merged
merged 104 commits into from
Nov 19, 2017
Merged
Show file tree
Hide file tree
Changes from 96 commits
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
b966ac9
add simple sampling of volume on mesh
jeromedockes Sep 28, 2017
07881d0
add 4d
jeromedockes Sep 28, 2017
0f29725
import joblib from sklearn.externals
jeromedockes Sep 28, 2017
0771374
remove tuple unpacking for python2
jeromedockes Sep 28, 2017
e15d04b
add tests for cortex projections
jeromedockes Sep 28, 2017
c0c39d3
mention niimg-like compatibility inv niimg_to_surf docstring
jeromedockes Sep 29, 2017
398965d
reorder imports in test_surf_plotting
jeromedockes Sep 29, 2017
4589a95
titles on vol to surf examples
jeromedockes Sep 29, 2017
c7af4b1
add computing normals at mesh vertices
jeromedockes Oct 10, 2017
9f84b24
add sampling along normal
jeromedockes Oct 11, 2017
0c3e1d4
use scipy for interpolation to allow linear interpolation
jeromedockes Oct 11, 2017
c5d83b5
choose kind and interpolation
jeromedockes Oct 17, 2017
d1d9536
make the direction of mesh normals more explicit
jeromedockes Oct 21, 2017
cdcbed1
remove handling of None affine in non-public functions in surf_plotting
jeromedockes Oct 21, 2017
58bd2f8
only ten points on normal projection by default
jeromedockes Oct 21, 2017
33bb932
improve surf plotting tests
jeromedockes Oct 21, 2017
536cc19
improve ball sampling example
jeromedockes Oct 21, 2017
389851d
change cortical projection illustration example
jeromedockes Nov 9, 2017
07d62bd
Merge branch 'master' into cortex_surface_projections
jeromedockes Nov 9, 2017
9fb10f0
img.affine rather than utils.compat.get_affine(img)
jeromedockes Nov 9, 2017
d836a6f
improve surface projection docstrings
jeromedockes Nov 9, 2017
5704948
update surface plotting docs, examples and whats new
jeromedockes Nov 9, 2017
eef5060
add possibility to give a mask + speedup nearest neighbour sampling
jeromedockes Nov 11, 2017
85624ab
more tests
jeromedockes Nov 11, 2017
6da5a92
improve docstrings
jeromedockes Nov 11, 2017
c234e60
surf_plotting details (mostly docstrings)
jeromedockes Nov 12, 2017
3cf1f35
add tests for sample locations and masking
jeromedockes Nov 12, 2017
8ad1071
whitespace for sphinx gallery
jeromedockes Nov 13, 2017
2e585d5
resample mask to image
jeromedockes Nov 13, 2017
21a7ff0
test projection_matrix
jeromedockes Nov 13, 2017
bfe4c74
more sampling tests
jeromedockes Nov 13, 2017
11e8c17
Merge branch 'master' into cortex_surface_projections
jeromedockes Nov 14, 2017
ecd96c3
3d brain image -> 3d brain volume
jeromedockes Nov 14, 2017
63533ec
rename example plot_3d_map_projection.py -> plot_3d_map_to_surface_pr…
jeromedockes Nov 14, 2017
9d16db7
relative import of nilearn.image.load_img
jeromedockes Nov 14, 2017
c6e34a1
shorter imports in plot_cortex_projection_strategies.py narrative
jeromedockes Nov 14, 2017
c49c5c0
rename plot_cortex_projection_strategies.py -> plot_surface_projectio…
jeromedockes Nov 14, 2017
25ae312
image -> img
jeromedockes Nov 14, 2017
2a20304
mask -> mask_img
jeromedockes Nov 14, 2017
4739d22
niimg -> Niimg
jeromedockes Nov 14, 2017
6ac2386
neighbouring -> neighboring
jeromedockes Nov 14, 2017
1f55f47
===== -> -----
jeromedockes Nov 14, 2017
e8cc677
in doc array -> numpy.ndarray
jeromedockes Nov 14, 2017
55878d6
detail niimg_to_surf_data docstring
jeromedockes Nov 14, 2017
6f309ad
avoid copy when resampling mask
jeromedockes Nov 14, 2017
c50a5c3
use localizer_button_task rather than a brainpedia image for surf pro…
jeromedockes Nov 14, 2017
77dd10f
niimg_to_surf_data -> vol_to_surf
jeromedockes Nov 14, 2017
c5eea1d
fix whats new
jeromedockes Nov 14, 2017
6aa3f12
fall back to nearest neighbours when scipy < 0.14
jeromedockes Nov 14, 2017
fe49498
move surface manipulation functions to nilearn.surface
jeromedockes Nov 14, 2017
244eab2
skip surf proj with interpolation test if scipy < 0.14
jeromedockes Nov 14, 2017
6ba9b53
move technical detail example
jeromedockes Nov 15, 2017
76d4f50
don't load fsaverage in tests if nibabel <= 1.2.0
jeromedockes Nov 15, 2017
1cbd3b3
use scipy for triangulation -> don't import matplotlib out of plotting
jeromedockes Nov 15, 2017
754b834
prettier surface projection example
jeromedockes Nov 15, 2017
7d85051
don't use scipy delaunay in tests if scipy < 0.14
jeromedockes Nov 15, 2017
f1f3836
check fsaverage file
jeromedockes Nov 15, 2017
fa838cf
missing teardown_mock in datasets test_func
jeromedockes Nov 15, 2017
83ca7a4
remove useless print
jeromedockes Nov 15, 2017
40bbfa4
make _uniform_ball_cloud deterministic
jeromedockes Nov 15, 2017
d3d69c2
improve surf projection example description + move imports
jeromedockes Nov 16, 2017
d0982ad
vol_to_surf docstring detail
jeromedockes Nov 16, 2017
2128c77
check_niimg of mask for surf projection
jeromedockes Nov 16, 2017
fcb8954
numpy.ndarray in docstrings
jeromedockes Nov 16, 2017
73c3d25
details about projection method in vol_to_surf docstring
jeromedockes Nov 16, 2017
e0bd9b8
Merge branch 'master' into cortex_surface_projections
jeromedockes Nov 16, 2017
756bd31
make projection_matrix private
jeromedockes Nov 16, 2017
6785cda
add readme to 01_plotting/technical_details
jeromedockes Nov 16, 2017
2c74c84
add precomputed ball positions for n_points in 10, 20, 40, 80, 160
jeromedockes Nov 16, 2017
43bae74
Merge branch 'master' into cortex_surface_projections
jeromedockes Nov 16, 2017
bf904e3
comment about returning NaN for bad vertices
jeromedockes Nov 16, 2017
5e5f264
check sample_locations shape
jeromedockes Nov 16, 2017
59049a8
comments about scipy version checks
jeromedockes Nov 16, 2017
ea3f39e
no need to check for nibabel > 2.0.2
jeromedockes Nov 16, 2017
94cf01c
test vol_to_surf with mask
jeromedockes Nov 16, 2017
b11d0d1
move example back to 01_plotting/
jeromedockes Nov 16, 2017
fca0e78
define EfficiencyWarning if not in sklearn (new in 0.18)
jeromedockes Nov 16, 2017
1f03b77
fix indentation
jeromedockes Nov 16, 2017
cec5afd
import assert_warns from nilearn
jeromedockes Nov 16, 2017
43e4be2
add ball cloud csvs to package data
jeromedockes Nov 16, 2017
91b2c17
surface in its own package
jeromedockes Nov 17, 2017
ed016cc
fix surface test data location in setup
jeromedockes Nov 17, 2017
a70bb83
skip comparison of ball sample locations with cached values if sklear…
jeromedockes Nov 17, 2017
f313e3d
example description detail
jeromedockes Nov 17, 2017
c0d56d6
link to function in example narrative
jeromedockes Nov 17, 2017
82d7670
minor comment
jeromedockes Nov 17, 2017
a00d642
docstring formatting
jeromedockes Nov 17, 2017
e38ecb9
add note example needs matplotlib 1.3.1 to plot_3d_map_to_surface...
jeromedockes Nov 17, 2017
9f294a8
state n_samples defaults in vol_to_surf doc
jeromedockes Nov 17, 2017
e717059
add versionadded to vol_to_surf
jeromedockes Nov 17, 2017
079ddcd
import load_surf_data from nilearn.surface in plot_surf_stat_map ex
jeromedockes Nov 18, 2017
25a09f6
add load_surf_data and load_surf_mesh to doc/index.rst
jeromedockes Nov 18, 2017
9e52d9f
add README.txt to nilearn/surface/data
jeromedockes Nov 18, 2017
1b2aec1
make interpolation='linear' the default in vol_to_surf
jeromedockes Nov 19, 2017
6c2244f
Merge branch 'master' into cortex_surface_projections
jeromedockes Nov 19, 2017
4cca98a
update vol_to_surf docstring to linear is the default
jeromedockes Nov 19, 2017
ed78a66
invert order of interpolation and kind in vol_to_surf signature
jeromedockes Nov 19, 2017
3376c51
improve vol_to_surf docstring
jeromedockes Nov 19, 2017
d1a5779
Merge branch 'master' into cortex_surface_projections
jeromedockes Nov 19, 2017
c8c440d
remove checks for scipy < 0.14 (supported version has been bumped)
jeromedockes Nov 19, 2017
1ad5546
add WARNING experimental to vol_to_surf docstring
jeromedockes Nov 19, 2017
28c26a0
unused import
jeromedockes Nov 19, 2017
256a8b1
use a _kept_indices rather than _masked_indices to avoid ~ operations
jeromedockes Nov 19, 2017
b86bfb9
revert to _masked_indices but manipulate kept internally
jeromedockes Nov 19, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 23 additions & 0 deletions doc/modules/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ uses.

.. _plotting_ref:


:mod:`nilearn.plotting`: Plotting brain data
================================================

Expand Down Expand Up @@ -348,3 +349,25 @@ uses.

clean
high_variance_confounds


:mod:`nilearn.surface`: Manipulating surface data
===================================================

.. automodule:: nilearn.surface
:no-members:
:no-inherited-members:

.. No relevant user manual section yet.

**Functions**:

.. currentmodule:: nilearn.surface

.. autosummary::
:toctree: generated/
:template: function.rst

load_surf_data
load_surf_mesh
vol_to_surf
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add load_surf_data and load_surf_mes here

3 changes: 1 addition & 2 deletions doc/plotting/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,6 @@ NOTE: These functions works for only with matplotlib higher than 1.3.1.
:target: ../auto_examples/01_plotting/plot_surf_stat_map.html
:scale: 50


===================== ===================================================================
===================== ===================================================================
|plot_surf_roi| :func:`plot_surf_roi`
Expand All @@ -362,5 +361,5 @@ NOTE: These functions works for only with matplotlib higher than 1.3.1.
|hack|
**Example:**
:ref:`sphx_glr_auto_examples_01_plotting_plot_surf_stat_map.py`
===================== ===================================================================

===================== ===================================================================
3 changes: 3 additions & 0 deletions doc/whats_new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ Enhancements
:func:`nilearn.plotting.plot_surf_stat_map` and
:func:`nilearn.plotting.plot_surf_roi`

- :func:`nilearn.surface.vol_to_surf` projects a 3d or 4d brain
volume on the cortical surface.

- :class:`nilearn.decoding.SearchLight` has new parameter "groups" to
do LeaveOneGroupOut type cv with new scikit-learn module model selection.

Expand Down
57 changes: 57 additions & 0 deletions examples/01_plotting/plot_3d_map_to_surface_projection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""
Making a surface plot of a 3D statistical map
=============================================

project a 3D statistical map onto a cortical mesh using
:func:`nilearn.surface.vol_to_surf`. Display a surface plot of the projected
map using :func:`nilearn.plotting.plot_surf_stat_map`.

NOTE: Example needs matplotlib version higher than 1.3.1.

"""

##############################################################################
# Get a statistical map
# ---------------------

from nilearn import datasets

localizer_dataset = datasets.fetch_localizer_button_task()
localizer_tmap = localizer_dataset.tmaps[0]

##############################################################################
# Get a cortical mesh
# -------------------

fsaverage = datasets.fetch_surf_fsaverage5()

##############################################################################
# Sample the 3D data around each node of the mesh
# -----------------------------------------------

from nilearn import surface

texture = surface.vol_to_surf(localizer_tmap, fsaverage.pial_right)

##############################################################################
# Plot the result
# ---------------

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be aware that could you please comment saying that plot_surf_stat_map works ?

NOTE: Example needs matplotlib version higher than 1.3.1.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry what should the comment say?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added your NOTE to the docstring

from nilearn import plotting

plotting.plot_surf_stat_map(fsaverage.infl_right, texture, hemi='right',
title='Surface right hemisphere',
threshold=1., bg_map=fsaverage.sulc_right,
cmap='cold_hot')

##############################################################################
# Plot 3D image for comparison
# ----------------------------

plotting.plot_glass_brain(localizer_tmap, display_mode='r', plot_abs=False,
title='Glass brain', threshold=2.)

plotting.plot_stat_map(localizer_tmap, display_mode='x', threshold=1.,
cut_coords=range(0, 51, 10), title='Slices')

plotting.show()
7 changes: 5 additions & 2 deletions examples/01_plotting/plot_surf_stat_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@
# --------------------------------

# Load resting state time series from nilearn
from nilearn import plotting
from nilearn import surface

timeseries = plotting.surf_plotting.load_surf_data(nki_dataset['func_left'][0])
timeseries = surface.load_surf_data(nki_dataset['func_left'][0])

# Extract seed region via label
pcc_region = b'G_cingul-Post-dorsal'
Expand Down Expand Up @@ -115,6 +115,9 @@

###############################################################################
# Display ROI on surface

from nilearn import plotting

plotting.plot_surf_roi(fsaverage['pial_left'], roi_map=pcc_labels,
hemi='left', view='medial',
bg_map=fsaverage['sulc_left'], bg_on_data=True,
Expand Down
69 changes: 69 additions & 0 deletions examples/01_plotting/plot_surface_projection_strategies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""
Technical point: Illustration of the volume to surface sampling schemes
=======================================================================

In nilearn, :func:`nilearn.surface.vol_to_surf` allows us to measure values of
a 3d volume at the nodes of a cortical mesh, transforming it into surface data.
This data can then be plotted with :func:`nilearn.plotting.plot_surf_stat_map`
for example.

This script shows, on a toy example, where samples are drawn around each mesh
vertex. Image values are interpolated at each sample location, then these
samples are averaged to produce a value for the vertex.

Two strategies are available to choose sample locations: they can be spread
along the normal to the mesh, or inside a ball around the vertex. Don't worry
too much about choosing one or the other: they take a similar amount of time
and give almost identical results for most images.

"""

import numpy as np

import matplotlib
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

from nilearn.surface import surface


######################################################################
# Build a mesh (of a cylinder)
######################################################################

N_Z = 5
N_T = 10
u, v = np.mgrid[:N_T, :N_Z]
triangulation = matplotlib.tri.Triangulation(u.flatten(), v.flatten())
angles = u.flatten() * 2 * np.pi / N_T
x, y = np.cos(angles), np.sin(angles)
z = v.flatten() * 2 / N_Z

mesh = [np.asarray([x, y, z]).T, triangulation.triangles]


#########################################################################
# Get the locations from which vol_to_surf would draw its samples
#########################################################################

line_sample_points = surface._line_sample_locations(
mesh, np.eye(4), segment_half_width=.2, n_points=6)

ball_sample_points = surface._ball_sample_locations(
mesh, np.eye(4), ball_radius=.15, n_points=20)


######################################################################
# Plot the mesh and the sample locations
######################################################################

for sample_points in [line_sample_points, ball_sample_points]:
fig = plt.figure()
ax = plt.subplot(projection='3d')
ax.set_aspect(1)

ax.plot_trisurf(x, y, z, triangles=triangulation.triangles)

ax.scatter(*sample_points.T, color='r')

plt.show()
2 changes: 1 addition & 1 deletion nilearn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,4 @@
# list all submodules available in nilearn and version
__all__ = ['datasets', 'decoding', 'decomposition', 'connectome',
'image', 'input_data', 'masking', 'mass_univariate', 'plotting',
'region', 'signal', '__version__']
'region', 'signal', 'surface', '__version__']
2 changes: 1 addition & 1 deletion nilearn/datasets/tests/test_func.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ def test__load_mixed_gambles():
assert_equal(len(zmaps), len(gain))


@with_setup(setup_mock)
@with_setup(setup_mock, teardown_mock)
@with_setup(tst.setup_tmpdata, tst.teardown_tmpdata)
def test_fetch_mixed_gambles():
local_url = "file://" + os.path.join(tst.datadir,
Expand Down
1 change: 0 additions & 1 deletion nilearn/plotting/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ def _set_mpl_backend():
from .img_plotting import plot_img, plot_anat, plot_epi, \
plot_roi, plot_stat_map, plot_glass_brain, plot_connectome, \
plot_prob_atlas, show
from .surf_plotting import plot_surf, plot_surf_stat_map, plot_surf_roi
from .find_cuts import find_xyz_cut_coords, find_cut_slices
from .matrix_plotting import plot_matrix
__all__ = ['cm', 'plot_img', 'plot_anat', 'plot_epi',
Expand Down
131 changes: 2 additions & 129 deletions nilearn/plotting/surf_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,144 +2,17 @@
Functions for surface visualization.
Only matplotlib is required.
"""

# Import libraries
import nibabel
import numpy as np

import matplotlib.pyplot as plt

from mpl_toolkits.mplot3d import Axes3D
from nibabel import gifti

from ..surface import load_surf_data, load_surf_mesh
from .._utils.compat import _basestring
from .img_plotting import _get_colorbar_and_data_ranges


# function to figure out datatype and load data
def load_surf_data(surf_data):
"""Loading data to be represented on a surface mesh.

Parameters
----------
surf_data : str or numpy.ndarray
Either a file containing surface data (valid format are .gii,
.mgz, .nii, .nii.gz, or Freesurfer specific files such as
.thickness, .curv, .sulc, .annot, .label) or
a Numpy array containing surface data.
Returns
-------
data : numpy.ndarray
An array containing surface data
"""
# if the input is a filename, load it
if isinstance(surf_data, _basestring):
if (surf_data.endswith('nii') or surf_data.endswith('nii.gz') or
surf_data.endswith('mgz')):
data = np.squeeze(nibabel.load(surf_data).get_data())
elif (surf_data.endswith('curv') or surf_data.endswith('sulc') or
surf_data.endswith('thickness')):
data = nibabel.freesurfer.io.read_morph_data(surf_data)
elif surf_data.endswith('annot'):
data = nibabel.freesurfer.io.read_annot(surf_data)[0]
elif surf_data.endswith('label'):
data = nibabel.freesurfer.io.read_label(surf_data)
elif surf_data.endswith('gii'):
gii = gifti.read(surf_data)
try:
data = np.zeros((len(gii.darrays[0].data), len(gii.darrays)))
for arr in range(len(gii.darrays)):
data[:, arr] = gii.darrays[arr].data
data = np.squeeze(data)
except IndexError:
raise ValueError('Gifti must contain at least one data array')
else:
raise ValueError(('The input type is not recognized. %r was given '
'while valid inputs are a Numpy array or one of '
'the following file formats: .gii, .mgz, .nii, '
'.nii.gz, Freesurfer specific files such as '
'.curv, .sulc, .thickness, .annot, '
'.label') % surf_data)
# if the input is a numpy array
elif isinstance(surf_data, np.ndarray):
data = np.squeeze(surf_data)
else:
raise ValueError('The input type is not recognized. '
'Valid inputs are a Numpy array or one of the '
'following file formats: .gii, .mgz, .nii, .nii.gz, '
'Freesurfer specific files such as .curv, .sulc, '
'.thickness, .annot, .label')
return data


# function to figure out datatype and load data
def load_surf_mesh(surf_mesh):
"""Loading a surface mesh geometry

Parameters
----------
surf_mesh : str or numpy.ndarray
Either a file containing surface mesh geometry (valid formats
are .gii or Freesurfer specific files such as .orig, .pial,
.sphere, .white, .inflated) or a list of two Numpy arrays,
the first containing the x-y-z coordinates of the mesh
vertices, the second containing the indices (into coords)
of the mesh faces.

Returns
--------
[coords, faces] : List of two numpy.ndarray
The first containing the x-y-z coordinates of the mesh vertices,
the second containing the indices (into coords) of the mesh faces.
"""
# if input is a filename, try to load it
if isinstance(surf_mesh, _basestring):
if (surf_mesh.endswith('orig') or surf_mesh.endswith('pial') or
surf_mesh.endswith('white') or surf_mesh.endswith('sphere') or
surf_mesh.endswith('inflated')):
coords, faces = nibabel.freesurfer.io.read_geometry(surf_mesh)
elif surf_mesh.endswith('gii'):
try:
coords = gifti.read(surf_mesh).getArraysFromIntent(
nibabel.nifti1.intent_codes['NIFTI_INTENT_POINTSET'])[0].data
except IndexError:
raise ValueError('Gifti file needs to contain a data array '
'with intent NIFTI_INTENT_POINTSET')
try:
faces = gifti.read(surf_mesh).getArraysFromIntent(
nibabel.nifti1.intent_codes['NIFTI_INTENT_TRIANGLE'])[0].data
except IndexError:
raise ValueError('Gifti file needs to contain a data array '
'with intent NIFTI_INTENT_TRIANGLE')
else:
raise ValueError(('The input type is not recognized. %r was given '
'while valid inputs are one of the following '
'file formats: .gii, Freesurfer specific files '
'such as .orig, .pial, .sphere, .white, '
'.inflated or a list containing two Numpy '
'arrays [vertex coordinates, face indices]'
) % surf_mesh)
elif isinstance(surf_mesh, list):
if len(surf_mesh) == 2:
coords, faces = surf_mesh[0], surf_mesh[1]
else:
raise ValueError(('If a list is given as input, it must have '
'two elements, the first is a Numpy array '
'containing the x-y-z coordinates of the mesh '
'vertices, the second is a Numpy array '
'containing the indices (into coords) of the '
'mesh faces. The input was a list with '
'%r elements.') % len(surf_mesh))
else:
raise ValueError('The input type is not recognized. '
'Valid inputs are one of the following file '
'formats: .gii, Freesurfer specific files such as '
'.orig, .pial, .sphere, .white, .inflated '
'or a list containing two Numpy arrays '
'[vertex coordinates, face indices]')

return [coords, faces]


def plot_surf(surf_mesh, surf_map=None, bg_map=None,
hemi='left', view='lateral', cmap=None,
avg_method='mean', threshold=None, alpha='auto',
Expand Down