Skip to content

Commit

Permalink
Merge pull request #283 from fooof-tools/time
Browse files Browse the repository at this point in the history
[ENH] - Add `SpectralTimeModel` and `SpectralTimeEventModel`
  • Loading branch information
TomDonoghue authored Mar 27, 2024
2 parents 5e655d7 + ebba007 commit 809a3cf
Show file tree
Hide file tree
Showing 64 changed files with 3,666 additions and 321 deletions.
8 changes: 2 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,16 @@ on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:

# Tag ubuntu version to 20.04, in order to support python 3.6
# See issue: https://github.com/actions/setup-python/issues/544
# When ready to drop 3.6, can revert from 'ubuntu-20.04' -> 'ubuntu-latest'
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
env:
MODULE_NAME: specparam
strategy:
matrix:
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ This documentation includes:
Dependencies
------------

SpecParam is written in Python, and requires Python >= 3.6 to run.
SpecParam is written in Python, and requires Python >= 3.7 to run.

It has the following required dependencies:

Expand Down
38 changes: 34 additions & 4 deletions doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ The SpectralGroupModel object allows for parameterizing groups of power spectra.

SpectralGroupModel

Time & Event Objects
~~~~~~~~~~~~~~~~~~~~

The time & event objects allows for parameterizing power spectra organized across time and/or events.

.. autosummary::
:toctree: generated/

SpectralTimeModel
SpectralTimeEventModel

Object Utilities
~~~~~~~~~~~~~~~~

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

get_band_peak
get_band_peak_group
get_band_peak_event

**Array Inputs**

Expand All @@ -178,7 +190,7 @@ Code & utilities for simulating power spectra.
Generate Power Spectra
~~~~~~~~~~~~~~~~~~~~~~

Functions for simulating neural power spectra.
Functions for simulating neural power spectra and spectrograms.

.. currentmodule:: specparam.sim

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

sim_power_spectrum
sim_group_power_spectra
sim_spectrogram

Manage Parameters
~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -242,14 +255,15 @@ Visualizations.
Plot Power Spectra
~~~~~~~~~~~~~~~~~~

Plots for visualizing power spectra.
Plots for visualizing power spectra and spectrograms.

.. currentmodule:: specparam.plts

.. autosummary::
:toctree: generated/

plot_spectra
plot_spectrogram

Plots for plotting power spectra with shaded regions.

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

plot_group
plot_group_model

.. currentmodule:: specparam.plts.time

.. autosummary::
:toctree: generated/

plot_time_model

.. currentmodule:: specparam.plts.event

.. autosummary::
:toctree: generated/

plot_event_model

Annotated Plots
~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -388,7 +416,9 @@ Input / Output (IO)
:toctree: generated/

load_model
load_group
load_group_model
load_time_model
load_event_model

Methods Reports
~~~~~~~~~~~~~~~
Expand Down
4 changes: 2 additions & 2 deletions examples/manage/plot_fit_models_3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
from specparam.sim import sim_group_power_spectra
from specparam.sim.utils import create_freqs
from specparam.sim.params import param_sampler
from specparam.utils.io import load_group
from specparam.utils.io import load_group_model

###################################################################################################
# Example Set-Up
Expand Down Expand Up @@ -229,7 +229,7 @@
###################################################################################################

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

###################################################################################################
Expand Down
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
'Operating System :: POSIX',
'Operating System :: Unix',
'Programming Language :: Python',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
Expand Down
2 changes: 1 addition & 1 deletion specparam/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
from .version import __version__

from .bands import Bands
from .objs import SpectralModel, SpectralGroupModel
from .objs import SpectralModel, SpectralGroupModel, SpectralTimeModel, SpectralTimeEventModel
from .objs.utils import fit_models_3d
2 changes: 1 addition & 1 deletion specparam/analysis/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Analysis sub-module for model parameters and related metrics."""

from .error import compute_pointwise_error, compute_pointwise_error_group
from .periodic import get_band_peak, get_band_peak_group
from .periodic import get_band_peak, get_band_peak_group, get_band_peak_event
38 changes: 36 additions & 2 deletions specparam/analysis/periodic.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def get_band_peak(model, band, select_highest=True, threshold=None,
Returns
-------
1d or 2d array
peaks : 1d or 2d array
Peak data. Each row is a peak, as [CF, PW, BW].
Examples
Expand Down Expand Up @@ -67,7 +67,7 @@ def get_band_peak_group(group, band, threshold=None, thresh_param='PW', attribut
Returns
-------
2d array
peaks : 2d array
Peak data. Each row is a peak, as [CF, PW, BW].
Each row represents an individual model from the input object.
Expand Down Expand Up @@ -101,6 +101,40 @@ def get_band_peak_group(group, band, threshold=None, thresh_param='PW', attribut
threshold, thresh_param)


def get_band_peak_event(event, band, threshold=None, thresh_param='PW', attribute='peak_params'):
"""Extract peaks from a band of interest from an event model object.
Parameters
----------
event : SpectralTimeEventModel
Object to extract peak data from.
band : tuple of (float, float)
Frequency range for the band of interest.
Defined as: (lower_frequency_bound, upper_frequency_bound).
select_highest : bool, optional, default: True
Whether to return single peak (if True) or all peaks within the range found (if False).
If True, returns the highest power peak within the search range.
threshold : float, optional
A minimum threshold value to apply.
thresh_param : {'PW', 'BW'}
Which parameter to threshold on. 'PW' is power and 'BW' is bandwidth.
attribute : {'peak_params', 'gaussian_params'}
Which attribute of peak data to extract data from.
Returns
-------
peaks : 3d array
Array of peak data, organized as [n_events, n_time_windows, n_peak_params].
"""

peaks = np.zeros([event.n_events, event.n_time_windows, 3])
for ind in range(event.n_events):
peaks[ind, :, :] = get_band_peak_group(\
event.get_group(ind, None, 'group'), band, threshold, thresh_param, attribute)

return peaks


def get_band_peak_group_arr(peak_params, band, n_fits, threshold=None, thresh_param='PW'):
"""Extract peaks within a given band of interest, from peaks from a group fit.
Expand Down
70 changes: 69 additions & 1 deletion specparam/core/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,32 @@ def fpath(file_path, file_name):
return full_path


