Skip to content

Commit 809a3cf

Browse files
authored
Merge pull request #283 from fooof-tools/time
[ENH] - Add `SpectralTimeModel` and `SpectralTimeEventModel`
2 parents 5e655d7 + ebba007 commit 809a3cf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+3666
-321
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,16 @@ on:
66
push:
77
branches: [ main ]
88
pull_request:
9-
branches: [ main ]
109

1110
jobs:
1211
build:
1312

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
13+
runs-on: ubuntu-latest
1814
env:
1915
MODULE_NAME: specparam
2016
strategy:
2117
matrix:
22-
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
18+
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
2319

2420
steps:
2521
- uses: actions/checkout@v3

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ This documentation includes:
7373
Dependencies
7474
------------
7575

76-
SpecParam is written in Python, and requires Python >= 3.6 to run.
76+
SpecParam is written in Python, and requires Python >= 3.7 to run.
7777

7878
It has the following required dependencies:
7979

doc/api.rst

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,17 @@ The SpectralGroupModel object allows for parameterizing groups of power spectra.
4040

4141
SpectralGroupModel
4242

43+
Time & Event Objects
44+
~~~~~~~~~~~~~~~~~~~~
45+
46+
The time & event objects allows for parameterizing power spectra organized across time and/or events.
47+
48+
.. autosummary::
49+
:toctree: generated/
50+
51+
SpectralTimeModel
52+
SpectralTimeEventModel
53+
4354
Object Utilities
4455
~~~~~~~~~~~~~~~~
4556

@@ -155,6 +166,7 @@ The following functions take in model objects directly, which is the typical use
155166

156167
get_band_peak
157168
get_band_peak_group
169+
get_band_peak_event
158170

159171
**Array Inputs**
160172

@@ -178,7 +190,7 @@ Code & utilities for simulating power spectra.
178190
Generate Power Spectra
179191
~~~~~~~~~~~~~~~~~~~~~~
180192

181-
Functions for simulating neural power spectra.
193+
Functions for simulating neural power spectra and spectrograms.
182194

183195
.. currentmodule:: specparam.sim
184196

@@ -187,6 +199,7 @@ Functions for simulating neural power spectra.
187199

188200
sim_power_spectrum
189201
sim_group_power_spectra
202+
sim_spectrogram
190203

191204
Manage Parameters
192205
~~~~~~~~~~~~~~~~~
@@ -242,14 +255,15 @@ Visualizations.
242255
Plot Power Spectra
243256
~~~~~~~~~~~~~~~~~~
244257

245-
Plots for visualizing power spectra.
258+
Plots for visualizing power spectra and spectrograms.
246259

247260
.. currentmodule:: specparam.plts
248261

249262
.. autosummary::
250263
:toctree: generated/
251264

252265
plot_spectra
266+
plot_spectrogram
253267

254268
Plots for plotting power spectra with shaded regions.
255269

@@ -311,7 +325,21 @@ Note that these are the same plotting functions that can be called from the mode
311325
.. autosummary::
312326
:toctree: generated/
313327

314-
plot_group
328+
plot_group_model
329+
330+
.. currentmodule:: specparam.plts.time
331+
332+
.. autosummary::
333+
:toctree: generated/
334+
335+
plot_time_model
336+
337+
.. currentmodule:: specparam.plts.event
338+
339+
.. autosummary::
340+
:toctree: generated/
341+
342+
plot_event_model
315343

316344
Annotated Plots
317345
~~~~~~~~~~~~~~~
@@ -388,7 +416,9 @@ Input / Output (IO)
388416
:toctree: generated/
389417

390418
load_model
391-
load_group
419+
load_group_model
420+
load_time_model
421+
load_event_model
392422

393423
Methods Reports
394424
~~~~~~~~~~~~~~~

examples/manage/plot_fit_models_3d.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
from specparam.sim import sim_group_power_spectra
6262
from specparam.sim.utils import create_freqs
6363
from specparam.sim.params import param_sampler
64-
from specparam.utils.io import load_group
64+
from specparam.utils.io import load_group_model
6565

6666
###################################################################################################
6767
# Example Set-Up
@@ -229,7 +229,7 @@
229229
###################################################################################################
230230

