Skip to content

Commit 3b7abb6

Browse files
authored
Merge pull request #134 from fooof-tools/plts
Update plots
2 parents e386aeb + 9b6138c commit 3b7abb6

File tree

4 files changed

+130
-29
lines changed

4 files changed

+130
-29
lines changed

fooof/plts/spectra.py

Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,24 @@
55
This file contains functions for plotting power spectra, that take in data directly.
66
"""
77

8+
from itertools import repeat
9+
810
import numpy as np
911

10-
from fooof.plts.utils import check_ax, add_shades
1112
from fooof.core.modutils import safe_import, check_dependency
1213

14+
from fooof.plts.settings import DEFAULT_FIGSIZE
15+
from fooof.plts.utils import check_ax, add_shades
16+
from fooof.plts.style import check_n_style, style_spectrum_plot
17+
1318
plt = safe_import('.pyplot', 'matplotlib')
1419

1520
###################################################################################################
1621
###################################################################################################
1722

1823
@check_dependency(plt, 'matplotlib')
19-
def plot_spectrum(freqs, power_spectrum, log_freqs=False, log_powers=False, ax=None, **kwargs):
24+
def plot_spectrum(freqs, power_spectrum, log_freqs=False, log_powers=False,
25+
ax=None, plot_style=style_spectrum_plot, **kwargs):
2026
"""Plot a power spectrum.
2127
2228
Parameters
@@ -31,102 +37,121 @@ def plot_spectrum(freqs, power_spectrum, log_freqs=False, log_powers=False, ax=N
3137
Whether or not to take the log of the power axis before plotting.
3238
ax : matplotlib.Axes, optional
3339
Figure axes upon which to plot.
40+
plot_style : callable, optional, default: style_spectrum_plot
41+
A function to call to apply styling & aesthetics to the plot.
3442
**kwargs
3543
Keyword arguments to be passed to the plot call.
3644
"""
3745

3846
# Create plot axes, if not provided
3947
if not ax:
40-
_, ax = plt.subplots(figsize=(12, 10))
48+
_, ax = plt.subplots(figsize=DEFAULT_FIGSIZE)
4149

42-
# Set plot data, logging if requested
50+
# Set plot data & labels, logging if requested
4351
plt_freqs = np.log10(freqs) if log_freqs else freqs
4452
plt_powers = np.log10(power_spectrum) if log_powers else power_spectrum
4553

46-
# Create the plot
47-
ax.plot(plt_freqs, plt_powers, **kwargs)
48-
49-
# Aesthetics and axis labels
50-
ax.set_xlabel('Frequency', fontsize=20)
51-
ax.set_ylabel('Power', fontsize=20)
52-
ax.tick_params(axis='both', which='major', labelsize=16)
53-
ax.grid(True)
54+
# Set default plot settings, that only apply if not over-written in kwargs
55+
if 'linewidth' not in kwargs:
56+
kwargs['linewidth'] = 2.0
5457

55-
# If labels were provided, add a legend
56-
if ax.get_legend_handles_labels()[0]:
57-
ax.legend(prop={'size': 16})
58+
# Create the plot & style
59+
ax.plot(plt_freqs, plt_powers, **kwargs)
60+
check_n_style(plot_style, ax, log_freqs, log_powers)
5861

5962

6063
@check_dependency(plt, 'matplotlib')
61-
def plot_spectra(freqs, power_spectra, log_freqs=False, log_powers=False, ax=None, **kwargs):
64+
def plot_spectra(freqs, power_spectra, log_freqs=False, log_powers=False, labels=None,
65+
ax=None, plot_style=style_spectrum_plot, **kwargs):
6266
"""Plot multiple power spectra on the same plot.
6367
6468
Parameters
6569
----------
66-
freqs : 1d array
70+
freqs : 2d array or 1d array or list of 1d array
6771
X-axis data, frequency values.
68-
power_spectra : list of 1d array
72+
power_spectra : 2d array or list of 1d array
6973
Y-axis data, power values for spectra to plot.
7074
log_freqs : boolean, optional, default: False
7175
Whether or not to take the log of the power axis before plotting.
7276
log_powers : boolean, optional, default: False
7377
Whether or not to take the log of the power axis before plotting.
78+
labels " "
7479
ax : matplotlib.Axes, optional
7580
Figure axes upon which to plot.
81+
plot_style : callable, optional, default: style_spectrum_plot
82+
A function to call to apply styling & aesthetics to the plot.
7683
**kwargs
7784
Keyword arguments to be passed to the plot call.
7885
"""
7986

87+
freqs = repeat(freqs) if isinstance(freqs, np.ndarray) and freqs.ndim == 1 else freqs
88+
labels = repeat(labels) if not isinstance(labels, list) else labels
89+
8090
ax = check_ax(ax)
81-
for power_spectrum in power_spectra:
82-
plot_spectrum(freqs, power_spectrum, log_freqs, log_powers, ax=ax, **kwargs)
91+
for freq, power_spectrum, label in zip(freqs, power_spectra, labels):
92+
plot_spectrum(freq, power_spectrum, log_freqs, log_powers, label=label,
93+
plot_style=None, ax=ax, **kwargs)
94+
check_n_style(plot_style, ax, log_freqs, log_powers)
8395

8496

8597
@check_dependency(plt, 'matplotlib')
86-
def plot_spectrum_shading(freqs, power_spectrum, shades, add_center=False, ax=None, **kwargs):
98+
def plot_spectrum_shading(freqs, power_spectrum, shades, add_center=False,
99+
ax=None, plot_style=style_spectrum_plot, **kwargs):
87100
"""Plot a power spectrum with a shaded frequency region (or regions).
88101
89102
Parameters
90103
----------
91104
freqs : 1d array
92105
X-axis data, frequency values.
93-
power_spectrum : list of 1d array
106+
power_spectrum : 1d array
94107
Y-axis data, power values for spectrum to plot.
95108
shades : list of [float, float] or list of list of [float, float]
96109
Shaded region(s) to add to plot, defined as [lower_bound, upper_bound].
97110
add_center : boolean, optional, default: False
98111
Whether to add a line at the center point of the shaded regions.
99112
ax : matplotlib.Axes, optional
100113
Figure axes upon which to plot.
114+
plot_style : callable, optional, default: style_spectrum_plot
115+
A function to call to apply styling & aesthetics to the plot.
101116
**kwargs
102117
Keyword arguments to be passed to the plot call.
103118
"""
104119

105120
ax = check_ax(ax)
106-
plot_spectrum(freqs, power_spectrum, ax=ax, **kwargs)
121+
plot_spectrum(freqs, power_spectrum, plot_style=None, ax=ax, **kwargs)
107122
add_shades(ax, shades, add_center, kwargs.get('log_freqs', False))
123+
check_n_style(plot_style, ax, kwargs.get('log_freqs', False), kwargs.get('log_powers', False))
108124

109125

110126
@check_dependency(plt, 'matplotlib')
111-
def plot_spectra_shading(freqs, power_spectra, shades, add_center=False, ax=None, **kwargs):
127+
def plot_spectra_shading(freqs, power_spectra, shades, add_center=False,
128+
ax=None, plot_style=style_spectrum_plot, **kwargs):
112129
"""Plot a group of power spectra with a shaded frequency region (or regions).
113130
114131
Parameters
115132
----------
116-
freqs : 1d array
133+
freqs : 2d array or 1d array or list of 1d array
117134
X-axis data, frequency values.
118-
power_spectra : list of 1d array
135+
power_spectra : 2d array or list of 1d array
119136
Y-axis data, power values for spectra to plot.
120137
shades : list of [float, float] or list of list of [float, float]
121138
Shaded region(s) to add to plot, defined as [lower_bound, upper_bound].
122139
add_center : boolean, optional, default: False
123140
Whether to add a line at the center point of the shaded regions.
124141
ax : matplotlib.Axes, optional
125142
Figure axes upon which to plot.
143+
plot_style : callable, optional, default: style_spectrum_plot
144+
A function to call to apply styling & aesthetics to the plot.
126145
**kwargs
127-
Keyword arguments to be passed to the plot call.
146+
Keyword arguments to be passed to plot_spectra or the plot call.
147+
148+
Notes
149+
-----
150+
Parameters for `plot_spectra` can also be passed into this function as **kwargs.
151+
This includes `log_freqs`, `log_powers` & `labels`. See `plot_spectra for usage details.
128152
"""
129153

130154
ax = check_ax(ax)
131-
plot_spectra(freqs, power_spectra, ax=ax, **kwargs)
155+
plot_spectra(freqs, power_spectra, ax=ax, plot_style=None, **kwargs)
132156
add_shades(ax, shades, add_center, kwargs.get('log_freqs', False))
157+
check_n_style(plot_style, ax, kwargs.get('log_freqs', False), kwargs.get('log_powers', False))

fooof/plts/style.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""Helper functions that apply style and decoration to plots."""
2+
3+
###################################################################################################
4+
###################################################################################################
5+
6+
def check_n_style(style_func, *args):
7+
""""Check is a style function has been passed, and apply if so."""
8+
9+
if style_func:
10+
style_func(*args)
11+
12+
13+
def style_spectrum_plot(ax, log_freqs, log_powers):
14+
"""Define to styling for a power spectrum plot."""
15+
16+
# Get labels, based on log status
17+
xlabel = 'Frequency' if not log_freqs else 'log(Frequency)'
18+
ylabel = 'Power' if not log_powers else 'log(Power)'
19+
20+
# Aesthetics and axis labels
21+
ax.set_xlabel(xlabel, fontsize=20)
22+
ax.set_ylabel(ylabel, fontsize=20)
23+
ax.tick_params(axis='both', which='major', labelsize=16)
24+
ax.grid(True)
25+
26+
# If labels were provided, add a legend
27+
if ax.get_legend_handles_labels()[0]:
28+
ax.legend(prop={'size': 16})

fooof/tests/test_plts_spectra.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,27 @@
99
@plot_test
1010
def test_plot_spectrum(tfm, skip_if_no_mpl):
1111

12-
plot_spectrum(tfm.freqs, tfm.power_spectrum, True)
12+
plot_spectrum(tfm.freqs, tfm.power_spectrum)
13+
14+
# Test with logging both axes
15+
plot_spectrum(tfm.freqs, tfm.power_spectrum, True, True)
1316

1417
@plot_test
1518
def test_plot_spectra(tfg, skip_if_no_mpl):
1619

20+
# Test with 1d inputs - 1d freq array and list of 1d power spectra
1721
plot_spectra(tfg.freqs, [tfg.power_spectra[0, :], tfg.power_spectra[1, :]])
1822

23+
# Test with multiple freq inputs - list of 1d freq array and list of 1d power spectra
24+
plot_spectra([tfg.freqs, tfg.freqs], [tfg.power_spectra[0, :], tfg.power_spectra[1, :]])
25+
26+
# Test with 2d array inputs
27+
plot_spectra(np.vstack([tfg.freqs, tfg.freqs]),
28+
np.vstack([tfg.power_spectra[0, :], tfg.power_spectra[1, :]]))
29+
30+
# Test with labels
31+
plot_spectra(tfg.freqs, [tfg.power_spectra[0, :], tfg.power_spectra[1, :]], labels=['A', 'B'])
32+
1933
@plot_test
2034
def test_plot_spectrum_shading(tfm, skip_if_no_mpl):
2135

@@ -26,3 +40,8 @@ def test_plot_spectra_shading(tfg, skip_if_no_mpl):
2640

2741
plot_spectra_shading(tfg.freqs, [tfg.power_spectra[0, :], tfg.power_spectra[1, :]],
2842
shades=[8, 12], add_center=True)
43+
44+
# Test with **kwargs that pass into plot_spectra
45+
plot_spectra_shading(tfg.freqs, [tfg.power_spectra[0, :], tfg.power_spectra[1, :]],
46+
shades=[8, 12], add_center=True, log_freqs=True, log_powers=True,
47+
labels=['A', 'B'])

fooof/tests/test_plts_styles.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""Tests for fooof.plts.styles."""
2+
3+
from fooof.plts.style import *
4+
5+
###################################################################################################
6+
###################################################################################################
7+
8+
def test_check_n_style(skip_if_no_mpl):
9+
10+
# Check can pass None and do nothing
11+
check_n_style(None)
12+
assert True
13+
14+
# Check can pass a callable
15+
def checker(*args):
16+
return True
17+
check_n_style(checker)
18+
19+
def test_style_spectrum_plot(skip_if_no_mpl):
20+
21+
# Create a dummy plot and style it
22+
from fooof.core.modutils import safe_import
23+
plt = safe_import('.pyplot', 'matplotlib')
24+
_, ax = plt.subplots()
25+
style_spectrum_plot(ax, False, False)
26+
27+
# Check that axis labels are added - use as proxy that it ran correctly
28+
assert ax.xaxis.get_label().get_text()
29+
assert ax.yaxis.get_label().get_text()

0 commit comments

Comments
 (0)