Skip to content
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

[ENH] Add option to store and return TFR taper weights #12910

Merged
merged 46 commits into from
Jan 13, 2025

Conversation

tsbinns
Copy link
Contributor

@tsbinns tsbinns commented Oct 22, 2024

Reference issue (if any)

PR for #12851

What does this implement/fix?

Adds an option to return taper weights for complex and phase outputs of the multitaper method in tfr_array_multitaper(), and also ensures taper weights are stored in TFR objects.

Additional information

When working on this, I discovered a couple of other issues with the per-taper TFR implementations (#12851 (comment)), including the fact that the TFR object plotting methods and to_data_frame methods do not account for a taper dimension, leading to errors. Wasn't sure if people want me to also address these here or in a separate PR.

@tsbinns
Copy link
Contributor Author

tsbinns commented Oct 22, 2024

I'm also somewhat confused about the design of the _make_dpss function:

for m in range(n_taps):
Wm = list()
Cm = list()
for k, f in enumerate(freqs):
if len(n_cycles) != 1:
this_n_cycles = n_cycles[k]
else:
this_n_cycles = n_cycles[0]
t_win = this_n_cycles / float(f)
t = np.arange(0.0, t_win, 1.0 / sfreq)
# Making sure wavelets are centered before tapering
oscillation = np.exp(2.0 * 1j * np.pi * f * (t - t_win / 2.0))
# Get dpss tapers
tapers, conc = dpss_windows(
t.shape[0], time_bandwidth / 2.0, n_taps, sym=False
)
Wk = oscillation * tapers[m]
if zero_mean: # to make it zero mean
real_offset = Wk.mean()
Wk -= real_offset
Wk /= np.sqrt(0.5) * np.linalg.norm(Wk.ravel())
Ck = np.sqrt(conc[m])
Wm.append(Wk)
Cm.append(Ck)
Ws.append(Wm)
Cs.append(Cm)

It is looping over tapers, and then over frequencies. However, the dpss_windows function it calls internally provides the tapers and their weights for all tapers of a given frequency.

Would it not be more efficient to only loop over frequencies and take advantage of the fact that this will also return information for each taper?

mne/time_frequency/tfr.py Outdated Show resolved Hide resolved
@tsbinns
Copy link
Contributor Author

tsbinns commented Oct 22, 2024

I also have a question regarding testing: for the I/O tests, we're reading TFR objects that do not have a weights property (just gets assigned to None) when loaded. Do I need to create new TFR objects that actually have some weights, or is the current test sufficient?

Apart from this there are still some tests I need to expand.

mne/time_frequency/multitaper.py Outdated Show resolved Hide resolved
mne/time_frequency/tfr.py Show resolved Hide resolved
mne/time_frequency/tfr.py Show resolved Hide resolved
mne/time_frequency/tfr.py Show resolved Hide resolved
@tsbinns
Copy link
Contributor Author

tsbinns commented Oct 29, 2024

Thanks for the review @drammock! I will sort out those remaining tests, although I'm in the process of moving at the moment so it might not be for some days.

Regarding those issues I came across with TFR multitapers and converting to dataframes / plotting: would you like me to incorporate that into this PR?

@tsbinns
Copy link
Contributor Author

tsbinns commented Dec 12, 2024

Just looking into the sorting the power this morning and I am a little confused by the procedure being used to convert the complex taper coeffs into power, as it seems that no taper weights are ever applied. I opened an issue to try and figure out if this is a mistake, or a misunderstanding on my part: #13023

Copy link
Contributor Author

@tsbinns tsbinns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Latest push adds support for plotting of data with a taper dimension (aggregates over tapers before plotting and converts to power (if complex coeffs) or keeps as phase data).
Also has test coverage.

mne/time_frequency/tests/test_tfr.py Show resolved Hide resolved
mne/time_frequency/tests/test_tfr.py Show resolved Hide resolved
mne/time_frequency/tfr.py Show resolved Hide resolved
mne/viz/topomap.py Show resolved Hide resolved
mne/time_frequency/tfr.py Outdated Show resolved Hide resolved
mne/time_frequency/tfr.py Show resolved Hide resolved
@larsoner larsoner added this to the 1.10 milestone Dec 16, 2024
@tsbinns
Copy link
Contributor Author

tsbinns commented Dec 19, 2024

Just looking into the sorting the power this morning and I am a little confused by the procedure being used to convert the complex taper coeffs into power, as it seems that no taper weights are ever applied. I opened an issue to try and figure out if this is a mistake, or a misunderstanding on my part: #13023

As discussed there this is a bug but will be addressed in a separate PR once this is merged.

Copy link
Member

@drammock drammock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@larsoner this one's ready for your eyes I think. I called out one question from @tsbinns that I'm hoping you can spot the answer to quickly (aggregating over tapers for phase data)

mne/time_frequency/tfr.py Show resolved Hide resolved
mne/time_frequency/tfr.py Outdated Show resolved Hide resolved
mne/time_frequency/tfr.py Show resolved Hide resolved
@tsbinns tsbinns mentioned this pull request Jan 10, 2025
Copy link
Member

@larsoner larsoner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a couple of minor comments otherwise LGTM!

doc/changes/devel/12910.bugfix.rst Outdated Show resolved Hide resolved
mne/time_frequency/multitaper.py Outdated Show resolved Hide resolved
mne/time_frequency/tfr.py Outdated Show resolved Hide resolved
mne/time_frequency/tfr.py Outdated Show resolved Hide resolved
mne/time_frequency/tfr.py Outdated Show resolved Hide resolved
@tsbinns
Copy link
Contributor Author

tsbinns commented Jan 13, 2025

So I think that's the comments from both of your reviews addressed @larsoner and @drammock; thanks for that!
Looking through the rest I don't see other outstanding points. Is this good to merge?

@drammock drammock enabled auto-merge (squash) January 13, 2025 18:45
@drammock
Copy link
Member

So I think that's the comments from both of your reviews addressed @larsoner and @drammock; thanks for that! Looking through the rest I don't see other outstanding points. Is this good to merge?

restarted the failing CI (which should pass this time 🤞🏻) and marked for auto-merge when green. Thanks @tsbinns

@drammock drammock merged commit 1d2635f into mne-tools:main Jan 13, 2025
30 checks passed
@tsbinns
Copy link
Contributor Author

tsbinns commented Jan 13, 2025

Thanks for all your help and patience with this @drammock and @larsoner! 🚀

@tsbinns tsbinns deleted the add_tfr_weights branch January 13, 2025 20:33
larsoner added a commit to emma-bailey/mne-python that referenced this pull request Jan 14, 2025
* upstream/main:
  [DOC] extend documentation for add_channels (mne-tools#13051)
  Add `combine_tfr` to API (mne-tools#13054)
  Add `combine_spectrum()` function and allow `grand_average()` to support `Spectrum` data (mne-tools#13058)
  BUG: Fix bug with helium anon (mne-tools#13056)
  [ENH] Add option to store and return TFR taper weights (mne-tools#12910)
qian-chu pushed a commit to qian-chu/mne-python that referenced this pull request Jan 20, 2025
larsoner added a commit to larsoner/mne-python that referenced this pull request Jan 24, 2025
* upstream/main: (57 commits)
  Allow lasso selection sensors in a plot_evoked_topo (mne-tools#12071)
  MAINT: Fix doc build (mne-tools#13076)
  BUG: Improve sklearn compliance (mne-tools#13065)
  [pre-commit.ci] pre-commit autoupdate (mne-tools#13073)
  MAINT: Add Numba to 3.13 test (mne-tools#13075)
  Bump autofix-ci/action from ff86a557419858bb967097bfc916833f5647fa8c to 551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef in the actions group (mne-tools#13071)
  [BUG] Correct annotation onset for exportation to EDF and EEGLAB (mne-tools#12656)
  New feature for removing heart artifacts from EEG or ESG data using a Principal Component Analysis - Optimal Basis Sets (PCA-OBS) algorithm (mne-tools#13037)
  [BUG] Fix taper weighting in computation of TFR multitaper power (mne-tools#13067)
  [FIX] Reading an EDF with preload=False and mixed frequency (mne-tools#13069)
  Fix evoked topomap colorbars, closes mne-tools#13050 (mne-tools#13063)
  [pre-commit.ci] pre-commit autoupdate (mne-tools#13060)
  BUG: Fix bug with interval calculation (mne-tools#13062)
  [DOC] extend documentation for add_channels (mne-tools#13051)
  Add `combine_tfr` to API (mne-tools#13054)
  Add `combine_spectrum()` function and allow `grand_average()` to support `Spectrum` data (mne-tools#13058)
  BUG: Fix bug with helium anon (mne-tools#13056)
  [ENH] Add option to store and return TFR taper weights (mne-tools#12910)
  BUG: viz plot window's 'title' argument showed no effect. (mne-tools#12828)
  MAINT: Ensure limited set of tests are skipped (mne-tools#13053)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants