{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n\n# Visualizing Evoked data\n\nThis tutorial shows the different visualization methods for\n:class:`~mne.Evoked` objects.\n\nAs usual we'll start by importing the modules we need:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Authors: The MNE-Python contributors.\n# License: BSD-3-Clause\n# Copyright the MNE-Python contributors." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import numpy as np\n\nimport mne" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Instead of creating the :class:`~mne.Evoked` object from an\n:class:`~mne.Epochs` object, we'll load an existing :class:`~mne.Evoked`\nobject from disk. Remember, the :file:`.fif` format can store multiple\n:class:`~mne.Evoked` objects, so we'll end up with a `list` of\n:class:`~mne.Evoked` objects after loading. Recall also from the\n`tut-section-load-evk` section of `the introductory Evoked tutorial\n` that the sample :class:`~mne.Evoked` objects have not\nbeen baseline-corrected and have unapplied projectors, so we'll take care of\nthat when loading:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "root = mne.datasets.sample.data_path() / \"MEG\" / \"sample\"\nevoked_file = root / \"sample_audvis-ave.fif\"\nevokeds_list = mne.read_evokeds(\n evoked_file, baseline=(None, 0), proj=True, verbose=False\n)\n\n# Show condition names and baseline intervals\nfor e in evokeds_list:\n print(f\"Condition: {e.comment}, baseline: {e.baseline}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To make our life easier, let's convert that list of :class:`~mne.Evoked`\nobjects into a :class:`dictionary `. We'll use ``/``-separated\ndictionary keys to encode the conditions (like is often done when epoching)\nbecause some of the plotting methods can take advantage of that style of\ncoding.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "conds = (\"aud/left\", \"aud/right\", \"vis/left\", \"vis/right\")\nevks = dict(zip(conds, evokeds_list))\n# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is equivalent to:\n# {'aud/left': evokeds_list[0], 'aud/right': evokeds_list[1],\n# 'vis/left': evokeds_list[2], 'vis/right': evokeds_list[3]}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Plotting signal traces\n\n.. admonition:: Butterfly plots\n :class: sidebar note\n\n Plots of superimposed sensor timeseries are called \"butterfly plots\"\n because the positive- and negative-going traces can resemble butterfly\n wings.\n\nThe most basic plot of :class:`~mne.Evoked` objects is a butterfly plot of\neach channel type, generated by the `evoked.plot() `\nmethod. By default, channels marked as \"bad\" are suppressed, but you can\ncontrol this by passing an empty :class:`list` to the ``exclude`` parameter\n(default is ``exclude='bads'``):\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "evks[\"aud/left\"].plot(exclude=[])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice the completely flat EEG channel and the noisy gradiometer channel\nplotted in red color. Like many MNE-Python plotting functions,\n`evoked.plot() ` has a ``picks`` parameter that can\nselect channels to plot by name, index, or type. In the next plot, we'll show\nonly magnetometer channels and also color-code the channel traces by their\nlocation by passing ``spatial_colors=True``. Finally, we'll superimpose a\ntrace of the root mean square (RMS) of the signal across channels by\npassing ``gfp=True``. This parameter is called ``gfp`` for historical\nreasons and behaves correctly for all supported channel types: for MEG data,\nit will plot the RMS; while for EEG, it would plot the\n:term:`global field power ` (an average-referenced RMS), hence its\nname:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "evks[\"aud/left\"].plot(picks=\"mag\", spatial_colors=True, gfp=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Interesting time periods can be highlighted via the ``highlight`` parameter.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "time_ranges_of_interest = [(0.05, 0.14), (0.22, 0.27)]\nevks[\"aud/left\"].plot(\n picks=\"mag\", spatial_colors=True, gfp=True, highlight=time_ranges_of_interest\n)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Plotting scalp topographies\n\nIn an interactive session, the butterfly plots seen above can be\nclick-dragged to select a time region, which will pop up a map of the average\nfield distribution over the scalp for the selected time span. You can also\ngenerate scalp topographies at specific times or time spans using the\n:meth:`~mne.Evoked.plot_topomap` method:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "times = np.linspace(0.05, 0.13, 5)\nevks[\"aud/left\"].plot_topomap(ch_type=\"mag\", times=times, colorbar=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig = evks[\"aud/left\"].plot_topomap(ch_type=\"mag\", times=times, average=0.1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It is also possible to pass different time durations to average over for each\ntime point. Passing a value of ``None`` will disable averaging for that\ntime point:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "averaging_durations = [0.01, 0.02, 0.03, None, None]\nfig = evks[\"aud/left\"].plot_topomap(\n ch_type=\"mag\", times=times, average=averaging_durations\n)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Additional examples of plotting scalp topographies can be found in\n`ex-evoked-topomap`.\n\n\n## Arrow maps\n\nScalp topographies at a given time point can be augmented with arrows to show\nthe estimated magnitude and direction of the magnetic field, using the\nfunction :func:`mne.viz.plot_arrowmap`:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "mags = evks[\"aud/left\"].copy().pick(picks=\"mag\")\nmne.viz.plot_arrowmap(mags.data[:, 175], mags.info, extrapolate=\"local\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Joint plots\n\nJoint plots combine butterfly plots with scalp topographies, and provide an\nexcellent first-look at evoked data; by default, topographies will be\nautomatically placed based on peak finding. Here we plot the\nright-visual-field condition; if no ``picks`` are specified we get a separate\nfigure for each channel type:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "evks[\"vis/right\"].plot_joint()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Like :meth:`~mne.Evoked.plot_topomap`, you can specify the ``times`` at which\nyou want the scalp topographies calculated, and you can customize the plot in\nvarious other ways as well. See :meth:`mne.Evoked.plot_joint` for details.\n\n\n## Comparing ``Evoked`` objects\n\nTo compare :class:`~mne.Evoked` objects from different experimental\nconditions, the function :func:`mne.viz.plot_compare_evokeds` can take a\n:class:`list` or :class:`dict` of :class:`~mne.Evoked` objects and plot them\nall on the same axes. Like most MNE-Python visualization functions, it has a\n``picks`` parameter for selecting channels, but by default will generate one\nfigure for each channel type, and combine information across channels of the\nsame type by calculating the :term:`global field power`. Information\nmay be combined across channels in other ways too; support for combining via\nmean, median, or standard deviation are built-in, and custom callable\nfunctions may also be used, as shown here:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def custom_func(x):\n return x.max(axis=1)\n\n\nfor combine in (\"mean\", \"median\", \"gfp\", custom_func):\n mne.viz.plot_compare_evokeds(evks, picks=\"eeg\", combine=combine)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One nice feature of :func:`~mne.viz.plot_compare_evokeds` is that when\npassing evokeds in a dictionary, it allows specifying plot styles based on\n``/``-separated substrings of the dictionary keys (similar to epoch\nselection; see `tut-section-subselect-epochs`). Here, we specify colors\nfor \"aud\" and \"vis\" conditions, and linestyles for \"left\" and \"right\"\nconditions, and the traces and legend are styled accordingly. Here we also\nshow the ``time_unit='ms'`` parameter in action.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "mne.viz.plot_compare_evokeds(\n evks,\n picks=\"MEG 1811\",\n colors=dict(aud=0, vis=1),\n linestyles=dict(left=\"solid\", right=\"dashed\"),\n time_unit=\"ms\",\n)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The legends generated by :func:`~mne.viz.plot_compare_evokeds` above used the\ndictionary keys provided by the ``evks`` variable. If instead you pass a\n:class:`list` or :class:`tuple` of :class:`~mne.Evoked` objects, the legend\nkeys will be generated automatically from the ``comment`` attribute of the\n:class:`~mne.Evoked` objects (or, as sequential integers if the comment\nattribute is empty or ambiguous). To illustrate this, we'll make a list of\nfive :class:`~mne.Evoked` objects: two with identical comments, two with\nempty comments (either an empty string or ``None``), and one with a unique\nnon-empty comment:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "temp_list = list()\nfor idx, _comment in enumerate((\"foo\", \"foo\", \"\", None, \"bar\"), start=1):\n _evk = evokeds_list[0].copy()\n _evk.comment = _comment\n _evk.data *= idx # so we can tell the traces apart\n temp_list.append(_evk)\n\nmne.viz.plot_compare_evokeds(temp_list, picks=\"mag\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Image plots\n\nLike :class:`~mne.Epochs`, :class:`~mne.Evoked` objects also have a\n:meth:`~mne.Evoked.plot_image` method, but unlike `epochs.plot_image()\n`, `evoked.plot_image() `\nshows one *channel* per row instead of one *epoch* per row. Again, a\n``picks`` parameter is available, as well as several other customization\noptions; see :meth:`~mne.Evoked.plot_image` for details.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "evks[\"vis/right\"].plot_image(picks=\"meg\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Topographical subplots\n\nFor sensor-level analyses, it can be useful to plot the response at each\nsensor in a topographical layout. The :func:`~mne.viz.plot_compare_evokeds`\nfunction can do this if you pass ``axes='topo'``, but it can be quite slow\nif the number of sensors is too large, so here we'll plot only the EEG\nchannels:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "mne.viz.plot_compare_evokeds(\n evks,\n picks=\"eeg\",\n colors=dict(aud=0, vis=1),\n linestyles=dict(left=\"solid\", right=\"dashed\"),\n axes=\"topo\",\n styles=dict(aud=dict(linewidth=1), vis=dict(linewidth=1)),\n)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For a larger number of sensors, the method `evoked.plot_topo()\n` and the function :func:`mne.viz.plot_evoked_topo`\ncan both be used. The :meth:`~mne.Evoked.plot_topo` method will plot only a\nsingle condition, while the :func:`~mne.viz.plot_evoked_topo` function can\nplot one or more conditions on the same axes, if passed a list of\n:class:`~mne.Evoked` objects. The legend entries will be automatically drawn\nfrom the :class:`~mne.Evoked` objects' ``comment`` attribute:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "mne.viz.plot_evoked_topo(evokeds_list)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By default, :func:`~mne.viz.plot_evoked_topo` will plot all MEG sensors (if\npresent), so to get EEG sensors you would need to modify the evoked objects\nfirst (e.g., using `mne.pick_types`).\n\n

Note

In interactive sessions, both approaches to topographical plotting allow\n you to click one of the sensor subplots to open a larger version of the\n evoked plot at that sensor.

\n\n\n## 3D Field Maps\n\nThe scalp topographies above were all projected into two-dimensional overhead\nviews of the field, but it is also possible to plot field maps in 3D. This\nrequires a :term:`trans` file to transform locations between the coordinate\nsystems of the MEG device and the head surface (based on the MRI). You *can*\ncompute 3D field maps without a ``trans`` file, but it will only work for\ncalculating the field *on the MEG helmet from the MEG sensors*.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "subjects_dir = root.parents[1] / \"subjects\"\ntrans_file = root / \"sample_audvis_raw-trans.fif\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By default, MEG sensors will be used to estimate the field on the helmet\nsurface, while EEG sensors will be used to estimate the field on the scalp.\nOnce the maps are computed, you can plot them with `evoked.plot_field()\n`:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "maps = mne.make_field_map(\n evks[\"aud/left\"], trans=str(trans_file), subject=\"sample\", subjects_dir=subjects_dir\n)\nevks[\"aud/left\"].plot_field(maps, time=0.1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also use MEG sensors to estimate the *scalp* field by passing\n``meg_surf='head'``. By selecting each sensor type in turn, you can compare\nthe scalp field estimates from each.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "for ch_type in (\"mag\", \"grad\", \"eeg\"):\n evk = evks[\"aud/right\"].copy().pick(ch_type)\n _map = mne.make_field_map(\n evk,\n trans=str(trans_file),\n subject=\"sample\",\n subjects_dir=subjects_dir,\n meg_surf=\"head\",\n )\n fig = evk.plot_field(_map, time=0.1)\n mne.viz.set_3d_title(fig, ch_type, size=20)" ] } ], "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 }