Skip to content

Commit d23ec46

Browse files
authored
Merge branch 'main' into shade
2 parents 201869e + 083cc08 commit d23ec46

31 files changed

+664
-309
lines changed

.github/workflows/build.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# This workflow runs CI tests: it installs Python dependencies and run tests across a variety of Python versions
2+
3+
name: build
4+
5+
on:
6+
push:
7+
branches: [ main ]
8+
pull_request:
9+
branches: [ main ]
10+
11+
jobs:
12+
build:
13+
14+
runs-on: ubuntu-latest
15+
strategy:
16+
matrix:
17+
python-version: [3.6, 3.7, 3.8, 3.9]
18+
19+
steps:
20+
- uses: actions/checkout@v2
21+
- name: Set up Python ${{ matrix.python-version }}
22+
uses: actions/setup-python@v2
23+
with:
24+
python-version: ${{ matrix.python-version }}
25+
- name: Install dependencies
26+
run: |
27+
python -m pip install --upgrade pip
28+
pip install pytest
29+
pip install pytest-cov
30+
pip install -r requirements.txt
31+
pip install -r optional-requirements.txt
32+
- name: Test with pytest
33+
run: |
34+
pytest --cov=./
35+
- name: Upload coverage to Codecov
36+
uses: codecov/codecov-action@v1

.travis.yml

Lines changed: 0 additions & 22 deletions
This file was deleted.

README.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ FOOOF - fitting oscillations & one over f
1010
.. |Version| image:: https://img.shields.io/pypi/v/fooof.svg
1111
.. _Version: https://pypi.python.org/pypi/fooof/
1212

13-
.. |BuildStatus| image:: https://travis-ci.com/fooof-tools/fooof.svg
14-
.. _BuildStatus: https://travis-ci.com/fooof-tools/fooof
13+
.. |BuildStatus| image:: https://github.com/fooof-tools/fooof/actions/workflows/build.yml/badge.svg
14+
.. _BuildStatus: https://github.com/fooof-tools/fooof/actions/workflows/build.yml
1515

1616
.. |Coverage| image:: https://codecov.io/gh/fooof-tools/fooof/branch/main/graph/badge.svg
1717
.. _Coverage: https://codecov.io/gh/fooof-tools/fooof
@@ -71,7 +71,7 @@ This documentation includes:
7171
Dependencies
7272
------------
7373

74-
FOOOF is written in Python, and requires Python >= 3.5 to run.
74+
FOOOF is written in Python, and requires Python >= 3.6 to run.
7575

7676
It has the following required dependencies:
7777

fooof/core/utils.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ def group_three(vec):
1313
1414
Parameters
1515
----------
16-
vec : 1d array
17-
Array of items to group by 3. Length of array must be divisible by three.
16+
vec : list or 1d array
17+
List or array of items to group by 3. Length of array must be divisible by three.
1818
1919
Returns
2020
-------
21-
list of list
22-
List of lists, each with three items.
21+
array or list of list
22+
Array or list of lists, each with three items. Output type will match input type.
2323
2424
Raises
2525
------
@@ -30,7 +30,11 @@ def group_three(vec):
3030
if len(vec) % 3 != 0:
3131
raise ValueError("Wrong size array to group by three.")
3232

33-
return [list(vec[ii:ii+3]) for ii in range(0, len(vec), 3)]
33+
# Reshape, if an array, as it's faster, otherwise asssume lise
34+
if isinstance(vec, np.ndarray):
35+
return np.reshape(vec, (-1, 3))
36+
else:
37+
return [list(vec[ii:ii+3]) for ii in range(0, len(vec), 3)]
3438

3539

3640
def nearest_ind(array, value):

fooof/objs/fit.py

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@
7474
gen_issue_str, gen_width_warning_str)
7575

