Skip to content

Commit a26ca29

Browse files
authored
Merge branch 'main' into flexible_report
2 parents d36d5c1 + bc415a9 commit a26ca29

File tree

18 files changed

+332
-57
lines changed

18 files changed

+332
-57
lines changed

README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ There are also optional dependencies, which are not required for model fitting i
8282

8383
- `matplotlib <https://github.com/matplotlib/matplotlib>`_ is needed to visualize data and model fits
8484
- `tqdm <https://github.com/tqdm/tqdm>`_ is needed to print progress bars when fitting many models
85+
- `pandas <https://github.com/pandas-dev/pandas>`_ is needed to for exporting model fit results to dataframes
8586
- `pytest <https://github.com/pytest-dev/pytest>`_ is needed to run the test suite locally
8687

8788
We recommend using the `Anaconda <https://www.anaconda.com/distribution/>`_ distribution to manage these requirements.

examples/analyses/plot_mne_example.py

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
Parameterizing neural power spectra with MNE, doing a topographical analysis.
66
77
This tutorial requires that you have `MNE <https://mne-tools.github.io/>`_
8-
installed.
8+
installed. This tutorial needs mne >= 1.2.
99
1010
If you don't already have MNE, you can follow instructions to get it
1111
`here <https://mne-tools.github.io/stable/getting_started.html>`_.
@@ -23,10 +23,7 @@
2323

2424
# Import MNE, as well as the MNE sample dataset
2525
import mne
26-
from mne import io
2726
from mne.datasets import sample
28-
from mne.viz import plot_topomap
29-
from mne.time_frequency import psd_welch
3027

3128
# FOOOF imports
3229
from fooof import FOOOFGroup
@@ -52,16 +49,15 @@
5249
###################################################################################################
5350

5451
# Get the data path for the MNE example data
55-
raw_fname = sample.data_path() + '/MEG/sample/sample_audvis_filt-0-40_raw.fif'
56-
event_fname = sample.data_path() + '/MEG/sample/sample_audvis_filt-0-40_raw-eve.fif'
52+
raw_fname = sample.data_path() / 'MEG' / 'sample' / 'sample_audvis_filt-0-40_raw.fif'
5753

5854
# Load the example MNE data
5955
raw = mne.io.read_raw_fif(raw_fname, preload=True, verbose=False)
6056

6157
###################################################################################################
6258

6359
# Select EEG channels from the dataset
64-
raw = raw.pick_types(meg=False, eeg=True, eog=False, exclude='bads')
60+
raw = raw.pick(['eeg'], exclude='bads')
6561

6662
###################################################################################################
6763

@@ -110,15 +106,16 @@ def check_nans(data, nan_policy='zero'):
110106
# frequency representations - meaning we have to calculate power spectra.
111107
#
112108
# To do so, we will leverage the time frequency tools available with MNE,
113-
# in the `time_frequency` module. In particular, we can use the ``psd_welch``
114-
# function, that takes in MNE data objects and calculates and returns power spectra.
109+
# in the `time_frequency` module. In particular, we can use the ``compute_psd``
110+
# method, that takes in MNE data objects and calculates and returns power spectra.
115111
#
116112

117113
###################################################################################################
118114

119-
# Calculate power spectra across the the continuous data
120-
spectra, freqs = psd_welch(raw, fmin=1, fmax=40, tmin=0, tmax=250,
121-
n_overlap=150, n_fft=300)
115+
# Calculate power spectra across the continuous data
116+
psd = raw.compute_psd(method="welch", fmin=1, fmax=40, tmin=0, tmax=250,
117+
n_overlap=150, n_fft=300)
118+
spectra, freqs = psd.get_data(return_freqs=True)
122119

123120
###################################################################################################
124121
# Fitting Power Spectrum Models
@@ -193,7 +190,7 @@ def check_nans(data, nan_policy='zero'):
193190
###################################################################################################
194191

195192
# Plot the topography of alpha power
196-
plot_topomap(alpha_pw, raw.info, cmap=cm.viridis, contours=0);
193+
mne.viz.plot_topomap(alpha_pw, raw.info, cmap=cm.viridis, contours=0, size=4)
197194

198195
###################################################################################################
199196
#
@@ -214,8 +211,7 @@ def check_nans(data, nan_policy='zero'):
214211
band_power = check_nans(get_band_peak_fg(fg, band_def)[:, 1])
215212

216213
# Create a topomap for the current oscillation band
217-
mne.viz.plot_topomap(band_power, raw.info, cmap=cm.viridis, contours=0,
218-
axes=axes[ind], show=False);
214+
mne.viz.plot_topomap(band_power, raw.info, cmap=cm.viridis, contours=0, axes=axes[ind])
219215

220216
# Set the plot title
221217
axes[ind].set_title(label + ' power', {'fontsize' : 20})
@@ -268,7 +264,7 @@ def check_nans(data, nan_policy='zero'):
268264
###################################################################################################
269265

270266
# Plot the topography of aperiodic exponents
271-
plot_topomap(exps, raw.info, cmap=cm.viridis, contours=0)
267+
mne.viz.plot_topomap(exps, raw.info, cmap=cm.viridis, contours=0, size=4)
272268

