Skip to content

Commit 5a835bd

Browse files
committed
fix merge
2 parents 4b4caf6 + 117cbc0 commit 5a835bd

File tree

26 files changed

+294
-92
lines changed

26 files changed

+294
-92
lines changed

.github/workflows/build.yml

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,20 @@ on:
1111
jobs:
1212
build:
1313

14-
runs-on: ubuntu-latest
14+
# Tag ubuntu version to 20.04, in order to support python 3.6
15+
# See issue: https://github.com/actions/setup-python/issues/544
16+
# When ready to drop 3.6, can revert from 'ubuntu-20.04' -> 'ubuntu-latest'
17+
runs-on: ubuntu-20.04
18+
env:
19+
MODULE_NAME: fooof
1520
strategy:
1621
matrix:
17-
python-version: [3.6, 3.7, 3.8, 3.9]
22+
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"]
1823

1924
steps:
20-
- uses: actions/checkout@v2
25+
- uses: actions/checkout@v3
2126
- name: Set up Python ${{ matrix.python-version }}
22-
uses: actions/setup-python@v2
27+
uses: actions/setup-python@v4
2328
with:
2429
python-version: ${{ matrix.python-version }}
2530
- name: Install dependencies
@@ -32,5 +37,8 @@ jobs:
3237
- name: Test with pytest
3338
run: |
3439
pytest --cov=./
40+
- name: Run doctests
41+
run: |
42+
pytest --doctest-modules --ignore=$MODULE_NAME/tests $MODULE_NAME
3543
- name: Upload coverage to Codecov
36-
uses: codecov/codecov-action@v1
44+
uses: codecov/codecov-action@v3

CITATION.cff

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
cff-version: 1.2.0
2+
message: "If you use this software, please cite it as below."
3+
preferred-citation:
4+
type: article
5+
authors:
6+
- family-names: "Donoghue"
7+
given-names: "Thomas"
8+
orcid: "https://orcid.org/0000-0001-5911-0472"
9+
- family-names: "Haller"
10+
given-names: "Matar"
11+
- family-names: "Peterson"
12+
given-names: "Erik J"
13+
- family-names: "Varma"
14+
given-names: "Paroma"
15+
- family-names: "Sebastian"
16+
given-names: "Priyadarshini"
17+
- family-names: "Gao"
18+
given-names: "Richard"
19+
- family-names: "Noto"
20+
given-names: "Torben"
21+
- family-names: "Lara"
22+
given-names: "Antonio H"
23+
- family-names: "Wallis"
24+
given-names: "Joni D"
25+
- family-names: "Knight"
26+
given-names: "Robert T"
27+
- family-names: "Shestyuk"
28+
given-names: "Avgusta"
29+
- family-names: "Voytek"
30+
given-names: "Bradley"
31+
doi: "10.1038/s41593-020-00744-x"
32+
journal: "Nature Neuroscience"
33+
title: "Parameterizing neural power spectra into periodic and aperiodic components"
34+
issue: 12
35+
volume: 23
36+
year: 2020
37+
start: 1655
38+
end: 1665

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# setuptools For creating distributions
1313
#
1414
# The following command line utilities are required:
15-
# cloc For counting code
15+
# cloc For counting lines of code
1616
#
1717

1818
##########################################################################

doc/Makefile

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
# Minimal makefile for Sphinx documentation
2+
# This make is adapted & extended from the original sphinx file
3+
4+
##########################################################################
5+
## Settings
26

37
# You can set these variables from the command line.
48
SPHINXOPTS =
@@ -7,6 +11,12 @@ SPHINXPROJ = fooof
711
SOURCEDIR = .
812
BUILDDIR = _build
913

14+
# Custom settings
15+
GITHUBORG = https://github.com/fooof-tools
16+
17+
##########################################################################
18+
## Standard sphinx tasks (from sphinx)
19+
1020
# Put it first so that "make" without argument is like "make help".
1121
help:
1222
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@@ -18,6 +28,9 @@ help:
1828
%: Makefile
1929
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
2030

