Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
244e2cc
source-snr-edit
padmasundaram Oct 1, 2019
908e7b9
FIX move estimate_snr to be a method
jasmainak Oct 1, 2019
f9fb26b
ENH: name change
jasmainak Oct 1, 2019
d954ae4
Make it work
jasmainak Oct 1, 2019
aef9f00
SNR works, now clean up code
padmasundaram Oct 1, 2019
a013fe7
fixed unnecessary stc copy, returns stc object
padmasundaram Oct 2, 2019
3b4a93d
added plot source space SNR tutorial file
padmasundaram Oct 2, 2019
a80c567
updated author names
padmasundaram Oct 2, 2019
5ad490f
fixed period for pydocsty and suggested documenation edit
padmasundaram Oct 2, 2019
9bf444e
edits suggested from @drammock
padmasundaram Oct 2, 2019
6e3106b
made edits suggested by reviewers
padmasundaram Oct 2, 2019
04012cf
made edits suggested by reviewers
padmasundaram Oct 2, 2019
3213d2b
N -> n_channels
padmasundaram Oct 2, 2019
3efeba9
Fixed formatting of Reference in comment
padmasundaram Oct 2, 2019
beac772
fixed formatting in doc and in code
padmasundaram Oct 2, 2019
60c1fd0
fixed snr_stc creation
padmasundaram Oct 2, 2019
dbcff92
further minor edits
padmasundaram Oct 2, 2019
66fdd89
fixed formatting issues and colormap choice in eg
padmasundaram Oct 3, 2019
39b64fb
removed colon after Reference
padmasundaram Oct 3, 2019
48a7c70
Corrected SNR calculation to take unwhitened sensor signal b instead …
klankinen Oct 3, 2019
7cd0057
Minor changes to formatting
klankinen Oct 4, 2019
fb2655f
FIX: transparent
larsoner Oct 4, 2019
40c4713
FIX: Rename
larsoner Oct 4, 2019
8c65e2e
FIX?
larsoner Oct 4, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions examples/inverse/plot_source_space_snr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
"""
===============================
Computing source space SNR
===============================

This example shows how to compute and plot source space SNR as in [1]_.
"""
# Author: Padma Sundaram <tottochan@gmail.com>
# Kaisu Lankinen <klankinen@mgh.harvard.edu>
#
# License: BSD (3-clause)

# sphinx_gallery_thumbnail_number = 2

import mne
from mne.datasets import sample
from mne.minimum_norm import make_inverse_operator, apply_inverse
import numpy as np
import matplotlib.pyplot as plt

print(__doc__)

data_path = sample.data_path()
subjects_dir = data_path + '/subjects'

# Read data
fname_evoked = data_path + '/MEG/sample/sample_audvis-ave.fif'
evoked = mne.read_evokeds(fname_evoked, condition='Left Auditory',
baseline=(None, 0))
fname_fwd = data_path + '/MEG/sample/sample_audvis-meg-eeg-oct-6-fwd.fif'
fname_cov = data_path + '/MEG/sample/sample_audvis-cov.fif'
fwd = mne.read_forward_solution(fname_fwd)
cov = mne.read_cov(fname_cov)

###############################################################################
# MEG-EEG
# -------

# Read inverse operator:
inv_op = make_inverse_operator(evoked.info, fwd, cov, fixed=True, verbose=True)

# Calculate MNE:
snr = 3.0
lambda2 = 1.0 / snr ** 2
stc = apply_inverse(evoked, inv_op, lambda2, 'MNE', verbose=True)

# Calculate SNR in source space:
snr_stc = stc.estimate_snr(evoked.info, fwd, cov)

# Plot an average SNR across source points over time:
ave = np.mean(snr_stc.data, axis=0)

fig, ax = plt.subplots()
ax.plot(evoked.times, ave)
ax.set(xlabel='Time (sec)', ylabel='SNR MEG-EEG')
fig.tight_layout()

# Find time point of maximum SNR:
maxidx = np.argmax(ave)

# Plot SNR on source space at the time point of maximum SNR:
kwargs = dict(initial_time=evoked.times[maxidx], hemi='split',
views=['lat', 'med'], subjects_dir=subjects_dir, size=(600, 600),
clim=dict(kind='value', lims=(-100, -70, -40)),
transparent=True, colormap='viridis')
snr_stc.plot(**kwargs)

###############################################################################
# EEG
# ---
# Next we do the same for EEG and plot the result on the cortex:

