Skip to content

Commit 4b4caf6

Browse files
authored
Merge branch 'main' into df
2 parents ec8c0e7 + f70a12f commit 4b4caf6

39 files changed

+1061
-404
lines changed

doc/api.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,6 @@ Plots for visualizing power spectra.
247247
.. autosummary::
248248
:toctree: generated/
249249

250-
plot_spectrum
251250
plot_spectra
252251

253252
Plots for plotting power spectra with shaded regions.
@@ -257,8 +256,8 @@ Plots for plotting power spectra with shaded regions.
257256
.. autosummary::
258257
:toctree: generated/
259258

260-
plot_spectrum_shading
261259
plot_spectra_shading
260+
plot_spectra_yshade
262261

263262
Plot Model Properties & Parameters
264263
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

doc/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@
126126
'examples_dirs': ['../examples', '../tutorials', '../motivations'],
127127
'gallery_dirs': ['auto_examples', 'auto_tutorials', 'auto_motivations'],
128128
'subsection_order' : ExplicitOrder(['../examples/manage',
129+
'../examples/processing',
129130
'../examples/plots',
130131
'../examples/sims',
131132
'../examples/analyses',

examples/analyses/plot_mne_example.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from fooof import FOOOFGroup
3333
from fooof.bands import Bands
3434
from fooof.analysis import get_band_peak_fg
35-
from fooof.plts.spectra import plot_spectrum
35+
from fooof.plts.spectra import plot_spectra
3636

3737
###################################################################################################
3838
# Load & Check MNE Data
@@ -284,10 +284,11 @@ def check_nans(data, nan_policy='zero'):
284284

285285
# Compare the power spectra between low and high exponent channels
286286
fig, ax = plt.subplots(figsize=(8, 6))
287-
plot_spectrum(fg.freqs, fg.get_fooof(np.argmin(exps)).power_spectrum,
288-
ax=ax, label='Low Exponent')
289-
plot_spectrum(fg.freqs, fg.get_fooof(np.argmax(exps)).power_spectrum,
290-
ax=ax, label='High Exponent')
287+
288+
spectra = [fg.get_fooof(np.argmin(exps)).power_spectrum,
289+
fg.get_fooof(np.argmax(exps)).power_spectrum]
290+
291+
plot_spectra(fg.freqs, spectra, ax=ax, labels=['Low Exponent', 'High Exponent'])
291292

292293
###################################################################################################
293294
# Conclusion

examples/plots/plot_power_spectra.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@
1111
import matplotlib.pyplot as plt
1212

1313
# Import plotting functions
14-
from fooof.plts.spectra import plot_spectrum, plot_spectra
15-
from fooof.plts.spectra import plot_spectrum_shading, plot_spectra_shading
14+
from fooof.plts.spectra import plot_spectra, plot_spectra_shading
1615

1716
# Import simulation utilities for creating test data
1817
from fooof.sim.gen import gen_power_spectrum, gen_group_power_spectra
@@ -59,7 +58,7 @@
5958
#
6059
# First we will start with the core plotting function that plots an individual power spectrum.
6160
#
62-
# The :func:`~.plot_spectrum` function takes in an array of frequency values and a 1d array of
61+
# The :func:`~.plot_spectra` function takes in an array of frequency values and a 1d array of
6362
# of power values, and plots the corresponding power spectrum.
6463
#
6564
# This function, as all the functions in the plotting module, takes in optional inputs
@@ -70,7 +69,7 @@
7069
###################################################################################################
7170

7271
# Create a spectrum plot with a single power spectrum
73-
plot_spectrum(freqs, powers1, log_powers=True)
72+
plot_spectra(freqs, powers1, log_powers=True)
7473

7574
###################################################################################################
7675
# Plotting Multiple Power Spectra
@@ -97,7 +96,7 @@
9796
# In some cases it may be useful to highlight or shade in particular frequency regions,
9897
# for example, when examining power in particular frequency regions.
9998
#
100-
# The :func:`~.plot_spectrum_shading` function takes in a power spectrum and one or more
99+
# The :func:`~.plot_spectra_shading` function takes in a power spectrum and one or more
101100
# shaded regions, and plot the power spectrum with the indicated region shaded.
102101
#
103102
# The same can be done for multiple power spectra with :func:`~.plot_spectra_shading`.
@@ -110,7 +109,7 @@
110109
###################################################################################################
111110

112111
# Plot a single power spectrum, with a shaded region covering alpha
113-
plot_spectrum_shading(freqs, powers1, [8, 12], log_powers=True)
112+
plot_spectra_shading(freqs, powers1, [8, 12], log_powers=True)
114113

115114
###################################################################################################
116115

@@ -157,6 +156,6 @@
157156
fig, ax = plt.subplots(figsize=[12, 8])
158157
plot_spectra_shading(freqs_al, powers_al, [8, 12],
159158
log_powers=True, alpha=0.6, ax=ax)
160-
plot_spectrum(freqs_al10, powers_al10, log_powers=True,
161-
color='black', linewidth=3, label='10 Hz Alpha', ax=ax)
159+
plot_spectra(freqs_al10, powers_al10, log_powers=True,
160+
color='black', linewidth=3, label='10 Hz Alpha', ax=ax)
162161
plt.title('Comparing Alphas', {'fontsize' : 20, 'fontweight' : 'bold'});

examples/processing/README.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Processing
2+
----------
3+
4+
Examples on how to process data related to spectral parameterization.
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
"""
2+
Dealing with Line Noise
3+
=======================
4+
5+
This example covers strategies for dealing with line noise.
6+
"""
7+
8+
###################################################################################################
9+
10+
# sphinx_gallery_thumbnail_number = 2
11+
12+
# Import the spectral parameterization object and utilities
13+
from fooof import FOOOF
14+
from fooof.plts import plot_spectra
15+
from fooof.utils import trim_spectrum, interpolate_spectrum
16+
17+
# Import simulation functions to create some example data
18+
from fooof.sim.gen import gen_power_spectrum
19+
20+
# Import NeuroDSP functions for simulating & processing time series
21+
from neurodsp.sim import sim_combined
22+
from neurodsp.filt import filter_signal
23+
from neurodsp.spectral import compute_spectrum
24+
25+
###################################################################################################
26+
# Line Noise Peaks
27+
# ----------------
28+
#
29+
# Neural recordings typically have power line artifacts, at either 50 or 60 Hz, depending on
30+
# where the data were collected, which can impact spectral parameterization.
31+
#
32+
# In this example, we explore some options for dealing with line noise artifacts.
33+
#
34+
# Interpolating Line Noise Peaks
35+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
36+
#
37+
# One approach is to interpolate away line noise peaks, in the frequency domain. This
38+
# approach simply gets rid of the peaks, interpolating the data to maintain the 1/f
39+
# character of the data, allowing for subsequent fitting.
40+
#
41+
# The :func:`~fooof.utils.interpolate_spectrum` function allows for doing simple
42+
# interpolation. Given a narrow frequency region, this function interpolates the spectrum,
43+
# such that the 'peak' of the line noise is removed.
44+
#
45+
46+
###################################################################################################
47+
48+
# Generate an example power spectrum, with line noise
49+
freqs1, powers1 = gen_power_spectrum([3, 75], [1, 1],
50+
[[10, 0.75, 2], [60, 1, 0.5]])
51+
52+
# Visualize the generated power spectrum
53+
plot_spectra(freqs1, powers1, log_powers=True)
54+
55+
###################################################################################################
56+
#
57+
# In the plot above, we have an example spectrum, with some power line noise.
58+
#
59+
# To prepare this data for fitting, we can interpolate away the line noise region.
60+
#
61+
62+
###################################################################################################
63+
64+
# Interpolate away the line noise region
65+
interp_range = [58, 62]
66+
freqs_int1, powers_int1 = interpolate_spectrum(freqs1, powers1, interp_range)
67+
68+
###################################################################################################
69+
70+
# Plot the spectra for the power spectra before and after interpolation
71+
plot_spectra(freqs1, [powers1, powers_int1], log_powers=True,
72+
labels=['Original Spectrum', 'Interpolated Spectrum'])
73+
74+
###################################################################################################
75+
#
76+
# As we can see in the above, the interpolation removed the peak from the data.
77+
#
78+
# We can now go ahead and parameterize the spectrum.
79+
#
80+
81+
###################################################################################################
82+
83+
# Initialize a power spectrum model
84+
fm1 = FOOOF(verbose=False)
85+
fm1.report(freqs_int1, powers_int1)
86+
87+
###################################################################################################
88+
# Multiple Interpolation Regions
89+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
90+
#
91+
# Line noise artifacts often also display harmonics, such that when analyzing broader
92+
# frequency ranges, there may be multiple peaks that need to be interpolated.
93+
#
94+
# This can be done by passing in multiple interpolation regions to
95+
# :func:`~fooof.utils.interpolate_spectrum`, which we will do in the next example.
96+
#
97+
98+
###################################################################################################
99+
100+
# Generate an example power spectrum, with line noise & harmonics
101+
freqs2, powers2 = gen_power_spectrum([1, 150], [1, 500, 1.5],
102+
[[10, 0.5, 2], [60, 0.75, 0.5], [120, 0.5, 0.5]])
103+
104+
# Interpolate away the line noise region & harmonics
105+
interp_ranges = [[58, 62], [118, 122]]
106+
freqs_int2, powers_int2 = interpolate_spectrum(freqs2, powers2, interp_ranges)
107+
108+
###################################################################################################
109+
110+
# Plot the power spectrum before and after interpolation
111+
plot_spectra(freqs2, [powers2, powers_int2], log_powers=True,
112+
labels=['Original Spectrum', 'Interpolated Spectrum'])
113+
114+
###################################################################################################
115+
116+
# Parameterize the interpolated power spectrum
117+
fm2 = FOOOF(aperiodic_mode='knee', verbose=False)
118+
fm2.report(freqs2, powers_int2)
119+
120+
###################################################################################################
121+
# Fitting Line Noise as Peaks
122+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~
123+
#
124+
# In some cases, you may also be able to simply allow the parameterization to peaks to the
125+
# line noise and harmonics. By simply fitting the line noise as peaks, the model can deal
126+
# with the peaks in order to accurately fit the aperiodic component.
127+
#
128+
# These peaks are of course not to be analyzed, but once the model has been fit, you can
129+
# simply ignore them. There should generally be no issue with fitting and having them in
130+
# the model, and allowing the model to account for these peaks typically helps the model
131+
# better fit the rest of the data.
132+
#
133+
# Below we can see that the model does indeed work when fitting data with line noise peaks.
134+
#
135+
136+
###################################################################################################
137+
138+
# Fit power spectrum models to original spectra
139+
fm1.report(freqs1, powers1)
140+
fm2.report(freqs2, powers2)
141+
142+
###################################################################################################
143+
# The Problem with Bandstop Filtering
144+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
145+
#
146+
# A common approach for getting rid of line noise activity is to use bandstop filtering to
147+
# remove activity at the line noise frequencies. Such a filter effectively set the power
148+
# of these frequencies to be approximately zero.
149+
#
150+
# Unfortunately, this doesn't work very well with spectral parameterization, since the
151+
# parameterization algorithm tries to fit each power value as either part of the aperiodic
152+
# component, or as an overlying peak. Frequencies that have filtered out are neither, and
153+
# the model has trouble, as it and has no concept of power values below the aperiodic component.
154+
#
155+
# In practice, this means that the "missing" power will impact the fit, and pull down the
156+
# aperiodic component. One way to think of this is that the power spectrum model can deal with,
157+
# and even expects, 'positive outliers' above the aperiodic (these are considered 'peaks'), but
158+
# not 'negative outliers', or values below the aperiodic, as there is no expectation of this
159+
# happening in the model.
160+
#
161+
# In the following example, we can see how bandstop filtering negatively impacts fitting.
162+
# Because of this, for the purposes of spectral parameterization, bandstop filters are not
163+
# recommended as a way to remove line noise.
164+
#
165+
# Note that if one has already applied a bandstop filter, then you can still
166+
# apply the interpolation from above.
167+
#
168+
169+
###################################################################################################
170+
171+
# General settings for the simulation
172+
n_seconds = 30
173+
fs = 1000
174+
175+
# Define the settings for the simulated signal
176+
components = {'sim_powerlaw' : {'exponent' : -1.5},
177+
'sim_oscillation' : [{'freq' : 10}, {'freq' : 60}]}
178+
comp_vars = [0.5, 1, 1]
179+
180+
# Simulate a time series
181+
sig = sim_combined(n_seconds, fs, components, comp_vars)
182+
183+
###################################################################################################
184+
185+
# Bandstop filter the signal to remove line noise frequencies
186+
sig_filt = filter_signal(sig, fs, 'bandstop', (57, 63),
187+
n_seconds=2, remove_edges=False)
188+
189+
###################################################################################################
190+
191+
# Compute a power spectrum of the simulated signal
192+
freqs, powers_pre = trim_spectrum(*compute_spectrum(sig, fs), [3, 75])
193+
freqs, powers_post = trim_spectrum(*compute_spectrum(sig_filt, fs), [3, 75])
194+
195+
###################################################################################################
196+
197+
# Plot the spectrum of the data, pre and post bandstop filtering
198+
plot_spectra(freqs, [powers_pre, powers_post], log_powers=True,
199+
labels=['Pre-Filter', 'Post-Filter'])
200+
201+
###################################################################################################
202+
#
203+
# In the above, we can see that the the bandstop filter removes power in the filtered range,
204+
# leaving a "dip" in the power spectrum. This dip causes issues with subsequent fitting.
205+
#
206+
207+
###################################################################################################
208+
209+
# Initialize and fit a power spectrum model
210+
fm = FOOOF()
211+
fm.report(freqs, powers_post)
212+
213+
###################################################################################################
214+
#
215+
# In order to try and capture the data points in the "dip", the power spectrum model
216+
# gets 'pulled' down, leading to an inaccurate fit of the aperiodic component. This is
217+
# why fitting frequency regions that included frequency regions that have been filtered
218+
# out is not recommended.
219+
#

examples/sims/plot_simulated_power_spectra.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from fooof.sim.gen import gen_power_spectrum, gen_group_power_spectra
1212

1313
# Import plotting functions
14-
from fooof.plts.spectra import plot_spectrum, plot_spectra
14+
from fooof.plts.spectra import plot_spectra
1515

1616
###################################################################################################
1717
# Creating Simulated Power Spectra
@@ -53,7 +53,7 @@
5353
###################################################################################################
5454

5555
# Plot the simulated power spectrum
56-
plot_spectrum(freqs, powers, log_freqs=True, log_powers=False)
56+
plot_spectra(freqs, powers, log_freqs=True, log_powers=False)
5757

5858
###################################################################################################
5959
# Simulating With Different Parameters
@@ -100,7 +100,7 @@
100100
###################################################################################################
101101

102102
# Plot the new simulated power spectrum
103-
plot_spectrum(freqs, powers, log_powers=True)
103+
plot_spectra(freqs, powers, log_powers=True)
104104

105105
###################################################################################################
106106
# Simulating a Group of Power Spectra

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):

0 commit comments

Comments
 (0)