273269
###################################################################################################
274270
#
@@ -297,6 +293,3 @@ def check_nans(data, nan_policy='zero'):
297293
# In this example, we have seen how to apply power spectrum models to data that is
298294
# managed and processed with MNE.
299295
#
300-
301-
###################################################################################################
302-
#

fooof/core/reports.py

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
###################################################################################################
2323

2424
@check_dependency(plt, 'matplotlib')
25-
def save_report_fm(fm, file_name, file_path=None, plt_log=True, **plot_kwargs):
25+
def save_report_fm(fm, file_name, file_path=None, plt_log=False, add_settings=True, **plot_kwargs):
2626
"""Generate and save out a PDF report for a power spectrum model fit.
2727
2828
Parameters
@@ -35,41 +35,46 @@ def save_report_fm(fm, file_name, file_path=None, plt_log=True, **plot_kwargs):
3535
Path to directory to save to. If None, saves to current directory.
3636
plt_log : bool, optional, default: False
3737
Whether or not to plot the frequency axis in log space.
38+
add_settings : bool, optional, default: True
39+
Whether to add a print out of the model settings to the end of the report.
3840
plot_kwargs : keyword arguments
3941
Keyword arguments to pass into the plot method.
4042
"""
4143

44+
# Define grid settings based on what is to be plotted
45+
n_rows = 3 if add_settings else 2
46+
height_ratios = [0.5, 1.0, 0.25] if add_settings else [0.45, 1.0]
47+
4248
# Set up outline figure, using gridspec
4349
_ = plt.figure(figsize=REPORT_FIGSIZE)
44-
grid = gridspec.GridSpec(3, 1, height_ratios=[0.45, 1.0, 0.25])
50+
grid = gridspec.GridSpec(n_rows, 1, hspace=0.25, height_ratios=height_ratios)
4551

4652
# First - text results
4753
ax0 = plt.subplot(grid[0])
4854
results_str = gen_results_fm_str(fm)
4955
ax0.text(0.5, 0.7, results_str, REPORT_FONT, ha='center', va='center')
5056
ax0.set_frame_on(False)
51-
ax0.set_xticks([])
52-
ax0.set_yticks([])
57+
ax0.set(xticks=[], yticks=[])
5358

5459
# Second - data plot
5560
ax1 = plt.subplot(grid[1])
5661
fm.plot(plt_log=plt_log, ax=ax1, **plot_kwargs)
5762

5863
# Third - FOOOF settings
59-
ax2 = plt.subplot(grid[2])
60-
settings_str = gen_settings_str(fm, False)
61-
ax2.text(0.5, 0.1, settings_str, REPORT_FONT, ha='center', va='center')
62-
ax2.set_frame_on(False)
63-
ax2.set_xticks([])
64-
ax2.set_yticks([])
64+
if add_settings:
65+
ax2 = plt.subplot(grid[2])
66+
settings_str = gen_settings_str(fm, False)
67+
ax2.text(0.5, 0.1, settings_str, REPORT_FONT, ha='center', va='center')
68+
ax2.set_frame_on(False)
69+
ax2.set(xticks=[], yticks=[])
6570

6671
# Save out the report
6772
plt.savefig(fpath(file_path, fname(file_name, SAVE_FORMAT)))
6873
plt.close()
6974

7075

7176
@check_dependency(plt, 'matplotlib')
72-
def save_report_fg(fg, file_name, file_path=None):
77+
def save_report_fg(fg, file_name, file_path=None, add_settings=True):
7378
"""Generate and save out a PDF report for a group of power spectrum models.
7479
7580
Parameters
@@ -80,19 +85,26 @@ def save_report_fg(fg, file_name, file_path=None):
8085
Name to give the saved out file.
8186
file_path : str, optional
8287
Path to directory to save to. If None, saves to current directory.
88+
add_settings : bool, optional, default: True
89+
Whether to add a print out of the model settings to the end of the report.
8390
"""
8491

92+
# Define grid settings based on what is to be plotted
93+
n_rows = 4 if add_settings else 3
94+
height_ratios = [1.0, 1.0, 1.0, 0.5] if add_settings else [0.8, 1.0, 1.0]
95+
8596
# Initialize figure
8697
_ = plt.figure(figsize=REPORT_FIGSIZE)
87-
grid = gridspec.GridSpec(3, 2, wspace=0.4, hspace=0.25, height_ratios=[0.8, 1.0, 1.0])
98+
grid = gridspec.GridSpec(n_rows, 2, wspace=0.4, hspace=0.25, height_ratios=height_ratios)
8899

89100
# First / top: text results
90101
ax0 = plt.subplot(grid[0, :])
91102
results_str = gen_results_fg_str(fg)
92103
ax0.text(0.5, 0.7, results_str, REPORT_FONT, ha='center', va='center')
93104
ax0.set_frame_on(False)
94-
ax0.set_xticks([])
95-
ax0.set_yticks([])
105+
ax0.set(xticks=[], yticks=[])
106+
107+
# Second - data plots
96108

