Skip to content

Commit

Permalink
Merge pull request #38 from schmidtfa/minor_fixes
Browse files Browse the repository at this point in the history
Improved Knee Testing
  • Loading branch information
schmidtfa authored Jul 31, 2024
2 parents 9030862 + 97d10c6 commit 87a384d
Show file tree
Hide file tree
Showing 116 changed files with 498 additions and 66 deletions.
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

0 comments on commit 87a384d

Please sign in to comment.