Skip to content

Commit

Permalink
BUG: updates for MPL 3.7 compatibility (#11409)
Browse files Browse the repository at this point in the history
Co-authored-by: Eric Larson <larson.eric.d@gmail.com>
  • Loading branch information
drammock and larsoner committed Feb 23, 2023
1 parent 96a4bc2 commit e042876
Show file tree
Hide file tree
Showing 13 changed files with 259 additions and 129 deletions.
9 changes: 9 additions & 0 deletions doc/changes/1.3.inc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@

Also add a corresponding entry for yourself in doc/changes/names.inc

.. _changes_1_3_1:

Version 1.3.1 (2023-02-23)
--------------------------

Bugs
~~~~
- Fix visualization dialog compatibility with matplotlib 3.7 (:gh:`11409` by `Daniel McCloy`_ and `Eric Larson`_)

.. _changes_1_3_0:

Version 1.3.0 (2022-12-21)
Expand Down
5 changes: 5 additions & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,11 @@ def reset_warnings(gallery_conf, fname):
'Enum value .* is marked as deprecated',
# matplotlib PDF output
'The py23 module has been deprecated',
# pkg_resources
'Implementing implicit namespace packages',
'Deprecated call to `pkg_resources',
# nilearn
r'The register_cmap function was deprecated in Matplotlib 3\.7',
):
warnings.filterwarnings( # deal with other modules having bad imports
'ignore', message=".*%s.*" % key, category=DeprecationWarning)
Expand Down
2 changes: 1 addition & 1 deletion mne/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
#
# License: BSD-3-Clause

__version__ = '1.3.0'
__version__ = '1.3.1'
3 changes: 1 addition & 2 deletions mne/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,7 @@ def pytest_configure(config):
ignore:Unclosed context <zmq.asyncio.Context.*:ResourceWarning
ignore:Jupyter is migrating its paths.*:DeprecationWarning
ignore:Widget\..* is deprecated\.:DeprecationWarning
# hopefully temporary https://github.com/matplotlib/matplotlib/pull/24455#issuecomment-1319318629
ignore:The circles attribute was deprecated in Matplotlib.*:
ignore:.*is deprecated in pyzmq.*:DeprecationWarning
# PySide6
ignore:Enum value .* is marked as deprecated:DeprecationWarning
ignore:Function.*is marked as deprecated, please check the documentation.*:DeprecationWarning
Expand Down
260 changes: 175 additions & 85 deletions mne/viz/_mpl_figure.py

Large diffs are not rendered by default.

47 changes: 32 additions & 15 deletions mne/viz/tests/test_raw.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import os
from copy import deepcopy

import matplotlib
import matplotlib.pyplot as plt
from matplotlib import backend_bases
import numpy as np
Expand All @@ -24,6 +23,18 @@
from mne.viz.utils import _fake_click, _fake_keypress


def _get_button_xy(buttons, idx):
from mne.viz._mpl_figure import _OLD_BUTTONS
if _OLD_BUTTONS:
return buttons.circles[idx].center
else:
# Each transform is to display coords, and our offsets are in Axes
# coords. We want data coords, so we go Axes -> display -> data.
return buttons.ax.transData.inverted().transform(
buttons.ax.transAxes.transform(
buttons.ax.collections[0].get_offsets()[idx]))


def _annotation_helper(raw, browse_backend, events=False):
"""Test interactive annotations."""
ismpl = browse_backend.name == 'matplotlib'
Expand Down Expand Up @@ -59,7 +70,7 @@ def _annotation_helper(raw, browse_backend, events=False):
fig._fake_keypress(key, fig=ann_fig)
# change annotation label
for ix in (-1, 0):
xy = ann_fig.mne.radio_ax.buttons.circles[ix].center
xy = _get_button_xy(ann_fig.mne.radio_ax.buttons, ix)
fig._fake_click(xy, fig=ann_fig, ax=ann_fig.mne.radio_ax,
xform='data')
else:
Expand Down Expand Up @@ -155,10 +166,8 @@ def _annotation_helper(raw, browse_backend, events=False):