231231
# Reload our list of SpectralGroupModels
232-
fgs = [load_group(file_name, file_path='results') \
232+
fgs = [load_group_model(file_name, file_path='results') \
233233
for file_name in os.listdir('results')]
234234

235235
###################################################################################################

setup.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
'Operating System :: POSIX',
4040
'Operating System :: Unix',
4141
'Programming Language :: Python',
42-
'Programming Language :: Python :: 3.6',
4342
'Programming Language :: Python :: 3.7',
4443
'Programming Language :: Python :: 3.8',
4544
'Programming Language :: Python :: 3.9',

specparam/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
from .version import __version__
44

55
from .bands import Bands
6-
from .objs import SpectralModel, SpectralGroupModel
6+
from .objs import SpectralModel, SpectralGroupModel, SpectralTimeModel, SpectralTimeEventModel
77
from .objs.utils import fit_models_3d

specparam/analysis/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
"""Analysis sub-module for model parameters and related metrics."""
22

33
from .error import compute_pointwise_error, compute_pointwise_error_group
4-
from .periodic import get_band_peak, get_band_peak_group
4+
from .periodic import get_band_peak, get_band_peak_group, get_band_peak_event

specparam/analysis/periodic.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def get_band_peak(model, band, select_highest=True, threshold=None,
3030
3131
Returns
3232
-------
33-
1d or 2d array
33+
peaks : 1d or 2d array
3434
Peak data. Each row is a peak, as [CF, PW, BW].
3535
3636
Examples
@@ -67,7 +67,7 @@ def get_band_peak_group(group, band, threshold=None, thresh_param='PW', attribut
6767
6868
Returns
6969
-------
70-
2d array
70+
peaks : 2d array
7171
Peak data. Each row is a peak, as [CF, PW, BW].
7272
Each row represents an individual model from the input object.
7373
@@ -101,6 +101,40 @@ def get_band_peak_group(group, band, threshold=None, thresh_param='PW', attribut
101101
threshold, thresh_param)
102102

103103

104+
def get_band_peak_event(event, band, threshold=None, thresh_param='PW', attribute='peak_params'):
105+
"""Extract peaks from a band of interest from an event model object.
106+
107+
Parameters
108+
----------
109+
event : SpectralTimeEventModel
110+
Object to extract peak data from.
111+
band : tuple of (float, float)
112+
Frequency range for the band of interest.
113+
Defined as: (lower_frequency_bound, upper_frequency_bound).
114+
select_highest : bool, optional, default: True
115+
Whether to return single peak (if True) or all peaks within the range found (if False).
116+
If True, returns the highest power peak within the search range.
117+
threshold : float, optional
118+
A minimum threshold value to apply.
119+
thresh_param : {'PW', 'BW'}
120+
Which parameter to threshold on. 'PW' is power and 'BW' is bandwidth.
121+
attribute : {'peak_params', 'gaussian_params'}
122+
Which attribute of peak data to extract data from.
123+
124+
Returns
125+
-------
126+
peaks : 3d array
127+
Array of peak data, organized as [n_events, n_time_windows, n_peak_params].
128+
"""
129+
130+
peaks = np.zeros([event.n_events, event.n_time_windows, 3])
131+
for ind in range(event.n_events):
132+
peaks[ind, :, :] = get_band_peak_group(\
133+
event.get_group(ind, None, 'group'), band, threshold, thresh_param, attribute)
134+
135+
return peaks
136+
137+
104138
def get_band_peak_group_arr(peak_params, band, n_fits, threshold=None, thresh_param='PW'):
105139
"""Extract peaks within a given band of interest, from peaks from a group fit.
106140

specparam/core/io.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,32 @@ def fpath(file_path, file_name):
6161
return full_path
6262

6363

64+
def get_files(file_path, select=None):
65+
"""Get a list of files from a directory.
66+
67+
Parameters
68+
----------
69+
file_path : Path or str
70+
Name of the folder to get the list of files from.
71+
select : str, optional
72+
A search string to use to select files.
73+
74+
Returns
75+
-------
76+
list of str
77+
A list of files.
78+
"""
79+
80+
# Get list of available files, and drop hidden files
81+
files = os.listdir(file_path)
82+
files = [file for file in files if file[0] != '.']
83+
84+
if select:
85+
files = [file for file in files if select in file]
86+
87+
return files
88+
89+
6490
def save_model(model, file_name, file_path=None, append=False,
6591
save_results=False, save_settings=False, save_data=False):
6692
"""Save out data, results and/or settings from a model object into a JSON file.
@@ -130,7 +156,7 @@ def save_group(group, file_name, file_path=None, append=False,
130156
file_name : str or FileObject
131157
File to save data to.
132158
file_path : Path or str, optional
133-
Path to directory to load from. If None, loads from current directory.
159+
Path to directory to load from. If None, saves to current directory.
134160
append : bool, optional, default: False
135161
Whether to append to an existing file, if available.
136162
This option is only valid (and only used) if 'file_name' is a str.
@@ -168,6 +194,48 @@ def save_group(group, file_name, file_path=None, append=False,
168194
raise ValueError("Save file not understood.")
169195

170196

197+
def save_event(event, file_name, file_path=None, append=False,
198+
save_results=False, save_settings=False, save_data=False):
199+
"""Save out results and/or settings from event object. Saves out to a JSON file.
200+
201+
Parameters
202+
----------
203+
event : SpectralTimeEventModel
204+
Object to save data from.
205+
file_name : str or FileObject
206+
File to save data to.
207+
file_path : str, optional
208+
Path to directory to load from. If None, saves to current directory.
209+
append : bool, optional, default: False
210+
Whether to append to an existing file, if available.
211+
This option is only valid (and only used) if 'file_name' is a str.
212+
save_results : bool, optional
213+
Whether to save out model fit results.
214+
save_settings : bool, optional
215+
Whether to save out settings.
216+
save_data : bool, optional
217+
Whether to save out power spectra data.
218+
219+
Raises
220+
------
221+
ValueError
222+
If the data or save file specified are not understood.
223+
"""
224+
225+
fg = event.get_group(None, None, 'group')
226+
if save_settings and not save_results and not save_data:
227+
fg.save(file_name, file_path, append=append, save_settings=True)
228+
else:
229+
ndigits = len(str(len(event)))
230+
for ind, gres in enumerate(event.event_group_results):
231+
fg.group_results = gres
232+
if save_data:
233+
fg.power_spectra = event.spectrograms[ind, :, :].T
234+
fg.save(file_name + '_{:0{ndigits}d}'.format(ind, ndigits=ndigits),
235+
file_path=file_path, append=append, save_results=save_results,
236+
save_settings=save_settings, save_data=save_data)
237+
238+
171239
def load_json(file_name, file_path):
172240
"""Load json file.
173241

specparam/core/modutils.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"""Utility functions & decorators for the module."""
22

3-
from importlib import import_module
3+
from copy import deepcopy
44
from functools import wraps
5+
from importlib import import_module
56

67
###################################################################################################
78
###################################################################################################
@@ -138,6 +139,42 @@ def docs_drop_param(docstring):
138139
return front + back
139140

140141

142+
def docs_replace_param(docstring, replace, new_param):
143+
"""Replace a parameter description in a docstring.
144+
145+
Parameters
146+
----------
147+
docstring : str
148+
Docstring to replace parameter description within.
149+
replace : str
150+
The name of the parameter to switch out.
151+
new_param : str
152+
The new parameter description to replace into the docstring.
153+
This should be a string structured to be copied directly into the docstring.
154+
155+
Returns
156+
-------
157+
new_docstring : str
158+
Update docstring, with parameter switched out.
159+
"""
160+
161+
# Take a copy to make sure to avoid any potential aliasing
162+
docstring = deepcopy(docstring)
163+
164+
# Find the index where the param to replace is
165+
p_ind = docstring.find(replace)
166+
167+
# Find the second newline (end of to-replace param)
168+
ti = docstring[p_ind:].find('\n')
169+
n_ind = docstring[p_ind + ti + 1:].find('\n')
170+
end_ind = p_ind + ti + 1 + n_ind
171+
172+
# Reconstitute docstring, replacing specified parameter
173+
new_docstring = docstring[:p_ind] + new_param + docstring[end_ind:]
174+
175+
return new_docstring
176+
177+
141178
def docs_append_to_section(docstring, section, add):
142179
"""Append extra information to a specified section of a docstring.
143180

0 commit comments

Comments
 (0)