New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MRG, BUG, DOC: derive GFP from average reference for EEG #8775
Conversation
Actually due to the nonlinearity in the term, even the relative changes in GFP were incorrect all along, if I'm not mistaken. |
I discussed this with @agramfort and therefore want to provide literature evidence why GFP must use the average-referenced signals. TL;DR: GFP is based on average-referenced signal. When using GFP, commonly two papers are cited:
In the 1980 paper, the term "global field power" does not yet occur; instead, it is called the "root-mean square", and they introduce the term "power": In the 1984 paper, the term "global field power" shows up, albeit only for the "reference-free" calculation; they mention the perks of using the average-referenced signal in the paragraph below the formula: In: Skrandies, W. Global field power and topographic similarity. Brain Topogr 3, 137–141 (1990), https://doi.org/10.1007/BF01128870, the GFP is explicitly named as being based on the average-referenced signal: More recently, in a 2008 review tutorial by Murray MM, Brunet D, Michel CM. Topographic ERP analyses: a step-by-step tutorial review. Brain Topogr. 2008 Jun;20(4):249-64. doi: 10.1007/s10548-008-0054-5, the GFP is presented as average-reference based too: Lastly, in a 2011 paper by Brunet et. al, GFP is also defined as average-reference-based (Brunet, Denis, Micah M. Murray, and Christoph M. Michel. "Spatiotemporal analysis of multichannel EEG: CARTOOL." Computational intelligence and neuroscience 2011, https://doi.org/10.1155/2011/813870) |
What's the difference to #8774 (which I just merged)? |
#8774 was not supposed to be merged anymore ;) Because I figured it doesn't address the underlying problem: the incorrect calculation of the GFP, which is addressed in this PR |
Ok I reverted the merge of #8774 @agramfort Your call on this one :) |
Here a comparison between the approach currently used in import os
import matplotlib.pyplot as plt
import numpy as np
import mne
sample_data_folder = mne.datasets.sample.data_path()
sample_data_raw_file = os.path.join(sample_data_folder, 'MEG', 'sample',
'sample_audvis_raw.fif')
raw = mne.io.read_raw_fif(sample_data_raw_file, verbose=False)
events = mne.find_events(raw, stim_channel='STI 014')
event_dict = {'auditory/left': 1, 'auditory/right': 2, 'visual/left': 3,
'visual/right': 4}
epochs = mne.Epochs(raw, events, tmin=-0.3, tmax=0.7, event_id=event_dict,
preload=False)
evoked = epochs['auditory/left'].average()
evoked.pick_types(meg=False, eeg=True)
fig, ax = plt.subplots(figsize=(12, 7))
ax.plot(np.sqrt((evoked.data ** 2).mean(axis=0)),
label='master', ls='--')
ax.plot(evoked.data.std(axis=0, ddof=0),
label='avg-ref', ls='--')
ax.set_xlabel('Sample', fontsize='large', fontweight='bold')
ax.set_ylabel('Global Field Power in V', fontsize='large', fontweight='bold')
ax.legend() |
to add some info to the debate. In
https://neuroimage.usc.edu/brainstorm/Tutorials/Averaging I read:
The green line represents the global field power (*GFP*), ie the sum of the
square of all the sensors at each time point. This measure is sometimes
used to identify transient or stable states in ERP/ERF. You can hide it
with the display options menu *Extra > Show GFP*.
can you check in brainstorm, fieldtrip or eeglab how it's done?
Remark: this is no such things as common average reference on MEG as you
don't need a reference :)
… |
But this would be something entirely different again! Unless there's a bug in their docs. They don't even take the root of the value, so it's actual power. GFP as outlined in all the publications I cited above is actually not a measure of power, as one can see by the resulting unit (µV, not µV^2).
I don't remember a built-in function in EEGLAB; I always had to use a plugin / additional toolbox. On the mailing list, they recommend to simply calculate the standard deviation across channels, as I'm doing in this PR. This automatically uses the average-referenced signal. Re Fieldtrip, Brainstorm I can do the research. We could also discuss simply offering different "GFP" calculation methods. |
Yep I'm aware of that, however you could still subtract the mean |
The reason for mne-tools#8772 is simply that we always calculated the GFP incorrectly, i.e. without re-referencing sensor signals to average first. When doing that, I now correctly get a flat GFP (0 at all time points) for single-channel recordings. Closes mne-tools#8772, mne-tools#8774
5ed2c3a
to
ed3e183
Compare
ed3e183
to
77210a9
Compare
@agramfort The FT docs are helpful regarding my previous-to-last comment (GFP not actually being a power):
That aside, this is how it's calculated: switch cfg.method
case 'amplitude'
dataout.avg = std(dataout.avg,1);
case 'power'
dataout.avg = std(dataout.avg,1).^2;
end I'm not really sure what |
@agramfort I'd propose to introduce an |
ERPLAB uses the standard deviation, too: MGFP_data = std(bindata(:,:,j)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some suggestions for making the tutorial a little tighter. Mostly nitpicks except the point about fixing ylim for the two plots that we're meant to compare
I couldn't get |
@hoechenberger the values changed for EEG (rightly so) and so we can just adjust the limits in the test, will push a commit. Good to know that the test is sufficiently sensitive to changes in values :) |
Thank you, @larsoner! |
Tutorials look good: API docs look good: This is good to merge once green. |
Whoops. No, It doesn't. The amplitude of the RMS is super big… the heck? scratches head |
you can use norm and divide by sqrt(n) or use the old formula, those two should be equivalent |
@larsoner I reverted to the old formula now because it's more intuitive to grasp when looking at the code. If you think using the approach based on the Frobenius norm would have strong advantages, I can switch to that too! |
Thanks @hoechenberger ! |
@larsoner Can you try to backport if you have a minute? Otherwise I can do it later today. |
* GFP was not derived from average-referenced signal as it must be The reason for #8772 is simply that we always calculated the GFP incorrectly, i.e. without re-referencing sensor signals to average first. When doing that, I now correctly get a flat GFP (0 at all time points) for single-channel recordings. Closes #8772, #8774 * Add GFP to EEG tutorial [skip azp][skip github] * Fix doc build [skip azp][skip github] * style [skip azp][skip github] * phrasing [skip github][skip azp] * Small fixes [skip azp][skip github] * Apply suggestions from code review [skip azp][skip github] Co-authored-by: Daniel McCloy <dan@mccloy.info> * Fix [skip azp][skip github] * Small fixes + adjust color [skip azp][skip github] * Add gfp='power', gfp='power-only' * Better docstring rendering * use np.linalg.norm Co-authored-by: Alexandre Gramfort <alexandre.gramfort@m4x.org> * power -> rms * GFP for EEG, RMS for MEG * Touch Evoked tutorial * Update changelog * Fix tests * FIX: Doc and test * Frobenius norm -> RMS Co-authored-by: Daniel McCloy <dan@mccloy.info> Co-authored-by: Alexandre Gramfort <alexandre.gramfort@m4x.org> Co-authored-by: Eric Larson <larson.eric.d@gmail.com>
done |
You're an angel <3 Or maybe a saint? who knows! Thank you! |
I've also rearranged the formula such that it becomes immediately obvious that we're dealing with RMS values. See mne-tools#8775 for our lengthy discussion on this for sensor-level data.
I've also rearranged the formula such that it becomes immediately obvious that we're dealing with RMS values. See mne-tools#8775 for our lengthy discussion on this for sensor-level data.
I've also rearranged the formula such that it becomes immediately obvious that we're dealing with RMS values. See mne-tools#8775 for our lengthy discussion on this for sensor-level data.
I've also rearranged the formula such that it becomes immediately obvious that we're dealing with RMS values. See mne-tools#8775 for our lengthy discussion on this for sensor-level data.
I've also rearranged the formula such that it becomes immediately obvious that we're dealing with RMS values. See mne-tools#8775 for our lengthy discussion on this for sensor-level data.
The reason for #8772 is simply that we always calculated the GFP incorrectly, i.e. without re-referencing sensor signals to average first. When doing that, I now correctly get a flat GFP (0 at all time points) for single-channel recordings:
This also means that since the introduction of this feature in 0.15 or so, our GFP plots haven't been correct in terms of absolute amplitude (which isn't too bad since it's difficult to judge the absolute amplitudes from the plot anyway)
Closes #8772, #8774