Repository: nilearn/nilearn

Issue:
Bug in _uncompress_file
Hi!
(...still nilearn-sprinting in the train back to Marseille ;) )

I found a bug when trying to download .tar.gz archive files from osf.

I got an error when using _fetch_files:

```
from nilearn.datasets.utils import _fetch_files
opts = {'uncompress': True}
data_dir = '~/nilearn_data'
url = 'https://osf.io/vym9k/download'
_fetch_files(data_dir, [('sphere_right.gii', url, opts)])
```

After digging, I realized the problem is in _uncompress_file: the temporary downloaded file is actually called 'download' on disk (because of the url that's given by osf), and  _uncompress_file does not extract the files if the archive was a .tar.gz one (it does not return an error, and it erases the 'download' file)

Note that:
- if the archive is a zip file, everything works... (as in the fetch_icbm152_2009 function); the temporary file is also called 'download' on disk, but _uncompress_file handles it properly
- if the file on disk is actually called 'whatever.tar.gz' and you run _uncompress_file on it, it also handles it properly

Soooooooooo, to reproduce the problem, the easiest is that:
1. you type https://osf.io/vym9k/download in your browser to get the file
2. you rename the file manually in your unix shell
```
> mv sphere_mesh.tar.gz download
```
3. you try to use _uncompress_file on it
```
from nilearn.datasets.utils import _uncompress_file
_uncompress_file('download')
```
It will: 1. not extract the files in the archive, 2. erase the 'download' file, 3. not return an error.

I hope this is clear enough...

Sylvain




Discussion:

Resolving patch:
diff --git a/examples/03_connectivity/plot_sphere_based_connectome.py b/examples/03_connectivity/plot_sphere_based_connectome.py
--- 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/plotting/__init__.py b/nilearn/plotting/__init__.py
--- 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/img_plotting.py b/nilearn/plotting/img_plotting.py
--- a/nilearn/plotting/img_plotting.py
+++ b/nilearn/plotting/img_plotting.py
@@ -18,6 +18,7 @@
 # delayed, so that the part module can be used without them).
 import numpy as np
 from scipy import ndimage
+from scipy import sparse
 from nibabel.spatialimages import SpatialImage
 
 from .._utils.numpy_conversions import as_ndarray
@@ -1305,3 +1306,138 @@ def plot_connectome(adjacency_matrix, node_coords,
         display = None
 
     return display
+
+
+def plot_connectome_strength(adjacency_matrix, node_coords, node_size="auto",
+                             cmap=None, output_file=None, display_mode="ortho",
+                             figure=None, axes=None, title=None):
+    """Plot connectome strength on top of the brain glass schematics.
+
+    The strength of a connection is define as the sum of absolute values of
+    the edges arriving to a node.
+
+    Parameters
+    ----------
+    adjacency_matrix : numpy array of shape (n, n)
+        represents the link strengths of the graph. Assumed to be
+        a symmetric matrix.
+    node_coords : numpy array_like of shape (n, 3)
+        3d coordinates of the graph nodes in world space.
+    node_size : 'auto' or scalar
+        size(s) of the nodes in points^2. By default the size of the node is
+        inversely propertionnal to the number of nodes.
+    cmap : str or colormap
+        colormap used to represent the strength of a node.
+    output_file : string, or None, optional
+        The name of an image file to export the plot to. Valid extensions
+        are .png, .pdf, .svg. If output_file is not None, the plot
+        is saved to a file, and the display is closed.
+    display_mode : string, optional. Default is 'ortho'.
+        Choose the direction of the cuts: 'x' - sagittal, 'y' - coronal,
+        'z' - axial, 'l' - sagittal left hemisphere only,
+        'r' - sagittal right hemisphere only, 'ortho' - three cuts are
+        performed in orthogonal directions. Possible values are: 'ortho',
+        'x', 'y', 'z', 'xz', 'yx', 'yz', 'l', 'r', 'lr', 'lzr', 'lyr',
+        'lzry', 'lyrz'.
+    figure : integer or matplotlib figure, optional
+        Matplotlib figure used or its number. If None is given, a
+        new figure is created.
+    axes : matplotlib axes or 4 tuple of float: (xmin, ymin, width, height), \
+optional
+        The axes, or the coordinates, in matplotlib figure space,
+        of the axes used to display the plot. If None, the complete
+        figure is used.
+    title : string, optional
+        The title displayed on the figure.
+
+    Notes
+    -----
+    The plotted image should in MNI space for this function to work properly.
+    """
+
+    # input validation
+    if cmap is None:
+        cmap = plt.cm.viridis_r
+    elif isinstance(cmap, str):
+        cmap = plt.get_cmap(cmap)
+    else:
+        cmap = cmap
+
+    node_size = (1 / len(node_coords) * 1e4
+                 if node_size == 'auto' else node_size)
+
+    node_coords = np.asarray(node_coords)
+
+    if sparse.issparse(adjacency_matrix):
+        adjacency_matrix = adjacency_matrix.toarray()
+
+    adjacency_matrix = np.nan_to_num(adjacency_matrix)
+
+    adjacency_matrix_shape = adjacency_matrix.shape
+    if (len(adjacency_matrix_shape) != 2 or
+            adjacency_matrix_shape[0] != adjacency_matrix_shape[1]):
+        raise ValueError(
+            "'adjacency_matrix' is supposed to have shape (n, n)."
+            ' Its shape was {0}'.format(adjacency_matrix_shape))
+
+    node_coords_shape = node_coords.shape
+    if len(node_coords_shape) != 2 or node_coords_shape[1] != 3:
+        message = (
+            "Invalid shape for 'node_coords'. You passed an "
+            "'adjacency_matrix' of shape {0} therefore "
+            "'node_coords' should be a array with shape ({0[0]}, 3) "
+            "while its shape was {1}").format(adjacency_matrix_shape,
+                                              node_coords_shape)
+
+        raise ValueError(message)
+
+    if node_coords_shape[0] != adjacency_matrix_shape[0]:
+        raise ValueError(
+            "Shape mismatch between 'adjacency_matrix' "
+            "and 'node_coords'"
+            "'adjacency_matrix' shape is {0}, 'node_coords' shape is {1}"
+            .format(adjacency_matrix_shape, node_coords_shape))
+
+    if not np.allclose(adjacency_matrix, adjacency_matrix.T, rtol=1e-3):
+        raise ValueError("'adjacency_matrix' should be symmetric")
+
+    # For a masked array, masked values are replaced with zeros
+    if hasattr(adjacency_matrix, 'mask'):
+        if not (adjacency_matrix.mask == adjacency_matrix.mask.T).all():
+            raise ValueError(
+                "'adjacency_matrix' was masked with a non symmetric mask")
+        adjacency_matrix = adjacency_matrix.filled(0)
+
+    # plotting
+    region_strength = np.sum(np.abs(adjacency_matrix), axis=0)
+    region_strength /= np.sum(region_strength)
+
+    region_idx_sorted = np.argsort(region_strength)[::-1]
+    strength_sorted = region_strength[region_idx_sorted]
+    coords_sorted = node_coords[region_idx_sorted]
+
+    display = plot_glass_brain(
+        None, display_mode=display_mode, figure=figure, axes=axes, title=title
+    )
+
+    for coord, region in zip(coords_sorted, strength_sorted):
+        color = list(
+            cmap((region - strength_sorted.min()) / strength_sorted.max())
+        )
+        # reduce alpha for the least strong regions
+        color[-1] = (
+            (region - strength_sorted.min()) *
+            (1 / (strength_sorted.max() - strength_sorted.min()))
+        )
+        # make color to be a 2D array
+        color = [color]
+        display.add_markers(
+            [coord], marker_color=color, marker_size=node_size
+        )
+
+    if output_file is not None:
+        display.savefig(output_file)
+        display.close()
+        display = None
+
+    return display


Test patch:
diff --git a/nilearn/plotting/tests/test_img_plotting.py b/nilearn/plotting/tests/test_img_plotting.py
--- a/nilearn/plotting/tests/test_img_plotting.py
+++ b/nilearn/plotting/tests/test_img_plotting.py
@@ -20,6 +20,7 @@
 from nilearn.plotting.img_plotting import (MNI152TEMPLATE, plot_anat, plot_img,
                                            plot_roi, plot_stat_map, plot_epi,
                                            plot_glass_brain, plot_connectome,
+                                           plot_connectome_strength,
                                            plot_prob_atlas,
                                            _get_colorbar_and_data_ranges)
 
@@ -518,7 +519,8 @@ def test_plot_connectome_exceptions():
                         **kwargs)
 
     wrong_adjacency_matrix = np.zeros((3, 3))
-    assert_raises_regex(ValueError, r'Shape mismatch.+\(3L?, 3L?\).+\(2L?, 3L?\)',
+    assert_raises_regex(ValueError,
+                        r'Shape mismatch.+\(3L?, 3L?\).+\(2L?, 3L?\)',
                         plot_connectome,
                         wrong_adjacency_matrix, node_coords, **kwargs)
 
@@ -548,6 +550,122 @@ def test_plot_connectome_exceptions():
                         **kwargs)
 
 
+def test_connectome_strength():
+    # symmetric up to 1e-3 relative tolerance
+    adjacency_matrix = np.array([[1., -2., 0.3, 0.],
+                                 [-2.002, 1, 0., 0.],
+                                 [0.3, 0., 1., 0.],
+                                 [0., 0., 0., 1.]])
+    node_coords = np.arange(3 * 4).reshape(4, 3)
+
+    args = adjacency_matrix, node_coords
+    kwargs = dict()
+    plot_connectome_strength(*args, **kwargs)
+    plt.close()
+
+    # used to speed-up tests for the net plots
+    kwargs['display_mode'] = 'x'
+
+    # node_coords not an array but a list of tuples
+    plot_connectome_strength(adjacency_matrix,
+                             [tuple(each) for each in node_coords],
+                             **kwargs)
+
+    # saving to file
+    filename = tempfile.mktemp(suffix='.png')
+    try:
+        display = plot_connectome_strength(
+            *args, output_file=filename, **kwargs
+        )
+        assert_true(display is None)
+        assert_true(os.path.isfile(filename) and
+                    os.path.getsize(filename) > 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]