def get_files(file_path, select=None):
"""Get a list of files from a directory.
Parameters
----------
file_path : Path or str
Name of the folder to get the list of files from.
select : str, optional
A search string to use to select files.
Returns
-------
list of str
A list of files.
"""

# Get list of available files, and drop hidden files
files = os.listdir(file_path)
files = [file for file in files if file[0] != '.']

if select:
files = [file for file in files if select in file]

return files


def save_model(model, file_name, file_path=None, append=False,
save_results=False, save_settings=False, save_data=False):
"""Save out data, results and/or settings from a model object into a JSON file.
Expand Down Expand Up @@ -130,7 +156,7 @@ def save_group(group, file_name, file_path=None, append=False,
file_name : str or FileObject
File to save data to.
file_path : Path or str, optional
Path to directory to load from. If None, loads from current directory.
Path to directory to load from. If None, saves to current directory.
append : bool, optional, default: False
Whether to append to an existing file, if available.
This option is only valid (and only used) if 'file_name' is a str.
Expand Down Expand Up @@ -168,6 +194,48 @@ def save_group(group, file_name, file_path=None, append=False,
raise ValueError("Save file not understood.")


def save_event(event, file_name, file_path=None, append=False,
save_results=False, save_settings=False, save_data=False):
"""Save out results and/or settings from event object. Saves out to a JSON file.
Parameters
----------
event : SpectralTimeEventModel
Object to save data from.
file_name : str or FileObject
File to save data to.
file_path : str, optional
Path to directory to load from. If None, saves to current directory.
append : bool, optional, default: False
Whether to append to an existing file, if available.
This option is only valid (and only used) if 'file_name' is a str.
save_results : bool, optional
Whether to save out model fit results.
save_settings : bool, optional
Whether to save out settings.
save_data : bool, optional
Whether to save out power spectra data.
Raises
------
ValueError
If the data or save file specified are not understood.
"""

fg = event.get_group(None, None, 'group')
if save_settings and not save_results and not save_data:
fg.save(file_name, file_path, append=append, save_settings=True)
else:
ndigits = len(str(len(event)))
for ind, gres in enumerate(event.event_group_results):
fg.group_results = gres
if save_data:
fg.power_spectra = event.spectrograms[ind, :, :].T
fg.save(file_name + '_{:0{ndigits}d}'.format(ind, ndigits=ndigits),
file_path=file_path, append=append, save_results=save_results,
save_settings=save_settings, save_data=save_data)


def load_json(file_name, file_path):
"""Load json file.
Expand Down
39 changes: 38 additions & 1 deletion specparam/core/modutils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""Utility functions & decorators for the module."""

from importlib import import_module
from copy import deepcopy
from functools import wraps
from importlib import import_module

###################################################################################################
###################################################################################################
Expand Down Expand Up @@ -138,6 +139,42 @@ def docs_drop_param(docstring):
return front + back


def docs_replace_param(docstring, replace, new_param):
"""Replace a parameter description in a docstring.
Parameters
----------
docstring : str
Docstring to replace parameter description within.
replace : str
The name of the parameter to switch out.
new_param : str
The new parameter description to replace into the docstring.
This should be a string structured to be copied directly into the docstring.
Returns
-------
new_docstring : str
Update docstring, with parameter switched out.
"""

# Take a copy to make sure to avoid any potential aliasing
docstring = deepcopy(docstring)

# Find the index where the param to replace is
p_ind = docstring.find(replace)

# Find the second newline (end of to-replace param)
ti = docstring[p_ind:].find('\n')
n_ind = docstring[p_ind + ti + 1:].find('\n')
end_ind = p_ind + ti + 1 + n_ind

# Reconstitute docstring, replacing specified parameter
new_docstring = docstring[:p_ind] + new_param + docstring[end_ind:]

return new_docstring


def docs_append_to_section(docstring, section, add):
"""Append extra information to a specified section of a docstring.
Expand Down
Loading

0 comments on commit 809a3cf

Please sign in to comment.