{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n\n# Visualize source leakage among labels using a circular graph\n\nThis example computes all-to-all pairwise leakage among 68 regions in\nsource space based on MNE inverse solutions and a FreeSurfer cortical\nparcellation. Label-to-label leakage is estimated as the correlation among the\nlabels' point-spread functions (PSFs). It is visualized using a circular graph\nwhich is ordered based on the locations of the regions in the axial plane.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Authors: Olaf Hauk \n# Martin Luessi \n# Alexandre Gramfort \n# Nicolas P. Rougier (graph code borrowed from his matplotlib gallery)\n#\n# License: BSD-3-Clause\n# Copyright the MNE-Python contributors." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\nimport numpy as np\nfrom mne_connectivity.viz import plot_connectivity_circle\n\nimport mne\nfrom mne.datasets import sample\nfrom mne.minimum_norm import (\n get_point_spread,\n make_inverse_resolution_matrix,\n read_inverse_operator,\n)\nfrom mne.viz import circular_layout\n\nprint(__doc__)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Load forward solution and inverse operator\n\nWe need a matching forward solution and inverse operator to compute\nresolution matrices for different methods.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "data_path = sample.data_path()\nsubjects_dir = data_path / \"subjects\"\nmeg_path = data_path / \"MEG\" / \"sample\"\nfname_fwd = meg_path / \"sample_audvis-meg-eeg-oct-6-fwd.fif\"\nfname_inv = meg_path / \"sample_audvis-meg-oct-6-meg-fixed-inv.fif\"\nforward = mne.read_forward_solution(fname_fwd)\n# Convert forward solution to fixed source orientations\nmne.convert_forward_solution(forward, surf_ori=True, force_fixed=True, copy=False)\ninverse_operator = read_inverse_operator(fname_inv)\n\n# Compute resolution matrices for MNE\nrm_mne = make_inverse_resolution_matrix(\n forward, inverse_operator, method=\"MNE\", lambda2=1.0 / 3.0**2\n)\nsrc = inverse_operator[\"src\"]\ndel forward, inverse_operator # save memory" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Read and organise labels for cortical parcellation\n\nGet labels for FreeSurfer 'aparc' cortical parcellation with 34 labels/hemi\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "labels = mne.read_labels_from_annot(\"sample\", parc=\"aparc\", subjects_dir=subjects_dir)\nn_labels = len(labels)\nlabel_colors = [label.color for label in labels]\n# First, we reorder the labels based on their location in the left hemi\nlabel_names = [label.name for label in labels]\nlh_labels = [name for name in label_names if name.endswith(\"lh\")]\n\n# Get the y-location of the label\nlabel_ypos = list()\nfor name in lh_labels:\n idx = label_names.index(name)\n ypos = np.mean(labels[idx].pos[:, 1])\n label_ypos.append(ypos)\n\n# Reorder the labels based on their location\nlh_labels = [label for (yp, label) in sorted(zip(label_ypos, lh_labels))]\n\n# For the right hemi\nrh_labels = [label[:-2] + \"rh\" for label in lh_labels]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Compute point-spread function summaries (PCA) for all labels\n\nWe summarise the PSFs per label by their first five principal components, and\nuse the first component to evaluate label-to-label leakage below.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Compute first PCA component across PSFs within labels.\n# Note the differences in explained variance, probably due to different\n# spatial extents of labels.\nn_comp = 5\nstcs_psf_mne, pca_vars_mne = get_point_spread(\n rm_mne, src, labels, mode=\"pca\", n_comp=n_comp, norm=None, return_pca_vars=True\n)\nn_verts = rm_mne.shape[0]\ndel rm_mne" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can show the explained variances of principal components per label. Note\nhow they differ across labels, most likely due to their varying spatial\nextent.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "with np.printoptions(precision=1):\n for [name, var] in zip(label_names, pca_vars_mne):\n print(f\"{name}: {var.sum():.1f}% {var}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The output shows the summed variance explained by the first five principal\ncomponents as well as the explained variances of the individual components.\n\n## Evaluate leakage based on label-to-label PSF correlations\n\nNote that correlations ignore the overall amplitude of PSFs, i.e. they do\nnot show which region will potentially be the bigger \"leaker\".\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# get PSFs from Source Estimate objects into matrix\npsfs_mat = np.zeros([n_labels, n_verts])\n# Leakage matrix for MNE, get first principal component per label\nfor [i, s] in enumerate(stcs_psf_mne):\n psfs_mat[i, :] = s.data[:, 0]\n# Compute label-to-label leakage as Pearson correlation of PSFs\n# Sign of correlation is arbitrary, so take absolute values\nleakage_mne = np.abs(np.corrcoef(psfs_mat))\n\n# Save the plot order and create a circular layout\nnode_order = lh_labels[::-1] + rh_labels # mirror label order across hemis\nnode_angles = circular_layout(\n label_names, node_order, start_pos=90, group_boundaries=[0, len(label_names) / 2]\n)\n# Plot the graph using node colors from the FreeSurfer parcellation. We only\n# show the 200 strongest connections.\nfig, ax = plt.subplots(\n figsize=(8, 8), facecolor=\"black\", subplot_kw=dict(projection=\"polar\")\n)\nplot_connectivity_circle(\n leakage_mne,\n label_names,\n n_lines=200,\n node_angles=node_angles,\n node_colors=label_colors,\n title=\"MNE Leakage\",\n ax=ax,\n)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Most leakage occurs for neighbouring regions, but also for deeper regions\nacross hemispheres.\n\n## Save the figure (optional)\n\nMatplotlib controls figure facecolor separately for interactive display\nversus for saved figures. Thus when saving you must specify ``facecolor``,\nelse your labels, title, etc will not be visible::\n\n >>> fname_fig = meg_path / 'plot_label_leakage.png'\n >>> fig.savefig(fname_fig, facecolor='black')\n\n## Plot PSFs for individual labels\n\nLet us confirm for left and right lateral occipital lobes that there is\nindeed no leakage between them, as indicated by the correlation graph.\nWe can plot the summary PSFs for both labels to examine the spatial extent of\ntheir leakage.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# left and right lateral occipital\nidx = [22, 23]\nstc_lh = stcs_psf_mne[idx[0]]\nstc_rh = stcs_psf_mne[idx[1]]\n\n# Maximum for scaling across plots\nmax_val = np.max([stc_lh.data, stc_rh.data])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Point-spread function for the lateral occipital label in the left hemisphere\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "brain_lh = stc_lh.plot(\n subjects_dir=subjects_dir,\n subject=\"sample\",\n hemi=\"both\",\n views=\"caudal\",\n clim=dict(kind=\"value\", pos_lims=(0, max_val / 2.0, max_val)),\n)\nbrain_lh.add_text(0.1, 0.9, label_names[idx[0]], \"title\", font_size=16)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "and in the right hemisphere.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "brain_rh = stc_rh.plot(\n subjects_dir=subjects_dir,\n subject=\"sample\",\n hemi=\"both\",\n views=\"caudal\",\n clim=dict(kind=\"value\", pos_lims=(0, max_val / 2.0, max_val)),\n)\nbrain_rh.add_text(0.1, 0.9, label_names[idx[1]], \"title\", font_size=16)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Both summary PSFs are confined to their respective hemispheres, indicating\nthat there is indeed low leakage between these two regions.\n\n" ] } ], "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 }