97109
# Aperiodic parameters plot
98110
ax1 = plt.subplot(grid[1, 0])
@@ -106,6 +118,14 @@ def save_report_fg(fg, file_name, file_path=None):
106118
ax3 = plt.subplot(grid[2, :])
107119
plot_fg_peak_cens(fg, ax3)
108120

121+
# Third - Model settings
122+
if add_settings:
123+
ax4 = plt.subplot(grid[3, :])
124+
settings_str = gen_settings_str(fg, False)
125+
ax4.text(0.5, 0.1, settings_str, REPORT_FONT, ha='center', va='center')
126+
ax4.set_frame_on(False)
127+
ax4.set(xticks=[], yticks=[])
128+
109129
# Save out the report
110130
plt.savefig(fpath(file_path, fname(file_name, SAVE_FORMAT)))
111131
plt.close()

fooof/core/strings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def gen_width_warning_str(freq_res, bwl):
3636
output = '\n'.join([
3737
'',
3838
'FOOOF WARNING: Lower-bound peak width limit is < or ~= the frequency resolution: ' + \
39-
'{:1.2f} <= {:1.2f}'.format(freq_res, bwl),
39+
'{:1.2f} <= {:1.2f}'.format(bwl, freq_res),
4040
'\tLower bounds below frequency-resolution have no effect ' + \
4141
'(effective lower bound is the frequency resolution).',
4242
'\tToo low a limit may lead to overfitting noise as small bandwidth peaks.',

fooof/data/conversions.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"""Conversion functions for organizing model results into alternate representations."""
2+
3+
import numpy as np
4+
5+
from fooof import Bands
6+
from fooof.core.funcs import infer_ap_func
7+
from fooof.core.info import get_ap_indices, get_peak_indices
8+
from fooof.core.modutils import safe_import, check_dependency
9+
from fooof.analysis.periodic import get_band_peak
10+
11+
pd = safe_import('pandas')
12+
13+
###################################################################################################
14+
###################################################################################################
15+
16+
def model_to_dict(fit_results, peak_org):
17+
"""Convert model fit results to a dictionary.
18+
19+
Parameters
20+
----------
21+
fit_results : FOOOFResults
22+
Results of a model fit.
23+
peak_org : int or Bands
24+
How to organize peaks.
25+
If int, extracts the first n peaks.
26+
If Bands, extracts peaks based on band definitions.
27+
28+
Returns
29+
-------
30+
dict
31+
Model results organized into a dictionary.
32+
"""
33+
34+
fr_dict = {}
35+
36+
# aperiodic parameters
37+
for label, param in zip(get_ap_indices(infer_ap_func(fit_results.aperiodic_params)),
38+
fit_results.aperiodic_params):
39+
fr_dict[label] = param
40+
41+
# periodic parameters
42+
peaks = fit_results.peak_params
43+
44+
if isinstance(peak_org, int):
45+
46+
if len(peaks) < peak_org:
47+
nans = [np.array([np.nan] * 3) for ind in range(peak_org-len(peaks))]
48+
peaks = np.vstack((peaks, nans))
49+
50+
for ind, peak in enumerate(peaks[:peak_org, :]):
51+
for pe_label, pe_param in zip(get_peak_indices(), peak):
52+
fr_dict[pe_label.lower() + '_' + str(ind)] = pe_param
53+
54+
elif isinstance(peak_org, Bands):
55+
for band, f_range in peak_org:
56+
for label, param in zip(get_peak_indices(), get_band_peak(peaks, f_range)):
57+
fr_dict[band + '_' + label.lower()] = param
58+
59+
# goodness-of-fit metrics
60+
fr_dict['error'] = fit_results.error
61+
fr_dict['r_squared'] = fit_results.r_squared
62+
63+
return fr_dict
64+
65+
@check_dependency(pd, 'pandas')
66+
def model_to_dataframe(fit_results, peak_org):
67+
"""Convert model fit results to a dataframe.
68+
69+
Parameters
70+
----------
71+
fit_results : FOOOFResults
72+
Results of a model fit.
73+
peak_org : int or Bands
74+
How to organize peaks.
75+
If int, extracts the first n peaks.
76+
If Bands, extracts peaks based on band definitions.
77+
78+
Returns
79+
-------
80+
pd.Series
81+
Model results organized into a dataframe.
82+
"""
83+
84+
return pd.Series(model_to_dict(fit_results, peak_org))
85+
86+
87+
@check_dependency(pd, 'pandas')
88+
def group_to_dataframe(fit_results, peak_org):
89+
"""Convert a group of model fit results into a dataframe.
90+
91+
Parameters
92+
----------
93+
fit_results : list of FOOOFResults
94+
List of FOOOFResults objects.
95+
peak_org : int or Bands
96+
How to organize peaks.
97+
If int, extracts the first n peaks.
98+
If Bands, extracts peaks based on band definitions.
99+
100+
Returns
101+
-------
102+
pd.DataFrame
103+
Model results organized into a dataframe.
104+
"""
105+
106+
return pd.DataFrame([model_to_dataframe(f_res, peak_org) for f_res in fit_results])

0 commit comments

Comments
 (0)