def _proj_status(ssp_fig, browse_backend):
if browse_backend.name == 'matplotlib':
ax = ssp_fig.mne.proj_checkboxes.ax
return [line.get_visible() for line
in ax.findobj(matplotlib.lines.Line2D)][::2]
if browse_backend == 'matplotlib' or browse_backend.name == 'matplotlib':
return ssp_fig.mne.proj_checkboxes.get_status()
else:
return [chkbx.isChecked() for chkbx in ssp_fig.checkboxes]

Expand All @@ -173,11 +182,12 @@ def _proj_label(ssp_fig, browse_backend):
def _proj_click(idx, fig, browse_backend):
ssp_fig = fig.mne.fig_proj
if browse_backend.name == 'matplotlib':
pos = np.array(ssp_fig.mne.proj_checkboxes.
labels[idx].get_position()) + 0.01

text_lab = ssp_fig.mne.proj_checkboxes.labels[idx]
pos = np.mean(
text_lab.get_tightbbox(renderer=fig.canvas.get_renderer()),
axis=0)
fig._fake_click(pos, fig=ssp_fig, ax=ssp_fig.mne.proj_checkboxes.ax,
xform='data')
xform='pix')
else:
# _fake_click on QCheckBox is inconsistent across platforms
# (also see comment in test_plot_raw_selection).
Expand Down Expand Up @@ -278,8 +288,9 @@ def test_plot_raw_selection(raw, browser_backend):
sel_fig = fig.mne.fig_selection
assert sel_fig is not None
# test changing selection with arrow keys
left_temp = 'Left-temporal'
sel_dict = fig.mne.ch_selections
assert len(fig.mne.traces) == len(sel_dict['Left-temporal']) # 6
assert len(fig.mne.traces) == len(sel_dict[left_temp]) # 6
fig._fake_keypress('down', fig=sel_fig)
assert len(fig.mne.traces) == len(sel_dict['Left-frontal']) # 3
fig._fake_keypress('down', fig=sel_fig)
Expand All @@ -302,20 +313,26 @@ def test_plot_raw_selection(raw, browser_backend):
assert fig.mne.butterfly
# test clicking on radio buttons → should cancel butterfly mode
if ismpl:
xy = sel_fig.mne.radio_ax.buttons.circles[0].center
print(f'Clicking button: {repr(left_temp)}')
assert sel_fig.mne.radio_ax.buttons.labels[0].get_text() == left_temp
xy = _get_button_xy(sel_fig.mne.radio_ax.buttons, 0)
lim = sel_fig.mne.radio_ax.get_xlim()
assert lim[0] < xy[0] < lim[1]
lim = sel_fig.mne.radio_ax.get_ylim()
assert lim[0] < xy[1] < lim[1]
fig._fake_click(xy, fig=sel_fig, ax=sel_fig.mne.radio_ax, xform='data')
else:
# For an unknown reason test-clicking on checkboxes is inconsistent
# across platforms.
# (QTest.mouseClick works isolated on all platforms but somehow
# not in this context. _fake_click isn't working on linux)
sel_fig._chkbx_changed(list(sel_fig.chkbxs.keys())[0])
assert len(fig.mne.traces) == len(sel_dict['Left-temporal']) # 6
assert not fig.mne.butterfly
assert len(fig.mne.traces) == len(sel_dict[left_temp]) # 6
# test clicking on "custom" when not defined: should be no-op
if ismpl:
before_state = sel_fig.mne.radio_ax.buttons.value_selected
xy = sel_fig.mne.radio_ax.buttons.circles[-1].center
xy = _get_button_xy(sel_fig.mne.radio_ax.buttons, -1)
fig._fake_click(xy, fig=sel_fig, ax=sel_fig.mne.radio_ax, xform='data')
lasso = sel_fig.lasso
sensor_ax = sel_fig.mne.sensor_ax
Expand All @@ -327,7 +344,7 @@ def test_plot_raw_selection(raw, browser_backend):
lasso = sel_fig.channel_fig.lasso
sensor_ax = sel_fig.channel_widget
assert before_state == sel_fig.mne.old_selection # unchanged
assert len(fig.mne.traces) == len(sel_dict['Left-temporal']) # unchanged
assert len(fig.mne.traces) == len(sel_dict[left_temp]) # unchanged
# test marking bad channel in selection mode → should make sensor red
assert lasso.ec[:, 0].sum() == 0 # R of RGBA zero for all chans
fig._click_ch_name(ch_index=1, button=1) # mark bad
Expand Down
16 changes: 7 additions & 9 deletions mne/viz/tests/test_topomap.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
_fake_scroll)
from mne.utils import requires_sklearn, check_version

