From 3cb28bfa0ca3e5aded085416a75f1f923be06f94 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Tue, 24 Sep 2019 12:29:08 -0400 Subject: [PATCH 1/6] ENH: Add option to smooth to nearest --- .gitignore | 1 + examples/plot_meg_inverse_solution.py | 5 +++-- surfer/tests/test_utils.py | 24 +++++++++++++++++++++--- surfer/utils.py | 24 ++++++++++++++++++++++-- 4 files changed, 47 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 35adb4a..884e2b8 100755 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ .#* *.swp *.orig +*.mov build dist/ diff --git a/examples/plot_meg_inverse_solution.py b/examples/plot_meg_inverse_solution.py index b3cb23e..9dd20a5 100644 --- a/examples/plot_meg_inverse_solution.py +++ b/examples/plot_meg_inverse_solution.py @@ -50,9 +50,10 @@ def time_label(t): # colormap to use colormap = 'hot' - # add data and set the initial time displayed to 100 ms + # add data and set the initial time displayed to 100 ms, + # plotted using the nearest relevant colors brain.add_data(data, colormap=colormap, vertices=vertices, - smoothing_steps=5, time=time, time_label=time_label, + smoothing_steps='nearest', time=time, time_label=time_label, hemi=hemi, initial_time=0.1, verbose=False) # scale colormap diff --git a/surfer/tests/test_utils.py b/surfer/tests/test_utils.py index d9a6d72..a580143 100644 --- a/surfer/tests/test_utils.py +++ b/surfer/tests/test_utils.py @@ -1,6 +1,9 @@ +from distutils.version import LooseVersion import numpy as np +import scipy +from scipy import sparse import matplotlib as mpl -from numpy.testing import assert_array_almost_equal, assert_array_equal +from numpy.testing import assert_allclose, assert_array_equal from surfer import utils @@ -44,12 +47,12 @@ def test_surface(): x = surface.x surface.apply_xfm(xfm) x_ = surface.x - assert_array_almost_equal(x + 2, x_) + assert_allclose(x + 2, x_) # normals nn = _slow_compute_normals(surface.coords, surface.faces[:10000]) nn_fast = utils._compute_normals(surface.coords, surface.faces[:10000]) - assert_array_almost_equal(nn, nn_fast) + assert_allclose(nn, nn_fast) assert 50 < np.linalg.norm(surface.coords, axis=-1).mean() < 100 # mm surface = utils.Surface('fsaverage', 'lh', 'inflated', subjects_dir=subj_dir, units='m') @@ -99,3 +102,18 @@ def test_create_color_lut(): # Test that we can ask for a specific number of colors cmap_out = utils.create_color_lut("Reds", 12) assert cmap_out.shape == (12, 4) + + +def test_smooth(): + """Test smoothing support.""" + adj_mat = sparse.csc_matrix(np.repeat(np.repeat(np.eye(2), 2, 0), 2, 1)) + vertices = np.array([0, 2]) + want = np.repeat(np.eye(2), 2, axis=0) + smooth = utils.smoothing_matrix(vertices, adj_mat).toarray() + assert_allclose(smooth, want) + if LooseVersion(scipy.__version__) < LooseVersion('1.3'): + with pytest.raises(RuntimeError, match='nearest.*requires'): + utils.smoothing_matrix(vertices, adj_mat, 'nearest') + else: + smooth = utils.smoothing_matrix(vertices, adj_mat, 'nearest').toarray() + assert_allclose(smooth, want) diff --git a/surfer/utils.py b/surfer/utils.py index 8e3e22c..aac2530 100644 --- a/surfer/utils.py +++ b/surfer/utils.py @@ -579,10 +579,30 @@ def smoothing_matrix(vertices, adj_mat, smoothing_steps=20, verbose=None): smooth_mat : sparse matrix smoothing matrix with size N x len(vertices) """ - from scipy import sparse + if smoothing_steps == 'nearest': + mat = _nearest(vertices, adj_mat) + else: + mat = _smooth(vertices, adj_mat, smoothing_steps) + assert mat.shape == (adj_mat.shape[0], len(vertices)) + return mat - logger.info("Updating smoothing matrix, be patient..") +def _nearest(vertices, adj_mat): + import scipy + from scipy.sparse.csgraph import dijkstra + if LooseVersion(scipy.__version__) < LooseVersion('1.3'): + raise RuntimeError('smoothing_steps="nearest" requires SciPy >= 1.3') + _, _, col = dijkstra(adj_mat, False, indices=vertices, min_only=True, + return_predecessors=True) + col = np.searchsorted(vertices, col) + row = np.arange(len(col)) + data = np.ones(len(col)) + return sparse.coo_matrix((data, (row, col))) + + +def _smooth(vertices, adj_mat, smoothing_steps): + from scipy import sparse + logger.info("Updating smoothing matrix, be patient..") e = adj_mat.copy() e.data[e.data == 2] = 1 n_vertices = e.shape[0] From 376db4e7bd3d0d3bdaf07d73e61b4acbd879f997 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Tue, 24 Sep 2019 13:15:29 -0400 Subject: [PATCH 2/6] DOC: Update docstring --- surfer/viz.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/surfer/viz.py b/surfer/viz.py index 102482d..c4ed3a8 100644 --- a/surfer/viz.py +++ b/surfer/viz.py @@ -1005,9 +1005,12 @@ def add_data(self, array, min=None, max=None, thresh=None, alpha level to control opacity of the overlay. vertices : numpy array vertices for which the data is defined (needed if len(data) < nvtx) - smoothing_steps : int or None - number of smoothing steps (smoothing is used if len(data) < nvtx) - Default : 20 + smoothing_steps : int | str | None + Number of smoothing steps (if data come from surface subsampling). + Can be None to use the fewest steps that result in all vertices + taking on data values, or "nearest" such that each high resolution + vertex takes the value of the its nearest (on the sphere) + low-resolution vertex. Default is 20. time : numpy array time points in the data array (if data is 2D or 3D) time_label : str | callable | None @@ -2114,13 +2117,17 @@ def data_time_index(self): raise RuntimeError("Brain instance has no data overlay") @verbose - def set_data_smoothing_steps(self, smoothing_steps, verbose=None): + def set_data_smoothing_steps(self, smoothing_steps=20, verbose=None): """Set the number of smoothing steps Parameters ---------- - smoothing_steps : int - Number of smoothing steps + smoothing_steps : int | str | None + Number of smoothing steps (if data come from surface subsampling). + Can be None to use the fewest steps that result in all vertices + taking on data values, or "nearest" such that each high resolution + vertex takes the value of the its nearest (on the sphere) + low-resolution vertex. Default is 20. verbose : bool, str, int, or None If not None, override default verbose level (see surfer.verbose). """ From 1ceda3a77b6296bf859000704e89a9ca62154727 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Tue, 24 Sep 2019 13:17:42 -0400 Subject: [PATCH 3/6] FIX: Flake --- surfer/tests/test_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/surfer/tests/test_utils.py b/surfer/tests/test_utils.py index a580143..f04cd82 100644 --- a/surfer/tests/test_utils.py +++ b/surfer/tests/test_utils.py @@ -2,6 +2,7 @@ import numpy as np import scipy from scipy import sparse +import pytest import matplotlib as mpl from numpy.testing import assert_allclose, assert_array_equal From 78df8e8649c8837c5a7b99bf822097e2daaa02be Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Tue, 24 Sep 2019 13:46:06 -0400 Subject: [PATCH 4/6] FIX: Undordered vertices are the worst --- surfer/utils.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/surfer/utils.py b/surfer/utils.py index aac2530..ed81260 100644 --- a/surfer/utils.py +++ b/surfer/utils.py @@ -583,7 +583,6 @@ def smoothing_matrix(vertices, adj_mat, smoothing_steps=20, verbose=None): mat = _nearest(vertices, adj_mat) else: mat = _smooth(vertices, adj_mat, smoothing_steps) - assert mat.shape == (adj_mat.shape[0], len(vertices)) return mat @@ -592,12 +591,19 @@ def _nearest(vertices, adj_mat): from scipy.sparse.csgraph import dijkstra if LooseVersion(scipy.__version__) < LooseVersion('1.3'): raise RuntimeError('smoothing_steps="nearest" requires SciPy >= 1.3') - _, _, col = dijkstra(adj_mat, False, indices=vertices, min_only=True, - return_predecessors=True) - col = np.searchsorted(vertices, col) + # Vertices can be out of order, so sort them to start ... + order = np.argsort(vertices) + vertices = vertices[order] + _, _, sources = dijkstra(adj_mat, False, indices=vertices, min_only=True, + return_predecessors=True) + col = np.searchsorted(vertices, sources) + # ... then restore the original order. + col = np.argsort(order)[col] row = np.arange(len(col)) data = np.ones(len(col)) - return sparse.coo_matrix((data, (row, col))) + mat = sparse.coo_matrix((data, (row, col))) + assert mat.shape == (adj_mat.shape[0], len(vertices)), mat.shape + return mat def _smooth(vertices, adj_mat, smoothing_steps): From 40412585aafe36d200cd1e9501b0d3ac0b58300f Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Tue, 24 Sep 2019 13:49:45 -0400 Subject: [PATCH 5/6] FIX: Orders are hard --- surfer/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/surfer/utils.py b/surfer/utils.py index ed81260..4a6e4cf 100644 --- a/surfer/utils.py +++ b/surfer/utils.py @@ -597,8 +597,8 @@ def _nearest(vertices, adj_mat): _, _, sources = dijkstra(adj_mat, False, indices=vertices, min_only=True, return_predecessors=True) col = np.searchsorted(vertices, sources) - # ... then restore the original order. - col = np.argsort(order)[col] + # ... then get things back to the correct configuration. + col = order[col] row = np.arange(len(col)) data = np.ones(len(col)) mat = sparse.coo_matrix((data, (row, col))) From 6af4fd44906d2e4543cbd9a93804c71bc97d9c3b Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Tue, 24 Sep 2019 15:27:16 -0400 Subject: [PATCH 6/6] DOC: Changes --- CHANGES | 23 +++++++++++++++++++++-- doc/changes.rst | 1 + doc/index.rst | 1 + 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 doc/changes.rst diff --git a/CHANGES b/CHANGES index dc0902e..fae96a9 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,24 @@ -PySurfer Changes -================ +Changelog +========= + +.. currentmodule:: surfer + +Development version (0.10.dev0) +------------------------------- + +- Added an option to smooth to nearest vertex in :meth:`Brain.add_data` using + ``smoothing_steps='nearest'`` +- Added options for using offscreen mode +- Improved integration with Jupyter notebook +- Avoided view changes when using :meth:`Brain.add_foci` + +Version 0.9 +----------- + +- Fixed transparency issues with colormaps with + :meth:`Brain.scale_data_colormap` +- Added an example of using custom colors +- Added options for choosing units for :class:`Brain` (``m`` or ``mm``) Version 0.8 ----------- diff --git a/doc/changes.rst b/doc/changes.rst new file mode 100644 index 0000000..8a05a51 --- /dev/null +++ b/doc/changes.rst @@ -0,0 +1 @@ +.. include:: ../CHANGES \ No newline at end of file diff --git a/doc/index.rst b/doc/index.rst index 4a95f39..940ffad 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -19,6 +19,7 @@ More Information auto_examples/index.rst documentation/index.rst python_reference.rst + changes.rst Authors -------