From 58d178805802d5e1ff298c36c522cf4712bf7de5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin?= Date: Wed, 7 Sep 2022 13:45:08 +0300 Subject: [PATCH 1/5] [MRG, ENH] Use spatial_colors by default in Evoked.plot() (#11017) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adds a default 'auto' option for spatial_colors argument in Evoked.plot(). Which makes spatial_colors True if channel locations exists and False otherwise. Changes docstring in plot_evoked() to include string too Adds option chek to plot_evoked for spatial_colors argument * simplifies code in _plot_evoked() changes argument definition from str to 'auto' * Fixes style issue * - Adds changes to doc changes - Fixes comment in evoked.py * - Fix to the location check logic * - Adds logic for checking the number of picks, if number of picks is 1, spatial_colors 'auto' becomes False - Adds spatial_colors = False to relevant tests to avoid errors arising from the change in the default argument - Fixes latest.inc issue * - Adds check if picks is None - Removes some wrong arguments * - styling * [ENH] Use spatial_colors by default in Evoked.plot() #11017 Adds a default 'auto' option for spatial_colors argument in Evoked.plot(). Which makes spatial_colors True if channel locations exists and False otherwise. Moves logic for checking spatial_colors argument to a private function named _check_spatial_colors. Changes docstring in plot_evoked() to include string. Adds option check to plot_evoked for spatial_colors argument. Adds changes to doc changes Adds logic for checking the number of picks, if number of picks is 1, spatial_colors 'auto' becomes False Adds spatial_colors = False to relevant tests to avoid errors arising from the change in the default argument Adds check if picks length is one or picks is None * [ENH] Use spatial_colors by default in Evoked.plot() (#11017) Adds a default 'auto' option for spatial_colors argument in Evoked.plot(). Which makes spatial_colors True if channel locations exists and False otherwise. Moves logic for checking spatial_colors argument to a private function named _check_spatial_colors. Changes docstring in plot_evoked() to include string. Adds option check to plot_evoked for spatial_colors argument. Adds changes to doc changes Adds logic for checking the number of picks, if number of picks is 1, spatial_colors 'auto' becomes False Adds spatial_colors = False to relevant tests to avoid errors arising from the change in the default argument Adds check if picks length is one or picks is None * Update latest.inc fixes duplicate line * Update doc/changes/latest.inc Co-authored-by: Richard Höchenberger * Update mne/viz/evoked.py Co-authored-by: Richard Höchenberger * Update mne/viz/evoked.py Co-authored-by: Richard Höchenberger * Update doc/changes/latest.inc Co-authored-by: Richard Höchenberger --- doc/changes/latest.inc | 1 + doc/changes/names.inc | 2 ++ mne/evoked.py | 2 +- mne/viz/_proj.py | 3 ++- mne/viz/evoked.py | 25 ++++++++++++++++---- mne/viz/ica.py | 4 ++-- mne/viz/tests/test_evoked.py | 44 ++++++++++++++++++++---------------- 7 files changed, 53 insertions(+), 28 deletions(-) diff --git a/doc/changes/latest.inc b/doc/changes/latest.inc index 68907b1fb87..be02ca529f9 100644 --- a/doc/changes/latest.inc +++ b/doc/changes/latest.inc @@ -76,6 +76,7 @@ Bugs API changes ~~~~~~~~~~~ +- In meth:`mne.Evoked.plot`, the default value of the ``spatial_colors`` parameter has been changed to ``'auto'``, which will use spatial colors if channel locations are available (:gh:`11017` by :newcontrib:`Hüseyin Orkun Elmas`) - Starting with this release we now follow the Python convention of using ``FutureWarning`` instead of ``DeprecationWarning`` to signal user-facing changes to our API (:gh:`11120` by `Daniel McCloy`_) - The ``bands`` parameter of :meth:`mne.Epochs.plot_psd_topomap` now accepts :class:`dict` input; legacy :class:`tuple` input is supported, but discouraged for new code (:gh:`11050` by `Daniel McCloy`_) - The :func:`mne.head_to_mri` new function parameter ``kind`` default will change from ``'ras'`` to ``'mri'`` (:gh:`11185` by `Eric Larson`_) diff --git a/doc/changes/names.inc b/doc/changes/names.inc index bd28ffd383b..ed309767b30 100644 --- a/doc/changes/names.inc +++ b/doc/changes/names.inc @@ -174,6 +174,8 @@ .. _Hubert Banville: https://github.com/hubertjb +.. _Hüseyin Orkun Elmas: https://github.com/HuseyinOrkun + .. _Ilias Machairas: https://github.com/JungleHippo .. _Ivana Kojcic: https://github.com/ikojcic diff --git a/mne/evoked.py b/mne/evoked.py index a369a1b909e..48c12f946b7 100644 --- a/mne/evoked.py +++ b/mne/evoked.py @@ -374,7 +374,7 @@ def ch_names(self): def plot(self, picks=None, exclude='bads', unit=True, show=True, ylim=None, xlim='tight', proj=False, hline=None, units=None, scalings=None, titles=None, axes=None, gfp=False, window_title=None, - spatial_colors=False, zorder='unsorted', selectable=True, + spatial_colors='auto', zorder='unsorted', selectable=True, noise_cov=None, time_unit='s', sphere=None, *, highlight=None, verbose=None): return plot_evoked( diff --git a/mne/viz/_proj.py b/mne/viz/_proj.py index ade298c40b9..ee06ec51e3b 100644 --- a/mne/viz/_proj.py +++ b/mne/viz/_proj.py @@ -157,7 +157,8 @@ def plot_projs_joint(projs, evoked, picks_trace=None, *, topomap_kwargs=None, ch_traces = evoked.data[picks_trace] ch_traces -= np.mean(ch_traces, axis=1, keepdims=True) ch_traces /= np.abs(ch_traces).max() - _plot_evoked(this_evoked, picks='all', axes=[tr_ax], **pe_kwargs) + _plot_evoked(this_evoked, picks='all', axes=[tr_ax], **pe_kwargs, + spatial_colors=False) for line in tr_ax.lines: line.set(lw=0.5, zorder=3) for t in list(tr_ax.texts): diff --git a/mne/viz/evoked.py b/mne/viz/evoked.py index 310f0317cb6..fa080fc750b 100644 --- a/mne/viz/evoked.py +++ b/mne/viz/evoked.py @@ -193,6 +193,16 @@ def _plot_legend(pos, colors, axis, bads, outlines, loc, size=30): _draw_outlines(ax, outlines) +def _check_spatial_colors(info, picks, spatial_colors): + """Use spatial colors if channel locations exist.""" + if spatial_colors == 'auto': + if picks and len(picks) == 1: + spatial_colors = False + else: + spatial_colors = _check_ch_locs(info) + return spatial_colors + + def _plot_evoked(evoked, picks=None, exclude='bads', unit=True, show=True, ylim=None, proj=False, xlim='tight', hline=None, units=None, scalings=None, titles=None, axes=None, @@ -217,7 +227,8 @@ def _plot_evoked(evoked, picks=None, exclude='bads', unit=True, show=True, If True, draw at the end. """ import matplotlib.pyplot as plt - + _check_option('spatial_colors', spatial_colors, [True, False, 'auto']) + spatial_colors = _check_spatial_colors(evoked.info, picks, spatial_colors) # For evoked.plot_image ... # First input checks for group_by and axes if any of them is not None. # Either both must be dicts, or neither. @@ -251,7 +262,8 @@ def _plot_evoked(evoked, picks=None, exclude='bads', unit=True, show=True, mask_style=mask_style, mask_cmap=mask_cmap, mask_alpha=mask_alpha, time_unit=time_unit, show_names=show_names, - sphere=sphere, draw=False) + sphere=sphere, draw=False, + spatial_colors=spatial_colors) if remove_xlabels and not _is_last_row(ax): ax.set_xticklabels([]) ax.set_xlabel("") @@ -736,11 +748,14 @@ def plot_evoked(evoked, picks=None, exclude='bads', unit=True, show=True, Plot GFP for EEG instead of RMS. Label RMS traces correctly as such. window_title : str | None The title to put at the top of the figure. - spatial_colors : bool + spatial_colors : bool | 'auto' If True, the lines are color coded by mapping physical sensor coordinates into color values. Spatially similar channels will have similar colors. Bad channels will be dotted. If False, the good - channels are plotted black and bad channels red. Defaults to False. + channels are plotted black and bad channels red. If ``'auto'``, uses + True if channel locations are present, and False if channel locations + are missing or if the data contains only a single channel. Defaults to + ``'auto'``. zorder : str | callable Which channels to put in the front or back. Only matters if ``spatial_colors`` is used. @@ -1243,7 +1258,7 @@ def whitened_gfp(x, rank=None): if not has_sss: evokeds_white[0].plot(unit=False, axes=axes_evoked, hline=[-1.96, 1.96], show=False, - time_unit=time_unit) + time_unit=time_unit, spatial_colors=False) else: for ((ch_type, picks), ax) in zip(picks_list, axes_evoked): ax.plot(times, evokeds_white[0].data[picks].T, color='k', diff --git a/mne/viz/ica.py b/mne/viz/ica.py index 7aee10cdfe7..33d1f6cb530 100644 --- a/mne/viz/ica.py +++ b/mne/viz/ica.py @@ -1006,12 +1006,12 @@ def _plot_ica_overlay_evoked(evoked, evoked_cln, title, show): fig.suptitle(title) axes = axes.flatten() if isinstance(axes, np.ndarray) else axes - evoked.plot(axes=axes, show=False, time_unit='s') + evoked.plot(axes=axes, show=False, time_unit='s', spatial_colors=False) for ax in fig.axes: for line in ax.get_lines(): line.set_color('r') fig.canvas.draw() - evoked_cln.plot(axes=axes, show=False, time_unit='s') + evoked_cln.plot(axes=axes, show=False, time_unit='s', spatial_colors=False) tight_layout(fig=fig) fig.subplots_adjust(top=0.90) diff --git a/mne/viz/tests/test_evoked.py b/mne/viz/tests/test_evoked.py index df995c71044..705db372092 100644 --- a/mne/viz/tests/test_evoked.py +++ b/mne/viz/tests/test_evoked.py @@ -79,18 +79,19 @@ def test_plot_evoked_cov(): cov = read_cov(cov_fname) cov['projs'] = [] # avoid warnings with pytest.warns(RuntimeWarning, match='No average EEG reference'): - evoked.plot(noise_cov=cov, time_unit='s') + evoked.plot(noise_cov=cov, time_unit='s', spatial_colors=False) with pytest.raises(TypeError, match='Covariance'): - evoked.plot(noise_cov=1., time_unit='s') + evoked.plot(noise_cov=1., time_unit='s', spatial_colors=False) with pytest.raises(FileNotFoundError, match='File does not exist'): - evoked.plot(noise_cov='nonexistent-cov.fif', time_unit='s') + evoked.plot(noise_cov='nonexistent-cov.fif', time_unit='s', + spatial_colors=False) raw = read_raw_fif(raw_sss_fname) events = make_fixed_length_events(raw) epochs = Epochs(raw, events, picks=default_picks) cov = compute_covariance(epochs) evoked_sss = epochs.average() with pytest.warns(RuntimeWarning, match='relative scaling'): - evoked_sss.plot(noise_cov=cov, time_unit='s') + evoked_sss.plot(noise_cov=cov, time_unit='s', spatial_colors=False) plt.close('all') @@ -101,7 +102,7 @@ def test_plot_evoked(): evoked = epochs.average() assert evoked.proj is False fig = evoked.plot(proj=True, hline=[1], exclude=[], window_title='foo', - time_unit='s') + time_unit='s', spatial_colors=False) amplitudes = _get_amplitudes(fig) assert len(amplitudes) == len(default_picks) assert evoked.proj is False @@ -113,12 +114,14 @@ def test_plot_evoked(): _fake_click(fig, ax, [ax.get_xlim()[0], ax.get_ylim()[1]], 'data') # plot with bad channels excluded & spatial_colors & zorder - evoked.plot(exclude='bads', time_unit='s') + evoked.plot(exclude='bads', time_unit='s', spatial_colors=False) # test selective updating of dict keys is working. - evoked.plot(hline=[1], units=dict(mag='femto foo'), time_unit='s') + evoked.plot(hline=[1], units=dict(mag='femto foo'), time_unit='s', + spatial_colors=False) evoked_delayed_ssp = _get_epochs_delayed_ssp().average() - evoked_delayed_ssp.plot(proj='interactive', time_unit='s') + evoked_delayed_ssp.plot(proj='interactive', time_unit='s', + spatial_colors=False) evoked_delayed_ssp.apply_proj() pytest.raises(RuntimeError, evoked_delayed_ssp.plot, proj='interactive', time_unit='s') @@ -132,7 +135,7 @@ def test_plot_evoked(): # test `gfp='only'`: GFP (EEG) and RMS (MEG) fig, ax = plt.subplots(3) - evoked.plot(gfp='only', time_unit='s', axes=ax) + evoked.plot(gfp='only', time_unit='s', axes=ax, spatial_colors=False) assert len(ax[0].lines) == len(ax[1].lines) == len(ax[2].lines) == 1 @@ -149,7 +152,7 @@ def test_plot_evoked(): # Test invalid `gfp` with pytest.raises(ValueError): - evoked.plot(gfp='foo', time_unit='s') + evoked.plot(gfp='foo', time_unit='s', spatial_colors=False) # plot with bad channels excluded, spatial_colors, zorder & pos. layout evoked.rename_channels({'MEG 0133': 'MEG 0000'}) @@ -165,7 +168,7 @@ def test_plot_evoked(): evoked.pick_channels(evoked.ch_names[:4]) with catch_logging() as log_file: - evoked.plot(verbose=True, time_unit='s') + evoked.plot(verbose=True, time_unit='s', spatial_colors=False) assert 'Need more than one' in log_file.getvalue() # Test highlight @@ -173,14 +176,15 @@ def test_plot_evoked(): (0, 0.1), [(0, 0.1), (0.1, 0.2)] ]: - fig = evoked.plot(time_unit='s', highlight=highlight) + fig = evoked.plot(time_unit='s', highlight=highlight, + spatial_colors=False) for ax in fig.get_axes(): highlighted_areas = [child for child in ax.get_children() if isinstance(child, PolyCollection)] assert len(highlighted_areas) == len(np.atleast_2d(highlight)) with pytest.raises(ValueError, match='must be reshapable into a 2D array'): - fig = evoked.plot(time_unit='s', highlight=0.1) + fig = evoked.plot(time_unit='s', highlight=0.1, spatial_colors=False) # set one channel location to nan, confirm spatial_colors still works evoked = _get_epochs().load_data().average('grad') # reload data @@ -196,7 +200,9 @@ def test_constrained_layout(): assert fig.get_constrained_layout() evoked = mne.read_evokeds(evoked_fname)[0] evoked.pick(evoked.ch_names[:2]) - evoked.plot(axes=ax) # smoke test that it does not break things + + # smoke test that it does not break things + evoked.plot(axes=ax, spatial_colors=False) assert fig.get_constrained_layout() plt.close('all') @@ -225,21 +231,21 @@ def test_plot_evoked_reconstruct(picks, rlims, avg_proj): assert len(evoked.info['projs']) == 0 assert evoked.proj is False fig = evoked.plot(proj=True, hline=[1], exclude=[], window_title='foo', - time_unit='s') + time_unit='s', spatial_colors=False) amplitudes = _get_amplitudes(fig) assert len(amplitudes) == len(picks) assert evoked.proj is avg_proj - fig = evoked.plot(proj='reconstruct', exclude=[]) + fig = evoked.plot(proj='reconstruct', exclude=[], spatial_colors=False) amplitudes_recon = _get_amplitudes(fig) if avg_proj is False: assert_allclose(amplitudes, amplitudes_recon) proj = compute_proj_evoked(evoked.copy().crop(None, 0).apply_proj()) evoked.add_proj(proj) assert len(evoked.info['projs']) == 2 if len(picks) == 3 else 4 - fig = evoked.plot(proj=True, exclude=[]) + fig = evoked.plot(proj=True, exclude=[], spatial_colors=False) amplitudes_proj = _get_amplitudes(fig) - fig = evoked.plot(proj='reconstruct', exclude=[]) + fig = evoked.plot(proj='reconstruct', exclude=[], spatial_colors=False) amplitudes_recon = _get_amplitudes(fig) assert len(amplitudes_recon) == len(picks) norm = np.linalg.norm(amplitudes) @@ -254,7 +260,7 @@ def test_plot_evoked_reconstruct(picks, rlims, avg_proj): cov = read_cov(cov_fname) with pytest.raises(ValueError, match='Cannot use proj="reconstruct"'): - evoked.plot(noise_cov=cov, proj='reconstruct') + evoked.plot(noise_cov=cov, proj='reconstruct', spatial_colors=False) plt.close('all') From 19c5a825449551d73ad2af327d2e1bdb2142900b Mon Sep 17 00:00:00 2001 From: Daniel McCloy Date: Fri, 23 Sep 2022 17:05:25 -0500 Subject: [PATCH 2/5] fix spatial colors logic --- mne/viz/evoked.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/mne/viz/evoked.py b/mne/viz/evoked.py index fa080fc750b..d8f9f4025a0 100644 --- a/mne/viz/evoked.py +++ b/mne/viz/evoked.py @@ -195,8 +195,10 @@ def _plot_legend(pos, colors, axis, bads, outlines, loc, size=30): def _check_spatial_colors(info, picks, spatial_colors): """Use spatial colors if channel locations exist.""" + # NB: this assumes `picks`` has already been through _picks_to_idx() + # and it reflects *just the picks for the current subplot* if spatial_colors == 'auto': - if picks and len(picks) == 1: + if len(picks) == 1: spatial_colors = False else: spatial_colors = _check_ch_locs(info) @@ -228,7 +230,6 @@ def _plot_evoked(evoked, picks=None, exclude='bads', unit=True, show=True, """ import matplotlib.pyplot as plt _check_option('spatial_colors', spatial_colors, [True, False, 'auto']) - spatial_colors = _check_spatial_colors(evoked.info, picks, spatial_colors) # For evoked.plot_image ... # First input checks for group_by and axes if any of them is not None. # Either both must be dicts, or neither. @@ -463,19 +464,19 @@ def _plot_lines(data, info, picks, fig, axes, spatial_colors, unit, units, if not gfp_only: chs = [info['chs'][i] for i in idx] locs3d = np.array([ch['loc'][:3] for ch in chs]) - if (spatial_colors is True and - not _check_ch_locs(info=info, picks=idx)): + _spat_col = _check_spatial_colors(info, idx, spatial_colors) + if (_spat_col and not _check_ch_locs(info=info, picks=idx)): warn('Channel locations not available. Disabling spatial ' 'colors.') - spatial_colors = selectable = False - if spatial_colors is True and len(idx) != 1: + _spat_col = selectable = False + if _spat_col and len(idx) != 1: x, y, z = locs3d.T colors = _rgb(x, y, z) _handle_spatial_colors(colors, info, idx, this_type, psd, ax, sphere) else: - if isinstance(spatial_colors, (tuple, str)): - col = [spatial_colors] + if isinstance(_spat_col, (tuple, str)): + col = [_spat_col] else: col = ['k'] colors = col * len(idx) @@ -500,7 +501,7 @@ def _plot_lines(data, info, picks, fig, axes, spatial_colors, unit, units, for ch_idx, z in enumerate(z_ord): line_list.append( ax.plot(times, D[ch_idx], picker=True, - zorder=z + 1 if spatial_colors is True else 1, + zorder=z + 1 if _spat_col else 1, color=colors[ch_idx], alpha=line_alpha, linewidth=0.5)[0]) line_list[-1].set_pickradius(3.) From 743d387d7f56012dcea15014170f1c8c8ba72687 Mon Sep 17 00:00:00 2001 From: Daniel McCloy Date: Fri, 23 Sep 2022 17:12:47 -0500 Subject: [PATCH 3/5] update PR number in changelog --- doc/changes/latest.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changes/latest.inc b/doc/changes/latest.inc index be02ca529f9..9ff728b9285 100644 --- a/doc/changes/latest.inc +++ b/doc/changes/latest.inc @@ -76,7 +76,7 @@ Bugs API changes ~~~~~~~~~~~ -- In meth:`mne.Evoked.plot`, the default value of the ``spatial_colors`` parameter has been changed to ``'auto'``, which will use spatial colors if channel locations are available (:gh:`11017` by :newcontrib:`Hüseyin Orkun Elmas`) +- In meth:`mne.Evoked.plot`, the default value of the ``spatial_colors`` parameter has been changed to ``'auto'``, which will use spatial colors if channel locations are available (:gh:`11201` by :newcontrib:`Hüseyin Orkun Elmas` and `Daniel McCloy`_) - Starting with this release we now follow the Python convention of using ``FutureWarning`` instead of ``DeprecationWarning`` to signal user-facing changes to our API (:gh:`11120` by `Daniel McCloy`_) - The ``bands`` parameter of :meth:`mne.Epochs.plot_psd_topomap` now accepts :class:`dict` input; legacy :class:`tuple` input is supported, but discouraged for new code (:gh:`11050` by `Daniel McCloy`_) - The :func:`mne.head_to_mri` new function parameter ``kind`` default will change from ``'ras'`` to ``'mri'`` (:gh:`11185` by `Eric Larson`_) From c4d84de6b64f60690559bd029595056935fab761 Mon Sep 17 00:00:00 2001 From: Daniel McCloy Date: Tue, 27 Sep 2022 09:56:50 -0500 Subject: [PATCH 4/5] fix tests --- mne/viz/tests/test_evoked.py | 51 ++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/mne/viz/tests/test_evoked.py b/mne/viz/tests/test_evoked.py index 705db372092..2bd46d45f40 100644 --- a/mne/viz/tests/test_evoked.py +++ b/mne/viz/tests/test_evoked.py @@ -17,6 +17,7 @@ import matplotlib.pyplot as plt from matplotlib import gridspec from matplotlib.collections import PolyCollection +from mpl_toolkits.axes_grid1.parasite_axes import HostAxes # spatial_colors import mne from mne import (read_events, Epochs, read_cov, compute_covariance, @@ -79,19 +80,18 @@ def test_plot_evoked_cov(): cov = read_cov(cov_fname) cov['projs'] = [] # avoid warnings with pytest.warns(RuntimeWarning, match='No average EEG reference'): - evoked.plot(noise_cov=cov, time_unit='s', spatial_colors=False) + evoked.plot(noise_cov=cov, time_unit='s') with pytest.raises(TypeError, match='Covariance'): - evoked.plot(noise_cov=1., time_unit='s', spatial_colors=False) + evoked.plot(noise_cov=1., time_unit='s') with pytest.raises(FileNotFoundError, match='File does not exist'): - evoked.plot(noise_cov='nonexistent-cov.fif', time_unit='s', - spatial_colors=False) + evoked.plot(noise_cov='nonexistent-cov.fif', time_unit='s') raw = read_raw_fif(raw_sss_fname) events = make_fixed_length_events(raw) epochs = Epochs(raw, events, picks=default_picks) cov = compute_covariance(epochs) evoked_sss = epochs.average() with pytest.warns(RuntimeWarning, match='relative scaling'): - evoked_sss.plot(noise_cov=cov, time_unit='s', spatial_colors=False) + evoked_sss.plot(noise_cov=cov, time_unit='s') plt.close('all') @@ -102,7 +102,7 @@ def test_plot_evoked(): evoked = epochs.average() assert evoked.proj is False fig = evoked.plot(proj=True, hline=[1], exclude=[], window_title='foo', - time_unit='s', spatial_colors=False) + time_unit='s') amplitudes = _get_amplitudes(fig) assert len(amplitudes) == len(default_picks) assert evoked.proj is False @@ -114,14 +114,12 @@ def test_plot_evoked(): _fake_click(fig, ax, [ax.get_xlim()[0], ax.get_ylim()[1]], 'data') # plot with bad channels excluded & spatial_colors & zorder - evoked.plot(exclude='bads', time_unit='s', spatial_colors=False) + evoked.plot(exclude='bads', time_unit='s') # test selective updating of dict keys is working. - evoked.plot(hline=[1], units=dict(mag='femto foo'), time_unit='s', - spatial_colors=False) + evoked.plot(hline=[1], units=dict(mag='femto foo'), time_unit='s') evoked_delayed_ssp = _get_epochs_delayed_ssp().average() - evoked_delayed_ssp.plot(proj='interactive', time_unit='s', - spatial_colors=False) + evoked_delayed_ssp.plot(proj='interactive', time_unit='s') evoked_delayed_ssp.apply_proj() pytest.raises(RuntimeError, evoked_delayed_ssp.plot, proj='interactive', time_unit='s') @@ -135,7 +133,7 @@ def test_plot_evoked(): # test `gfp='only'`: GFP (EEG) and RMS (MEG) fig, ax = plt.subplots(3) - evoked.plot(gfp='only', time_unit='s', axes=ax, spatial_colors=False) + evoked.plot(gfp='only', time_unit='s', axes=ax) assert len(ax[0].lines) == len(ax[1].lines) == len(ax[2].lines) == 1 @@ -152,7 +150,7 @@ def test_plot_evoked(): # Test invalid `gfp` with pytest.raises(ValueError): - evoked.plot(gfp='foo', time_unit='s', spatial_colors=False) + evoked.plot(gfp='foo', time_unit='s') # plot with bad channels excluded, spatial_colors, zorder & pos. layout evoked.rename_channels({'MEG 0133': 'MEG 0000'}) @@ -168,7 +166,7 @@ def test_plot_evoked(): evoked.pick_channels(evoked.ch_names[:4]) with catch_logging() as log_file: - evoked.plot(verbose=True, time_unit='s', spatial_colors=False) + evoked.plot(verbose=True, time_unit='s') assert 'Need more than one' in log_file.getvalue() # Test highlight @@ -176,15 +174,15 @@ def test_plot_evoked(): (0, 0.1), [(0, 0.1), (0.1, 0.2)] ]: - fig = evoked.plot(time_unit='s', highlight=highlight, - spatial_colors=False) - for ax in fig.get_axes(): + fig = evoked.plot(time_unit='s', highlight=highlight) + regular_axes = [ax for ax in fig.axes if not isinstance(ax, HostAxes)] + for ax in regular_axes: highlighted_areas = [child for child in ax.get_children() if isinstance(child, PolyCollection)] assert len(highlighted_areas) == len(np.atleast_2d(highlight)) with pytest.raises(ValueError, match='must be reshapable into a 2D array'): - fig = evoked.plot(time_unit='s', highlight=0.1, spatial_colors=False) + fig = evoked.plot(time_unit='s', highlight=0.1) # set one channel location to nan, confirm spatial_colors still works evoked = _get_epochs().load_data().average('grad') # reload data @@ -202,14 +200,17 @@ def test_constrained_layout(): evoked.pick(evoked.ch_names[:2]) # smoke test that it does not break things - evoked.plot(axes=ax, spatial_colors=False) + evoked.plot(axes=ax) assert fig.get_constrained_layout() plt.close('all') def _get_amplitudes(fig): - amplitudes = [line.get_ydata() for ax in fig.axes + # ignore the spatial_colors parasite axes + regular_axes = [ax for ax in fig.axes if not isinstance(ax, HostAxes)] + amplitudes = [line.get_ydata() for ax in regular_axes for line in ax.get_lines()] + # this will exclude hlines, which are lists not arrays amplitudes = np.array( [line for line in amplitudes if isinstance(line, np.ndarray)]) return amplitudes @@ -231,21 +232,21 @@ def test_plot_evoked_reconstruct(picks, rlims, avg_proj): assert len(evoked.info['projs']) == 0 assert evoked.proj is False fig = evoked.plot(proj=True, hline=[1], exclude=[], window_title='foo', - time_unit='s', spatial_colors=False) + time_unit='s') amplitudes = _get_amplitudes(fig) assert len(amplitudes) == len(picks) assert evoked.proj is avg_proj - fig = evoked.plot(proj='reconstruct', exclude=[], spatial_colors=False) + fig = evoked.plot(proj='reconstruct', exclude=[]) amplitudes_recon = _get_amplitudes(fig) if avg_proj is False: assert_allclose(amplitudes, amplitudes_recon) proj = compute_proj_evoked(evoked.copy().crop(None, 0).apply_proj()) evoked.add_proj(proj) assert len(evoked.info['projs']) == 2 if len(picks) == 3 else 4 - fig = evoked.plot(proj=True, exclude=[], spatial_colors=False) + fig = evoked.plot(proj=True, exclude=[]) amplitudes_proj = _get_amplitudes(fig) - fig = evoked.plot(proj='reconstruct', exclude=[], spatial_colors=False) + fig = evoked.plot(proj='reconstruct', exclude=[]) amplitudes_recon = _get_amplitudes(fig) assert len(amplitudes_recon) == len(picks) norm = np.linalg.norm(amplitudes) @@ -260,7 +261,7 @@ def test_plot_evoked_reconstruct(picks, rlims, avg_proj): cov = read_cov(cov_fname) with pytest.raises(ValueError, match='Cannot use proj="reconstruct"'): - evoked.plot(noise_cov=cov, proj='reconstruct', spatial_colors=False) + evoked.plot(noise_cov=cov, proj='reconstruct') plt.close('all') From a1ff4dbe91cd75f7ff80db4f15a34e210cd7c4fa Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Wed, 28 Sep 2022 01:04:54 -0400 Subject: [PATCH 5/5] FIX: Check --- mne/viz/evoked.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mne/viz/evoked.py b/mne/viz/evoked.py index d8f9f4025a0..6ad8f6f68f0 100644 --- a/mne/viz/evoked.py +++ b/mne/viz/evoked.py @@ -464,12 +464,15 @@ def _plot_lines(data, info, picks, fig, axes, spatial_colors, unit, units, if not gfp_only: chs = [info['chs'][i] for i in idx] locs3d = np.array([ch['loc'][:3] for ch in chs]) + # _plot_psd can pass spatial_colors=color (e.g., "black") so + # we need to use "is True" here _spat_col = _check_spatial_colors(info, idx, spatial_colors) - if (_spat_col and not _check_ch_locs(info=info, picks=idx)): + if (_spat_col is True and + not _check_ch_locs(info=info, picks=idx)): warn('Channel locations not available. Disabling spatial ' 'colors.') _spat_col = selectable = False - if _spat_col and len(idx) != 1: + if _spat_col is True and len(idx) != 1: x, y, z = locs3d.T colors = _rgb(x, y, z) _handle_spatial_colors(colors, info, idx, this_type, psd,