7676
from fooof.plts.fm import plot_fm
77-
from fooof.plts.style import style_spectrum_plot
7877
from fooof.utils.data import trim_spectrum
7978
from fooof.utils.params import compute_gauss_std
8079
from fooof.data import FOOOFResults, FOOOFSettings, FOOOFMetaData
@@ -586,9 +585,7 @@ def get_params(self, name, col=None):
586585
587586
Notes
588587
-----
589-
For further description of the data you can extract, check the FOOOFResults documentation.
590-
591-
If there is no data on periodic features, this method will return NaN.
588+
If there are no fit peak (no peak parameters), this method will return NaN.
592589
"""
593590

594591
if not self.has_model:
@@ -635,12 +632,13 @@ def get_results(self):
635632
@copy_doc_func_to_method(plot_fm)
636633
def plot(self, plot_peaks=None, plot_aperiodic=True, plt_log=False,
637634
add_legend=True, save_fig=False, file_name=None, file_path=None,
638-
ax=None, plot_style=style_spectrum_plot,
639-
data_kwargs=None, model_kwargs=None, aperiodic_kwargs=None, peak_kwargs=None):
635+
ax=None, data_kwargs=None, model_kwargs=None,
636+
aperiodic_kwargs=None, peak_kwargs=None, **plot_kwargs):
640637

641-
plot_fm(self, plot_peaks, plot_aperiodic, plt_log, add_legend,
642-
save_fig, file_name, file_path, ax, plot_style,
643-
data_kwargs, model_kwargs, aperiodic_kwargs, peak_kwargs)
638+
plot_fm(self, plot_peaks=plot_peaks, plot_aperiodic=plot_aperiodic, plt_log=plt_log,
639+
add_legend=add_legend, save_fig=save_fig, file_name=file_name,
640+
file_path=file_path, ax=ax, data_kwargs=data_kwargs, model_kwargs=model_kwargs,
641+
aperiodic_kwargs=aperiodic_kwargs, peak_kwargs=peak_kwargs, **plot_kwargs)
644642

645643

646644
@copy_doc_func_to_method(save_report_fm)
@@ -1007,18 +1005,16 @@ def _create_peak_params(self, gaus_params):
10071005
with `freqs`, `fooofed_spectrum_` and `_ap_fit` all required to be available.
10081006
"""
10091007

1010-
peak_params = np.empty([0, 3])
1008+
peak_params = np.empty((len(gaus_params), 3))
10111009

10121010
for ii, peak in enumerate(gaus_params):
10131011

10141012
# Gets the index of the power_spectrum at the frequency closest to the CF of the peak
1015-
ind = min(range(len(self.freqs)), key=lambda ii: abs(self.freqs[ii] - peak[0]))
1013+
ind = np.argmin(np.abs(self.freqs - peak[0]))
10161014

10171015
# Collect peak parameter data
1018-
peak_params = np.vstack((peak_params,
1019-
[peak[0],
1020-
self.fooofed_spectrum_[ind] - self._ap_fit[ind],
1021-
peak[2] * 2]))
1016+
peak_params[ii] = [peak[0], self.fooofed_spectrum_[ind] - self._ap_fit[ind],
1017+
peak[2] * 2]
10221018

10231019
return peak_params
10241020

@@ -1037,8 +1033,8 @@ def _drop_peak_cf(self, guess):
10371033
Guess parameters for gaussian peak fits. Shape: [n_peaks, 3].
10381034
"""
10391035

1040-
cf_params = [item[0] for item in guess]
1041-
bw_params = [item[2] * self._bw_std_edge for item in guess]
1036+
cf_params = guess[:, 0]
1037+
bw_params = guess[:, 2] * self._bw_std_edge
10421038

10431039
# Check if peaks within drop threshold from the edge of the frequency range
10441040
keep_peak = \

fooof/objs/group.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,8 @@ def get_params(self, name, col=None):
354354
355355
Notes
356356
-----
357-
For further description of the data you can extract, check the FOOOFResults documentation.
357+
When extracting peak information ('peak_params' or 'gaussian_params'), an additional column
358+
is appended to the returned array, indicating the index of the model that the peak came from.
358359
"""
359360

360361
if not self.has_model:
@@ -375,21 +376,18 @@ def get_params(self, name, col=None):
375376
# As a special case, peak_params are pulled out in a way that appends
376377
# an extra column, indicating which FOOOF run each peak comes from
377378
if name in ('peak_params', 'gaussian_params'):
378-
out = np.array([np.insert(getattr(data, name), 3, index, axis=1)
379-
for index, data in enumerate(self.group_results)])
379+
380+
# Collect peak data, appending the index of the model it comes from
381+
out = np.vstack([np.insert(getattr(data, name), 3, index, axis=1)
382+
for index, data in enumerate(self.group_results)])
383+
380384
# This updates index to grab selected column, and the last column
381385
# This last column is the 'index' column (FOOOF object source)
382386
if col is not None:
383387
col = [col, -1]
384388
else:
385389
out = np.array([getattr(data, name) for data in self.group_results])
386390