31+
##########################################################################
32+
## Custom tasks
33+
2134
# Custom cleaner that also removes the generated files from sphinx-gallery
2235
clean:
2336
rm -rf $(BUILDDIR)/*
@@ -35,24 +48,28 @@ check:
3548
plots:
3649
python make_doc_plots.py
3750

38-
# Build the html site, and push it to gh-pages branch of repo to deploy
39-
install:
40-
# Clean out existing build
41-
make clean
51+
# Deploy the site to github pages site
52+
deploy:
4253
# Clone, specifically, the gh-pages branch, putting it into '_build/gh_pages/'
54+
# -b gh-pages --single-branch fetches specifically and only the gh-pages branch
4355
# --no-checkout just fetches the root folder without content
4456
# --depth 1 is a speed optimization since we don't need the history prior to the last commit
45-
# -b gh-pages fetches only the branch for the gh-pages
46-
git clone -b gh-pages --single-branch --no-checkout --depth 1 https://github.com/fooof-tools/fooof _build/gh_pages
47-
# A .nojekyll file tells Github pages to bypass Jekyll processing
57+
git clone -b gh-pages --single-branch --no-checkout --depth 1 $(GITHUBORG)/$(SPHINXPROJ) _build/gh_pages
58+
59+
# Add a .nojekyll file to tell Github pages to bypass Jekyll processing
4860
touch _build/gh_pages/.nojekyll
49-
# Build the sphinx site
50-
make html
61+
5162
# Copy site into the gh-pages branch folder, then push to Github to deploy
5263
cd _build/ && \
5364
cp -r html/* gh_pages && \
5465
cd gh_pages && \
5566
git add * && \
5667
git add .nojekyll && \
57-
git commit -a -m 'Make install' && \
68+
git commit -a -m 'deploy docsite' && \
5869
git push
70+
71+
# Clean & rebuild the html site, then deploy docsite
72+
install:
73+
make clean
74+
make html
75+
make deploy

doc/api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ Utilities for working with data.
341341

342342
trim_spectrum
343343
interpolate_spectrum
344+
subsample_spectra
344345

345346
Parameter Utilities
346347
~~~~~~~~~~~~~~~~~~~

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/modutils.py

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,38 @@ def safe_import(*args):
4545
return mod
4646

4747

48+
def check_dependency(dep, name):
49+
"""Decorator that checks if an optional dependency is available.
50+
51+
Parameters
52+
----------
53+
dep : module or False
54+
Module, if successfully imported, or boolean (False) if not.
55+
name : str
56+
Full name of the module, to be printed in message.
57+
58+
Returns
59+
-------
60+
wrap : callable
61+
The decorated function.
62+
63+
Raises
64+
------
65+
ImportError
66+
If the requested dependency is not available.
67+
"""
68+
69+
def wrap(func):
70+
@wraps(func)
71+
def wrapped_func(*args, **kwargs):
72+
if not dep:
73+
raise ImportError("Optional FOOOF dependency " + name + \
74+
" is required for this functionality.")
75+
return func(*args, **kwargs)
76+
return wrapped_func
77+
return wrap
78+
79+
4880
def docs_drop_param(docstring):
4981
"""Drop the first parameter description for a string representation of a docstring.
5082
@@ -148,35 +180,3 @@ def wrapper(func):
148180
return func
149181

150182
return wrapper
151-
152-
153-
def check_dependency(dep, name):
154-
"""Decorator that checks if an optional dependency is available.
155-
156-
Parameters
157-
----------
158-
dep : module or False
159-
Module, if successfully imported, or boolean (False) if not.
160-
name : str
161-
Full name of the module, to be printed in message.
162-
163-
Returns
164-
-------
165-
wrap : callable
166-
The decorated function.
167-
168-
Raises
169-
------
170-
ImportError
171-
If the requested dependency is not available.
172-
"""
173-
174-
def wrap(func):
175-
@wraps(func)
176-
def wrapped_func(*args, **kwargs):
177-
if not dep:
178-
raise ImportError("Optional FOOOF dependency " + name + \
179-
" is required for this functionality.")
180-
return func(*args, **kwargs)
181-
return wrapped_func
182-
return wrap

fooof/objs/fit.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -762,16 +762,16 @@ def _simple_ap_fit(self, freqs, power_spectrum):
762762
# Note that these are collected as lists, to concatenate with or without knee later
763763
off_guess = [power_spectrum[0] if not self._ap_guess[0] else self._ap_guess[0]]
764764
kne_guess = [self._ap_guess[1]] if self.aperiodic_mode == 'knee' else []
765-
exp_guess = [np.abs(self.power_spectrum[-1] - self.power_spectrum[0] /
766-
np.log10(self.freqs[-1]) - np.log10(self.freqs[0]))
765+
exp_guess = [np.abs((self.power_spectrum[-1] - self.power_spectrum[0]) /
766+
(np.log10(self.freqs[-1]) - np.log10(self.freqs[0])))
767767
if not self._ap_guess[2] else self._ap_guess[2]]
768768

769769
# Get bounds for aperiodic fitting, dropping knee bound if not set to fit knee
770770
ap_bounds = self._ap_bounds if self.aperiodic_mode == 'knee' \
771771
else tuple(bound[0::2] for bound in self._ap_bounds)
772772

773773
# Collect together guess parameters
774-
guess = np.array([off_guess + kne_guess + exp_guess])
774+
guess = np.array(off_guess + kne_guess + exp_guess)
775775

776776
# Ignore warnings that are raised in curve_fit
777777
# A runtime warning can occur while exploring parameters in curve fitting
@@ -1128,7 +1128,10 @@ def _calc_error(self, metric=None):
11281128
Parameters
11291129
----------
11301130
metric : {'MAE', 'MSE', 'RMSE'}, optional
1131-
Which error measure to calculate.
1131+
Which error measure to calculate:
1132+
* 'MAE' : mean absolute error
1133+
* 'MSE' : mean squared error
1134+
* 'RMSE' : root mean squared error
11321135
11331136
Raises
11341137
------

fooof/plts/annotate.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ def plot_annotated_peak_search(fm):
4141
# Calculate ylims of the plot that are scaled to the range of the data
4242
ylims = [min(flatspec) - 0.1 * np.abs(min(flatspec)), max(flatspec) + 0.1 * max(flatspec)]
4343

44+
# Sort parameters by peak height
45+
gaussian_params = fm.gaussian_params_[fm.gaussian_params_[:, 1].argsort()][::-1]
46+
4447
# Loop through the iterative search for each peak
4548
for ind in range(fm.n_peaks_ + 1):
4649

@@ -63,7 +66,7 @@ def plot_annotated_peak_search(fm):
6366

6467
if ind < fm.n_peaks_:
6568

66-
gauss = gaussian_function(fm.freqs, *fm.gaussian_params_[ind, :])
69+
gauss = gaussian_function(fm.freqs, *gaussian_params[ind, :])
6770
plot_spectra(fm.freqs, gauss, ax=ax, label='Gaussian Fit',
6871
color=PLT_COLORS['periodic'], linestyle=':', linewidth=3.0)
6972

fooof/plts/fg.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ def plot_fg_ap(fg, ax=None, **plot_kwargs):
8080
"""
8181

8282
if fg.aperiodic_mode == 'knee':
83-
plot_scatter_2(fg.get_params('aperiodic_params', 'exponent'), 'Knee',
84-
fg.get_params('aperiodic_params', 'knee'), 'Exponent',
83+
plot_scatter_2(fg.get_params('aperiodic_params', 'exponent'), 'Exponent',
84+
fg.get_params('aperiodic_params', 'knee'), 'Knee',
8585
'Aperiodic Fit', ax=ax)
8686
else:
8787
plot_scatter_1(fg.get_params('aperiodic_params', 'exponent'), 'Exponent',

0 commit comments

Comments
 (0)