from mne.viz.tests.test_raw import _proj_status

data_dir = testing.data_path(download=False)
subjects_dir = op.join(data_dir, 'subjects')
ecg_fname = op.join(data_dir, 'MEG', 'sample', 'sample_audvis_ecg-proj.fif')
Expand Down Expand Up @@ -81,22 +83,18 @@ def test_plot_topomap_interactive(constrained_layout):
assert len(plt.get_fignums()) == 1

ax.clear()
evoked.copy().plot_topomap(proj='interactive', **kwargs)
fig = evoked.copy().plot_topomap(proj='interactive', **kwargs)
canvas.draw()
image_interactive = np.frombuffer(canvas.tostring_rgb(), dtype='uint8')
assert_array_equal(image_noproj, image_interactive)
assert not np.array_equal(image_proj, image_interactive)
assert len(plt.get_fignums()) == 2

proj_fig = plt.figure(plt.get_fignums()[-1])
assert len(proj_fig.axes[0].lines) == 2
for line in proj_fig.axes[0].lines:
assert not line.get_visible()
_fake_click(proj_fig, proj_fig.axes[0], [0.5, 0.5], xform='data')
assert len(proj_fig.axes[0].lines) == 2
assert _proj_status(fig, 'matplotlib') == [False]
_fake_click(proj_fig, proj_fig.axes[0], [0.5, 0.5], xform='ax')
proj_fig.canvas.draw_idle()
for li, line in enumerate(proj_fig.axes[0].lines):
assert line.get_visible(), f'line {li} not visible'
assert _proj_status(fig, 'matplotlib') == [True]
canvas.draw()
image_interactive_click = np.frombuffer(
canvas.tostring_rgb(), dtype='uint8')
Expand All @@ -107,7 +105,7 @@ def test_plot_topomap_interactive(constrained_layout):
image_noproj.ravel(), image_interactive_click.ravel())[0, 1]
assert 0.85 < corr < 0.9

_fake_click(proj_fig, proj_fig.axes[0], [0.5, 0.5], xform='data')
_fake_click(proj_fig, proj_fig.axes[0], [0.5, 0.5], xform='ax')
canvas.draw()
image_interactive_click = np.frombuffer(
canvas.tostring_rgb(), dtype='uint8')
Expand Down
4 changes: 4 additions & 0 deletions mne/viz/topomap.py
Original file line number Diff line number Diff line change
Expand Up @@ -1847,6 +1847,10 @@ def plot_evoked_topomap(
merge_channels=merge_channels, scale=scaling, axes=axes,
contours=contours, interp=interp, extrapolate=extrapolate)
_draw_proj_checkbox(None, params)
# This is mostly for testing purposes, but it's also consistent with
# raw.plot, so maybe not a bad thing in principle either
from mne.viz._figure import BrowserParams
fig.mne = BrowserParams(proj_checkboxes=params['proj_checks'])

