{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n\n# Using the event system to link figures\n\nMany of MNE-Python's figures are interactive. For example, you can select channels or\nscroll through time. The event system allows you to link figures together so that\ninteracting with one figure will simultaneously update another figure.\n\nIn this example, we'll be looking at linking a topomap plot with a source estimate plot,\nsuch that selecting the time in one will also update the time in the other, as well as\nhooking our own custom plot into MNE-Python's event system.\n\nSince the figures on our website don't have any interaction capabilities, this example\nwill only work properly when run in an interactive environment.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Author: Marijn van Vliet \n#\n# License: BSD-3-Clause\n# Copyright the MNE-Python contributors.\n\nimport matplotlib.pyplot as plt\n\nimport mne\nfrom mne.viz.ui_events import TimeChange, link, publish, subscribe\n\n# Turn on interactivity\nplt.ion()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Linking interactive plots\nWe load sensor-level and source-level data for the MNE-Sample dataset and create\ntwo plots that have sliders controlling the time-point that is shown. By default, both\nfigures are independent, but we will link the event channels of the figures together,\nso that moving the slider in one figure will also move the slider in the other.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "data_path = mne.datasets.sample.data_path()\nevokeds_fname = data_path / \"MEG\" / \"sample\" / \"sample_audvis-ave.fif\"\nevokeds = mne.read_evokeds(evokeds_fname)\nfor ev in evokeds:\n ev.apply_baseline()\navg_evokeds = mne.combine_evoked(evokeds, \"nave\")\nfig1 = avg_evokeds.plot_topomap(\"interactive\")\n\nstc_fname = data_path / \"MEG\" / \"sample\" / \"sample_audvis-meg-eeg\"\nstc = mne.read_source_estimate(stc_fname)\nfig2 = stc.plot(\"sample\", subjects_dir=data_path / \"subjects\")\n\nlink(fig1, fig2) # link the event channels" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Overlaying one figure over another\n\nA common scenario in which the UI event system comes in handy is when\nplotting multiple things in the same figure. For example, if we want to draw\nthe magnetic fieldlines on top of a source estimate, we can link the UI event\nchannels together, so that when a different time is selected, both the source\nestimate and the fieldlines are updated together.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig_brain = stc.plot(\"sample\", subjects_dir=data_path / \"subjects\", surface=\"white\")\nfig_brain.show_view(distance=400) # zoom out a little\n\nfield_map = mne.make_field_map(\n avg_evokeds,\n trans=data_path / \"MEG\" / \"sample\" / \"sample_audvis_raw-trans.fif\",\n subject=\"sample\",\n subjects_dir=data_path / \"subjects\",\n)\nfig_field = mne.viz.plot_evoked_field(\n avg_evokeds,\n field_map,\n alpha=0.2,\n fig=fig_brain, # plot inside the existing source estimate figure\n time_label=None, # there is already a time label in the figure\n)\n\nlink(fig_brain, fig_field)\nfig_brain.set_time(0.1) # updates both source estimate and field lines" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Hooking a custom plot into the event system\nIn MNE-Python, each figure has an associated event channel. Drawing routines can\n:func:`publish ` events on the channel and receive events\nby :func:`subscribe `-ing to the channel. When\nsubscribing to an event on a channel, you specify a callback function to be called\nwhenever a drawing routine publishes that event on the event channel.\n\nThe events are modeled after matplotlib's event system. Each event has a string name\n(the snake-case version of its class name) and a list of relevant values. For example,\nthe \"time_change\" event should have the new time as a value. Values can be any python\nobject. When publishing an event, the publisher creates a new instance of the event's\nclass. When subscribing to an event, having to dig up and import the correct class is\na bit of a hassle. Following matplotlib's example, subscribers use the string name of\nthe event to refer to it.\n\nBelow, we create a custom plot and then make it publish and subscribe to\n:class:`~mne.viz.ui_events.TimeChange` events so it can work together with the\nplots we created earlier.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Recreate the earlier plots\nfig3 = avg_evokeds.plot_topomap(\"interactive\")\nfig4 = stc.plot(\"sample\", subjects_dir=data_path / \"subjects\")\n\n# Create a custom plot\nfig5, ax = plt.subplots()\nax.plot(avg_evokeds.times, avg_evokeds.pick(\"mag\").data.max(axis=0))\ntime_bar = ax.axvline(0, color=\"black\") # Our time slider\nax.set_xlabel(\"Time (s)\")\nax.set_ylabel(\"Maximum magnetic field strength\")\nax.set_title(\"A custom plot\")\n\n\ndef on_motion_notify(mpl_event):\n \"\"\"Respond to matplotlib's mouse event.\n\n Publishes an MNE-Python TimeChange event. When the mouse goes out of bounds, the\n xdata will be None, which is a special case that needs to be handled.\n \"\"\"\n if mpl_event.xdata is not None:\n publish(fig5, TimeChange(time=mpl_event.xdata))\n\n\ndef on_time_change(event):\n \"\"\"Respond to MNE-Python's TimeChange event. Updates the plot.\"\"\"\n time_bar.set_xdata([event.time])\n fig5.canvas.draw() # update the figure\n\n\n# Setup the events for the curstom plot. Moving the mouse will trigger a\n# matplotlib event, which we will respond to by publishing an MNE-Python UI\n# event. Upon receiving a UI event, we will move the vertical line.\nplt.connect(\"motion_notify_event\", on_motion_notify)\nsubscribe(fig5, \"time_change\", on_time_change)\n\n# Link all the figures together.\nlink(fig3, fig4, fig5)\n\n# Method calls like this also emit the appropriate UI event.\nfig4.set_time(0.1)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.2" } }, "nbformat": 4, "nbformat_minor": 0 }