387-
# Some data can end up as a list of separate arrays
388-
# If so, concatenate it all into one 2d array
389-
if isinstance(out[0], np.ndarray):
390-
out = np.concatenate([arr.reshape(1, len(arr)) \
391-
if arr.ndim == 1 else arr for arr in out], 0)
392-
393391
# Select out a specific column, if requested
394392
if col is not None:
395393
out = out[:, col]
@@ -398,9 +396,9 @@ def get_params(self, name, col=None):
398396

399397

400398
@copy_doc_func_to_method(plot_fg)
401-
def plot(self, save_fig=False, file_name=None, file_path=None):
399+
def plot(self, save_fig=False, file_name=None, file_path=None, **plot_kwargs):
402400

403-
plot_fg(self, save_fig, file_name, file_path)
401+
plot_fg(self, save_fig=save_fig, file_name=file_name, file_path=file_path, **plot_kwargs)
404402

405403

406404
@copy_doc_func_to_method(save_report_fg)

fooof/objs/utils.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,14 @@ def fit_fooof_3d(fg, freqs, power_spectra, freq_range=None, n_jobs=1):
219219
>>> fgs = fit_fooof_3d(fg, freqs, power_spectra, freq_range=[3, 30]) # doctest:+SKIP
220220
"""
221221

222-
fgs = []
223-
for cond_spectra in power_spectra:
224-
fg.fit(freqs, cond_spectra, freq_range, n_jobs)
225-
fgs.append(fg.copy())
222+
# Reshape 3d data to 2d and fit, in order to fit with a single group model object
223+
shape = np.shape(power_spectra)
224+
powers_2d = np.reshape(power_spectra, (shape[0] * shape[1], shape[2]))
225+
226+
fg.fit(freqs, powers_2d, freq_range, n_jobs)
227+
228+
# Reorganize 2d results into a list of model group objects, to reflect original shape
229+
fgs = [fg.get_group(range(dim_a * shape[1], (dim_a + 1) * shape[1])) \
230+
for dim_a in range(shape[0])]
226231

227232
return fgs

fooof/plts/annotate.py

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
from fooof.core.funcs import gaussian_function
88
from fooof.core.modutils import safe_import, check_dependency
99
from fooof.sim.gen import gen_aperiodic
10-
from fooof.plts.utils import check_ax
10+
from fooof.plts.utils import check_ax, savefig
1111
from fooof.plts.spectra import plot_spectrum
1212
from fooof.plts.settings import PLT_FIGSIZES, PLT_COLORS
13-
from fooof.plts.style import check_n_style, style_spectrum_plot
13+
from fooof.plts.style import style_spectrum_plot
1414
from fooof.analysis.periodic import get_band_peak_fm
1515
from fooof.utils.params import compute_knee_frequency, compute_fwhm
1616

@@ -20,16 +20,15 @@
2020
###################################################################################################
2121
###################################################################################################
2222

23+
@savefig
2324
@check_dependency(plt, 'matplotlib')
24-
def plot_annotated_peak_search(fm, plot_style=style_spectrum_plot):
25+
def plot_annotated_peak_search(fm):
2526
"""Plot a series of plots illustrating the peak search from a flattened spectrum.
2627
2728
Parameters
2829
----------
2930
fm : FOOOF
3031
FOOOF object, with model fit, data and settings available.
31-
plot_style : callable, optional, default: style_spectrum_plot
32-
A function to call to apply styling & aesthetics to the plots.
3332
"""
3433

3534
# Recalculate the initial aperiodic fit and flattened spectrum that
@@ -46,14 +45,12 @@ def plot_annotated_peak_search(fm, plot_style=style_spectrum_plot):
4645
# This forces the creation of a new plotting axes per iteration
4746
ax = check_ax(None, PLT_FIGSIZES['spectral'])
4847

49-
plot_spectrum(fm.freqs, flatspec, ax=ax, plot_style=None,
50-
label='Flattened Spectrum', color=PLT_COLORS['data'], linewidth=2.5)
51-
plot_spectrum(fm.freqs, [fm.peak_threshold * np.std(flatspec)]*len(fm.freqs),
52-
ax=ax, plot_style=None, label='Relative Threshold',
53-
color='orange', linewidth=2.5, linestyle='dashed')
54-
plot_spectrum(fm.freqs, [fm.min_peak_height]*len(fm.freqs),
55-
ax=ax, plot_style=None, label='Absolute Threshold',
56-
color='red', linewidth=2.5, linestyle='dashed')
48+
plot_spectrum(fm.freqs, flatspec, ax=ax, linewidth=2.5,
49+
label='Flattened Spectrum', color=PLT_COLORS['data'])
50+
plot_spectrum(fm.freqs, [fm.peak_threshold * np.std(flatspec)]*len(fm.freqs), ax=ax,
51+
label='Relative Threshold', color='orange', linewidth=2.5, linestyle='dashed')
52+
plot_spectrum(fm.freqs, [fm.min_peak_height]*len(fm.freqs), ax=ax,
53+
label='Absolute Threshold', color='red', linewidth=2.5, linestyle='dashed')
5754

5855
maxi = np.argmax(flatspec)
5956
ax.plot(fm.freqs[maxi], flatspec[maxi], '.',
@@ -65,18 +62,18 @@ def plot_annotated_peak_search(fm, plot_style=style_spectrum_plot):
6562
if ind < fm.n_peaks_:
6663

6764
gauss = gaussian_function(fm.freqs, *fm.gaussian_params_[ind, :])
68-
plot_spectrum(fm.freqs, gauss, ax=ax, plot_style=None,
69-
label='Gaussian Fit', color=PLT_COLORS['periodic'],
70-
linestyle=':', linewidth=3.0)
65+
plot_spectrum(fm.freqs, gauss, ax=ax, label='Gaussian Fit',
66+
color=PLT_COLORS['periodic'], linestyle=':', linewidth=3.0)
7167

7268
flatspec = flatspec - gauss
7369

74-
check_n_style(plot_style, ax, False, True)
70+
style_spectrum_plot(ax, False, True)
7571

7672

73+
@savefig
7774
@check_dependency(plt, 'matplotlib')
78-
def plot_annotated_model(fm, plt_log=False, annotate_peaks=True, annotate_aperiodic=True,
79-
ax=None, plot_style=style_spectrum_plot):
75+
def plot_annotated_model(fm, plt_log=False, annotate_peaks=True,
76+
annotate_aperiodic=True, ax=None):
8077
"""Plot a an annotated power spectrum and model, from a FOOOF object.
8178
8279
Parameters
@@ -91,8 +88,6 @@ def plot_annotated_model(fm, plt_log=False, annotate_peaks=True, annotate_aperio
9188
Whether to annotate the aperiodic components of the model fit.
9289
ax : matplotlib.Axes, optional
9390
Figure axes upon which to plot.
94-
plot_style : callable, optional, default: style_spectrum_plot
95-
A function to call to apply styling & aesthetics to the plots.
9691
9792
Raises
9893
------
@@ -112,7 +107,7 @@ def plot_annotated_model(fm, plt_log=False, annotate_peaks=True, annotate_aperio
112107

113108
# Create the baseline figure
114109
ax = check_ax(ax, PLT_FIGSIZES['spectral'])
115-
fm.plot(plot_peaks='dot-shade-width', plt_log=plt_log, ax=ax, plot_style=None,
110+
fm.plot(plot_peaks='dot-shade-width', plt_log=plt_log, ax=ax,
116111
data_kwargs={'lw' : lw1, 'alpha' : 0.6},
117112
aperiodic_kwargs={'lw' : lw1, 'zorder' : 10},
118113
model_kwargs={'lw' : lw1, 'alpha' : 0.5},
@@ -133,7 +128,7 @@ def plot_annotated_model(fm, plt_log=False, annotate_peaks=True, annotate_aperio
133128
# See: https://github.com/matplotlib/matplotlib/issues/12820. Fixed in 3.2.1.
134129
bug_buff = 0.000001
135130

136-
if annotate_peaks:
131+
if annotate_peaks and fm.n_peaks_:
137132

138133
# Extract largest peak, to annotate, grabbing gaussian params
139134
gauss = get_band_peak_fm(fm, fm.freq_range, attribute='gaussian_params')
@@ -219,7 +214,7 @@ def plot_annotated_model(fm, plt_log=False, annotate_peaks=True, annotate_aperio
219214
color=PLT_COLORS['aperiodic'], fontsize=fontsize)
220215

221216
# Apply style to plot & tune grid styling
222-
check_n_style(plot_style, ax, plt_log, True)
217+
style_spectrum_plot(ax, plt_log, True)
223218
ax.grid(True, alpha=0.5)
224219

225220
# Add labels to plot in the legend

0 commit comments

Comments
 (0)