plt_show(show, block=False)
if axes_given:
Expand Down
33 changes: 21 additions & 12 deletions mne/viz/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,20 +495,27 @@ def _draw_proj_checkbox(event, params, draw_current_state=True):
ax_temp = fig_proj.add_axes((0, offset, 1, 0.8 - offset), frameon=False)
ax_temp.set_title('Projectors marked with "X" are active')

proj_checks = widgets.CheckButtons(ax_temp, labels=labels, actives=actives)
# make edges around checkbox areas
for rect in proj_checks.rectangles:
rect.set_edgecolor('0.5')
rect.set_linewidth(1.)

# change already-applied projectors to red
for ii, p in enumerate(projs):
if p['active']:
for x in proj_checks.lines[ii]:
x.set_color('#ff0000')
# make edges around checkbox areas and change already-applied projectors
# to red
from ._mpl_figure import _OLD_BUTTONS
check_kwargs = dict()
if not _OLD_BUTTONS:
checkcolor = ['#ff0000' if p['active'] else 'k' for p in projs]
check_kwargs['check_props'] = dict(facecolor=checkcolor)
check_kwargs['frame_props'] = dict(edgecolor='0.5', linewidth=1)
proj_checks = widgets.CheckButtons(
ax_temp, labels=labels, actives=actives, **check_kwargs)
if _OLD_BUTTONS:
for rect in proj_checks.rectangles:
rect.set_edgecolor('0.5')
rect.set_linewidth(1.)
for ii, p in enumerate(projs):
if p['active']:
for x in proj_checks.lines[ii]:
x.set_color('#ff0000')

# make minimal size
# pass key presses from option dialog over

proj_checks.on_clicked(partial(_toggle_proj, params=params))
params['proj_checks'] = proj_checks
fig_proj.canvas.mpl_connect('key_press_event', _key_press)
Expand Down Expand Up @@ -785,6 +792,8 @@ def _fake_click(fig, ax, point, xform='ax', button=1, kind='press', key=None):
assert kind == 'motion'
kind = 'motion_notify_event'
button = None
logger.debug(
f'Faking {kind} @ ({x}, {y}) with button={button} and key={key}')
fig.canvas.callbacks.process(
kind,
backend_bases.MouseEvent(
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ xlrd
imageio>=2.6.1
imageio-ffmpeg>=0.4.1
traitlets
pyvista>=0.32,!=0.35.2
pyvista>=0.32,!=0.35.2,!=0.38.0,!=0.38.1,!=0.38.2,!=0.38.3
pyvistaqt>=0.4
mffpy>=0.5.7
ipywidgets
Expand Down
4 changes: 2 additions & 2 deletions tutorials/clinical/30_ecog.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib import colormaps
from mne_bids import BIDSPath, read_raw_bids

import mne
Expand Down Expand Up @@ -147,7 +147,7 @@
# scale values to be between 0 and 1, then map to colors
gamma_power_at_15s -= gamma_power_at_15s.min()
gamma_power_at_15s /= gamma_power_at_15s.max()
rgba = cm.get_cmap("viridis")
rgba = colormaps.get_cmap("viridis")
sensor_colors = gamma_power_at_15s.map(rgba).tolist()

fig = plot_alignment(raw.info, trans='fsaverage',
Expand Down
2 changes: 1 addition & 1 deletion tutorials/inverse/20_dipole_fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Source localization with equivalent current dipole (ECD) fit
============================================================
This shows how to fit a dipole :footcite:`Sarvas1987` using mne-python.
This shows how to fit a dipole :footcite:`Sarvas1987` using MNE-Python.
For a comparison of fits between MNE-C and MNE-Python, see
`this gist <https://gist.github.com/larsoner/ca55f791200fe1dc3dd2>`__.
Expand Down
1 change: 0 additions & 1 deletion tutorials/preprocessing/50_artifact_correction_ssp.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
# See :ref:`tut-artifact-overview` for guidance on detecting and
# visualizing various types of artifact.
#
#
# What is SSP?
# ^^^^^^^^^^^^
#
Expand Down

0 comments on commit e042876

Please sign in to comment.