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

Improved Knee Testing #38

Merged
merged 11 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# GitHub syntax highlighting
pixi.lock linguist-language=YAML linguist-generated=true
tests/test_data/knee_data/*.npy filter=lfs diff=lfs merge=lfs -text
tests/test_data/knee_osc_data/*.npy filter=lfs diff=lfs merge=lfs -text
2 changes: 1 addition & 1 deletion pixi.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions simulations/sim4tests/sim_knees.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#%%
from neurodsp.sim import sim_combined, sim_knee
import numpy as np
from os.path import join

n_secs=60
fs_list=[500, 750, 1000]
exp1=0
exp2_list=[-1., -1.5, -2.0]
knee_freq_list=[10, 15, 25]
osc_list = [5, 10, 20]

base_folder = '/Users/fabian.schmidt/git/pyrasa/tests/test_data/'

for fs in fs_list:
for exp2 in exp2_list:
for osc_freq in osc_list:
for knee_freq in knee_freq_list:
#% generate and save knee osc
knee = knee_freq ** np.abs(exp2)
components = {'sim_knee': {'exponent1': exp1, 'exponent2': exp2, 'knee': knee},
'sim_oscillation': {'freq': osc_freq}
}
cmb_sim = sim_combined(n_seconds=n_secs, fs=fs, components=components)

fname = f'cmb_sim__fs_{fs}__exp1_{np.abs(exp1)}__exp2_{np.abs(exp2)}_knee_{np.round(knee, 0)}__osc_freq_{osc_freq}_.npy'
np.save(join(base_folder + 'knee_osc_data', fname), cmb_sim, allow_pickle=False)

#% generate and save knee
knee_sim = sim_knee(n_seconds=n_secs, fs=fs, exponent1=exp1, exponent2=exp2, knee=knee)

fname = f'knee_sim__fs_{fs}__exp1_{np.abs(exp1)}__exp2_{np.abs(exp2)}_knee_{np.round(knee, 0)}_.npy'
np.save(join(base_folder + 'knee_data', fname), knee_sim, allow_pickle=False)

# %%
41 changes: 21 additions & 20 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,24 @@ def fixed_aperiodic_signal(exponent, fs):

@pytest.fixture(scope='session')
def knee_aperiodic_signal(exponent, fs, knee_freq):
yield sim_knee(n_seconds=N_SECONDS, fs=fs, exponent1=0, exponent2=exponent, knee=knee_freq ** np.abs(exponent))
yield sim_knee(n_seconds=N_SECONDS, fs=fs, exponent1=0, exponent2=exponent, knee=knee_freq ** np.abs(exponent)) # noqa: E501


@pytest.fixture(scope='session')
def load_knee_aperiodic_signal(exponent, fs, knee):
base_dir = 'tests/test_data/knee_data/'
yield np.load(
base_dir + f'knee_sim__fs_{fs}__exp1_0__exp2_{exponent}_knee_{knee}_.npy',
) # allow_pickle=True)


@pytest.fixture(scope='session')
def load_knee_cmb_signal(exponent, fs, knee, osc_freq):
base_dir = 'tests/test_data/knee_osc_data/'
yield np.load(
base_dir + f'cmb_sim__fs_{fs}__exp1_0__exp2_{exponent}_knee_{knee}__osc_freq_{osc_freq}_.npy',
) # allow_pickle=True)
# noqa: E501


@pytest.fixture(scope='session')
Expand Down Expand Up @@ -66,20 +83,8 @@ def gen_mne_data_raw():

meg_path = data_path / 'MEG' / 'sample'
raw_fname = meg_path / 'sample_audvis_raw.fif'

raw = mne.io.read_raw_fif(raw_fname)
picks = mne.pick_types(raw.info, meg='mag', eeg=False, stim=False, eog=False, exclude='bads')
raw.pick(picks)

yield raw


@pytest.fixture(scope='session')
def gen_mne_data_epoched():
data_path = sample.data_path()

meg_path = data_path / 'MEG' / 'sample'
raw_fname = meg_path / 'sample_audvis_raw.fif'
event_fname = meg_path / 'sample_audvis_filt-0-40_raw-eve.fif'
events = mne.read_events(event_fname)

raw = mne.io.read_raw_fif(raw_fname)
picks = mne.pick_types(raw.info, meg='mag', eeg=False, stim=False, eog=False, exclude='bads')
Expand All @@ -96,19 +101,15 @@ def gen_mne_data_epoched():
tmax = 0.5

# Load real data as the template
event_fname = meg_path / 'sample_audvis_filt-0-40_raw-eve.fif'
events = mne.read_events(event_fname)

epochs = mne.Epochs(
raw,
events,
event_id,
tmin,
tmax,
# picks=picks,
baseline=None,
preload=True,
verbose=False,
)

yield epochs
yield raw, epochs
21 changes: 18 additions & 3 deletions tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,25 @@
FS = [500, 750, 1000]
OSC_FREQ = [5, 10, 20]
MANY_OSC_FREQ = np.arange(2, 30, 1)
EXPONENT = [-0.5, -1.0, -2.0]
EXPONENT = [-1, -1.5, -2.0]
KNEE_FREQ = 15
TOLERANCE = 0.2
KNEE_TOLERANCE = 3
# There seems to be a higher error in knee fits for knee and exponent estimates when
# the difference in pre and post knee exponent is low. This kinda makes sense
# TODO: Test this systematically -> see whether this is an issue with irasa or slope fitting in general

EXP_KNEE_COMBO = [
(1.0, 10.0),
(1.0, 15.0),
(1.0, 25.0),
(1.5, 125.0),
(1.5, 32.0),
(1.5, 58.0),
(2.0, 100.0),
(2.0, 225.0),
(2.0, 625.0),
] # we test exp + knee combined as both relate to each other
TOLERANCE = 0.3 # 0.15
KNEE_TOLERANCE = 5
MIN_R2 = 0.8 # seems like a sensible minimum
MIN_R2_SPRINT = 0.7
MIN_CORR_PSD_CMB = 0.99
49 changes: 18 additions & 31 deletions tests/test_compute_slope.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from pyrasa.utils.aperiodic_utils import compute_slope

from .settings import EXPONENT, FS, KNEE_FREQ, KNEE_TOLERANCE, MIN_R2, TOLERANCE
from .settings import EXPONENT, FS, MIN_R2, TOLERANCE


# Test slope fitting functionality
Expand Down Expand Up @@ -67,35 +67,22 @@ def test_slope_fitting_settings(


# Takes too long need to pregenerate
@pytest.mark.parametrize('exponent, fs, knee_freq', [(-1, 500, 15)], scope='session')
def test_slope_fitting_knee(knee_aperiodic_signal, fs, exponent):
f_range = [1, 200]
# test whether recombining periodic and aperiodic spectrum is equivalent to the original spectrum
freqs, psd = dsp.welch(knee_aperiodic_signal, fs, nperseg=int(4 * fs))
freq_logical = np.logical_and(freqs >= f_range[0], freqs <= f_range[1])
freqs, psd = freqs[freq_logical], psd[freq_logical]
# test whether we can reconstruct the exponent correctly
ap_params_k, gof_k = compute_slope(psd, freqs, fit_func='knee')
ap_params_f, gof_f = compute_slope(psd, freqs, fit_func='fixed')
# assert pytest.approx(ap_params_k['Exponent_1'][0], abs=TOLERANCE) == 0
assert bool(np.isclose(ap_params_k['Exponent_2'][0], np.abs(exponent), atol=TOLERANCE))
assert bool(np.isclose(ap_params_k['Knee Frequency (Hz)'][0], KNEE_FREQ, atol=KNEE_TOLERANCE))
# test goodness of fit
assert gof_k['r_squared'][0] > MIN_R2
assert gof_k['r_squared'][0] > gof_f['r_squared'][0] # r2 for knee model should be higher than knee if knee
# bic and aic for knee model should be better if knee
assert gof_k['AIC'][0] < gof_f['AIC'][0]
assert gof_k['BIC'][0] < gof_f['BIC'][0]


# #pytest.mark.usefixtures(['exponent', 'fs'], [EXPONENT[0], FS[0]], scope='session')
# @pytest.mark.parametrize('exponent', EXPONENT, scope='session')
# @pytest.mark.parametrize('fs', FS, scope='session')
# def test_slope_fitting_settings(fixed_aperiodic_signal, fs, exponent):
# f_range = [0, 100]
# @pytest.mark.parametrize('exponent, fs, knee_freq', [(-1, 500, 15)], scope='session')
# def test_slope_fitting_knee(knee_aperiodic_signal, fs, exponent):
# f_range = [1, 200]
# # test whether recombining periodic and aperiodic spectrum is equivalent to the original spectrum
# freqs, psd = dsp.welch(fixed_aperiodic_signal, 500, nperseg=int(4 * 500))
# freqs, psd = dsp.welch(knee_aperiodic_signal, fs, nperseg=int(4 * fs))
# freq_logical = np.logical_and(freqs >= f_range[0], freqs <= f_range[1])
# ap_params, gof = compute_slope(psd, freqs, fit_func='fixed')
# ap_params, gof = compute_slope(psd, freqs, fit_func='fixed', fit_bound=(0, 40))
# ap_params, gof = compute_slope(psd, freqs, fit_func='fixed', fit_bound=(-1, 500))
# freqs, psd = freqs[freq_logical], psd[freq_logical]
# # test whether we can reconstruct the exponent correctly
# ap_params_k, gof_k = compute_slope(psd, freqs, fit_func='knee')
# ap_params_f, gof_f = compute_slope(psd, freqs, fit_func='fixed')
# # assert pytest.approx(ap_params_k['Exponent_1'][0], abs=TOLERANCE) == 0
# assert bool(np.isclose(ap_params_k['Exponent_2'][0], np.abs(exponent), atol=TOLERANCE))
# assert bool(np.isclose(ap_params_k['Knee Frequency (Hz)'][0], KNEE_FREQ, atol=KNEE_TOLERANCE))
# # test goodness of fit
# assert gof_k['r_squared'][0] > MIN_R2
# assert gof_k['r_squared'][0] > gof_f['r_squared'][0] # r2 for knee model should be higher than knee if knee
# # bic and aic for knee model should be better if knee
# assert gof_k['AIC'][0] < gof_f['AIC'][0]
# assert gof_k['BIC'][0] < gof_f['BIC'][0]
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Loading