Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] - Add explicit {aperiodic, periodic} 'Modes' support #298

Open
wants to merge 29 commits into
base: basemodel
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
946b01b
first round updates to use fit modes
TomDonoghue Aug 7, 2023
d23fa59
temporarily turn off IO related tests
TomDonoghue Aug 7, 2023
f4edc40
acess param inds from Mode object
TomDonoghue Aug 10, 2023
8a015f8
clean ups
TomDonoghue Aug 10, 2023
5c2ce37
update modes orgs & add n_params
TomDonoghue Aug 10, 2023
f4c35d2
add periodic_mode as a passable input
TomDonoghue Aug 10, 2023
88cdd0a
group_three -> groupby
TomDonoghue Aug 10, 2023
42af5bc
generalize processes for number of params (including temp updates)
TomDonoghue Aug 10, 2023
280b382
add labels to funcs file
TomDonoghue Aug 14, 2023
8c9b5b1
add double exp function
TomDonoghue Aug 14, 2023
7f2bd95
add normalize util func
TomDonoghue Aug 14, 2023
8eb27ad
add skewed gaussian fit function
TomDonoghue Aug 14, 2023
5f0cca3
add new modes
TomDonoghue Aug 14, 2023
d046a3c
add docs to Mode object
TomDonoghue Aug 14, 2023
bf3944d
start process new bounds / guess funcs
TomDonoghue Aug 20, 2023
d06b9de
Merge branch 'basemodel' into modes
TomDonoghue Sep 13, 2023
7663b91
Merge branch 'main' into modes
TomDonoghue Feb 15, 2024
b213edc
Update readme badge links
TomDonoghue Apr 4, 2024
ac1d71b
fix merge
TomDonoghue Apr 9, 2024
9609cf1
Merge branch 'basemodel' into modes
TomDonoghue Apr 10, 2024
024fb4a
add setable alpha level to add_shades
TomDonoghue Aug 3, 2024
f48b849
Merge pull request #332 from fooof-tools/shade
TomDonoghue Aug 3, 2024
90f0bea
Merge branch 'main' into modes
TomDonoghue Aug 3, 2024
bc77ac5
Fixed matplotlib is required
kazuki Sep 20, 2024
bf28787
Merge pull request #335 from kazuki/fix-matplotlib-required
TomDonoghue Sep 23, 2024
8c2ff3e
add py13 tests
TomDonoghue Nov 28, 2024
eab2ce5
Merge branch 'main' of https://github.com/fooof-tools/fooof into main
TomDonoghue Nov 28, 2024
cbbec68
list py3.13 support
TomDonoghue Nov 28, 2024
a839bd1
Merge branch 'main' into modes
TomDonoghue Nov 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
MODULE_NAME: specparam
strategy:
matrix:
python-version: ["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", "3.13"]

steps:
- uses: actions/checkout@v3
Expand Down
26 changes: 16 additions & 10 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,35 @@
Spectral Parameterization
=========================

|ProjectStatus|_ |Version|_ |BuildStatus|_ |Coverage|_ |License|_ |PythonVersions|_ |Paper|_
|ProjectStatus| |Version| |BuildStatus| |Coverage| |License| |PythonVersions| |Publication|

.. |ProjectStatus| image:: http://www.repostatus.org/badges/latest/active.svg
.. _ProjectStatus: https://www.repostatus.org/#active
:target: https://www.repostatus.org/#active
:alt: project status

.. |Version| image:: https://img.shields.io/pypi/v/fooof.svg
.. _Version: https://pypi.python.org/pypi/fooof/
:target: https://pypi.python.org/pypi/fooof/
:alt: version

.. |BuildStatus| image:: https://github.com/fooof-tools/fooof/actions/workflows/build.yml/badge.svg
.. _BuildStatus: https://github.com/fooof-tools/fooof/actions/workflows/build.yml
:target: https://github.com/fooof-tools/fooof/actions/workflows/build.yml
:alt: build status

.. |Coverage| image:: https://codecov.io/gh/fooof-tools/fooof/branch/main/graph/badge.svg
.. _Coverage: https://codecov.io/gh/fooof-tools/fooof
:target: https://codecov.io/gh/fooof-tools/fooof
:alt: coverage

.. |License| image:: https://img.shields.io/pypi/l/fooof.svg
.. _License: https://opensource.org/licenses/Apache-2.0
:target: https://opensource.org/licenses/Apache-2.0
:alt: license

.. |PythonVersions| image:: https://img.shields.io/pypi/pyversions/fooof.svg
.. _PythonVersions: https://pypi.python.org/pypi/fooof/

.. |Paper| image:: https://img.shields.io/badge/paper-nn10.1038-informational.svg
.. _Paper: https://doi.org/10.1038/s41593-020-00744-x
:target: https://pypi.python.org/pypi/fooof/
:alt: python versions

.. |Publication| image:: https://img.shields.io/badge/paper-nn10.1038-informational.svg
:target: https://doi.org/10.1038/s41593-020-00744-x
:alt: publication

