From bff866abec1b949a38abbde6c62a27a618591b7e Mon Sep 17 00:00:00 2001 From: jaeilepp Date: Wed, 8 Jun 2016 11:30:35 +0200 Subject: [PATCH 01/19] Draggable colorbar. --- mne/time_frequency/tests/test_tfr.py | 3 ++ mne/time_frequency/tfr.py | 12 ++++- mne/viz/topo.py | 14 ++++- mne/viz/utils.py | 76 ++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 4 deletions(-) diff --git a/mne/time_frequency/tests/test_tfr.py b/mne/time_frequency/tests/test_tfr.py index 532ebb7fdbc..8d469c187e4 100644 --- a/mne/time_frequency/tests/test_tfr.py +++ b/mne/time_frequency/tests/test_tfr.py @@ -375,6 +375,9 @@ def test_plot(): tfr.plot_topo(picks=[1, 2]) plt.close('all') + tfr.plot(picks=[1, 2], cmap='interactive') + plt.close('all') + def test_add_channels(): """Test tfr splitting / re-appending channel types diff --git a/mne/time_frequency/tfr.py b/mne/time_frequency/tfr.py index 6e6f35746cb..321d2c7290f 100644 --- a/mne/time_frequency/tfr.py +++ b/mne/time_frequency/tfr.py @@ -736,7 +736,8 @@ def plot(self, picks=None, baseline=None, mode='mean', tmin=None, colorbar=colorbar, picker=False, cmap=cmap) if title: fig.suptitle(title) - colorbar = False # only one colorbar for multiple axes + if cmap != 'interactive': + colorbar = False # only one colorbar for multiple axes plt_show(show) return fig @@ -766,7 +767,14 @@ def _onselect(self, eclick, erelease, baseline, mode, layout): if 'mag' in self: types.append('mag') if 'grad' in self: - types.append('grad') + chs = [ch for ch in self.ch_names if ch.startswith('MEG') and + ch.endswith(('2', '3'))] + if len(chs) < 2: + warn('No grad pairs found.') + if len(types) == 0: + return # Don't draw a figure for nothing. + else: + types.append('grad') fig = figure_nobar() fig.suptitle('{:.2f} s - {:.2f} s, {:.2f} Hz - {:.2f} Hz'.format(tmin, tmax, diff --git a/mne/viz/topo.py b/mne/viz/topo.py index 62b45c0c055..bc57b64c807 100644 --- a/mne/viz/topo.py +++ b/mne/viz/topo.py @@ -21,7 +21,8 @@ from ..channels.layout import _merge_grad_data, _pair_grad_sensors, find_layout from ..defaults import _handle_default from .utils import (_check_delayed_ssp, COLORS, _draw_proj_checkbox, - add_background_image, plt_show, _setup_vmin_vmax) + add_background_image, plt_show, _setup_vmin_vmax, + DraggableColorbar) def iter_topography(info, layout=None, on_pick=None, fig=None, @@ -258,6 +259,11 @@ def _imshow_tfr(ax, ch_idx, tmin, tmax, vmin, vmax, onselect, ylim=None, from matplotlib.widgets import RectangleSelector extent = (tmin, tmax, freq[0], freq[-1]) + if cmap == 'interactive': + cmap = 'RdBu_r' + interactive_cmap = True + else: + interactive_cmap = False img = ax.imshow(tfr[ch_idx], extent=extent, aspect="auto", origin="lower", vmin=vmin, vmax=vmax, picker=picker, cmap=cmap) if isinstance(ax, plt.Axes): @@ -271,7 +277,11 @@ def _imshow_tfr(ax, ch_idx, tmin, tmax, vmin, vmax, onselect, ylim=None, if y_label is not None: plt.ylabel(y_label) if colorbar: - plt.colorbar(mappable=img) + cbar = plt.colorbar(mappable=img) + if interactive_cmap: + cbar = DraggableColorbar(cbar, img) + cbar.connect() + ax.colorbar = cbar # For keeping reference if title: plt.title(title) if not isinstance(ax, plt.Axes): diff --git a/mne/viz/utils.py b/mne/viz/utils.py index 978a48ce138..da38ad4d5ea 100644 --- a/mne/viz/utils.py +++ b/mne/viz/utils.py @@ -1323,3 +1323,79 @@ def _compute_scalings(scalings, inst): scale_factor = np.max(np.abs(scale_factor)) scalings[key] = scale_factor return scalings + + +class DraggableColorbar(object): + """Class for enabling interactive colorbar. + See http://www.ster.kuleuven.be/~pieterd/python/html/plotting/interactive_colorbar.html # doctest: +SKIP + """ + def __init__(self, cbar, mappable): + import matplotlib.pyplot as plt + self.cbar = cbar + self.mappable = mappable + self.press = None + self.cycle = sorted([i for i in dir(plt.cm) if + hasattr(getattr(plt.cm, i), 'N')]) + self.index = self.cycle.index(cbar.get_cmap().name) + + def connect(self): + """Connect to all the events we need.""" + self.cidpress = self.cbar.patch.figure.canvas.mpl_connect( + 'button_press_event', self.on_press) + self.cidrelease = self.cbar.patch.figure.canvas.mpl_connect( + 'button_release_event', self.on_release) + self.cidmotion = self.cbar.patch.figure.canvas.mpl_connect( + 'motion_notify_event', self.on_motion) + self.keypress = self.cbar.patch.figure.canvas.mpl_connect( + 'key_press_event', self.key_press) + + def on_press(self, event): + """Callback for button press.""" + if event.inaxes != self.cbar.ax: + return + self.press = event.y + + def key_press(self, event): + """Callback for key press.""" + if event.key == 'down': + self.index += 1 + elif event.key == 'up': + self.index -= 1 + else: + return + if self.index < 0: + self.index = len(self.cycle) + elif self.index >= len(self.cycle): + self.index = 0 + cmap = self.cycle[self.index] + self.cbar.set_cmap(cmap) + self.cbar.draw_all() + self.mappable.set_cmap(cmap) + self.cbar.patch.figure.canvas.draw() + + def on_motion(self, event): + """Callback for mouse movements.""" + if self.press is None: + return + if event.inaxes != self.cbar.ax: + return + yprev = self.press + dy = event.y - yprev + self.press = event.y + scale = self.cbar.norm.vmax - self.cbar.norm.vmin + perc = 0.03 + if event.button == 1: + self.cbar.norm.vmin -= (perc * scale) * np.sign(dy) + self.cbar.norm.vmax -= (perc * scale) * np.sign(dy) + elif event.button == 3: + self.cbar.norm.vmin -= (perc * scale) * np.sign(dy) + self.cbar.norm.vmax += (perc * scale) * np.sign(dy) + self.cbar.draw_all() + self.mappable.set_norm(self.cbar.norm) + self.cbar.patch.figure.canvas.draw() + + def on_release(self, event): + """Callback for release.""" + self.press = None + self.mappable.set_norm(self.cbar.norm) + self.cbar.patch.figure.canvas.draw() From 7c32d1c42b3c55a331ffaf7b7d9ab79604a9b59b Mon Sep 17 00:00:00 2001 From: jaeilepp Date: Wed, 8 Jun 2016 14:07:38 +0200 Subject: [PATCH 02/19] Interactive cmap for topomap. Fixes. Docs. --- mne/evoked.py | 9 +++++++-- mne/time_frequency/tfr.py | 28 ++++++++++++++++------------ mne/viz/evoked.py | 16 +++++++++++++--- mne/viz/topomap.py | 18 +++++++++++++++++- mne/viz/utils.py | 6 +++--- 5 files changed, 56 insertions(+), 21 deletions(-) diff --git a/mne/evoked.py b/mne/evoked.py index af0b0cafa47..58a1330ae13 100644 --- a/mne/evoked.py +++ b/mne/evoked.py @@ -490,8 +490,13 @@ def plot_topomap(self, times="auto", ch_type=None, layout=None, vmin=None, but vmax is not, defaults to np.max(data). If callable, the output equals vmax(data). cmap : matplotlib colormap | None - Colormap to use. If None, 'Reds' is used for all positive data, - otherwise defaults to 'RdBu_r'. + Colormap to use. If 'interactive', the colors are adjustable by + clicking and dragging the colorbar with left and right mouse + button. Left mouse button moves the scale up and down and right + mouse button adjusts the range. Up and down arrows can be used to + change the colormap. Interactive mode works smoothly only for a + small amount of topomaps. If None (default), 'Reds' is used for all + positive data, otherwise defaults to 'RdBu_r'. sensors : bool | str Add markers for sensor locations to the plot. Accepts matplotlib plot format string (e.g., 'r+' for red plusses). If True, a circle diff --git a/mne/time_frequency/tfr.py b/mne/time_frequency/tfr.py index 321d2c7290f..3aeba9b06bd 100644 --- a/mne/time_frequency/tfr.py +++ b/mne/time_frequency/tfr.py @@ -22,6 +22,7 @@ from ..channels.channels import ContainsMixin, UpdateChannelsMixin from ..io.pick import pick_info, pick_types from ..io.meas_info import Info +from ..io.constants import FIFF from .multitaper import dpss_windows from ..viz.utils import figure_nobar, plt_show from ..externals.h5io import write_hdf5, read_hdf5 @@ -673,7 +674,11 @@ def plot(self, picks=None, baseline=None, mode='mean', tmin=None, The maxinum value an the color scale. If vmax is None, the data maximum value is used. cmap : matplotlib colormap | str - The colormap to use. Defaults to 'RdBu_r'. + The colormap to use. If 'interactive', the colors are adjustable by + clicking and dragging the colorbar with left and right mouse + button. Left mouse button moves the scale up and down and right + mouse button adjusts the range. Up and down arrows can be used to + change the colormap. Defaults to 'RdBu_r'. dB : bool If True, 20*log10 is applied to the data to get dB. colorbar : bool @@ -767,20 +772,19 @@ def _onselect(self, eclick, erelease, baseline, mode, layout): if 'mag' in self: types.append('mag') if 'grad' in self: - chs = [ch for ch in self.ch_names if ch.startswith('MEG') and - ch.endswith(('2', '3'))] - if len(chs) < 2: - warn('No grad pairs found.') - if len(types) == 0: - return # Don't draw a figure for nothing. + if (FIFF.FIFFV_COIL_VV_PLANAR_T1 in + np.unique([ch['coil_type'] for ch in self.info['chs']])): + chs = [ch for ch in self.ch_names if ch.startswith('MEG') and + ch.endswith(('2', '3'))] + if len(chs) < 2: + warn('No grad pairs found.') + if len(types) == 0: + return # Don't draw a figure for nothing. else: types.append('grad') fig = figure_nobar() - fig.suptitle('{:.2f} s - {:.2f} s, {:.2f} Hz - {:.2f} Hz'.format(tmin, - tmax, - fmin, - fmax), - y=0.04) + fig.suptitle('{:.2f} s - {:.2f} s, {:.2f} Hz - {:.2f} Hz'.format( + tmin, tmax, fmin, fmax), y=0.04) for idx, ch_type in enumerate(types): ax = plt.subplot(1, len(types), idx + 1) plot_tfr_topomap(self, ch_type=ch_type, tmin=tmin, tmax=tmax, diff --git a/mne/viz/evoked.py b/mne/viz/evoked.py index a6f71a52356..c8d18256b89 100644 --- a/mne/viz/evoked.py +++ b/mne/viz/evoked.py @@ -22,6 +22,7 @@ from ..utils import logger, _clean_names, warn from ..fixes import partial from ..io.pick import pick_info +from ..io.constants import FIFF from .topo import _plot_evoked_topo from .topomap import (_prepare_topo_plot, plot_topomap, _check_outlines, _draw_outlines, _prepare_topomap, _topomap_animation) @@ -71,6 +72,16 @@ def _butterfly_onselect(xmin, xmax, ch_types, evoked, text=None): """Function for drawing topomaps from the selected area.""" import matplotlib.pyplot as plt ch_types = [type for type in ch_types if type in ('eeg', 'grad', 'mag')] + if ('grad' in ch_types and FIFF.FIFFV_COIL_VV_PLANAR_T1 in np.unique( + [ch['coil_type'] for ch in evoked.info['chs']])): + chs = [ch for ch in evoked.info['ch_names'] if + ch.startswith('MEG') and ch.endswith(('2', '3'))] + if len(chs) < 2: + warn('No grad pairs found.') + ch_types.remove('grad') + if len(ch_types) == 0: + return + vert_lines = list() if text is not None: text.set_visible(True) @@ -91,9 +102,8 @@ def _butterfly_onselect(xmin, xmax, ch_types, evoked, text=None): fig, axarr = plt.subplots(1, len(ch_types), squeeze=False, figsize=(3 * len(ch_types), 3)) for idx, ch_type in enumerate(ch_types): - picks, pos, merge_grads, _, ch_type = _prepare_topo_plot(evoked, - ch_type, - layout=None) + picks, pos, merge_grads, _, ch_type = _prepare_topo_plot( + evoked, ch_type, layout=None) data = evoked.data[picks, minidx:maxidx] if merge_grads: from ..channels.layout import _merge_grad_data diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py index 0bde35d8159..615b9a9f2c4 100644 --- a/mne/viz/topomap.py +++ b/mne/viz/topomap.py @@ -1120,7 +1120,12 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None, If None, the maximum absolute value is used. If callable, the output equals vmax(data). Defaults to None. cmap : matplotlib colormap | None - Colormap to use. If None, 'Reds' is used for all positive data, + Colormap to use. If 'interactive', the colors are adjustable by + clicking and dragging the colorbar with left and right mouse button. + Left mouse button moves the scale up and down and right mouse button + adjusts the range. Up and down arrows can be used to change the + colormap. Interactive mode works smoothly only for a small amount of + topomaps. If None (default), 'Reds' is used for all positive data, otherwise defaults to 'RdBu_r'. sensors : bool | str Add markers for sensor locations to the plot. Accepts matplotlib plot @@ -1317,6 +1322,11 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None, for i in range(len(times))] vmin = np.min(vlims) vmax = np.max(vlims) + if cmap == 'interactive': + cmap = None + interactive_cmap = True + else: + interactive_cmap = False for idx, time in enumerate(times): tp, cn = plot_topomap(data[:, idx], pos, vmin=vmin, vmax=vmax, sensors=sensors, res=res, names=names, @@ -1352,6 +1362,12 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None, cax.set_title(unit) cbar = fig.colorbar(images[-1], ax=cax, cax=cax, format=cbar_fmt) cbar.set_ticks([cbar.vmin, 0, cbar.vmax]) + if interactive_cmap: + from .utils import DraggableColorbar + for im in images: + cb = DraggableColorbar(cbar, im) + cb.connect() + im.colorbar = cb # For keeping reference if proj == 'interactive': _check_delayed_ssp(evoked) diff --git a/mne/viz/utils.py b/mne/viz/utils.py index da38ad4d5ea..ac717eaf4f2 100644 --- a/mne/viz/utils.py +++ b/mne/viz/utils.py @@ -1327,8 +1327,8 @@ def _compute_scalings(scalings, inst): class DraggableColorbar(object): """Class for enabling interactive colorbar. - See http://www.ster.kuleuven.be/~pieterd/python/html/plotting/interactive_colorbar.html # doctest: +SKIP - """ + See http://www.ster.kuleuven.be/~pieterd/python/html/plotting/interactive_colorbar.html + """ # noqa def __init__(self, cbar, mappable): import matplotlib.pyplot as plt self.cbar = cbar @@ -1364,7 +1364,7 @@ def key_press(self, event): else: return if self.index < 0: - self.index = len(self.cycle) + self.index = len(self.cycle) - 1 elif self.index >= len(self.cycle): self.index = 0 cmap = self.cycle[self.index] From 4e74965b5f8c9c453e79e6fbd34d4f3f07136e2c Mon Sep 17 00:00:00 2001 From: jaeilepp Date: Wed, 8 Jun 2016 15:41:57 +0200 Subject: [PATCH 03/19] Tests. Docs. Fixes. --- mne/time_frequency/tests/test_tfr.py | 20 +++++++++++++++++++- mne/time_frequency/tfr.py | 8 +++++--- mne/viz/tests/test_topomap.py | 19 +++++++++++++++++++ mne/viz/topo.py | 11 +++++++---- mne/viz/topomap.py | 2 +- 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/mne/time_frequency/tests/test_tfr.py b/mne/time_frequency/tests/test_tfr.py index 8d469c187e4..e5b34962651 100644 --- a/mne/time_frequency/tests/test_tfr.py +++ b/mne/time_frequency/tests/test_tfr.py @@ -12,6 +12,7 @@ _dpss_wavelet, tfr_multitaper, AverageTFR, read_tfrs, write_tfrs, combine_tfr, cwt) +from mne.viz.utils import _fake_click import matplotlib matplotlib.use('Agg') # for testing don't use X server @@ -353,6 +354,7 @@ def test_io(): def test_plot(): """Test TFR plotting.""" import matplotlib.pyplot as plt + from matplotlib import backend_bases data = np.zeros((3, 2, 3)) times = np.array([.1, .2, .3]) @@ -375,7 +377,23 @@ def test_plot(): tfr.plot_topo(picks=[1, 2]) plt.close('all') - tfr.plot(picks=[1, 2], cmap='interactive') + fig = tfr.plot(picks=[1, 2], cmap='interactive') # test interactive cmap + fig.canvas.key_press_event('up') + fig.canvas.key_press_event('down') + + cbar = fig.get_axes()[0].CB # Fake dragging with mouse. + event = backend_bases.MouseEvent('button_press_event', fig.canvas, 0.1, + 0.1, button=1) + event.inaxes = fig.get_axes()[1] + cbar.on_press(event) + event.y = 0.2 + cbar.on_motion(event) + cbar.on_release(event) + event.button = 3 + cbar.on_press(event) + event.y = 0.3 + cbar.on_motion(event) + cbar.on_release(event) plt.close('all') diff --git a/mne/time_frequency/tfr.py b/mne/time_frequency/tfr.py index 3aeba9b06bd..4558a901792 100644 --- a/mne/time_frequency/tfr.py +++ b/mne/time_frequency/tfr.py @@ -678,7 +678,8 @@ def plot(self, picks=None, baseline=None, mode='mean', tmin=None, clicking and dragging the colorbar with left and right mouse button. Left mouse button moves the scale up and down and right mouse button adjusts the range. Up and down arrows can be used to - change the colormap. Defaults to 'RdBu_r'. + change the colormap. Interactive mode works smoothly only for a + small amount of images. Defaults to 'RdBu_r'. dB : bool If True, 20*log10 is applied to the data to get dB. colorbar : bool @@ -741,8 +742,9 @@ def plot(self, picks=None, baseline=None, mode='mean', tmin=None, colorbar=colorbar, picker=False, cmap=cmap) if title: fig.suptitle(title) - if cmap != 'interactive': - colorbar = False # only one colorbar for multiple axes + # Only draw 1 cbar. For interactive mode we pass the ref to cbar. + colorbar = ax.CB if cmap == 'interactive' else False + plt_show(show) return fig diff --git a/mne/viz/tests/test_topomap.py b/mne/viz/tests/test_topomap.py index b86ec9e3d67..3ab6757e6f8 100644 --- a/mne/viz/tests/test_topomap.py +++ b/mne/viz/tests/test_topomap.py @@ -56,6 +56,7 @@ def test_plot_topomap(): """ import matplotlib.pyplot as plt from matplotlib.patches import Circle + from matplotlib import backend_bases # evoked warnings.simplefilter('always') res = 16 @@ -197,6 +198,24 @@ def get_texts(p): evoked.plot_topomap(times, ch_type='eeg', outlines=outlines) plt.close('all') + # Test interactive cmap + fig = plot_evoked_topomap(evoked, times=[0., 0.1], ch_type='eeg', + cmap='interactive') + cbar = fig.get_axes()[0].CB # Fake dragging with mouse. + event = backend_bases.MouseEvent('button_press_event', fig.canvas, 0.1, + 0.1, button=1) + event.inaxes = fig.get_axes()[-1] + cbar.on_press(event) + event.y = 0.2 + cbar.on_motion(event) + cbar.on_release(event) + event.button = 3 + cbar.on_press(event) + event.y = 0.3 + cbar.on_motion(event) + cbar.on_release(event) + plt.close('all') + # Pass custom outlines with patch callable def patch(): return Circle((0.5, 0.4687), radius=.46, diff --git a/mne/viz/topo.py b/mne/viz/topo.py index bc57b64c807..b1616d5fb9f 100644 --- a/mne/viz/topo.py +++ b/mne/viz/topo.py @@ -259,11 +259,11 @@ def _imshow_tfr(ax, ch_idx, tmin, tmax, vmin, vmax, onselect, ylim=None, from matplotlib.widgets import RectangleSelector extent = (tmin, tmax, freq[0], freq[-1]) + interactive_cmap = False if cmap == 'interactive': cmap = 'RdBu_r' interactive_cmap = True - else: - interactive_cmap = False + img = ax.imshow(tfr[ch_idx], extent=extent, aspect="auto", origin="lower", vmin=vmin, vmax=vmax, picker=picker, cmap=cmap) if isinstance(ax, plt.Axes): @@ -277,11 +277,14 @@ def _imshow_tfr(ax, ch_idx, tmin, tmax, vmin, vmax, onselect, ylim=None, if y_label is not None: plt.ylabel(y_label) if colorbar: - cbar = plt.colorbar(mappable=img) + if isinstance(colorbar, DraggableColorbar): + cbar = colorbar.cbar # this happens with multiaxes case + else: + cbar = plt.colorbar(mappable=img) if interactive_cmap: cbar = DraggableColorbar(cbar, img) cbar.connect() - ax.colorbar = cbar # For keeping reference + ax.CB = cbar # For keeping reference if title: plt.title(title) if not isinstance(ax, plt.Axes): diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py index 615b9a9f2c4..263423740c9 100644 --- a/mne/viz/topomap.py +++ b/mne/viz/topomap.py @@ -1367,7 +1367,7 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None, for im in images: cb = DraggableColorbar(cbar, im) cb.connect() - im.colorbar = cb # For keeping reference + im.axes.CB = cb # For keeping reference if proj == 'interactive': _check_delayed_ssp(evoked) From 3aed69743aad1e68dd04d69af87bdee3ed947034 Mon Sep 17 00:00:00 2001 From: jaeilepp Date: Wed, 8 Jun 2016 17:56:28 +0200 Subject: [PATCH 04/19] Fix tests. --- mne/time_frequency/tests/test_tfr.py | 3 +-- mne/viz/tests/test_topomap.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mne/time_frequency/tests/test_tfr.py b/mne/time_frequency/tests/test_tfr.py index e5b34962651..ebba84b4ea1 100644 --- a/mne/time_frequency/tests/test_tfr.py +++ b/mne/time_frequency/tests/test_tfr.py @@ -12,7 +12,6 @@ _dpss_wavelet, tfr_multitaper, AverageTFR, read_tfrs, write_tfrs, combine_tfr, cwt) -from mne.viz.utils import _fake_click import matplotlib matplotlib.use('Agg') # for testing don't use X server @@ -384,7 +383,7 @@ def test_plot(): cbar = fig.get_axes()[0].CB # Fake dragging with mouse. event = backend_bases.MouseEvent('button_press_event', fig.canvas, 0.1, 0.1, button=1) - event.inaxes = fig.get_axes()[1] + event.inaxes = cbar.cbar.ax cbar.on_press(event) event.y = 0.2 cbar.on_motion(event) diff --git a/mne/viz/tests/test_topomap.py b/mne/viz/tests/test_topomap.py index 3ab6757e6f8..c56b5ecb22f 100644 --- a/mne/viz/tests/test_topomap.py +++ b/mne/viz/tests/test_topomap.py @@ -204,7 +204,7 @@ def get_texts(p): cbar = fig.get_axes()[0].CB # Fake dragging with mouse. event = backend_bases.MouseEvent('button_press_event', fig.canvas, 0.1, 0.1, button=1) - event.inaxes = fig.get_axes()[-1] + event.inaxes = cbar.cbar.ax cbar.on_press(event) event.y = 0.2 cbar.on_motion(event) From 68e28b889af7188b99ab1e2dc8b069f2e31aada1 Mon Sep 17 00:00:00 2001 From: jaeilepp Date: Thu, 9 Jun 2016 11:54:09 +0200 Subject: [PATCH 05/19] Interactive colorbars to evoked and epochs images. Updated whats_new. Tests. --- doc/whats_new.rst | 4 ++++ mne/epochs.py | 8 ++++++-- mne/evoked.py | 8 ++++++-- mne/time_frequency/tests/test_tfr.py | 2 +- mne/viz/epochs.py | 29 ++++++++++++++++++++-------- mne/viz/evoked.py | 18 ++++++++++++++--- mne/viz/tests/test_epochs.py | 2 +- mne/viz/tests/test_evoked.py | 2 +- mne/viz/tests/test_topomap.py | 4 +++- 9 files changed, 58 insertions(+), 19 deletions(-) diff --git a/doc/whats_new.rst b/doc/whats_new.rst index 3747b8e9687..f30a34edbc1 100644 --- a/doc/whats_new.rst +++ b/doc/whats_new.rst @@ -27,6 +27,8 @@ Changelog - Add second-order sections (instead of ``(b, a)`` form) IIR filtering for reduced numerical error by `Eric Larson`_ + - Add interactive colormap option to image plotting functions by `Jaakko Leppakangas`_ + BUG ~~~ @@ -46,6 +48,8 @@ BUG - Fixed a bug when setting multiple bipolar references with :func:`mne.io.set_bipolar_reference` by `Marijn van Vliet`_. + - Fix to image scaling in :func:`mne.viz.plot_epochs_image` when plotting more than one channel by `Jaakko Leppakangas`_ + API ~~~ diff --git a/mne/epochs.py b/mne/epochs.py index 9e462737939..2964a5d7588 100644 --- a/mne/epochs.py +++ b/mne/epochs.py @@ -1198,8 +1198,12 @@ def plot_image(self, picks=None, sigma=0., vmin=None, The scalings of the channel types to be applied for plotting. If None, defaults to `scalings=dict(eeg=1e6, grad=1e13, mag=1e15, eog=1e6)`. - cmap : matplotlib colormap - Colormap. + cmap : matplotlib colormap | 'interactive' + Colormap. If 'interactive', the colors are adjustable by clicking + and dragging the colorbar with left and right mouse button. Left + mouse button moves the scale up and down and right mouse button + adjusts the range. Up and down arrows can be used to change the + colormap. fig : matplotlib figure | None Figure instance to draw the image to. Figure must contain two axes for drawing the single trials and evoked responses. If diff --git a/mne/evoked.py b/mne/evoked.py index 58a1330ae13..567765a2ce4 100644 --- a/mne/evoked.py +++ b/mne/evoked.py @@ -364,8 +364,12 @@ def plot_image(self, picks=None, exclude='bads', unit=True, show=True, The axes to plot to. If list, the list must be a list of Axes of the same length as the number of channel types. If instance of Axes, there must be only one channel type plotted. - cmap : matplotlib colormap - Colormap. + cmap : matplotlib colormap | 'interactive' + Colormap. If 'interactive', the colors are adjustable by clicking + and dragging the colorbar with left and right mouse button. Left + mouse button moves the scale up and down and right mouse button + adjusts the range. Up and down arrows can be used to change the + colormap. Returns ------- diff --git a/mne/time_frequency/tests/test_tfr.py b/mne/time_frequency/tests/test_tfr.py index ebba84b4ea1..ccca3735a5f 100644 --- a/mne/time_frequency/tests/test_tfr.py +++ b/mne/time_frequency/tests/test_tfr.py @@ -376,7 +376,7 @@ def test_plot(): tfr.plot_topo(picks=[1, 2]) plt.close('all') - fig = tfr.plot(picks=[1, 2], cmap='interactive') # test interactive cmap + fig = tfr.plot(picks=[1], cmap='interactive') # test interactive cmap fig.canvas.key_press_event('up') fig.canvas.key_press_event('down') diff --git a/mne/viz/epochs.py b/mne/viz/epochs.py index 2ff60d98699..fcc8c38151e 100644 --- a/mne/viz/epochs.py +++ b/mne/viz/epochs.py @@ -22,7 +22,7 @@ from .utils import (tight_layout, figure_nobar, _toggle_proj, _toggle_options, _layout_figure, _setup_vmin_vmax, _channels_changed, _plot_raw_onscroll, _onclick_help, plt_show, - _compute_scalings) + _compute_scalings, DraggableColorbar) from ..defaults import _handle_default @@ -65,8 +65,11 @@ def plot_epochs_image(epochs, picks=None, sigma=0., vmin=None, The scalings of the channel types to be applied for plotting. If None, defaults to `scalings=dict(eeg=1e6, grad=1e13, mag=1e15, eog=1e6)`. - cmap : matplotlib colormap - Colormap. + cmap : matplotlib colormap | 'interactive' + Colormap. If 'interactive', the colors are adjustable by clicking and + dragging the colorbar with left and right mouse button. Left mouse + button moves the scale up and down and right mouse button adjusts the + range. Up and down arrows can be used to change the colormap. fig : matplotlib figure | None Figure instance to draw the image to. Figure must contain two axes for drawing the single trials and evoked responses. If None a new figure is @@ -176,15 +179,20 @@ def plot_epochs_image(epochs, picks=None, sigma=0., vmin=None, ax2 = plt.subplot2grid((3, 10), (2, 0), colspan=9, rowspan=1) if colorbar: ax3 = plt.subplot2grid((3, 10), (0, 9), colspan=1, rowspan=3) + if scale_vmin: - vmin *= scalings[ch_type] + this_vmin = vmin * scalings[ch_type] if scale_vmax: - vmax *= scalings[ch_type] + this_vmax = vmax * scalings[ch_type] + + if cmap == 'interactive': + interactive_cmap = True + cmap = 'RdBu_r' im = ax1.imshow(this_data, extent=[1e3 * epochs.times[0], 1e3 * epochs.times[-1], 0, len(data)], aspect='auto', origin='lower', interpolation='nearest', - vmin=vmin, vmax=vmax, cmap=cmap) + vmin=this_vmin, vmax=this_vmax, cmap=cmap) if this_overlay_times is not None: plt.plot(1e3 * this_overlay_times, 0.5 + np.arange(len(this_data)), 'k', linewidth=2) @@ -206,10 +214,15 @@ def plot_epochs_image(epochs, picks=None, sigma=0., vmin=None, ax2.set_ylim([evoked_vmin, evoked_vmax]) ax2.axvline(0, color='m', linewidth=3, linestyle='--') if colorbar: - plt.colorbar(im, cax=ax3) + cbar = plt.colorbar(im, cax=ax3) + if interactive_cmap: + cbar = DraggableColorbar(cbar, im) + cbar.connect() + ax1.CB = cbar # For keeping reference + cmap = 'interactive' # For other channels tight_layout(fig=this_fig) - plt_show(show) + return figs diff --git a/mne/viz/evoked.py b/mne/viz/evoked.py index c8d18256b89..febe747e39f 100644 --- a/mne/viz/evoked.py +++ b/mne/viz/evoked.py @@ -18,7 +18,7 @@ from ..externals.six import string_types from ..defaults import _handle_default from .utils import (_draw_proj_checkbox, tight_layout, _check_delayed_ssp, - plt_show, _process_times) + plt_show, _process_times, DraggableColorbar) from ..utils import logger, _clean_names, warn from ..fixes import partial from ..io.pick import pick_info @@ -373,11 +373,20 @@ def _plot_evoked(evoked, picks, exclude, unit, show, horizontalalignment='left', fontweight='bold', alpha=0)) elif plot_type == 'image': + interactive_cbar = False + if cmap == 'interactive': + interactive_cbar = True + cmap = 'RdBu_r' im = ax.imshow(D, interpolation='nearest', origin='lower', extent=[times[0], times[-1], 0, D.shape[0]], aspect='auto', cmap=cmap) cbar = plt.colorbar(im, ax=ax) cbar.ax.set_title(ch_unit) + if interactive_cbar: + cbar = DraggableColorbar(cbar, im) + cbar.connect() + ax.CB = cbar # For keeping reference + cmap = 'interactive' # For other channel types ax.set_ylabel('channels (%s)' % 'index') else: raise ValueError("plot_type has to be 'butterfly' or 'image'." @@ -690,8 +699,11 @@ def plot_evoked_image(evoked, picks=None, exclude='bads', unit=True, show=True, The axes to plot to. If list, the list must be a list of Axes of the same length as the number of channel types. If instance of Axes, there must be only one channel type plotted. - cmap : matplotlib colormap - Colormap. + cmap : matplotlib colormap | 'interactive' + Colormap. If 'interactive', the colors are adjustable by clicking and + dragging the colorbar with left and right mouse button. Left mouse + button moves the scale up and down and right mouse button adjusts the + range. Up and down arrows can be used to change the colormap. Returns ------- diff --git a/mne/viz/tests/test_epochs.py b/mne/viz/tests/test_epochs.py index 6adbbfed9da..4133fc42717 100644 --- a/mne/viz/tests/test_epochs.py +++ b/mne/viz/tests/test_epochs.py @@ -137,7 +137,7 @@ def test_plot_epochs_image(): epochs.plot_image(picks=[1, 2]) overlay_times = [0.1] epochs.plot_image(order=[0], overlay_times=overlay_times) - epochs.plot_image(overlay_times=overlay_times) + epochs.plot_image(overlay_times=overlay_times, cmap='interactive') assert_raises(ValueError, epochs.plot_image, overlay_times=[0.1, 0.2]) assert_raises(ValueError, epochs.plot_image, diff --git a/mne/viz/tests/test_evoked.py b/mne/viz/tests/test_evoked.py index 471d36cb5dc..6c98e453615 100644 --- a/mne/viz/tests/test_evoked.py +++ b/mne/viz/tests/test_evoked.py @@ -116,7 +116,7 @@ def test_plot_evoked(): evoked.plot_image(proj=True) # plot with bad channels excluded - evoked.plot_image(exclude='bads') + evoked.plot_image(exclude='bads', cmap='interactive') evoked.plot_image(exclude=evoked.info['bads']) # does the same thing plt.close('all') diff --git a/mne/viz/tests/test_topomap.py b/mne/viz/tests/test_topomap.py index c56b5ecb22f..56693fdb687 100644 --- a/mne/viz/tests/test_topomap.py +++ b/mne/viz/tests/test_topomap.py @@ -200,7 +200,9 @@ def get_texts(p): # Test interactive cmap fig = plot_evoked_topomap(evoked, times=[0., 0.1], ch_type='eeg', - cmap='interactive') + cmap='interactive', title='title') + fig.canvas.key_press_event('up') + fig.canvas.key_press_event('down') cbar = fig.get_axes()[0].CB # Fake dragging with mouse. event = backend_bases.MouseEvent('button_press_event', fig.canvas, 0.1, 0.1, button=1) From 87ded2968e967b83181aa27fa25ac0d337705c58 Mon Sep 17 00:00:00 2001 From: jaeilepp Date: Thu, 9 Jun 2016 15:18:29 +0200 Subject: [PATCH 06/19] Fix. Refactoring. --- mne/viz/epochs.py | 5 ++--- mne/viz/evoked.py | 4 +--- mne/viz/topo.py | 4 +--- mne/viz/topomap.py | 4 +--- mne/viz/utils.py | 1 + 5 files changed, 6 insertions(+), 12 deletions(-) diff --git a/mne/viz/epochs.py b/mne/viz/epochs.py index fcc8c38151e..9feaf37089f 100644 --- a/mne/viz/epochs.py +++ b/mne/viz/epochs.py @@ -185,6 +185,7 @@ def plot_epochs_image(epochs, picks=None, sigma=0., vmin=None, if scale_vmax: this_vmax = vmax * scalings[ch_type] + interactive_cmap = False if cmap == 'interactive': interactive_cmap = True cmap = 'RdBu_r' @@ -216,9 +217,7 @@ def plot_epochs_image(epochs, picks=None, sigma=0., vmin=None, if colorbar: cbar = plt.colorbar(im, cax=ax3) if interactive_cmap: - cbar = DraggableColorbar(cbar, im) - cbar.connect() - ax1.CB = cbar # For keeping reference + ax1.CB = DraggableColorbar(cbar, im) cmap = 'interactive' # For other channels tight_layout(fig=this_fig) plt_show(show) diff --git a/mne/viz/evoked.py b/mne/viz/evoked.py index febe747e39f..267dae06802 100644 --- a/mne/viz/evoked.py +++ b/mne/viz/evoked.py @@ -383,9 +383,7 @@ def _plot_evoked(evoked, picks, exclude, unit, show, cbar = plt.colorbar(im, ax=ax) cbar.ax.set_title(ch_unit) if interactive_cbar: - cbar = DraggableColorbar(cbar, im) - cbar.connect() - ax.CB = cbar # For keeping reference + ax.CB = DraggableColorbar(cbar, im) cmap = 'interactive' # For other channel types ax.set_ylabel('channels (%s)' % 'index') else: diff --git a/mne/viz/topo.py b/mne/viz/topo.py index b1616d5fb9f..21a904df5cf 100644 --- a/mne/viz/topo.py +++ b/mne/viz/topo.py @@ -282,9 +282,7 @@ def _imshow_tfr(ax, ch_idx, tmin, tmax, vmin, vmax, onselect, ylim=None, else: cbar = plt.colorbar(mappable=img) if interactive_cmap: - cbar = DraggableColorbar(cbar, img) - cbar.connect() - ax.CB = cbar # For keeping reference + ax.CB = DraggableColorbar(cbar, img) if title: plt.title(title) if not isinstance(ax, plt.Axes): diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py index 263423740c9..a1773a497f2 100644 --- a/mne/viz/topomap.py +++ b/mne/viz/topomap.py @@ -1365,9 +1365,7 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None, if interactive_cmap: from .utils import DraggableColorbar for im in images: - cb = DraggableColorbar(cbar, im) - cb.connect() - im.axes.CB = cb # For keeping reference + im.axes.CB = DraggableColorbar(cbar, im) if proj == 'interactive': _check_delayed_ssp(evoked) diff --git a/mne/viz/utils.py b/mne/viz/utils.py index ac717eaf4f2..1c79d9ad628 100644 --- a/mne/viz/utils.py +++ b/mne/viz/utils.py @@ -1337,6 +1337,7 @@ def __init__(self, cbar, mappable): self.cycle = sorted([i for i in dir(plt.cm) if hasattr(getattr(plt.cm, i), 'N')]) self.index = self.cycle.index(cbar.get_cmap().name) + self.connect() def connect(self): """Connect to all the events we need.""" From 9a382f87bbbf18dbe4998e27a98f549b64ccb898 Mon Sep 17 00:00:00 2001 From: jaeilepp Date: Mon, 13 Jun 2016 15:03:10 +0200 Subject: [PATCH 07/19] Fixes. Refactoring. Scroll event. --- mne/evoked.py | 9 +++++--- mne/time_frequency/tests/test_tfr.py | 26 ++++++++++----------- mne/time_frequency/tfr.py | 22 ++++++++---------- mne/viz/evoked.py | 11 ++++----- mne/viz/tests/test_topomap.py | 27 +++++++++++----------- mne/viz/topomap.py | 11 ++++++--- mne/viz/utils.py | 34 +++++++++++++++++++++++++--- 7 files changed, 85 insertions(+), 55 deletions(-) diff --git a/mne/evoked.py b/mne/evoked.py index 567765a2ce4..f823a343647 100644 --- a/mne/evoked.py +++ b/mne/evoked.py @@ -493,14 +493,17 @@ def plot_topomap(self, times="auto", ch_type=None, layout=None, vmin=None, If None, the maximum absolute value is used. If vmin is None, but vmax is not, defaults to np.max(data). If callable, the output equals vmax(data). - cmap : matplotlib colormap | None + cmap : matplotlib colormap | 'interactive' | None Colormap to use. If 'interactive', the colors are adjustable by clicking and dragging the colorbar with left and right mouse button. Left mouse button moves the scale up and down and right mouse button adjusts the range. Up and down arrows can be used to - change the colormap. Interactive mode works smoothly only for a - small amount of topomaps. If None (default), 'Reds' is used for all + change the colormap. If None (default), 'Reds' is used for all positive data, otherwise defaults to 'RdBu_r'. + + .. warning:: Interactive mode works smoothly only for a small + amount of topomaps. + sensors : bool | str Add markers for sensor locations to the plot. Accepts matplotlib plot format string (e.g., 'r+' for red plusses). If True, a circle diff --git a/mne/time_frequency/tests/test_tfr.py b/mne/time_frequency/tests/test_tfr.py index ccca3735a5f..a21ab47edb8 100644 --- a/mne/time_frequency/tests/test_tfr.py +++ b/mne/time_frequency/tests/test_tfr.py @@ -12,6 +12,7 @@ _dpss_wavelet, tfr_multitaper, AverageTFR, read_tfrs, write_tfrs, combine_tfr, cwt) +from mne.viz.utils import _fake_click import matplotlib matplotlib.use('Agg') # for testing don't use X server @@ -353,7 +354,6 @@ def test_io(): def test_plot(): """Test TFR plotting.""" import matplotlib.pyplot as plt - from matplotlib import backend_bases data = np.zeros((3, 2, 3)) times = np.array([.1, .2, .3]) @@ -381,18 +381,18 @@ def test_plot(): fig.canvas.key_press_event('down') cbar = fig.get_axes()[0].CB # Fake dragging with mouse. - event = backend_bases.MouseEvent('button_press_event', fig.canvas, 0.1, - 0.1, button=1) - event.inaxes = cbar.cbar.ax - cbar.on_press(event) - event.y = 0.2 - cbar.on_motion(event) - cbar.on_release(event) - event.button = 3 - cbar.on_press(event) - event.y = 0.3 - cbar.on_motion(event) - cbar.on_release(event) + ax = cbar.cbar.ax + _fake_click(fig, ax, (0.1, 0.1)) + _fake_click(fig, ax, (0.1, 0.2), kind='motion') + _fake_click(fig, ax, (0.1, 0.3), kind='release') + + _fake_click(fig, ax, (0.1, 0.1), button=3) + _fake_click(fig, ax, (0.1, 0.2), button=3, kind='motion') + _fake_click(fig, ax, (0.1, 0.3), kind='release') + + fig.canvas.scroll_event(0.5, 0.5, -0.5) # scroll down + fig.canvas.scroll_event(0.5, 0.5, 0.5) # scroll up + plt.close('all') diff --git a/mne/time_frequency/tfr.py b/mne/time_frequency/tfr.py index 4558a901792..84c9155a935 100644 --- a/mne/time_frequency/tfr.py +++ b/mne/time_frequency/tfr.py @@ -22,11 +22,11 @@ from ..channels.channels import ContainsMixin, UpdateChannelsMixin from ..io.pick import pick_info, pick_types from ..io.meas_info import Info -from ..io.constants import FIFF from .multitaper import dpss_windows from ..viz.utils import figure_nobar, plt_show from ..externals.h5io import write_hdf5, read_hdf5 from ..externals.six import string_types +from ..viz.utils import _check_grad_pairs def _get_data(inst, return_itc): @@ -678,8 +678,11 @@ def plot(self, picks=None, baseline=None, mode='mean', tmin=None, clicking and dragging the colorbar with left and right mouse button. Left mouse button moves the scale up and down and right mouse button adjusts the range. Up and down arrows can be used to - change the colormap. Interactive mode works smoothly only for a - small amount of images. Defaults to 'RdBu_r'. + change the colormap. Defaults to 'RdBu_r'. + + .. warning:: Interactive mode works smoothly only for a small + amount of images. + dB : bool If True, 20*log10 is applied to the data to get dB. colorbar : bool @@ -774,16 +777,11 @@ def _onselect(self, eclick, erelease, baseline, mode, layout): if 'mag' in self: types.append('mag') if 'grad' in self: - if (FIFF.FIFFV_COIL_VV_PLANAR_T1 in - np.unique([ch['coil_type'] for ch in self.info['chs']])): - chs = [ch for ch in self.ch_names if ch.startswith('MEG') and - ch.endswith(('2', '3'))] - if len(chs) < 2: - warn('No grad pairs found.') - if len(types) == 0: - return # Don't draw a figure for nothing. - else: + chs = _check_grad_pairs(self.info) + if len(chs) >= 2: types.append('grad') + elif len(types) == 0: + return # Don't draw a figure for nothing. fig = figure_nobar() fig.suptitle('{:.2f} s - {:.2f} s, {:.2f} Hz - {:.2f} Hz'.format( tmin, tmax, fmin, fmax), y=0.04) diff --git a/mne/viz/evoked.py b/mne/viz/evoked.py index 267dae06802..a160fab2e67 100644 --- a/mne/viz/evoked.py +++ b/mne/viz/evoked.py @@ -18,11 +18,11 @@ from ..externals.six import string_types from ..defaults import _handle_default from .utils import (_draw_proj_checkbox, tight_layout, _check_delayed_ssp, - plt_show, _process_times, DraggableColorbar) + plt_show, _process_times, DraggableColorbar, + _check_grad_pairs) from ..utils import logger, _clean_names, warn from ..fixes import partial from ..io.pick import pick_info -from ..io.constants import FIFF from .topo import _plot_evoked_topo from .topomap import (_prepare_topo_plot, plot_topomap, _check_outlines, _draw_outlines, _prepare_topomap, _topomap_animation) @@ -72,12 +72,9 @@ def _butterfly_onselect(xmin, xmax, ch_types, evoked, text=None): """Function for drawing topomaps from the selected area.""" import matplotlib.pyplot as plt ch_types = [type for type in ch_types if type in ('eeg', 'grad', 'mag')] - if ('grad' in ch_types and FIFF.FIFFV_COIL_VV_PLANAR_T1 in np.unique( - [ch['coil_type'] for ch in evoked.info['chs']])): - chs = [ch for ch in evoked.info['ch_names'] if - ch.startswith('MEG') and ch.endswith(('2', '3'))] + if 'grad' in ch_types: + chs = _check_grad_pairs(evoked.info) if len(chs) < 2: - warn('No grad pairs found.') ch_types.remove('grad') if len(ch_types) == 0: return diff --git a/mne/viz/tests/test_topomap.py b/mne/viz/tests/test_topomap.py index 56693fdb687..06b75be807d 100644 --- a/mne/viz/tests/test_topomap.py +++ b/mne/viz/tests/test_topomap.py @@ -23,7 +23,7 @@ from mne.viz import plot_evoked_topomap, plot_projs_topomap from mne.viz.topomap import (_check_outlines, _onselect, plot_topomap) -from mne.viz.utils import _find_peaks +from mne.viz.utils import _find_peaks, _fake_click # Set our plotters to test mode @@ -56,7 +56,6 @@ def test_plot_topomap(): """ import matplotlib.pyplot as plt from matplotlib.patches import Circle - from matplotlib import backend_bases # evoked warnings.simplefilter('always') res = 16 @@ -204,18 +203,18 @@ def get_texts(p): fig.canvas.key_press_event('up') fig.canvas.key_press_event('down') cbar = fig.get_axes()[0].CB # Fake dragging with mouse. - event = backend_bases.MouseEvent('button_press_event', fig.canvas, 0.1, - 0.1, button=1) - event.inaxes = cbar.cbar.ax - cbar.on_press(event) - event.y = 0.2 - cbar.on_motion(event) - cbar.on_release(event) - event.button = 3 - cbar.on_press(event) - event.y = 0.3 - cbar.on_motion(event) - cbar.on_release(event) + ax = cbar.cbar.ax + _fake_click(fig, ax, (0.1, 0.1)) + _fake_click(fig, ax, (0.1, 0.2), kind='motion') + _fake_click(fig, ax, (0.1, 0.3), kind='release') + + _fake_click(fig, ax, (0.1, 0.1), button=3) + _fake_click(fig, ax, (0.1, 0.2), button=3, kind='motion') + _fake_click(fig, ax, (0.1, 0.3), kind='release') + + fig.canvas.scroll_event(0.5, 0.5, -0.5) # scroll down + fig.canvas.scroll_event(0.5, 0.5, 0.5) # scroll up + plt.close('all') # Pass custom outlines with patch callable diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py index a1773a497f2..631d40cc99b 100644 --- a/mne/viz/topomap.py +++ b/mne/viz/topomap.py @@ -1119,14 +1119,17 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None, The value specifying the upper bound of the color range. If None, the maximum absolute value is used. If callable, the output equals vmax(data). Defaults to None. - cmap : matplotlib colormap | None + cmap : matplotlib colormap | 'interactive' | None Colormap to use. If 'interactive', the colors are adjustable by clicking and dragging the colorbar with left and right mouse button. Left mouse button moves the scale up and down and right mouse button adjusts the range. Up and down arrows can be used to change the - colormap. Interactive mode works smoothly only for a small amount of - topomaps. If None (default), 'Reds' is used for all positive data, + colormap. If None (default), 'Reds' is used for all positive data, otherwise defaults to 'RdBu_r'. + + .. warning:: Interactive mode works smoothly only for a small amount + of topomaps. + sensors : bool | str Add markers for sensor locations to the plot. Accepts matplotlib plot format string (e.g., 'r+' for red plusses). If True, a circle will be @@ -1323,6 +1326,8 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None, vmin = np.min(vlims) vmax = np.max(vlims) if cmap == 'interactive': + if nax > 2: + warn('Interactive colorbar may be slow for multiple axes.') cmap = None interactive_cmap = True else: diff --git a/mne/viz/utils.py b/mne/viz/utils.py index 1c79d9ad628..988d4d4f8dc 100644 --- a/mne/viz/utils.py +++ b/mne/viz/utils.py @@ -23,6 +23,7 @@ from ..defaults import _handle_default from ..io import show_fiff, Info from ..io.pick import channel_type, channel_indices_by_type, pick_channels +from ..io.constants import FIFF from ..utils import verbose, set_config, warn from ..externals.six import string_types from ..fixes import _get_argrelmax @@ -868,6 +869,18 @@ def _setup_browser_offsets(params, n_channels): line.set_data(line._x, np.array(params['ax'].get_ylim())) +def _check_grad_pairs(info): + """Helper for checking gradiometer pairs.""" + if FIFF.FIFFV_COIL_VV_PLANAR_T1 in np.unique( + [ch['coil_type'] for ch in info['chs']]): + chs = [ch for ch in info['ch_names'] if + ch.startswith('MEG') and ch.endswith(('2', '3'))] + if len(chs) < 2: + warn('No grad pairs found. Cannot compute RMS.') + return chs + return list() + + class ClickableImage(object): """ @@ -958,7 +971,7 @@ def to_layout(self, **kwargs): return lt -def _fake_click(fig, ax, point, xform='ax', button=1): +def _fake_click(fig, ax, point, xform='ax', button=1, kind='press'): """Helper to fake a click at a relative point within axes.""" if xform == 'ax': x, y = ax.transAxes.transform_point(point) @@ -966,10 +979,18 @@ def _fake_click(fig, ax, point, xform='ax', button=1): x, y = ax.transData.transform_point(point) else: raise ValueError('unknown transform') + if kind == 'press': + func = partial(fig.canvas.button_press_event, x=x, y=y, button=button, + dblclick=False) + elif kind == 'release': + func = partial(fig.canvas.button_release_event, x=x, y=y, + button=button) + elif kind == 'motion': + func = partial(fig.canvas.motion_notify_event, x=x, y=y) try: - fig.canvas.button_press_event(x, y, button, False, None) + func(guiEvent=None) except Exception: # for old MPL - fig.canvas.button_press_event(x, y, button, False) + func() def add_background_image(fig, im, set_ratios=None): @@ -1349,6 +1370,8 @@ def connect(self): 'motion_notify_event', self.on_motion) self.keypress = self.cbar.patch.figure.canvas.mpl_connect( 'key_press_event', self.key_press) + self.scroll = self.cbar.patch.figure.canvas.mpl_connect( + 'scroll_event', self.on_scroll) def on_press(self, event): """Callback for button press.""" @@ -1400,3 +1423,8 @@ def on_release(self, event): self.press = None self.mappable.set_norm(self.cbar.norm) self.cbar.patch.figure.canvas.draw() + + def on_scroll(self, event): + """Callback for scroll.""" + event.key = 'down' if event.step < 0 else 'up' + self.key_press(event) From 027d644334da3be60b98afcba0ed724bd0107e3b Mon Sep 17 00:00:00 2001 From: jaeilepp Date: Mon, 13 Jun 2016 15:12:07 +0200 Subject: [PATCH 08/19] block=False for topomap animation. --- mne/viz/topomap.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py index 631d40cc99b..8dfdec1e7c0 100644 --- a/mne/viz/topomap.py +++ b/mne/viz/topomap.py @@ -1999,8 +1999,7 @@ def _topomap_animation(evoked, ch_type='mag', times=None, frame_rate=None, frames=len(frames), interval=interval, blit=blit) fig.mne_animation = anim # to make sure anim is not garbage collected - if show: - plt.show() + plt_show(show, block=False) if 'line' in params: # Finally remove the vertical line so it does not appear in saved fig. params['line'].remove() From 23dab5fda0983ec132f2b7e6f0ae6c391c525a39 Mon Sep 17 00:00:00 2001 From: jaeilepp Date: Tue, 14 Jun 2016 10:13:59 +0200 Subject: [PATCH 09/19] Interactive mode on by default. Space bar resets scale. --- mne/evoked.py | 30 +++++++++++++++----------- mne/time_frequency/tests/test_tfr.py | 3 ++- mne/time_frequency/tfr.py | 19 +++++++++++------ mne/viz/epochs.py | 19 ++++++----------- mne/viz/evoked.py | 25 +++++++++++----------- mne/viz/tests/test_topomap.py | 3 ++- mne/viz/topo.py | 10 ++++----- mne/viz/topomap.py | 32 +++++++++++++++------------- mne/viz/utils.py | 4 ++++ 9 files changed, 78 insertions(+), 67 deletions(-) diff --git a/mne/evoked.py b/mne/evoked.py index f823a343647..0e2de67d92e 100644 --- a/mne/evoked.py +++ b/mne/evoked.py @@ -364,12 +364,14 @@ def plot_image(self, picks=None, exclude='bads', unit=True, show=True, The axes to plot to. If list, the list must be a list of Axes of the same length as the number of channel types. If instance of Axes, there must be only one channel type plotted. - cmap : matplotlib colormap | 'interactive' - Colormap. If 'interactive', the colors are adjustable by clicking - and dragging the colorbar with left and right mouse button. Left - mouse button moves the scale up and down and right mouse button - adjusts the range. Up and down arrows can be used to change the - colormap. + cmap : matplotlib colormap | (colormap, bool) + Colormap. If tuple, the first value indicates the colormap to use + and the second value is a boolean defining interactivity. In + interactive mode the colors are adjustable by clicking and dragging + the colorbar with left and right mouse button. Left mouse button + moves the scale up and down and right mouse button adjusts the + range. Hitting space bar resets the scale. Up and down arrows can + be used to change the colormap. Defaults to 'RdBu_r'. Returns ------- @@ -493,13 +495,15 @@ def plot_topomap(self, times="auto", ch_type=None, layout=None, vmin=None, If None, the maximum absolute value is used. If vmin is None, but vmax is not, defaults to np.max(data). If callable, the output equals vmax(data). - cmap : matplotlib colormap | 'interactive' | None - Colormap to use. If 'interactive', the colors are adjustable by - clicking and dragging the colorbar with left and right mouse - button. Left mouse button moves the scale up and down and right - mouse button adjusts the range. Up and down arrows can be used to - change the colormap. If None (default), 'Reds' is used for all - positive data, otherwise defaults to 'RdBu_r'. + cmap : matplotlib colormap | (colormap, bool) | None + Colormap to use. If tuple, the first value indicates the colormap + to use and the second value is a boolean defining interactivity. In + interactive mode the colors are adjustable by clicking and dragging + the colorbar with left and right mouse button. Left mouse button + moves the scale up and down and right mouse button adjusts the + range. Hitting space bar resets the range. Up and down arrows can + be used to change the colormap. If None (default), 'Reds' is used + for all positive data, otherwise defaults to 'RdBu_r'. .. warning:: Interactive mode works smoothly only for a small amount of topomaps. diff --git a/mne/time_frequency/tests/test_tfr.py b/mne/time_frequency/tests/test_tfr.py index a21ab47edb8..5dfab99fc2b 100644 --- a/mne/time_frequency/tests/test_tfr.py +++ b/mne/time_frequency/tests/test_tfr.py @@ -376,8 +376,9 @@ def test_plot(): tfr.plot_topo(picks=[1, 2]) plt.close('all') - fig = tfr.plot(picks=[1], cmap='interactive') # test interactive cmap + fig = tfr.plot(picks=[1], cmap='RdBu_r') # interactive mode on by default fig.canvas.key_press_event('up') + fig.canvas.key_press_event(' ') fig.canvas.key_press_event('down') cbar = fig.get_axes()[0].CB # Fake dragging with mouse. diff --git a/mne/time_frequency/tfr.py b/mne/time_frequency/tfr.py index 84c9155a935..585c2364faf 100644 --- a/mne/time_frequency/tfr.py +++ b/mne/time_frequency/tfr.py @@ -673,12 +673,15 @@ def plot(self, picks=None, baseline=None, mode='mean', tmin=None, vmax : float | None The maxinum value an the color scale. If vmax is None, the data maximum value is used. - cmap : matplotlib colormap | str - The colormap to use. If 'interactive', the colors are adjustable by + cmap : matplotlib colormap | str | (colormap, bool) + The colormap to use. If tuple, the first value indicates the + colormap to use and the second value is a boolean defining + interactivity. In interactive mode the colors are adjustable by clicking and dragging the colorbar with left and right mouse button. Left mouse button moves the scale up and down and right - mouse button adjusts the range. Up and down arrows can be used to - change the colormap. Defaults to 'RdBu_r'. + mouse button adjusts the range. Hitting space bar resets the range. + Up and down arrows can be used to change the colormap. Defaults to + 'RdBu_r'. .. warning:: Interactive mode works smoothly only for a small amount of images. @@ -730,6 +733,8 @@ def plot(self, picks=None, baseline=None, mode='mean', tmin=None, raise RuntimeError('There must be an axes for each picked ' 'channel.') + if not isinstance(cmap, tuple): + cmap = (cmap, True) for idx in range(len(data)): if axes is None: fig = plt.figure() @@ -746,7 +751,7 @@ def plot(self, picks=None, baseline=None, mode='mean', tmin=None, if title: fig.suptitle(title) # Only draw 1 cbar. For interactive mode we pass the ref to cbar. - colorbar = ax.CB if cmap == 'interactive' else False + colorbar = ax.CB if cmap[1] else False plt_show(show) return fig @@ -884,8 +889,8 @@ def plot_topo(self, picks=None, baseline=None, mode='mean', tmin=None, onselect_callback = partial(self._onselect, baseline=baseline, mode=mode, layout=layout) - click_fun = partial(_imshow_tfr, tfr=data, freq=freqs, cmap=cmap, - onselect=onselect_callback) + click_fun = partial(_imshow_tfr, tfr=data, freq=freqs, + cmap=(cmap, True), onselect=onselect_callback) imshow = partial(_imshow_tfr_unified, tfr=data, freq=freqs, cmap=cmap, onselect=onselect_callback) diff --git a/mne/viz/epochs.py b/mne/viz/epochs.py index 9feaf37089f..e8d99cb759b 100644 --- a/mne/viz/epochs.py +++ b/mne/viz/epochs.py @@ -180,20 +180,16 @@ def plot_epochs_image(epochs, picks=None, sigma=0., vmin=None, if colorbar: ax3 = plt.subplot2grid((3, 10), (0, 9), colspan=1, rowspan=3) - if scale_vmin: - this_vmin = vmin * scalings[ch_type] - if scale_vmax: - this_vmax = vmax * scalings[ch_type] - - interactive_cmap = False - if cmap == 'interactive': - interactive_cmap = True - cmap = 'RdBu_r' + this_vmin = vmin * scalings[ch_type] if scale_vmin else vmin + this_vmax = vmax * scalings[ch_type] if scale_vmax else vmax + + if not isinstance(cmap, tuple): + cmap = (cmap, True) im = ax1.imshow(this_data, extent=[1e3 * epochs.times[0], 1e3 * epochs.times[-1], 0, len(data)], aspect='auto', origin='lower', interpolation='nearest', - vmin=this_vmin, vmax=this_vmax, cmap=cmap) + vmin=this_vmin, vmax=this_vmax, cmap=cmap[0]) if this_overlay_times is not None: plt.plot(1e3 * this_overlay_times, 0.5 + np.arange(len(this_data)), 'k', linewidth=2) @@ -216,9 +212,8 @@ def plot_epochs_image(epochs, picks=None, sigma=0., vmin=None, ax2.axvline(0, color='m', linewidth=3, linestyle='--') if colorbar: cbar = plt.colorbar(im, cax=ax3) - if interactive_cmap: + if cmap[1]: ax1.CB = DraggableColorbar(cbar, im) - cmap = 'interactive' # For other channels tight_layout(fig=this_fig) plt_show(show) diff --git a/mne/viz/evoked.py b/mne/viz/evoked.py index a160fab2e67..2297cd21d40 100644 --- a/mne/viz/evoked.py +++ b/mne/viz/evoked.py @@ -188,7 +188,8 @@ def _plot_evoked(evoked, picks, exclude, unit, show, ' for interactive SSP selection.') if isinstance(gfp, string_types) and gfp != 'only': raise ValueError('gfp must be boolean or "only". Got %s' % gfp) - + if not isinstance(cmap, tuple): + cmap = (cmap, True) scalings = _handle_default('scalings', scalings) titles = _handle_default('titles', titles) units = _handle_default('units', units) @@ -370,18 +371,13 @@ def _plot_evoked(evoked, picks, exclude, unit, show, horizontalalignment='left', fontweight='bold', alpha=0)) elif plot_type == 'image': - interactive_cbar = False - if cmap == 'interactive': - interactive_cbar = True - cmap = 'RdBu_r' im = ax.imshow(D, interpolation='nearest', origin='lower', extent=[times[0], times[-1], 0, D.shape[0]], - aspect='auto', cmap=cmap) + aspect='auto', cmap=cmap[0]) cbar = plt.colorbar(im, ax=ax) cbar.ax.set_title(ch_unit) - if interactive_cbar: + if cmap[1]: ax.CB = DraggableColorbar(cbar, im) - cmap = 'interactive' # For other channel types ax.set_ylabel('channels (%s)' % 'index') else: raise ValueError("plot_type has to be 'butterfly' or 'image'." @@ -694,11 +690,14 @@ def plot_evoked_image(evoked, picks=None, exclude='bads', unit=True, show=True, The axes to plot to. If list, the list must be a list of Axes of the same length as the number of channel types. If instance of Axes, there must be only one channel type plotted. - cmap : matplotlib colormap | 'interactive' - Colormap. If 'interactive', the colors are adjustable by clicking and - dragging the colorbar with left and right mouse button. Left mouse - button moves the scale up and down and right mouse button adjusts the - range. Up and down arrows can be used to change the colormap. + cmap : matplotlib colormap | (colormap, bool) + Colormap. If tuple, the first value indicates the colormap to use and + the second value is a boolean defining interactivity. In interactive + mode the colors are adjustable by clicking and dragging the colorbar + with left and right mouse button. Left mouse button moves the scale up + and down and right mouse button adjusts the range. Hitting space bar + resets the scale. Up and down arrows can be used to change the + colormap. Defaults to 'RdBu_r'. Returns ------- diff --git a/mne/viz/tests/test_topomap.py b/mne/viz/tests/test_topomap.py index 06b75be807d..d0b4ee0ee31 100644 --- a/mne/viz/tests/test_topomap.py +++ b/mne/viz/tests/test_topomap.py @@ -199,8 +199,9 @@ def get_texts(p): # Test interactive cmap fig = plot_evoked_topomap(evoked, times=[0., 0.1], ch_type='eeg', - cmap='interactive', title='title') + cmap=('Reds', True), title='title') fig.canvas.key_press_event('up') + fig.canvas.key_press_event(' ') fig.canvas.key_press_event('down') cbar = fig.get_axes()[0].CB # Fake dragging with mouse. ax = cbar.cbar.ax diff --git a/mne/viz/topo.py b/mne/viz/topo.py index 21a904df5cf..eb47aee7649 100644 --- a/mne/viz/topo.py +++ b/mne/viz/topo.py @@ -252,17 +252,17 @@ def _check_vlim(vlim): def _imshow_tfr(ax, ch_idx, tmin, tmax, vmin, vmax, onselect, ylim=None, tfr=None, freq=None, vline=None, x_label=None, y_label=None, - colorbar=False, picker=True, cmap='RdBu_r', title=None, + colorbar=False, picker=True, cmap=('RdBu_r', True), title=None, hline=None): """ Aux function to show time-freq map on topo """ import matplotlib.pyplot as plt from matplotlib.widgets import RectangleSelector extent = (tmin, tmax, freq[0], freq[-1]) - interactive_cmap = False - if cmap == 'interactive': - cmap = 'RdBu_r' - interactive_cmap = True + cmap, interactive_cmap = cmap + #if cmap[1]: + # cmap = 'RdBu_r' + # interactive_cmap = True img = ax.imshow(tfr[ch_idx], extent=extent, aspect="auto", origin="lower", vmin=vmin, vmax=vmax, picker=picker, cmap=cmap) diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py index 8dfdec1e7c0..4a96afd62ab 100644 --- a/mne/viz/topomap.py +++ b/mne/viz/topomap.py @@ -1119,12 +1119,14 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None, The value specifying the upper bound of the color range. If None, the maximum absolute value is used. If callable, the output equals vmax(data). Defaults to None. - cmap : matplotlib colormap | 'interactive' | None - Colormap to use. If 'interactive', the colors are adjustable by - clicking and dragging the colorbar with left and right mouse button. - Left mouse button moves the scale up and down and right mouse button - adjusts the range. Up and down arrows can be used to change the - colormap. If None (default), 'Reds' is used for all positive data, + cmap : matplotlib colormap | (colormap, bool) | None + Colormap to use. If tuple, the first value indicates the colormap to + use and the second value is a boolean defining interactivity. In + interactive mode the colors are adjustable by clicking and dragging the + colorbar with left and right mouse button. Left mouse button moves the + scale up and down and right mouse button adjusts the range. Hitting + space bar resets the range. Up and down arrows can be used to change + the colormap. If None (default), 'Reds' is used for all positive data, otherwise defaults to 'RdBu_r'. .. warning:: Interactive mode works smoothly only for a small amount @@ -1325,17 +1327,17 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None, for i in range(len(times))] vmin = np.min(vlims) vmax = np.max(vlims) - if cmap == 'interactive': - if nax > 2: - warn('Interactive colorbar may be slow for multiple axes.') - cmap = None - interactive_cmap = True - else: - interactive_cmap = False + if not isinstance(cmap, tuple): + if len(times) > 2: + warn('Disabling interactive colorbar for multiple axes. Turn ' + 'interactivity on explicitly by passing cmap as a tuple.') + cmap = (cmap, False) + else: + cmap = (cmap, True) for idx, time in enumerate(times): tp, cn = plot_topomap(data[:, idx], pos, vmin=vmin, vmax=vmax, sensors=sensors, res=res, names=names, - show_names=show_names, cmap=cmap, + show_names=show_names, cmap=cmap[0], mask=mask_[:, idx] if mask is not None else None, mask_params=mask_params, axes=axes[idx], outlines=outlines, image_mask=image_mask, @@ -1367,7 +1369,7 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None, cax.set_title(unit) cbar = fig.colorbar(images[-1], ax=cax, cax=cax, format=cbar_fmt) cbar.set_ticks([cbar.vmin, 0, cbar.vmax]) - if interactive_cmap: + if cmap[1]: from .utils import DraggableColorbar for im in images: im.axes.CB = DraggableColorbar(cbar, im) diff --git a/mne/viz/utils.py b/mne/viz/utils.py index 988d4d4f8dc..fa44b6d10cf 100644 --- a/mne/viz/utils.py +++ b/mne/viz/utils.py @@ -1358,6 +1358,7 @@ def __init__(self, cbar, mappable): self.cycle = sorted([i for i in dir(plt.cm) if hasattr(getattr(plt.cm, i), 'N')]) self.index = self.cycle.index(cbar.get_cmap().name) + self.lims = (self.cbar.norm.vmin, self.cbar.norm.vmax) self.connect() def connect(self): @@ -1385,6 +1386,9 @@ def key_press(self, event): self.index += 1 elif event.key == 'up': self.index -= 1 + elif event.key == ' ': # space key resets scale + self.cbar.norm.vmin = self.lims[0] + self.cbar.norm.vmax = self.lims[1] else: return if self.index < 0: From 3aef586710d6b50f289f2db6e23ec5a3b9cce4ab Mon Sep 17 00:00:00 2001 From: jaeilepp Date: Tue, 14 Jun 2016 11:57:25 +0200 Subject: [PATCH 10/19] Updated tests. --- mne/viz/tests/test_epochs.py | 2 +- mne/viz/tests/test_evoked.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mne/viz/tests/test_epochs.py b/mne/viz/tests/test_epochs.py index 4133fc42717..6adbbfed9da 100644 --- a/mne/viz/tests/test_epochs.py +++ b/mne/viz/tests/test_epochs.py @@ -137,7 +137,7 @@ def test_plot_epochs_image(): epochs.plot_image(picks=[1, 2]) overlay_times = [0.1] epochs.plot_image(order=[0], overlay_times=overlay_times) - epochs.plot_image(overlay_times=overlay_times, cmap='interactive') + epochs.plot_image(overlay_times=overlay_times) assert_raises(ValueError, epochs.plot_image, overlay_times=[0.1, 0.2]) assert_raises(ValueError, epochs.plot_image, diff --git a/mne/viz/tests/test_evoked.py b/mne/viz/tests/test_evoked.py index 6c98e453615..e49b9e1e8e5 100644 --- a/mne/viz/tests/test_evoked.py +++ b/mne/viz/tests/test_evoked.py @@ -116,7 +116,7 @@ def test_plot_evoked(): evoked.plot_image(proj=True) # plot with bad channels excluded - evoked.plot_image(exclude='bads', cmap='interactive') + evoked.plot_image(exclude='bads', cmap=('Reds', True)) evoked.plot_image(exclude=evoked.info['bads']) # does the same thing plt.close('all') From 4d79cff193787b770f06a113aae0b77a43480656 Mon Sep 17 00:00:00 2001 From: jaeilepp Date: Tue, 14 Jun 2016 14:28:19 +0200 Subject: [PATCH 11/19] Allow cmap=interactive. --- mne/epochs.py | 15 +++++++++------ mne/evoked.py | 10 ++++++---- mne/time_frequency/tfr.py | 9 ++++++--- mne/viz/epochs.py | 18 ++++++++++++------ mne/viz/evoked.py | 9 ++++++--- mne/viz/tests/test_evoked.py | 2 +- mne/viz/topo.py | 3 --- mne/viz/topomap.py | 9 ++++++--- 8 files changed, 46 insertions(+), 29 deletions(-) diff --git a/mne/epochs.py b/mne/epochs.py index 2964a5d7588..9818effadce 100644 --- a/mne/epochs.py +++ b/mne/epochs.py @@ -1198,12 +1198,15 @@ def plot_image(self, picks=None, sigma=0., vmin=None, The scalings of the channel types to be applied for plotting. If None, defaults to `scalings=dict(eeg=1e6, grad=1e13, mag=1e15, eog=1e6)`. - cmap : matplotlib colormap | 'interactive' - Colormap. If 'interactive', the colors are adjustable by clicking - and dragging the colorbar with left and right mouse button. Left - mouse button moves the scale up and down and right mouse button - adjusts the range. Up and down arrows can be used to change the - colormap. + cmap : matplotlib colormap | (colormap, bool) | 'interactive' + Colormap. If tuple, the first value indicates the colormap to use + and the second value is a boolean defining interactivity. In + interactive mode the colors are adjustable by clicking and dragging + the colorbar with left and right mouse button. Left mouse button + moves the scale up and down and right mouse button adjusts the + range. Hitting space bar resets the scale. Up and down arrows can + be used to change the colormap. If 'interactive', translates to + ('RdBu_r', True). Defaults to 'RdBu_r'. fig : matplotlib figure | None Figure instance to draw the image to. Figure must contain two axes for drawing the single trials and evoked responses. If diff --git a/mne/evoked.py b/mne/evoked.py index 0e2de67d92e..f7ed66f6a8c 100644 --- a/mne/evoked.py +++ b/mne/evoked.py @@ -364,14 +364,15 @@ def plot_image(self, picks=None, exclude='bads', unit=True, show=True, The axes to plot to. If list, the list must be a list of Axes of the same length as the number of channel types. If instance of Axes, there must be only one channel type plotted. - cmap : matplotlib colormap | (colormap, bool) + cmap : matplotlib colormap | (colormap, bool) | 'interactive' Colormap. If tuple, the first value indicates the colormap to use and the second value is a boolean defining interactivity. In interactive mode the colors are adjustable by clicking and dragging the colorbar with left and right mouse button. Left mouse button moves the scale up and down and right mouse button adjusts the range. Hitting space bar resets the scale. Up and down arrows can - be used to change the colormap. Defaults to 'RdBu_r'. + be used to change the colormap. If 'interactive', translates to + ('RdBu_r', True). Defaults to 'RdBu_r'. Returns ------- @@ -495,7 +496,7 @@ def plot_topomap(self, times="auto", ch_type=None, layout=None, vmin=None, If None, the maximum absolute value is used. If vmin is None, but vmax is not, defaults to np.max(data). If callable, the output equals vmax(data). - cmap : matplotlib colormap | (colormap, bool) | None + cmap : matplotlib colormap | (colormap, bool) | 'interactive' | None Colormap to use. If tuple, the first value indicates the colormap to use and the second value is a boolean defining interactivity. In interactive mode the colors are adjustable by clicking and dragging @@ -503,7 +504,8 @@ def plot_topomap(self, times="auto", ch_type=None, layout=None, vmin=None, moves the scale up and down and right mouse button adjusts the range. Hitting space bar resets the range. Up and down arrows can be used to change the colormap. If None (default), 'Reds' is used - for all positive data, otherwise defaults to 'RdBu_r'. + for all positive data, otherwise defaults to 'RdBu_r'. If + 'interactive', translates to (None, True). .. warning:: Interactive mode works smoothly only for a small amount of topomaps. diff --git a/mne/time_frequency/tfr.py b/mne/time_frequency/tfr.py index 585c2364faf..1057cfd2ded 100644 --- a/mne/time_frequency/tfr.py +++ b/mne/time_frequency/tfr.py @@ -673,14 +673,15 @@ def plot(self, picks=None, baseline=None, mode='mean', tmin=None, vmax : float | None The maxinum value an the color scale. If vmax is None, the data maximum value is used. - cmap : matplotlib colormap | str | (colormap, bool) + cmap : matplotlib colormap | 'interactive' | (colormap, bool) The colormap to use. If tuple, the first value indicates the colormap to use and the second value is a boolean defining interactivity. In interactive mode the colors are adjustable by clicking and dragging the colorbar with left and right mouse button. Left mouse button moves the scale up and down and right mouse button adjusts the range. Hitting space bar resets the range. - Up and down arrows can be used to change the colormap. Defaults to + Up and down arrows can be used to change the colormap. If + 'interactive', translates to ('RdBu_r', True). Defaults to 'RdBu_r'. .. warning:: Interactive mode works smoothly only for a small @@ -733,7 +734,9 @@ def plot(self, picks=None, baseline=None, mode='mean', tmin=None, raise RuntimeError('There must be an axes for each picked ' 'channel.') - if not isinstance(cmap, tuple): + if cmap == 'interactive': + cmap = ('RdBu_r', True) + elif not isinstance(cmap, tuple): cmap = (cmap, True) for idx in range(len(data)): if axes is None: diff --git a/mne/viz/epochs.py b/mne/viz/epochs.py index e8d99cb759b..b64846fc5c6 100644 --- a/mne/viz/epochs.py +++ b/mne/viz/epochs.py @@ -65,11 +65,15 @@ def plot_epochs_image(epochs, picks=None, sigma=0., vmin=None, The scalings of the channel types to be applied for plotting. If None, defaults to `scalings=dict(eeg=1e6, grad=1e13, mag=1e15, eog=1e6)`. - cmap : matplotlib colormap | 'interactive' - Colormap. If 'interactive', the colors are adjustable by clicking and - dragging the colorbar with left and right mouse button. Left mouse - button moves the scale up and down and right mouse button adjusts the - range. Up and down arrows can be used to change the colormap. + cmap : matplotlib colormap | (colormap, bool) | 'interactive' + Colormap. If tuple, the first value indicates the colormap to use and + the second value is a boolean defining interactivity. In interactive + mode the colors are adjustable by clicking and dragging the colorbar + with left and right mouse button. Left mouse button moves the scale up + and down and right mouse button adjusts the range. Hitting space bar + resets the scale. Up and down arrows can be used to change the + colormap. If 'interactive', translates to ('RdBu_r', True). Defaults to + 'RdBu_r'. fig : matplotlib figure | None Figure instance to draw the image to. Figure must contain two axes for drawing the single trials and evoked responses. If None a new figure is @@ -183,7 +187,9 @@ def plot_epochs_image(epochs, picks=None, sigma=0., vmin=None, this_vmin = vmin * scalings[ch_type] if scale_vmin else vmin this_vmax = vmax * scalings[ch_type] if scale_vmax else vmax - if not isinstance(cmap, tuple): + if cmap == 'interactive': + cmap = ('RdBu_r', True) + elif not isinstance(cmap, tuple): cmap = (cmap, True) im = ax1.imshow(this_data, extent=[1e3 * epochs.times[0], 1e3 * epochs.times[-1], diff --git a/mne/viz/evoked.py b/mne/viz/evoked.py index 2297cd21d40..65d6fbb6dfb 100644 --- a/mne/viz/evoked.py +++ b/mne/viz/evoked.py @@ -188,7 +188,9 @@ def _plot_evoked(evoked, picks, exclude, unit, show, ' for interactive SSP selection.') if isinstance(gfp, string_types) and gfp != 'only': raise ValueError('gfp must be boolean or "only". Got %s' % gfp) - if not isinstance(cmap, tuple): + if cmap == 'interactive': + cmap = ('RdBu_r', True) + elif not isinstance(cmap, tuple): cmap = (cmap, True) scalings = _handle_default('scalings', scalings) titles = _handle_default('titles', titles) @@ -690,14 +692,15 @@ def plot_evoked_image(evoked, picks=None, exclude='bads', unit=True, show=True, The axes to plot to. If list, the list must be a list of Axes of the same length as the number of channel types. If instance of Axes, there must be only one channel type plotted. - cmap : matplotlib colormap | (colormap, bool) + cmap : matplotlib colormap | (colormap, bool) | 'interactive' Colormap. If tuple, the first value indicates the colormap to use and the second value is a boolean defining interactivity. In interactive mode the colors are adjustable by clicking and dragging the colorbar with left and right mouse button. Left mouse button moves the scale up and down and right mouse button adjusts the range. Hitting space bar resets the scale. Up and down arrows can be used to change the - colormap. Defaults to 'RdBu_r'. + colormap. If 'interactive', translates to ('RdBu_r', True). Defaults to + 'RdBu_r'. Returns ------- diff --git a/mne/viz/tests/test_evoked.py b/mne/viz/tests/test_evoked.py index e49b9e1e8e5..6c98e453615 100644 --- a/mne/viz/tests/test_evoked.py +++ b/mne/viz/tests/test_evoked.py @@ -116,7 +116,7 @@ def test_plot_evoked(): evoked.plot_image(proj=True) # plot with bad channels excluded - evoked.plot_image(exclude='bads', cmap=('Reds', True)) + evoked.plot_image(exclude='bads', cmap='interactive') evoked.plot_image(exclude=evoked.info['bads']) # does the same thing plt.close('all') diff --git a/mne/viz/topo.py b/mne/viz/topo.py index eb47aee7649..91bd9e2144c 100644 --- a/mne/viz/topo.py +++ b/mne/viz/topo.py @@ -260,9 +260,6 @@ def _imshow_tfr(ax, ch_idx, tmin, tmax, vmin, vmax, onselect, ylim=None, extent = (tmin, tmax, freq[0], freq[-1]) cmap, interactive_cmap = cmap - #if cmap[1]: - # cmap = 'RdBu_r' - # interactive_cmap = True img = ax.imshow(tfr[ch_idx], extent=extent, aspect="auto", origin="lower", vmin=vmin, vmax=vmax, picker=picker, cmap=cmap) diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py index 4a96afd62ab..86b29972272 100644 --- a/mne/viz/topomap.py +++ b/mne/viz/topomap.py @@ -1119,7 +1119,7 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None, The value specifying the upper bound of the color range. If None, the maximum absolute value is used. If callable, the output equals vmax(data). Defaults to None. - cmap : matplotlib colormap | (colormap, bool) | None + cmap : matplotlib colormap | (colormap, bool) | 'interactive' | None Colormap to use. If tuple, the first value indicates the colormap to use and the second value is a boolean defining interactivity. In interactive mode the colors are adjustable by clicking and dragging the @@ -1127,7 +1127,8 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None, scale up and down and right mouse button adjusts the range. Hitting space bar resets the range. Up and down arrows can be used to change the colormap. If None (default), 'Reds' is used for all positive data, - otherwise defaults to 'RdBu_r'. + otherwise defaults to 'RdBu_r'. If 'interactive', translates to + (None, True). .. warning:: Interactive mode works smoothly only for a small amount of topomaps. @@ -1327,7 +1328,9 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None, for i in range(len(times))] vmin = np.min(vlims) vmax = np.max(vlims) - if not isinstance(cmap, tuple): + if cmap == 'interactive': + cmap = (None. True) + elif not isinstance(cmap, tuple): if len(times) > 2: warn('Disabling interactive colorbar for multiple axes. Turn ' 'interactivity on explicitly by passing cmap as a tuple.') From 23a7f99340b4facc3eba784b12b253720a4522a3 Mon Sep 17 00:00:00 2001 From: jaeilepp Date: Tue, 14 Jun 2016 16:39:44 +0200 Subject: [PATCH 12/19] Fixes. --- mne/viz/topomap.py | 2 +- mne/viz/utils.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py index 86b29972272..b6e61efd76c 100644 --- a/mne/viz/topomap.py +++ b/mne/viz/topomap.py @@ -1329,7 +1329,7 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None, vmin = np.min(vlims) vmax = np.max(vlims) if cmap == 'interactive': - cmap = (None. True) + cmap = (None, True) elif not isinstance(cmap, tuple): if len(times) > 2: warn('Disabling interactive colorbar for multiple axes. Turn ' diff --git a/mne/viz/utils.py b/mne/viz/utils.py index fa44b6d10cf..606c165a224 100644 --- a/mne/viz/utils.py +++ b/mne/viz/utils.py @@ -980,8 +980,7 @@ def _fake_click(fig, ax, point, xform='ax', button=1, kind='press'): else: raise ValueError('unknown transform') if kind == 'press': - func = partial(fig.canvas.button_press_event, x=x, y=y, button=button, - dblclick=False) + func = partial(fig.canvas.button_press_event, x=x, y=y, button=button) elif kind == 'release': func = partial(fig.canvas.button_release_event, x=x, y=y, button=button) From 7eb6368c079622d7dc02bc5c96ddc0699d4de69c Mon Sep 17 00:00:00 2001 From: jaeilepp Date: Wed, 15 Jun 2016 08:17:45 +0200 Subject: [PATCH 13/19] Small fix. --- mne/viz/evoked.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/viz/evoked.py b/mne/viz/evoked.py index 65d6fbb6dfb..069f428685e 100644 --- a/mne/viz/evoked.py +++ b/mne/viz/evoked.py @@ -189,7 +189,7 @@ def _plot_evoked(evoked, picks, exclude, unit, show, if isinstance(gfp, string_types) and gfp != 'only': raise ValueError('gfp must be boolean or "only". Got %s' % gfp) if cmap == 'interactive': - cmap = ('RdBu_r', True) + cmap = (None, True) elif not isinstance(cmap, tuple): cmap = (cmap, True) scalings = _handle_default('scalings', scalings) From b6ee141a36d4852d5fdd2f16c57a7770544a77a2 Mon Sep 17 00:00:00 2001 From: jaeilepp Date: Thu, 16 Jun 2016 13:38:43 +0200 Subject: [PATCH 14/19] Interactive cbar for some functions. Docs. Bug fixes. --- mne/decoding/base.py | 34 +++++++-- mne/decoding/csp.py | 34 +++++++-- mne/epochs.py | 13 +++- mne/preprocessing/ica.py | 16 +++- mne/time_frequency/tfr.py | 14 +++- mne/viz/topomap.py | 157 ++++++++++++++++++++++++++++---------- mne/viz/utils.py | 8 +- 7 files changed, 211 insertions(+), 65 deletions(-) diff --git a/mne/decoding/base.py b/mne/decoding/base.py index 8e65dcb48ab..038547ac3c0 100644 --- a/mne/decoding/base.py +++ b/mne/decoding/base.py @@ -368,9 +368,20 @@ def plot_patterns(self, info, times=None, ch_type=None, layout=None, If None, the maximum absolute value is used. If vmin is None, but vmax is not, defaults to np.min(data). If callable, the output equals vmax(data). - cmap : matplotlib colormap - Colormap. For magnetometers and eeg defaults to 'RdBu_r', else - 'Reds'. + cmap : matplotlib colormap | (colormap, bool) | 'interactive' | None + Colormap to use. If tuple, the first value indicates the colormap + to use and the second value is a boolean defining interactivity. In + interactive mode the colors are adjustable by clicking and dragging + the colorbar with left and right mouse button. Left mouse button + moves the scale up and down and right mouse button adjusts the + range. Hitting space bar resets the range. Up and down arrows can + be used to change the colormap. If None, 'Reds' is used for all + positive data, otherwise defaults to 'RdBu_r'. If 'interactive', + translates to (None, True). Defaults to 'RdBu_r'. + + .. warning:: Interactive mode works smoothly only for a small + amount of topomaps. + sensors : bool | str Add markers for sensor locations to the plot. Accepts matplotlib plot format string (e.g., 'r+' for red plusses). If True, @@ -518,9 +529,20 @@ def plot_filters(self, info, times=None, ch_type=None, layout=None, If None, the maximum absolute value is used. If vmin is None, but vmax is not, defaults to np.min(data). If callable, the output equals vmax(data). - cmap : matplotlib colormap - Colormap. For magnetometers and eeg defaults to 'RdBu_r', else - 'Reds'. + cmap : matplotlib colormap | (colormap, bool) | 'interactive' | None + Colormap to use. If tuple, the first value indicates the colormap + to use and the second value is a boolean defining interactivity. In + interactive mode the colors are adjustable by clicking and dragging + the colorbar with left and right mouse button. Left mouse button + moves the scale up and down and right mouse button adjusts the + range. Hitting space bar resets the range. Up and down arrows can + be used to change the colormap. If None, 'Reds' is used for all + positive data, otherwise defaults to 'RdBu_r'. If 'interactive', + translates to (None, True). Defaults to 'RdBu_r'. + + .. warning:: Interactive mode works smoothly only for a small + amount of topomaps. + sensors : bool | str Add markers for sensor locations to the plot. Accepts matplotlib plot format string (e.g., 'r+' for red plusses). If True, diff --git a/mne/decoding/csp.py b/mne/decoding/csp.py index 74294bb00a5..8979ea07634 100644 --- a/mne/decoding/csp.py +++ b/mne/decoding/csp.py @@ -236,9 +236,20 @@ def plot_patterns(self, info, components=None, ch_type=None, layout=None, If None, the maximum absolute value is used. If vmin is None, but vmax is not, defaults to np.min(data). If callable, the output equals vmax(data). - cmap : matplotlib colormap - Colormap. For magnetometers and eeg defaults to 'RdBu_r', else - 'Reds'. + cmap : matplotlib colormap | (colormap, bool) | 'interactive' | None + Colormap to use. If tuple, the first value indicates the colormap + to use and the second value is a boolean defining interactivity. In + interactive mode the colors are adjustable by clicking and dragging + the colorbar with left and right mouse button. Left mouse button + moves the scale up and down and right mouse button adjusts the + range. Hitting space bar resets the range. Up and down arrows can + be used to change the colormap. If None, 'Reds' is used for all + positive data, otherwise defaults to 'RdBu_r'. If 'interactive', + translates to (None, True). Defaults to 'RdBu_r'. + + .. warning:: Interactive mode works smoothly only for a small + amount of topomaps. + sensors : bool | str Add markers for sensor locations to the plot. Accepts matplotlib plot format string (e.g., 'r+' for red plusses). If True, @@ -381,9 +392,20 @@ def plot_filters(self, info, components=None, ch_type=None, layout=None, If None, the maximum absolute value is used. If vmin is None, but vmax is not, defaults to np.min(data). If callable, the output equals vmax(data). - cmap : matplotlib colormap - Colormap. For magnetometers and eeg defaults to 'RdBu_r', else - 'Reds'. + cmap : matplotlib colormap | (colormap, bool) | 'interactive' | None + Colormap to use. If tuple, the first value indicates the colormap + to use and the second value is a boolean defining interactivity. In + interactive mode the colors are adjustable by clicking and dragging + the colorbar with left and right mouse button. Left mouse button + moves the scale up and down and right mouse button adjusts the + range. Hitting space bar resets the range. Up and down arrows can + be used to change the colormap. If None, 'Reds' is used for all + positive data, otherwise defaults to 'RdBu_r'. If 'interactive', + translates to (None, True). Defaults to 'RdBu_r'. + + .. warning:: Interactive mode works smoothly only for a small + amount of topomaps. + sensors : bool | str Add markers for sensor locations to the plot. Accepts matplotlib plot format string (e.g., 'r+' for red plusses). If True, diff --git a/mne/epochs.py b/mne/epochs.py index 9818effadce..f1fd0fea573 100644 --- a/mne/epochs.py +++ b/mne/epochs.py @@ -944,9 +944,16 @@ def plot_psd_topomap(self, bands=None, vmin=None, vmax=None, proj=False, file is inferred from the data; if no appropriate layout file was found, the layout is automatically generated from the sensor locations. - cmap : matplotlib colormap - Colormap. For magnetometers and eeg defaults to 'RdBu_r', else - 'Reds'. + cmap : matplotlib colormap | (colormap, bool) | 'interactive' | None + Colormap to use. If tuple, the first value indicates the colormap + to use and the second value is a boolean defining interactivity. In + interactive mode the colors are adjustable by clicking and dragging + the colorbar with left and right mouse button. Left mouse button + moves the scale up and down and right mouse button adjusts the + range. Hitting space bar resets the range. Up and down arrows can + be used to change the colormap. If None (default), 'Reds' is used + for all positive data, otherwise defaults to 'RdBu_r'. If + 'interactive', translates to (None, True). agg_fun : callable The function used to aggregate over frequencies. Defaults to np.sum. if normalize is True, else np.mean. diff --git a/mne/preprocessing/ica.py b/mne/preprocessing/ica.py index f745e85151c..c0ef63f99ba 100644 --- a/mne/preprocessing/ica.py +++ b/mne/preprocessing/ica.py @@ -1353,8 +1353,20 @@ def plot_components(self, picks=None, ch_type=None, res=64, layout=None, If None, the maximum absolute value is used. If vmin is None, but vmax is not, defaults to np.min(data). If callable, the output equals vmax(data). - cmap : matplotlib colormap - Colormap. + cmap : matplotlib colormap | (colormap, bool) | 'interactive' | None + Colormap to use. If tuple, the first value indicates the colormap + to use and the second value is a boolean defining interactivity. In + interactive mode the colors are adjustable by clicking and dragging + the colorbar with left and right mouse button. Left mouse button + moves the scale up and down and right mouse button adjusts the + range. Hitting space bar resets the range. Up and down arrows can + be used to change the colormap. If None, 'Reds' is used for all + positive data, otherwise defaults to 'RdBu_r'. If 'interactive', + translates to (None, True). Defaults to 'RdBu_r'. + + .. warning:: Interactive mode works smoothly only for a small + amount of topomaps. + sensors : bool | str Add markers for sensor locations to the plot. Accepts matplotlib plot format string (e.g., 'r+' for red plusses). If True, a circle diff --git a/mne/time_frequency/tfr.py b/mne/time_frequency/tfr.py index 1057cfd2ded..cdc3c883493 100644 --- a/mne/time_frequency/tfr.py +++ b/mne/time_frequency/tfr.py @@ -1027,10 +1027,16 @@ def plot_topomap(self, tmin=None, tmax=None, fmin=None, fmax=None, The value specifying the upper bound of the color range. If None, the maximum value is used. If callable, the output equals vmax(data). Defaults to None. - cmap : matplotlib colormap | None - Colormap. If None and the plotted data is all positive, defaults to - 'Reds'. If None and data contains also negative values, defaults to - 'RdBu_r'. Defaults to None. + cmap : matplotlib colormap | (colormap, bool) | 'interactive' | None + Colormap to use. If tuple, the first value indicates the colormap + to use and the second value is a boolean defining interactivity. In + interactive mode the colors are adjustable by clicking and dragging + the colorbar with left and right mouse button. Left mouse button + moves the scale up and down and right mouse button adjusts the + range. Hitting space bar resets the range. Up and down arrows can + be used to change the colormap. If None (default), 'Reds' is used + for all positive data, otherwise defaults to 'RdBu_r'. If + 'interactive', translates to (None, True). sensors : bool | str Add markers for sensor locations to the plot. Accepts matplotlib plot format string (e.g., 'r+' for red plusses). If True, a circle diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py index b6e61efd76c..1cd6ad3e878 100644 --- a/mne/viz/topomap.py +++ b/mne/viz/topomap.py @@ -23,7 +23,7 @@ from ..utils import _clean_names, _time_mask, verbose, logger, warn from .utils import (tight_layout, _setup_vmin_vmax, _prepare_trellis, _check_delayed_ssp, _draw_proj_checkbox, figure_nobar, - plt_show, _process_times) + plt_show, _process_times, DraggableColorbar) from ..time_frequency import psd_multitaper from ..defaults import _handle_default from ..channels.layout import _find_topomap_coords @@ -135,9 +135,16 @@ def plot_projs_topomap(projs, layout=None, cmap=None, sensors=True, Layout instance specifying sensor positions (does not need to be specified for Neuromag data). Or a list of Layout if projections are from different sensor types. - cmap : matplotlib colormap | None - Colormap to use. If None, 'Reds' is used for all positive data, - otherwise defaults to 'RdBu_r'. + cmap : matplotlib colormap | (colormap, bool) | 'interactive' | None + Colormap to use. If tuple, the first value indicates the colormap to + use and the second value is a boolean defining interactivity. In + interactive mode (only works if ``colorbar=True``) the colors are + adjustable by clicking and dragging the colorbar with left and right + mouse button. Left mouse button moves the scale up and down and right + mouse button adjusts the range. Hitting space bar resets the range. Up + and down arrows can be used to change the colormap. If None (default), + 'Reds' is used for all positive data, otherwise defaults to 'RdBu_r'. + If 'interactive', translates to (None, True). sensors : bool | str Add markers for sensor locations to the plot. Accepts matplotlib plot format string (e.g., 'r+' for red plusses). If True, a circle will be @@ -182,7 +189,7 @@ def plot_projs_topomap(projs, layout=None, cmap=None, sensors=True, .. versionadded:: 0.9.0 """ import matplotlib.pyplot as plt - + from mpl_toolkits.axes_grid1 import make_axes_locatable if layout is None: from ..channels import read_layout layout = read_layout('Vectorview-all') @@ -194,6 +201,10 @@ def plot_projs_topomap(projs, layout=None, cmap=None, sensors=True, nrows = math.floor(math.sqrt(n_projs)) ncols = math.ceil(n_projs / nrows) + if cmap == 'interactive': + cmap = (None, True) + elif not isinstance(cmap, tuple): + cmap = (cmap, True) if axes is None: plt.figure() axes = list() @@ -232,12 +243,16 @@ def plot_projs_topomap(projs, layout=None, cmap=None, sensors=True, break if len(idx): - plot_topomap(data, pos[:, :2], vmax=None, cmap=cmap, - sensors=sensors, res=res, axes=axes[proj_idx], - outlines=outlines, contours=contours, - image_interp=image_interp, show=False) + im = plot_topomap(data, pos[:, :2], vmax=None, cmap=cmap[0], + sensors=sensors, res=res, axes=axes[proj_idx], + outlines=outlines, contours=contours, + image_interp=image_interp, show=False)[0] if colorbar: - plt.colorbar() + divider = make_axes_locatable(axes[proj_idx]) + cax = divider.append_axes("right", size="5%", pad=0.05) + cbar = plt.colorbar(im, cax=cax, cmap=cmap) + if cmap[1]: + axes[proj_idx].CB = DraggableColorbar(cbar, im) else: raise RuntimeError('Cannot find a proper layout for projection %s' % proj['desc']) @@ -785,8 +800,20 @@ def plot_ica_components(ica, picks=None, ch_type=None, res=64, The value specifying the upper bound of the color range. If None, the maximum absolute value is used. If callable, the output equals vmax(data). Defaults to None. - cmap : matplotlib colormap - Colormap. + cmap : matplotlib colormap | (colormap, bool) | 'interactive' | None + Colormap to use. If tuple, the first value indicates the colormap to + use and the second value is a boolean defining interactivity. In + interactive mode the colors are adjustable by clicking and dragging the + colorbar with left and right mouse button. Left mouse button moves the + scale up and down and right mouse button adjusts the range. Hitting + space bar resets the range. Up and down arrows can be used to change + the colormap. If None, 'Reds' is used for all positive data, + otherwise defaults to 'RdBu_r'. If 'interactive', translates to + (None, True). Defaults to 'RdBu_r'. + + .. warning:: Interactive mode works smoothly only for a small amount + of topomaps. + sensors : bool | str Add markers for sensor locations to the plot. Accepts matplotlib plot format string (e.g., 'r+' for red plusses). If True, a circle @@ -835,9 +862,9 @@ def plot_ica_components(ica, picks=None, ch_type=None, res=64, figs = [] for k in range(0, n_components, p): picks = range(k, min(k + p, n_components)) - fig = plot_ica_components(ica, picks=picks, - ch_type=ch_type, res=res, layout=layout, - vmax=vmax, cmap=cmap, sensors=sensors, + fig = plot_ica_components(ica, picks=picks, ch_type=ch_type, + res=res, layout=layout, vmax=vmax, + cmap=cmap, sensors=sensors, colorbar=colorbar, title=title, show=show, outlines=outlines, contours=contours, @@ -848,6 +875,15 @@ def plot_ica_components(ica, picks=None, ch_type=None, res=64, picks = [picks] ch_type = _get_ch_type(ica, ch_type) + if cmap == 'interactive': + cmap = ('RdBu_r', True) + elif not isinstance(cmap, tuple): + if len(picks) > 2: + warn('Disabling interactive colorbar for multiple axes. Turn ' + 'interactivity on explicitly by passing cmap as a tuple.') + cmap = (cmap, False) + else: + cmap = (cmap, True) data = np.dot(ica.mixing_matrix_[:, picks].T, ica.pca_components_[:ica.n_components_]) @@ -879,7 +915,7 @@ def plot_ica_components(ica, picks=None, ch_type=None, res=64, data_ = _merge_grad_data(data_) if merge_grads else data_ vmin_, vmax_ = _setup_vmin_vmax(data_, vmin, vmax) im = plot_topomap(data_.flatten(), pos, vmin=vmin_, vmax=vmax_, - res=res, axes=ax, cmap=cmap, outlines=outlines, + res=res, axes=ax, cmap=cmap[0], outlines=outlines, image_mask=image_mask, contours=contours, image_interp=image_interp, show=False)[0] if colorbar: @@ -889,6 +925,8 @@ def plot_ica_components(ica, picks=None, ch_type=None, res=64, cbar.ax.tick_params(labelsize=12) cbar.set_ticks((vmin_, vmax_)) cbar.ax.set_title('AU', fontsize=10) + if cmap[1]: + ax.CB = DraggableColorbar(cbar, im) _hide_frame(ax) tight_layout(fig=fig) fig.subplots_adjust(top=0.95) @@ -954,10 +992,16 @@ def plot_tfr_topomap(tfr, tmin=None, tmax=None, fmin=None, fmax=None, The value specifying the upper bound of the color range. If None, the maximum value is used. If callable, the output equals vmax(data). Defaults to None. - cmap : matplotlib colormap | None - Colormap. If None and the plotted data is all positive, defaults to - 'Reds'. If None and data contains also negative values, defaults to - 'RdBu_r'. Defaults to None. + cmap : matplotlib colormap | (colormap, bool) | 'interactive' | None + Colormap to use. If tuple, the first value indicates the colormap to + use and the second value is a boolean defining interactivity. In + interactive mode the colors are adjustable by clicking and dragging the + colorbar with left and right mouse button. Left mouse button moves the + scale up and down and right mouse button adjusts the range. Hitting + space bar resets the range. Up and down arrows can be used to change + the colormap. If None (default), 'Reds' is used for all positive data, + otherwise defaults to 'RdBu_r'. If 'interactive', translates to + (None, True). sensors : bool | str Add markers for sensor locations to the plot. Accepts matplotlib plot format string (e.g., 'r+' for red plusses). If True, a circle will @@ -1044,8 +1088,10 @@ def plot_tfr_topomap(tfr, tmin=None, tmax=None, fmin=None, fmax=None, norm = False if np.min(data) < 0 else True vmin, vmax = _setup_vmin_vmax(data, vmin, vmax, norm) - if cmap is None: - cmap = 'Reds' if norm else 'RdBu_r' + if cmap is None or cmap == 'interactive': + cmap = ('Reds', True) if norm else ('RdBu_r', True) + elif not isinstance(cmap, tuple): + cmap = (cmap, True) if axes is None: fig = plt.figure() @@ -1061,21 +1107,23 @@ def plot_tfr_topomap(tfr, tmin=None, tmax=None, fmin=None, fmax=None, fig_wrapper = list() selection_callback = partial(_onselect, tfr=tfr, pos=pos, ch_type=ch_type, itmin=itmin, itmax=itmax, ifmin=ifmin, - ifmax=ifmax, cmap=cmap, fig=fig_wrapper, + ifmax=ifmax, cmap=cmap[0], fig=fig_wrapper, layout=layout) im, _ = plot_topomap(data[:, 0], pos, vmin=vmin, vmax=vmax, - axes=ax, cmap=cmap, image_interp='bilinear', + axes=ax, cmap=cmap[0], image_interp='bilinear', contours=False, names=names, show_names=show_names, show=False, onselect=selection_callback) if colorbar: divider = make_axes_locatable(ax) cax = divider.append_axes("right", size="5%", pad=0.05) - cbar = plt.colorbar(im, cax=cax, format=cbar_fmt, cmap=cmap) + cbar = plt.colorbar(im, cax=cax, format=cbar_fmt, cmap=cmap[0]) cbar.set_ticks((vmin, vmax)) cbar.ax.tick_params(labelsize=12) cbar.ax.set_title('AU') + if cmap[1]: + ax.CB = DraggableColorbar(cbar, im) plt_show(show) return fig @@ -1373,7 +1421,6 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None, cbar = fig.colorbar(images[-1], ax=cax, cax=cax, format=cbar_fmt) cbar.set_ticks([cbar.vmin, 0, cbar.vmax]) if cmap[1]: - from .utils import DraggableColorbar for im in images: im.axes.CB = DraggableColorbar(cbar, im) @@ -1390,8 +1437,8 @@ def plot_evoked_topomap(evoked, times="auto", ch_type=None, layout=None, return fig -def _plot_topomap_multi_cbar(data, pos, ax, title=None, unit=None, - vmin=None, vmax=None, cmap='RdBu_r', +def _plot_topomap_multi_cbar(data, pos, ax, title=None, unit=None, vmin=None, + vmax=None, cmap=None, outlines='head', colorbar=False, cbar_fmt='%3.3f'): """Aux Function""" import matplotlib.pyplot as plt @@ -1401,11 +1448,15 @@ def _plot_topomap_multi_cbar(data, pos, ax, title=None, unit=None, vmin = np.min(data) if vmin is None else vmin vmax = np.max(data) if vmax is None else vmax + if cmap == 'interactive': + cmap = (None, True) + elif not isinstance(cmap, tuple): + cmap = (cmap, True) if title is not None: ax.set_title(title, fontsize=10) im, _ = plot_topomap(data, pos, vmin=vmin, vmax=vmax, axes=ax, - cmap=cmap, image_interp='bilinear', contours=False, - show=False) + cmap=cmap[0], image_interp='bilinear', contours=False, + outlines=outlines, show=False) if colorbar is True: divider = make_axes_locatable(ax) @@ -1415,6 +1466,8 @@ def _plot_topomap_multi_cbar(data, pos, ax, title=None, unit=None, if unit is not None: cbar.ax.set_title(unit, fontsize=8) cbar.ax.tick_params(labelsize=8) + if cmap[1]: + ax.CB = DraggableColorbar(cbar, im) @verbose @@ -1476,9 +1529,16 @@ def plot_epochs_psd_topomap(epochs, bands=None, vmin=None, vmax=None, file is inferred from the data; if no appropriate layout file was found, the layout is automatically generated from the sensor locations. - cmap : matplotlib colormap - Colormap. For magnetometers and eeg defaults to 'RdBu_r', else - 'Reds'. + cmap : matplotlib colormap | (colormap, bool) | 'interactive' | None + Colormap to use. If tuple, the first value indicates the colormap to + use and the second value is a boolean defining interactivity. In + interactive mode the colors are adjustable by clicking and dragging the + colorbar with left and right mouse button. Left mouse button moves the + scale up and down and right mouse button adjusts the range. Hitting + space bar resets the range. Up and down arrows can be used to change + the colormap. If None (default), 'Reds' is used for all positive data, + otherwise defaults to 'RdBu_r'. If 'interactive', translates to + (None, True). agg_fun : callable The function used to aggregate over frequencies. Defaults to np.sum. if normalize is True, else np.mean. @@ -1539,8 +1599,8 @@ def plot_epochs_psd_topomap(epochs, bands=None, vmin=None, vmax=None, def plot_psds_topomap( psds, freqs, pos, agg_fun=None, vmin=None, vmax=None, bands=None, - cmap='RdBu_r', dB=True, normalize=False, cbar_fmt='%0.3f', - outlines='head', show=True): + cmap=None, dB=True, normalize=False, cbar_fmt='%0.3f', outlines='head', + show=True): """Plot spatial maps of PSDs Parameters @@ -1569,9 +1629,16 @@ def plot_psds_topomap( bands = [(0, 4, 'Delta'), (4, 8, 'Theta'), (8, 12, 'Alpha'), (12, 30, 'Beta'), (30, 45, 'Gamma')] - cmap : matplotlib colormap - Colormap. For magnetometers and eeg defaults to 'RdBu_r', else - 'Reds'. + cmap : matplotlib colormap | (colormap, bool) | 'interactive' | None + Colormap to use. If tuple, the first value indicates the colormap to + use and the second value is a boolean defining interactivity. In + interactive mode the colors are adjustable by clicking and dragging the + colorbar with left and right mouse button. Left mouse button moves the + scale up and down and right mouse button adjusts the range. Hitting + space bar resets the range. Up and down arrows can be used to change + the colormap. If None (default), 'Reds' is used for all positive data, + otherwise defaults to 'RdBu_r'. If 'interactive', translates to + (None, True). dB : bool If True, transform data to decibels (with ``10 * np.log10(data)``) following the application of `agg_fun`. Only valid if normalize is @@ -1631,8 +1698,8 @@ def plot_psds_topomap( else: unit = 'power' - _plot_topomap_multi_cbar(data, pos, ax, title=title, - vmin=vmin, vmax=vmax, cmap=cmap, + _plot_topomap_multi_cbar(data, pos, ax, title=title, vmin=vmin, + vmax=vmax, cmap=cmap, outlines=outlines, colorbar=True, unit=unit, cbar_fmt=cbar_fmt) tight_layout(fig=fig) fig.canvas.draw() @@ -1724,8 +1791,14 @@ def _onselect(eclick, erelease, tfr, pos, ch_type, itmin, itmax, ifmin, ifmax, if not plt.fignum_exists(fig[0].number): fig[0] = figure_nobar() ax = fig[0].add_subplot(111) - itmax = min(itmax, len(tfr.times) - 1) - ifmax = min(ifmax, len(tfr.freqs) - 1) + itmax = len(tfr.times) - 1 if itmax is None else min(itmax, + len(tfr.times) - 1) + ifmax = len(tfr.freqs) - 1 if ifmax is None else min(ifmax, + len(tfr.freqs) - 1) + if itmin is None: + itmin = 0 + if ifmin is None: + ifmin = 0 extent = (tfr.times[itmin] * 1e3, tfr.times[itmax] * 1e3, tfr.freqs[ifmin], tfr.freqs[ifmax]) diff --git a/mne/viz/utils.py b/mne/viz/utils.py index 606c165a224..55b94a5e81e 100644 --- a/mne/viz/utils.py +++ b/mne/viz/utils.py @@ -1429,5 +1429,9 @@ def on_release(self, event): def on_scroll(self, event): """Callback for scroll.""" - event.key = 'down' if event.step < 0 else 'up' - self.key_press(event) + scale = 1.1 if event.step < 0 else 1. / 1.1 + self.cbar.norm.vmin *= scale + self.cbar.norm.vmax *= scale + self.cbar.draw_all() + self.mappable.set_norm(self.cbar.norm) + self.cbar.patch.figure.canvas.draw() From eaa51566d4a0cfa11459e594ce56daedb1054885 Mon Sep 17 00:00:00 2001 From: jaeilepp Date: Thu, 16 Jun 2016 14:44:58 +0200 Subject: [PATCH 15/19] Coverage. --- mne/viz/tests/test_ica.py | 3 ++- mne/viz/tests/test_topomap.py | 3 ++- mne/viz/topomap.py | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/mne/viz/tests/test_ica.py b/mne/viz/tests/test_ica.py index 4c3aa31a649..21fb6fc8b5b 100644 --- a/mne/viz/tests/test_ica.py +++ b/mne/viz/tests/test_ica.py @@ -65,7 +65,8 @@ def test_plot_ica_components(): warnings.simplefilter('always', UserWarning) with warnings.catch_warnings(record=True): for components in [0, [0], [0, 1], [0, 1] * 2, None]: - ica.plot_components(components, image_interp='bilinear', res=16) + ica.plot_components(components, image_interp='bilinear', res=16, + colorbar=True) ica.info = None assert_raises(ValueError, ica.plot_components, 1) assert_raises(RuntimeError, ica.plot_components, 1, ch_type='mag') diff --git a/mne/viz/tests/test_topomap.py b/mne/viz/tests/test_topomap.py index d0b4ee0ee31..41e0e77f788 100644 --- a/mne/viz/tests/test_topomap.py +++ b/mne/viz/tests/test_topomap.py @@ -152,7 +152,7 @@ def get_texts(p): warnings.simplefilter('always') projs = read_proj(ecg_fname) projs = [pp for pp in projs if pp['desc'].lower().find('eeg') < 0] - plot_projs_topomap(projs, res=res) + plot_projs_topomap(projs, res=res, colorbar=True) plt.close('all') ax = plt.subplot(111) plot_projs_topomap([projs[0]], res=res, axes=ax) # test axes param @@ -294,6 +294,7 @@ def test_plot_tfr_topomap(): erelease.xdata = 0.3 erelease.ydata = 0.2 pos = [[0.11, 0.11], [0.25, 0.5], [0.0, 0.2], [0.2, 0.39]] + _onselect(eclick, erelease, tfr, pos, 'grad', 1, 3, 1, 3, 'RdBu_r', list()) _onselect(eclick, erelease, tfr, pos, 'mag', 1, 3, 1, 3, 'RdBu_r', list()) tfr._onselect(eclick, erelease, None, 'mean', None) plt.close('all') diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py index 1cd6ad3e878..7461d1ee120 100644 --- a/mne/viz/topomap.py +++ b/mne/viz/topomap.py @@ -1771,7 +1771,6 @@ def _onselect(eclick, erelease, tfr, pos, ch_type, itmin, itmax, ifmin, ifmax, data = np.mean(data[indices, ifmin:ifmax, itmin:itmax], axis=0) chs = [tfr.ch_names[picks[x]] for x in indices] elif ch_type == 'grad': - picks = pick_types(tfr.info, meg=ch_type, ref_meg=False) from ..channels.layout import _pair_grad_sensors grads = _pair_grad_sensors(tfr.info, layout=layout, topomap_coords=False) From e75c9e67167a84d9572c4611c13c9a10855ab9d1 Mon Sep 17 00:00:00 2001 From: jaeilepp Date: Thu, 16 Jun 2016 15:31:14 +0200 Subject: [PATCH 16/19] More coverage. --- mne/viz/tests/test_topomap.py | 5 +++-- mne/viz/topomap.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mne/viz/tests/test_topomap.py b/mne/viz/tests/test_topomap.py index 41e0e77f788..c0f30a1da96 100644 --- a/mne/viz/tests/test_topomap.py +++ b/mne/viz/tests/test_topomap.py @@ -286,8 +286,7 @@ def test_plot_tfr_topomap(): eclick = mpl.backend_bases.MouseEvent('button_press_event', plt.gcf().canvas, 0, 0, 1) - eclick.xdata = 0.1 - eclick.ydata = 0.1 + eclick.xdata = eclick.ydata = 0.1 eclick.inaxes = plt.gca() erelease = mpl.backend_bases.MouseEvent('button_release_event', plt.gcf().canvas, 0.9, 0.9, 1) @@ -296,6 +295,8 @@ def test_plot_tfr_topomap(): pos = [[0.11, 0.11], [0.25, 0.5], [0.0, 0.2], [0.2, 0.39]] _onselect(eclick, erelease, tfr, pos, 'grad', 1, 3, 1, 3, 'RdBu_r', list()) _onselect(eclick, erelease, tfr, pos, 'mag', 1, 3, 1, 3, 'RdBu_r', list()) + eclick.xdata = eclick.ydata = 0. + erelease.xdata = erelease.ydata = 0.9 tfr._onselect(eclick, erelease, None, 'mean', None) plt.close('all') diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py index 7461d1ee120..c6514b3d9d8 100644 --- a/mne/viz/topomap.py +++ b/mne/viz/topomap.py @@ -1829,8 +1829,8 @@ def _prepare_topomap(pos, ax): def _hide_frame(ax): """Helper to hide axis frame for topomaps.""" - ax.set_xticks([]) - ax.set_yticks([]) + ax.xaxis.set_ticks([]) + ax.yaxis.set_ticks([]) ax.set_frame_on(False) From 8262410bdc307df3fe03861a588387c0dba5055f Mon Sep 17 00:00:00 2001 From: jaeilepp Date: Fri, 17 Jun 2016 09:03:18 +0200 Subject: [PATCH 17/19] Fix. --- mne/time_frequency/tfr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/time_frequency/tfr.py b/mne/time_frequency/tfr.py index cdc3c883493..358783cb784 100644 --- a/mne/time_frequency/tfr.py +++ b/mne/time_frequency/tfr.py @@ -791,7 +791,7 @@ def _onselect(self, eclick, erelease, baseline, mode, layout): elif len(types) == 0: return # Don't draw a figure for nothing. fig = figure_nobar() - fig.suptitle('{:.2f} s - {:.2f} s, {:.2f} Hz - {:.2f} Hz'.format( + fig.suptitle('{0:.2f} s - {1:.2f} s, {2:.2f} Hz - {3:.2f} Hz'.format( tmin, tmax, fmin, fmax), y=0.04) for idx, ch_type in enumerate(types): ax = plt.subplot(1, len(types), idx + 1) From 696721dd1c5f30f89be84f8c032927e3e8e10719 Mon Sep 17 00:00:00 2001 From: jaeilepp Date: Fri, 17 Jun 2016 10:55:53 +0200 Subject: [PATCH 18/19] Refactoring. --- mne/channels/layout.py | 14 +++++++++++--- mne/time_frequency/tfr.py | 6 +++--- mne/viz/evoked.py | 16 ++++++++-------- mne/viz/tests/test_evoked.py | 2 +- mne/viz/topomap.py | 1 + mne/viz/utils.py | 13 ------------- 6 files changed, 24 insertions(+), 28 deletions(-) diff --git a/mne/channels/layout.py b/mne/channels/layout.py index 911add1aca1..c3d874355e1 100644 --- a/mne/channels/layout.py +++ b/mne/channels/layout.py @@ -20,7 +20,7 @@ from ..io.pick import pick_types from ..io.constants import FIFF from ..io.meas_info import Info -from ..utils import _clean_names +from ..utils import _clean_names, warn from ..externals.six.moves import map @@ -705,7 +705,8 @@ def _topo_to_sphere(pos, eegs): return np.column_stack([xs, ys, zs]) -def _pair_grad_sensors(info, layout=None, topomap_coords=True, exclude='bads'): +def _pair_grad_sensors(info, layout=None, topomap_coords=True, exclude='bads', + raise_error=True): """Find the picks for pairing grad channels Parameters @@ -720,6 +721,9 @@ def _pair_grad_sensors(info, layout=None, topomap_coords=True, exclude='bads'): exclude : list of str | str List of channels to exclude. If empty do not exclude any (default). If 'bads', exclude channels in info['bads']. Defaults to 'bads'. + raise_error : bool + Whether to raise an error when no pairs are found. If False, raises a + warning. Returns ------- @@ -741,7 +745,11 @@ def _pair_grad_sensors(info, layout=None, topomap_coords=True, exclude='bads'): pairs[key].append(ch) pairs = [p for p in pairs.values() if len(p) == 2] if len(pairs) == 0: - raise ValueError("No 'grad' channel pairs found.") + if raise_error: + raise ValueError("No 'grad' channel pairs found.") + else: + warn("No 'grad' channel pairs found.") + return list() # find the picks corresponding to the grad channels grad_chs = sum(pairs, []) diff --git a/mne/time_frequency/tfr.py b/mne/time_frequency/tfr.py index 358783cb784..e25380e2054 100644 --- a/mne/time_frequency/tfr.py +++ b/mne/time_frequency/tfr.py @@ -20,13 +20,13 @@ from ..parallel import parallel_func from ..utils import logger, verbose, _time_mask, warn, check_fname from ..channels.channels import ContainsMixin, UpdateChannelsMixin +from ..channels.layout import _pair_grad_sensors from ..io.pick import pick_info, pick_types from ..io.meas_info import Info from .multitaper import dpss_windows from ..viz.utils import figure_nobar, plt_show from ..externals.h5io import write_hdf5, read_hdf5 from ..externals.six import string_types -from ..viz.utils import _check_grad_pairs def _get_data(inst, return_itc): @@ -785,8 +785,8 @@ def _onselect(self, eclick, erelease, baseline, mode, layout): if 'mag' in self: types.append('mag') if 'grad' in self: - chs = _check_grad_pairs(self.info) - if len(chs) >= 2: + if len(_pair_grad_sensors(self.info, topomap_coords=False, + raise_error=False)) >= 2: types.append('grad') elif len(types) == 0: return # Don't draw a figure for nothing. diff --git a/mne/viz/evoked.py b/mne/viz/evoked.py index 069f428685e..11998e5d9a2 100644 --- a/mne/viz/evoked.py +++ b/mne/viz/evoked.py @@ -18,8 +18,7 @@ from ..externals.six import string_types from ..defaults import _handle_default from .utils import (_draw_proj_checkbox, tight_layout, _check_delayed_ssp, - plt_show, _process_times, DraggableColorbar, - _check_grad_pairs) + plt_show, _process_times, DraggableColorbar) from ..utils import logger, _clean_names, warn from ..fixes import partial from ..io.pick import pick_info @@ -27,6 +26,7 @@ from .topomap import (_prepare_topo_plot, plot_topomap, _check_outlines, _draw_outlines, _prepare_topomap, _topomap_animation) from ..channels import find_layout +from ..channels.layout import _pair_grad_sensors def _butterfly_onpick(event, params): @@ -72,12 +72,12 @@ def _butterfly_onselect(xmin, xmax, ch_types, evoked, text=None): """Function for drawing topomaps from the selected area.""" import matplotlib.pyplot as plt ch_types = [type for type in ch_types if type in ('eeg', 'grad', 'mag')] - if 'grad' in ch_types: - chs = _check_grad_pairs(evoked.info) - if len(chs) < 2: - ch_types.remove('grad') - if len(ch_types) == 0: - return + if ('grad' in ch_types and + len(_pair_grad_sensors(evoked.info, topomap_coords=False, + raise_error=False)) < 2): + ch_types.remove('grad') + if len(ch_types) == 0: + return vert_lines = list() if text is not None: diff --git a/mne/viz/tests/test_evoked.py b/mne/viz/tests/test_evoked.py index 6c98e453615..7e733340273 100644 --- a/mne/viz/tests/test_evoked.py +++ b/mne/viz/tests/test_evoked.py @@ -121,7 +121,7 @@ def test_plot_evoked(): plt.close('all') evoked.plot_topo() # should auto-find layout - _butterfly_onselect(0, 200, ['mag'], evoked) # test averaged topomap + _butterfly_onselect(0, 200, ['mag', 'grad'], evoked) plt.close('all') cov = read_cov(cov_fname) diff --git a/mne/viz/topomap.py b/mne/viz/topomap.py index c6514b3d9d8..66611518341 100644 --- a/mne/viz/topomap.py +++ b/mne/viz/topomap.py @@ -1829,6 +1829,7 @@ def _prepare_topomap(pos, ax): def _hide_frame(ax): """Helper to hide axis frame for topomaps.""" + ax.get_yticks() ax.xaxis.set_ticks([]) ax.yaxis.set_ticks([]) ax.set_frame_on(False) diff --git a/mne/viz/utils.py b/mne/viz/utils.py index 55b94a5e81e..8a6d95dfcb2 100644 --- a/mne/viz/utils.py +++ b/mne/viz/utils.py @@ -23,7 +23,6 @@ from ..defaults import _handle_default from ..io import show_fiff, Info from ..io.pick import channel_type, channel_indices_by_type, pick_channels -from ..io.constants import FIFF from ..utils import verbose, set_config, warn from ..externals.six import string_types from ..fixes import _get_argrelmax @@ -869,18 +868,6 @@ def _setup_browser_offsets(params, n_channels): line.set_data(line._x, np.array(params['ax'].get_ylim())) -def _check_grad_pairs(info): - """Helper for checking gradiometer pairs.""" - if FIFF.FIFFV_COIL_VV_PLANAR_T1 in np.unique( - [ch['coil_type'] for ch in info['chs']]): - chs = [ch for ch in info['ch_names'] if - ch.startswith('MEG') and ch.endswith(('2', '3'))] - if len(chs) < 2: - warn('No grad pairs found. Cannot compute RMS.') - return chs - return list() - - class ClickableImage(object): """ From 5bf1674c1a8cd93419814405bd315cfc7c7a6664 Mon Sep 17 00:00:00 2001 From: jaeilepp Date: Wed, 29 Jun 2016 09:54:15 +0200 Subject: [PATCH 19/19] Modified test and tutorial. --- mne/viz/tests/test_epochs.py | 4 ++-- tutorials/plot_visualize_epochs.py | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/mne/viz/tests/test_epochs.py b/mne/viz/tests/test_epochs.py index 6adbbfed9da..0da767af1ec 100644 --- a/mne/viz/tests/test_epochs.py +++ b/mne/viz/tests/test_epochs.py @@ -136,8 +136,8 @@ def test_plot_epochs_image(): epochs = _get_epochs() epochs.plot_image(picks=[1, 2]) overlay_times = [0.1] - epochs.plot_image(order=[0], overlay_times=overlay_times) - epochs.plot_image(overlay_times=overlay_times) + epochs.plot_image(order=[0], overlay_times=overlay_times, vmin=0.01) + epochs.plot_image(overlay_times=overlay_times, vmin=-0.001, vmax=0.001) assert_raises(ValueError, epochs.plot_image, overlay_times=[0.1, 0.2]) assert_raises(ValueError, epochs.plot_image, diff --git a/tutorials/plot_visualize_epochs.py b/tutorials/plot_visualize_epochs.py index eca13d4bd08..a95ee17662e 100644 --- a/tutorials/plot_visualize_epochs.py +++ b/tutorials/plot_visualize_epochs.py @@ -44,8 +44,12 @@ # To plot individual channels as an image, where you see all the epochs at one # glance, you can use function :func:`mne.Epochs.plot_image`. It shows the # amplitude of the signal over all the epochs plus an average of the -# activation. -epochs.plot_image(97) +# activation. We explicitly set interactive colorbar on (it is also on by +# default for plotting functions with a colorbar except the topo plots). In +# interactive mode you can scale and change the colormap with mouse scroll and +# up/down arrow keys. You can also drag the colorbar with left/right mouse +# button. Hitting space bar resets the scale. +epochs.plot_image(97, cmap='interactive') # You also have functions for plotting channelwise information arranged into a # shape of the channel array. The image plotting uses automatic scaling by