{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n\n# How to convert 3D electrode positions to a 2D image\n\nSometimes we want to convert a 3D representation of electrodes into a 2D\nimage. For example, if we are using electrocorticography it is common to create\nscatterplots on top of a brain, with each point representing an electrode.\n\nIn this example, we'll show two ways of doing this in MNE-Python. First,\nif we have the 3D locations of each electrode then we can use PyVista to\ntake a snapshot of a view of the brain. If we do not have these 3D locations,\nand only have a 2D image of the electrodes on the brain, we can use the\n:class:`mne.viz.ClickableImage` class to choose our own electrode positions\non the image.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Authors: Christopher Holdgraf \n# Alex Rockhill \n#\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\nfrom matplotlib import pyplot as plt\n\nimport mne\nfrom mne.io.fiff.raw import read_raw_fif\nfrom mne.viz import (\n ClickableImage, # noqa: F401\n plot_alignment,\n set_3d_view,\n snapshot_brain_montage,\n)\n\nmisc_path = mne.datasets.misc.data_path()\nsubjects_dir = misc_path / \"ecog\"\necog_data_fname = subjects_dir / \"sample_ecog_ieeg.fif\"\n\n# We've already clicked and exported\nlayout_name = subjects_dir / \"custom_layout.lout\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Load data\n\nFirst we will load a sample ECoG dataset which we'll use for generating\na 2D snapshot.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "raw = read_raw_fif(ecog_data_fname)\nraw.pick([f\"G{i}\" for i in range(1, 257)]) # pick just one grid\n\n# Since we loaded in the ecog data from FIF, the coordinates\n# are in 'head' space, but we actually want them in 'mri' space.\n# So we will apply the head->mri transform that was used when\n# generating the dataset (the estimated head->mri transform).\nmontage = raw.get_montage()\ntrans = mne.coreg.estimate_head_mri_t(\"sample_ecog\", subjects_dir)\nmontage.apply_trans(trans)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Project 3D electrodes to a 2D snapshot\n\nBecause we have the 3D location of each electrode, we can use the\n:func:`mne.viz.snapshot_brain_montage` function to return a 2D image along\nwith the electrode positions on that image. We use this in conjunction with\n:func:`mne.viz.plot_alignment`, which visualizes electrode positions.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "fig = plot_alignment(\n raw.info,\n trans=trans,\n subject=\"sample_ecog\",\n subjects_dir=subjects_dir,\n surfaces=dict(pial=0.9),\n)\nset_3d_view(figure=fig, azimuth=20, elevation=80)\nxy, im = snapshot_brain_montage(fig, montage)\n\n# Convert from a dictionary to array to plot\nxy_pts = np.vstack([xy[ch] for ch in raw.ch_names])\n\n# Compute beta power to visualize\nraw.load_data()\nbeta_power = raw.filter(20, 30).apply_hilbert(envelope=True).get_data()\nbeta_power = beta_power.max(axis=1) # take maximum over time\n\n# This allows us to use matplotlib to create arbitrary 2d scatterplots\nfig2, ax = plt.subplots(figsize=(10, 10))\nax.imshow(im)\ncmap = ax.scatter(*xy_pts.T, c=beta_power, s=100, cmap=\"coolwarm\")\ncbar = fig2.colorbar(cmap)\ncbar.ax.set_ylabel(\"Beta Power\")\nax.set_axis_off()\n\n# fig2.savefig('./brain.png', bbox_inches='tight') # For ClickableImage" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Manually creating 2D electrode positions\n\nIf we don't have the 3D electrode positions then we can still create a\n2D representation of the electrodes. Assuming that you can see the electrodes\non the 2D image, we can use :class:`mne.viz.ClickableImage` to open the image\ninteractively. You can click points on the image and the x/y coordinate will\nbe stored.\n\nWe'll open an image file, then use ClickableImage to\nreturn 2D locations of mouse clicks (or load a file already created).\nThen, we'll return these xy positions as a layout for use with plotting topo\nmaps.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# This code opens the image so you can click on it. Commented out\n# because we've stored the clicks as a layout file already.\n\n# # The click coordinates are stored as a list of tuples\n# im = plt.imread('./brain.png')\n# click = ClickableImage(im)\n# click.plot_clicks()\n\n# # Generate a layout from our clicks and normalize by the image\n# print('Generating and saving layout...')\n# lt = click.to_layout()\n# lt.save(layout_name) # save if we want\n\n# # We've already got the layout, load it\nlt = mne.channels.read_layout(layout_name, scale=False)\nx = lt.pos[:, 0] * float(im.shape[1])\ny = (1 - lt.pos[:, 1]) * float(im.shape[0]) # Flip the y-position\nfig, ax = plt.subplots(layout=\"constrained\")\nax.imshow(im)\nax.scatter(x, y, s=80, color=\"r\")\nax.set_axis_off()" ] } ], "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 }