Skip to content

Commit 2bd249b

Browse files
mh105PragnyaKhandelwal
authored andcommitted
Fix: Bind browser close output to creation context (#14077)
1 parent 6b1b987 commit 2bd249b

3 files changed

Lines changed: 39 additions & 1 deletion

File tree

doc/changes/dev/14077.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix Qt browser close messages from :meth:`mne.io.Raw.plot` appearing in the wrong Jupyter notebook cell, by `Mingjian He`_.

mne/viz/_figure.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from abc import ABC, abstractmethod
1111
from collections import OrderedDict
1212
from contextlib import contextmanager
13+
from contextvars import copy_context
1314
from copy import deepcopy
1415
from itertools import cycle
1516

@@ -56,6 +57,7 @@ def __init__(self, **kwargs):
5657
from ..preprocessing import ICA
5758

5859
self.backend_name = None
60+
self._close_context = copy_context()
5961

6062
self._data = None
6163
self._times = None
@@ -490,6 +492,13 @@ def _redraw(self, update_data=True, annotations=False):
490492

491493
def _close(self, event=None):
492494
"""Handle close events (via keypress or window [x])."""
495+
# As specified by PEP 567: https://peps.python.org/pep-0567/#asyncio
496+
# we explicitly retain the python Context used to create the figure
497+
# in order to route stdout on close within IPykernel. See gh #14077
498+
self._close_context.run(self._close_impl, event)
499+
500+
def _close_impl(self, event=None):
501+
"""Handle close events in the context that created the browser."""
493502
from matplotlib.pyplot import close
494503

495504
logger.debug(f"Closing {self.mne.instance_type} browser...")

mne/viz/tests/test_figure.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
# License: BSD-3-Clause
33
# Copyright the MNE-Python contributors.
44

5+
from contextvars import ContextVar
6+
7+
import matplotlib.pyplot as plt
58
import numpy as np
69
import pytest
710

811
from mne import create_info
912
from mne.io import RawArray
10-
from mne.viz._figure import _get_browser
13+
from mne.viz._figure import _get_browser, use_browser_backend
1114

1215

1316
def test_browse_figure_constructor():
@@ -23,3 +26,28 @@ def test_browse_figure_requires_two_timepoints():
2326
assert len(raw.times) == 1
2427
with pytest.raises(ValueError, match="at least two time points"):
2528
_get_browser(show=False, block=False, inst=raw)
29+
30+
31+
def test_browse_figure_close_context():
32+
"""Test that deferred browser close uses its creation context."""
33+
marker = ContextVar("browser_context", default=None)
34+
created_token = marker.set("created")
35+
try:
36+
info = create_info(ch_names=["CH1"], sfreq=100.0, ch_types="eeg")
37+
raw = RawArray(np.zeros((1, 100)), info)
38+
with use_browser_backend("matplotlib"):
39+
fig = raw.plot(show=False)
40+
41+
current_token = marker.set("current")
42+
observed = []
43+
close_impl = fig._close_impl
44+
fig._close_impl = lambda event=None: observed.append(marker.get())
45+
try:
46+
fig._close()
47+
finally:
48+
fig._close_impl = close_impl
49+
plt.close(fig)
50+
marker.reset(current_token)
51+
assert observed == ["created"]
52+
finally:
53+
marker.reset(created_token)

0 commit comments

Comments
 (0)