diff --git a/doc/modules/reference.rst b/doc/modules/reference.rst index 41839e63fe..6f43d06135 100644 --- a/doc/modules/reference.rst +++ b/doc/modules/reference.rst @@ -324,6 +324,7 @@ uses. plot_stat_map plot_glass_brain plot_connectome + plot_connectome_strength plot_prob_atlas plot_surf plot_surf_roi diff --git a/doc/plotting/index.rst b/doc/plotting/index.rst index 5ae5b569f6..f0698463fa 100644 --- a/doc/plotting/index.rst +++ b/doc/plotting/index.rst @@ -45,6 +45,10 @@ different heuristics to find cutting coordinates. :target: ../auto_examples/03_connectivity/plot_sphere_based_connectome.html :scale: 50 +.. |plot_strength| image:: ../auto_examples/03_connectivity/images/sphx_glr_plot_sphere_based_connectome_004.png + :target: ../auto_examples/03_connectivity/plot_sphere_based_connectome.html + :scale: 50 + .. |plot_anat| image:: ../auto_examples/01_plotting/images/sphx_glr_plot_demo_plotting_003.png :target: ../auto_examples/01_plotting/plot_demo_plotting.html :scale: 50 @@ -102,6 +106,15 @@ different heuristics to find cutting coordinates. are demonstrated in **Example:** :ref:`sphx_glr_auto_examples_03_connectivity_plot_atlas_comparison.py` +|plot_strength| :func:`plot_connectome_strength` + |hack| + Plotting a connectome strength + + Functions for automatic extraction of coords based on + brain parcellations useful for :func:`plot_connectome` + are demonstrated in + **Example:** :ref:`sphx_glr_auto_examples_03_connectivity_plot_atlas_comparison.py` + |plot_prob_atlas| :func:`plot_prob_atlas` |hack| Plotting 4D probabilistic atlas maps diff --git a/doc/whats_new.rst b/doc/whats_new.rst index c4633b7692..76e11427d9 100644 --- a/doc/whats_new.rst +++ b/doc/whats_new.rst @@ -23,7 +23,9 @@ NEW Yields very fast & accurate models, without creation of giant clusters. :class:`nilearn.regions.ReNA` - +- Plot connectome strength + Use :func:`nilearn.plotting.plot_connectome_strength` to plot the strength of a + connectome on a glass brain. Strength is absolute sum of the edges at a node. - Optimization to image resampling :func:`nilearn.image.resample_img` has been optimized to pad rather than resample images in the special case when there is only a translation @@ -68,6 +70,9 @@ Changes colormap. These arguments were already accepted in `kwargs` but not documented before. +- :func:`nilearn.plotting.view_connectome` now converts NaNs in the adjacency + matrix to 0. + Fixes ----- diff --git a/examples/03_connectivity/plot_sphere_based_connectome.py b/examples/03_connectivity/plot_sphere_based_connectome.py index 4c5bd58440..b3ba1a3593 100644 --- a/examples/03_connectivity/plot_sphere_based_connectome.py +++ b/examples/03_connectivity/plot_sphere_based_connectome.py @@ -24,8 +24,8 @@ covariance** and **partial_correlation**, to recover the functional brain **networks structure**. -We'll start by extracting signals from Default Mode Network regions and computing a -connectome from them. +We'll start by extracting signals from Default Mode Network regions and +computing a connectome from them. """ @@ -37,7 +37,7 @@ # connectivity dataset. from nilearn import datasets -dataset = datasets.fetch_development_fmri(n_subjects=1) +dataset = datasets.fetch_development_fmri(n_subjects=20) # print basic information on the dataset print('First subject functional nifti image (4D) is at: %s' % @@ -49,21 +49,21 @@ # ------------------------------------ dmn_coords = [(0, -52, 18), (-46, -68, 32), (46, -68, 32), (1, 50, -5)] labels = [ - 'Posterior Cingulate Cortex', - 'Left Temporoparietal junction', - 'Right Temporoparietal junction', - 'Medial prefrontal cortex', - ] + 'Posterior Cingulate Cortex', + 'Left Temporoparietal junction', + 'Right Temporoparietal junction', + 'Medial prefrontal cortex', + ] ########################################################################## # Extracts signal from sphere around DMN seeds # ---------------------------------------------- # -# We can compute the mean signal within **spheres** of a fixed radius +# We can compute the mean signal within **spheres** of a fixed radius # around a sequence of (x, y, z) coordinates with the object # :class:`nilearn.input_data.NiftiSpheresMasker`. -# The resulting signal is then prepared by the masker object: Detrended, -# band-pass filtered and **standardized to 1 variance**. +# The resulting signal is then prepared by the masker object: Detrended, +# band-pass filtered and **standardized to 1 variance**. from nilearn import input_data @@ -73,7 +73,7 @@ low_pass=0.1, high_pass=0.01, t_r=2, memory='nilearn_cache', memory_level=1, verbose=2) -# Additionally, we pass confound information so ensure our extracted +# Additionally, we pass confound information to ensure our extracted # signal is cleaned from confounds. func_filename = dataset.func[0] @@ -114,7 +114,8 @@ # Display connectome # ------------------- # -# We display the graph of connections with `:func: nilearn.plotting.plot_connectome`. +# We display the graph of connections with +# `:func: nilearn.plotting.plot_connectome`. from nilearn import plotting @@ -172,7 +173,8 @@ # # # You can retrieve the coordinates for any atlas, including atlases -# not included in nilearn, using :func:`nilearn.plotting.find_parcellation_cut_coords`. +# not included in nilearn, using +# :func:`nilearn.plotting.find_parcellation_cut_coords`. ############################################################################### @@ -231,8 +233,8 @@ ############################################################################### -# Plot matrix and graph -# --------------------- +# Plot matrix, graph, and strength +# -------------------------------- # # We use `:func: nilearn.plotting.plot_matrix` to visualize our correlation matrix # and display the graph of connections with `nilearn.plotting.plot_connectome`. @@ -247,13 +249,41 @@ ############################################################################### -# .. note:: -# -# Note the 1. on the matrix diagonal: These are the signals variances, set to -# 1. by the `spheres_masker`. Hence the covariance of the signal is a +# .. note:: +# +# Note the 1. on the matrix diagonal: These are the signals variances, set +# to 1. by the `spheres_masker`. Hence the covariance of the signal is a # correlation matrix. +############################################################################### +# Sometimes, the information in the correlation matrix is overwhelming and +# aggregating edge strength from the graph would help. Use the function +# `nilearn.plotting.plot_connectome_strength` to visualize this information. + +plotting.plot_connectome_strength( + matrix, coords, title='Connectome strength for Power atlas' +) + +############################################################################### +# From the correlation matrix, we observe that there is a positive and negative +# structure. We could make two different plots by plotting these strengths +# separately. + +from matplotlib.pyplot import cm + +# plot the positive part of of the matrix +plotting.plot_connectome_strength( + np.clip(matrix, 0, matrix.max()), coords, cmap=cm.YlOrRd, + title='Strength of the positive edges of the Power correlation matrix' +) + +# plot the negative part of of the matrix +plotting.plot_connectome_strength( + np.clip(matrix, matrix.min(), 0), coords, cmap=cm.PuBu, + title='Strength of the negative edges of the Power correlation matrix' +) + ############################################################################### # Connectome extracted from Dosenbach's atlas # ------------------------------------------- @@ -283,6 +313,17 @@ plotting.plot_connectome(matrix, coords, title='Dosenbach correlation graph', edge_threshold="99.7%", node_size=20, colorbar=True) +plotting.plot_connectome_strength( + matrix, coords, title='Connectome strength for Power atlas' +) +plotting.plot_connectome_strength( + np.clip(matrix, 0, matrix.max()), coords, cmap=cm.YlOrRd, + title='Strength of the positive edges of the Power correlation matrix' +) +plotting.plot_connectome_strength( + np.clip(matrix, matrix.min(), 0), coords, cmap=cm.PuBu, + title='Strength of the negative edges of the Power correlation matrix' +) ############################################################################### diff --git a/nilearn/datasets/neurovault.py b/nilearn/datasets/neurovault.py index 7843ddcd3e..d541ce16dd 100644 --- a/nilearn/datasets/neurovault.py +++ b/nilearn/datasets/neurovault.py @@ -1651,19 +1651,25 @@ def _update_image(image_info, download_params): """ if not download_params['write_ok']: return image_info - collection = _fetch_collection_for_image( - image_info, download_params) - image_info, collection = _download_image_terms( - image_info, collection, download_params) - metadata_file_path = os.path.join( - os.path.dirname(image_info['absolute_path']), - 'image_{0}_metadata.json'.format(image_info['id'])) - _write_metadata(image_info, metadata_file_path) + try: + collection = _fetch_collection_for_image( + image_info, download_params) + image_info, collection = _download_image_terms( + image_info, collection, download_params) + metadata_file_path = os.path.join( + os.path.dirname(image_info['absolute_path']), + 'image_{0}_metadata.json'.format(image_info['id'])) + _write_metadata(image_info, metadata_file_path) + except OSError: + warnings.warn( + "could not update metadata for image {}, " + "most likely because you do not have write " + "permissions to its metadata file".format(image_info["id"])) return image_info def _update(image_info, collection, download_params): - """Update local metadata for an image and its collection.""" + "Update local metadata for an image and its collection.""" image_info = _update_image(image_info, download_params) return image_info, collection diff --git a/nilearn/plotting/__init__.py b/nilearn/plotting/__init__.py index a47a92d3e2..9fab7632d0 100644 --- a/nilearn/plotting/__init__.py +++ b/nilearn/plotting/__init__.py @@ -37,7 +37,7 @@ def _set_mpl_backend(): from . import cm from .img_plotting import plot_img, plot_anat, plot_epi, \ plot_roi, plot_stat_map, plot_glass_brain, plot_connectome, \ - plot_prob_atlas, show + plot_connectome_strength, plot_prob_atlas, show from .find_cuts import find_xyz_cut_coords, find_cut_slices, \ find_parcellation_cut_coords, find_probabilistic_atlas_cut_coords from .matrix_plotting import plot_matrix @@ -48,7 +48,7 @@ def _set_mpl_backend(): __all__ = ['cm', 'plot_img', 'plot_anat', 'plot_epi', 'plot_roi', 'plot_stat_map', 'plot_glass_brain', - 'plot_connectome', 'plot_prob_atlas', + 'plot_connectome_strength', 'plot_connectome', 'plot_prob_atlas', 'find_xyz_cut_coords', 'find_cut_slices', 'show', 'plot_matrix', 'view_surf', 'view_img_on_surf', 'view_img', 'view_connectome', 'view_markers', diff --git a/nilearn/plotting/html_connectome.py b/nilearn/plotting/html_connectome.py index 66ffe6ba2d..559ef019f2 100644 --- a/nilearn/plotting/html_connectome.py +++ b/nilearn/plotting/html_connectome.py @@ -31,7 +31,7 @@ def _get_connectome(adjacency_matrix, coords, threshold=None, marker_size=None, cmap=cm.cold_hot, symmetric_cmap=True): connectome = {} coords = np.asarray(coords, dtype=' 0) + finally: + os.remove(filename) + plt.close() + + # passing node args + plot_connectome_strength(*args, node_size=10, cmap='RdBu') + plt.close() + plot_connectome_strength(*args, node_size=10, cmap=plt.cm.RdBu) + plt.close() + + # masked array support + masked_adjacency_matrix = np.ma.masked_array( + adjacency_matrix, np.abs(adjacency_matrix) < 0.5 + ) + plot_connectome_strength( + masked_adjacency_matrix, node_coords, **kwargs + ) + plt.close() + + # sparse matrix support + sparse_adjacency_matrix = sparse.coo_matrix(adjacency_matrix) + plot_connectome_strength( + sparse_adjacency_matrix, node_coords, **kwargs + ) + plt.close() + + # NaN matrix support + nan_adjacency_matrix = np.array([[1., np.nan, 0.], + [np.nan, 1., 2.], + [np.nan, 2., 1.]]) + nan_node_coords = np.arange(3 * 3).reshape(3, 3) + plot_connectome_strength(nan_adjacency_matrix, nan_node_coords, **kwargs) + plt.close() + + # smoke-test with hemispheric sagital cuts + plot_connectome_strength(*args, display_mode='lzry') + plt.close() + + +def test_plot_connectome_strength_exceptions(): + node_coords = np.arange(2 * 3).reshape((2, 3)) + + # Used to speed-up tests because the glass brain is always plotted + # before any error occurs + kwargs = {'display_mode': 'x'} + + # adjacency_matrix is not symmetric + non_symmetric_adjacency_matrix = np.array([[1., 2], + [0.4, 1.]]) + assert_raises_regex(ValueError, + 'should be symmetric', + plot_connectome_strength, + non_symmetric_adjacency_matrix, node_coords, + **kwargs) + + adjacency_matrix = np.array([[1., 2.], + [2., 1.]]) + # adjacency_matrix mask is not symmetric + masked_adjacency_matrix = np.ma.masked_array( + adjacency_matrix, [[False, True], [False, False]]) + + assert_raises_regex(ValueError, + 'non symmetric mask', + plot_connectome_strength, + masked_adjacency_matrix, node_coords, + **kwargs) + + # wrong shapes for node_coords or adjacency_matrix + assert_raises_regex(ValueError, + r'supposed to have shape \(n, n\).+\(1L?, 2L?\)', + plot_connectome_strength, adjacency_matrix[:1, :], + node_coords, + **kwargs) + + assert_raises_regex(ValueError, r'shape \(2L?, 3L?\).+\(2L?,\)', + plot_connectome_strength, adjacency_matrix, + node_coords[:, 2], **kwargs) + + wrong_adjacency_matrix = np.zeros((3, 3)) + assert_raises_regex(ValueError, + r'Shape mismatch.+\(3L?, 3L?\).+\(2L?, 3L?\)', + plot_connectome_strength, + wrong_adjacency_matrix, node_coords, **kwargs) + + + + def test_singleton_ax_dim(): for axis, direction in enumerate("xyz"): shape = [5, 6, 7] diff --git a/nilearn/tests/test_cache_mixin.py b/nilearn/tests/test_cache_mixin.py index 7f9b711a68..0e6493c9d7 100644 --- a/nilearn/tests/test_cache_mixin.py +++ b/nilearn/tests/test_cache_mixin.py @@ -1,14 +1,12 @@ """ Test the _utils.cache_mixin module """ -import glob import json import os import shutil import tempfile -from distutils.version import LooseVersion +from pathlib import Path -import sklearn from nose.tools import assert_false, assert_true, assert_equal from nilearn._utils.compat import Memory @@ -17,6 +15,11 @@ from nilearn._utils.testing import assert_raises_regex +def _get_subdirs(top_dir): + top_dir = Path(top_dir) + children = list(top_dir.glob("*")) + return [child for child in children if child.is_dir()] + def f(x): # A simple test function @@ -26,8 +29,7 @@ def f(x): def test_check_memory(): # Test if _check_memory returns a memory object with the cachedir equal to # input path - try: - temp_dir = tempfile.mkdtemp() + with tempfile.TemporaryDirectory() as temp_dir: mem_none = Memory(cachedir=None) mem_temp = Memory(cachedir=temp_dir) @@ -42,17 +44,11 @@ def test_check_memory(): assert_equal(memory.cachedir, mem_temp.cachedir) assert_true(memory, Memory) - finally: - if os.path.exists(temp_dir): - shutil.rmtree(temp_dir) - - def test__safe_cache_dir_creation(): # Test the _safe_cache function that is supposed to flush the # cache if the nibabel version changes - try: - temp_dir = tempfile.mkdtemp() + with tempfile.TemporaryDirectory() as temp_dir: mem = Memory(cachedir=temp_dir) version_file = os.path.join(temp_dir, 'joblib', 'module_versions.json') assert_false(os.path.exists(version_file)) @@ -63,16 +59,12 @@ def test__safe_cache_dir_creation(): os.unlink(version_file) cache_mixin._safe_cache(mem, f) assert_false(os.path.exists(version_file)) - finally: - if os.path.exists(temp_dir): - shutil.rmtree(temp_dir) def test__safe_cache_flush(): # Test the _safe_cache function that is supposed to flush the # cache if the nibabel version changes - try: - temp_dir = tempfile.mkdtemp() + with tempfile.TemporaryDirectory() as temp_dir: mem = Memory(cachedir=temp_dir) version_file = os.path.join(temp_dir, 'joblib', 'module_versions.json') # Create an mock version_file with old module versions @@ -96,25 +88,21 @@ def test__safe_cache_flush(): cache_mixin._safe_cache(mem, f) assert_true(os.path.exists(version_file)) assert_false(os.path.exists(nibabel_dir)) - finally: - pass - # if os.path.exists(temp_dir): - # shutil.rmtree(temp_dir) def test_cache_memory_level(): - temp_dir = tempfile.mkdtemp() - job_glob = os.path.join(temp_dir, 'joblib', 'nilearn', 'tests', - 'test_cache_mixin', 'f', '*') - mem = Memory(cachedir=temp_dir, verbose=0) - cache_mixin.cache(f, mem, func_memory_level=2, memory_level=1)(2) - assert_equal(len(glob.glob(job_glob)), 0) - cache_mixin.cache(f, Memory(cachedir=None))(2) - assert_equal(len(glob.glob(job_glob)), 0) - cache_mixin.cache(f, mem, func_memory_level=2, memory_level=3)(2) - assert_equal(len(glob.glob(job_glob)), 2) - cache_mixin.cache(f, mem)(3) - assert_equal(len(glob.glob(job_glob)), 3) + with tempfile.TemporaryDirectory() as temp_dir: + joblib_dir = Path( + temp_dir, 'joblib', 'nilearn', 'tests', 'test_cache_mixin', 'f') + mem = Memory(cachedir=temp_dir, verbose=0) + cache_mixin.cache(f, mem, func_memory_level=2, memory_level=1)(2) + assert_equal(len(_get_subdirs(joblib_dir)), 0) + cache_mixin.cache(f, Memory(cachedir=None))(2) + assert_equal(len(_get_subdirs(joblib_dir)), 0) + cache_mixin.cache(f, mem, func_memory_level=2, memory_level=3)(2) + assert_equal(len(_get_subdirs(joblib_dir)), 1) + cache_mixin.cache(f, mem)(3) + assert_equal(len(_get_subdirs(joblib_dir)), 2) class CacheMixinTest(CacheMixin): @@ -182,17 +170,13 @@ def test_cache_mixin_wrong_dirs(): def test_cache_shelving(): - try: - temp_dir = tempfile.mkdtemp() - job_glob = os.path.join(temp_dir, 'joblib', 'nilearn', 'tests', - 'test_cache_mixin', 'f', '*') + with tempfile.TemporaryDirectory() as temp_dir: + joblib_dir = Path( + temp_dir, 'joblib', 'nilearn', 'tests', 'test_cache_mixin', 'f') mem = Memory(cachedir=temp_dir, verbose=0) res = cache_mixin.cache(f, mem, shelve=True)(2) assert_equal(res.get(), 2) - assert_equal(len(glob.glob(job_glob)), 1) + assert_equal(len(_get_subdirs(joblib_dir)), 1) res = cache_mixin.cache(f, mem, shelve=True)(2) assert_equal(res.get(), 2) - assert_equal(len(glob.glob(job_glob)), 1) - finally: - del mem - shutil.rmtree(temp_dir, ignore_errors=True) + assert_equal(len(_get_subdirs(joblib_dir)), 1)