evoked_eeg = evoked.copy().pick_types(eeg=True, meg=False)
inv_op_eeg = make_inverse_operator(evoked_eeg.info, fwd, cov, fixed=True,
verbose=True)
stc_eeg = apply_inverse(evoked_eeg, inv_op_eeg, lambda2, 'MNE', verbose=True)
snr_stc_eeg = stc_eeg.estimate_snr(evoked_eeg.info, fwd, cov)
snr_stc_eeg.plot(**kwargs)

###############################################################################
# MEG
# ---
# Finally we do this for MEG:

evoked_meg = evoked.copy().pick_types(eeg=False, meg=True)
inv_op_meg = make_inverse_operator(evoked_meg.info, fwd, cov, fixed=True,
verbose=True)
stc_meg = apply_inverse(evoked_meg, inv_op_meg, lambda2, 'MNE', verbose=True)
snr_stc_meg = stc_meg.estimate_snr(evoked_meg.info, fwd, cov)
snr_stc_meg.plot(**kwargs)

##############################################################################
# References
# ----------
# .. [1] Goldenholz, D. M., Ahlfors, S. P., Hämäläinen, M. S., Sharon, D.,
# Ishitobi, M., Vaina, L. M., & Stufflebeam, S. M. (2009). Mapping the
# Signal-To-Noise-Ratios of Cortical Sources in Magnetoencephalography
# and Electroencephalography. Human Brain Mapping, 30(4), 1077–1086.
# doi:10.1002/hbm.20571
71 changes: 71 additions & 0 deletions mne/source_estimate.py
Original file line number Diff line number Diff line change
Expand Up @@ -1505,6 +1505,77 @@ def extract_label_time_course(self, labels, src, mode='mean_flip',

return label_tc

@verbose
def estimate_snr(self, info, fwd, cov, verbose=None):
r"""Compute time-varying SNR in the source space.

This function should only be used with source estimates with units
nanoAmperes (i.e., MNE-like solutions, *not* dSPM or sLORETA).

.. warning:: This function currently only works properly for fixed
orientation.

Parameters
----------
info : instance Info
The measurement info.
fwd : instance of Forward
The forward solution used to create the source estimate.
cov : instance of Covariance
The noise covariance used to estimate the resting cortical
activations. Should be an evoked covariance, not empty room.
%(verbose)s

Returns
-------
snr_stc : instance of SourceEstimate
The source estimate with the SNR computed.

Notes
-----
We define the SNR in decibels for each source location at each
time point as:

.. math::

{\rm SNR} = 10\log_10[\frac{a^2}{N}\sum_k\frac{b_k^2}{s_k^2}]

where :math:`\\b_k` is the signal on sensor :math:`k` provided by the
forward model for a source with unit amplitude, :math:`a` is the
source amplitude, :math:`N` is the number of sensors, and
:math:`s_k^2` is the noise variance on sensor :math:`k`.

References
----------
.. [1] Goldenholz, D. M., Ahlfors, S. P., Hämäläinen, M. S., Sharon,
D., Ishitobi, M., Vaina, L. M., & Stufflebeam, S. M. (2009).
Mapping the Signal-To-Noise-Ratios of Cortical Sources in
Magnetoencephalography and Electroencephalography.
Human Brain Mapping, 30(4), 1077–1086. doi:10.1002/hbm.20571
"""
from .forward import convert_forward_solution
from .minimum_norm.inverse import _prepare_forward

if (self.data >= 0).all():
warn_('This STC appears to be from free orientation, currently SNR'
' function is valid only for fixed orientation')

fwd = convert_forward_solution(fwd, surf_ori=True, force_fixed=False)

# G is gain matrix [ch x src], cov is noise covariance [ch x ch]
G, _, _, _, _, _, _, cov, _ = _prepare_forward(
fwd, info, cov, fixed=True, loose=0, rank=None, pca=False,
use_cps=True, exp=None, limit_depth_chs=False, combine_xyz='fro',
allow_fixed_depth=False, limit=None)
G = G['sol']['data']
n_channels = cov['dim'] # number of sensors/channels
b_k2 = (G * G).T
s_k2 = np.diag(cov['data'])
scaling = (1 / n_channels) * np.sum(b_k2 / s_k2, axis=1, keepdims=True)
snr_stc = self.copy()
snr_stc._data[:] = 10 * np.log10((self.data * self.data) * scaling)
return snr_stc

def get_peak(self, hemi=None, tmin=None, tmax=None, mode='abs',
vert_as_index=False, time_as_index=False):
"""Get location and latency of peak amplitude.
Expand Down