Spectral parameterization (`specparam`, formerly `fooof`) is a fast, efficient, and physiologically-informed tool to parameterize neural power spectra.

Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
],
platforms = 'any',
project_urls = {
Expand Down
74 changes: 66 additions & 8 deletions specparam/core/funcs.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
"""Functions that can be used for model fitting.

NOTES
-----
- Model fitting currently (only) uses the exponential and gaussian functions.
- Linear & Quadratic functions are from previous versions.
- They are left available for easy swapping back in, if desired.
"""
"""Functions that can be used for model fitting."""

import numpy as np
from scipy.special import erf

from specparam.core.utils import normalize
from specparam.core.errors import InconsistentDataError

###################################################################################################
###################################################################################################

## PEAK FUNCTIONS

def gaussian_function(xs, *params):
"""Gaussian fitting function.

Expand All @@ -39,6 +36,37 @@ def gaussian_function(xs, *params):
return ys


def skewnorm_function(xs, *params):
"""Skewed normal distribution fitting function.

Parameters
----------
xs : 1d array
Input x-axis values.
*params : float
Parameters that define the skewed normal distribution function.

Returns
-------
ys : 1d array
Output values for skewed normal distribution function.
"""

ys = np.zeros_like(xs)

for ii in range(0, len(params), 4):

ctr, hgt, wid, skew = params[ii:ii+4]

ts = (xs - ctr) / wid
temp = 2 / wid * (1 / np.sqrt(2 * np.pi) * np.exp(-ts**2 / 2)) * \
((1 + erf(skew * ts / np.sqrt(2))) / 2)
ys = ys + hgt * normalize(temp)

return ys

## APERIODIC FUNCTIONS

def expo_function(xs, *params):
"""Exponential fitting function, for fitting aperiodic component with a 'knee'.

Expand Down Expand Up @@ -89,6 +117,34 @@ def expo_nk_function(xs, *params):
return ys


def double_expo_function(xs, *params):
"""Double exponential fitting function, for fitting aperiodic component with two exponents and a knee.

NOTE: this function requires linear frequency (not log).

Parameters
----------
xs : 1d array
Input x-axis values.
*params : float
Parameters (offset, exp0, knee, exp1) that define the function:
y = 10^offset * (1/((x**exp0) * (knee + x^exp1))

Returns
-------
ys : 1d array
Output values for exponential function.
"""

ys = np.zeros_like(xs)

offset, exp0, knee, exp1 = params

ys = ys + offset - np.log10((xs**exp0) * (knee + xs**exp1))

return ys


def linear_function(xs, *params):
"""Linear fitting function.

Expand Down Expand Up @@ -133,6 +189,8 @@ def quadratic_function(xs, *params):
return ys


## GETTER FUNCTIONS

def get_pe_func(periodic_mode):
"""Select and return specified function for periodic component.

Expand Down
6 changes: 6 additions & 0 deletions specparam/core/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ def get_ap_indices(aperiodic_mode):
Mapping of the column labels and indices for the aperiodic parameters.
"""

# TEMP / TEST:
aperiodic_mode = str(aperiodic_mode)

if aperiodic_mode == 'fixed':
labels = ('offset', 'exponent')
elif aperiodic_mode == 'knee':
Expand All @@ -101,6 +104,9 @@ def get_indices(aperiodic_mode):
Mapping of the column labels and indices for all parameters.
"""

# TEMP / TEST:
aperiodic_mode = str(aperiodic_mode)

# Get the periodic indices, and then update dictionary with aperiodic ones
indices = get_peak_indices()
indices.update(get_ap_indices(aperiodic_mode))
Expand Down
7 changes: 4 additions & 3 deletions specparam/core/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ def gen_model_results_str(model, concise=False):

# Aperiodic parameters
('Aperiodic Parameters (offset, ' + \
('knee, ' if model.aperiodic_mode == 'knee' else '') + \
('knee, ' if str(model.aperiodic_mode) == 'knee' else '') + \
'exponent): '),
', '.join(['{:2.4f}'] * len(model.aperiodic_params_)).format(*model.aperiodic_params_),
'',
Expand Down Expand Up @@ -357,7 +357,7 @@ def gen_group_results_str(group, concise=False):
errors = group.get_params('error')
exps = group.get_params('aperiodic_params', 'exponent')
kns = group.get_params('aperiodic_params', 'knee') \
if group.aperiodic_mode == 'knee' else np.array([0])
if str(group.aperiodic_mode) == 'knee' else np.array([0])

str_lst = [

Expand All @@ -380,12 +380,13 @@ def gen_group_results_str(group, concise=False):

# Aperiodic parameters - knee fit status, and quick exponent description
'Power spectra were fit {} a knee.'.format(\
'with' if group.aperiodic_mode == 'knee' else 'without'),
'with' if str(group.aperiodic_mode) == 'knee' else 'without'),
'',
'Aperiodic Fit Values:',
*[el for el in [' Knees - Min: {:6.2f}, Max: {:6.2f}, Mean: {:5.2f}'
.format(*compute_arr_desc(kns)),
] if group.aperiodic_mode == 'knee'],

'Exponents - Min: {:6.3f}, Max: {:6.3f}, Mean: {:5.3f}'
.format(*compute_arr_desc(exps)),
'',
Expand Down
33 changes: 26 additions & 7 deletions specparam/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,32 @@ def unlog(arr, base=10):
return np.power(base, arr)


def group_three(vec):
"""Group an array of values into threes.
def normalize(data):
"""Normalize an array of numerical data (to the range of 0-1).

Parameters
----------
data : np.ndarray
Array of data to normalize.

Returns
-------
np.ndarray
Normalized data.
"""

return (data - np.min(data)) / (np.max(data) - np.min(data))


def groupby(vec, groupby):
"""Group an array of values by a specified number.

Parameters
----------
vec : list or 1d array
List or array of items to group by 3. Length of array must be divisible by three.
num : int
Number to group by.

Returns
-------
Expand All @@ -43,17 +62,17 @@ def group_three(vec):
Raises
------
ValueError
If input data cannot be evenly grouped into threes.
If input data cannot be evenly grouped into specified number.
"""

if len(vec) % 3 != 0:
if len(vec) % groupby != 0:
raise ValueError("Wrong size array to group by three.")

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


def nearest_ind(array, value):
Expand Down
Loading