Is it possible to have the evoked.plot() update in real-time as I reject epochs in epochs.plot()?
Unfortunately this is not possible currently;, however this is a functionality one could, in theory, implement using the callback interface introduced by @wmvanvliet a while ago. Tagging him here to get his opinion, as I believe this is an interesting use case.
Richard
It would require hooking into an event that gets fired whenever you select an epoch. We currently don’t have a nice UI event for that (we really don’t have many of them yet, since it’s quite a big job to implement them). But in the meantime, it should be possible to monkeypatch the figure.
Here is something to get you started:
import mne
import numpy as np
path = mne.datasets.sample.data_path()
raw = mne.io.read_raw_fif(path / "MEG/sample/sample_audvis_raw.fif")
events = mne.find_events(raw)
epochs = mne.Epochs(raw, events, tmin=-0.2, tmax=0.5)
fig = epochs.plot()
orig_toggle_bad_epoch = fig._toggle_bad_epoch
def toggle_bad_epoch(*params):
"""Monkey patch for the toggle_bad_epoch function."""
# Call the original toggle_bad_epoch function
return_val = orig_toggle_bad_epoch(*params)
# Now we can do whatever we want. Here are the epochs currently marked bad:
print(fig.mne.bad_epochs)
# These are the "good" epochs then:
good_epochs = np.setdiff1d(np.arange(len(epochs)), fig.mne.bad_epochs)
# Create evokeds based on only the good epochs
evoked = epochs[good_epochs].average()
# Plot them
evoked.plot_topo()
# Make sure to return whatever the original toggle_bad_epoch returned.
return return_val
fig._toggle_bad_epoch = toggle_bad_epoch
Thank you very much for your helpful example. For now, I have come up with a working example of how I want it to be. It gives the epochs plot as well as the initial evoked plot and subsequently updates it when I (un)toggle bad epochs. The update process of the evoked plot isn’t as smooth as I wished it would be (ideally without closing and reopening the plot), but I feel like this is the best I could do. Do you have any recommendations?
import mne
import numpy as np
from mne.viz import plot_evoked_topo
from PySide6.QtWidgets import QApplication
from PySide6.QtCore import QTimer
import sys
import matplotlib.pyplot as plt
def load_data():
path = mne.datasets.sample.data_path()
raw = mne.io.read_raw_fif(path / "MEG/sample/sample_audvis_raw.fif")
events = mne.find_events(raw)
return mne.Epochs(raw, events, tmin=-0.2, tmax=0.5)
def update_evoked_plot(epochs, fig):
# Get indices of good epochs (excluding bad epochs)
good_epochs = np.setdiff1d(np.arange(len(epochs)), fig.mne.bad_epochs)
evoked = epochs[good_epochs].average()
# Close previous evoked topo plot if it exists
if hasattr(update_evoked_plot, 'topo_fig'):
plt.close(update_evoked_plot.topo_fig)
# Create and display new evoked topo plot
update_evoked_plot.topo_fig = plot_evoked_topo(evoked, show=False)
update_evoked_plot.topo_fig.show()
def main():
# Load the epoched data
epochs = load_data()
# Create Qt application
app = QApplication(sys.argv)
# Plot epochs and get the figure
fig = epochs.plot(block=False)
# Store the original toggle_bad_epoch function
orig_toggle_bad_epoch = fig._toggle_bad_epoch
def toggle_bad_epoch(*params):
result = orig_toggle_bad_epoch(*params)
update_evoked_plot(epochs, fig)
return result
# Replace the original toggle_bad_epoch function with our wrapper
fig._toggle_bad_epoch = toggle_bad_epoch
# Initial update of evoked plot after a short delay. Without this there seems to be a conflict with the epochs.plot() call.
QTimer.singleShot(1000, lambda: update_evoked_plot(epochs, fig))
# Start the Qt event loop
sys.exit(app.exec())
if __name__ == "__main__":
main()