Skip to content

Commit

Permalink
Merge pull request #221 from elybrand/central_frequency
Browse files Browse the repository at this point in the history
[ENH] - Simulation of Central Frequency in the Time Domain
  • Loading branch information
ryanhammonds authored Oct 21, 2020
2 parents dec3b81 + 4b39a60 commit 4c561c9
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 2 deletions.
2 changes: 1 addition & 1 deletion neurodsp/sim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
from .aperiodic import sim_powerlaw, sim_random_walk, sim_synaptic_current, sim_poisson_pop
from .cycles import sim_cycle
from .transients import sim_synaptic_kernel
from .combined import sim_combined
from .combined import sim_combined, sim_peak_oscillation
80 changes: 80 additions & 0 deletions neurodsp/sim/combined.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
from itertools import repeat

import numpy as np
from scipy.linalg import norm

from neurodsp.sim.info import get_sim_func
from neurodsp.utils.decorators import normalize
from neurodsp.utils.data import create_times

###################################################################################################
###################################################################################################
Expand Down Expand Up @@ -82,3 +84,81 @@ def sim_combined(n_seconds, fs, components, component_variances=1):
sig = np.sum(sig_components, axis=0)

return sig


@normalize
def sim_peak_oscillation(sig_ap, fs, freq, bw, height):
"""Returns a time series whose full power spectrum consists of an aperiodic component
and a gaussian peak at ``freq`` with standard deviation ``bw`` and relative ``height``.
Parameters
-----------
sig_ap : 1d array
The timeseries of the aperiodic component.
fs: float
Sampling rate of ``sig_ap``.
freq: float
Central frequency for the gaussian peak in Hz.
bw: float
Bandwidth, or standard deviation, of gaussian peak in Hz.
height: float
Relative height in log_10(Hz) over the aperiodic component of the gaussian peak at the
central ``freq``.
Returns
-------
sig: 1d array
Time series with desired power spectrum.
Notes
-----
The periodic component of the signal will be sinusoidal.
Examples
--------
Simulate an aperiodic component with exponent -2 superimposed over
an oscillatory component with central frequency 20.
>>> from neurodsp.sim import sim_powerlaw
>>> fs = 500
>>> sig_ap = sim_powerlaw(n_seconds=10, fs=fs)
>>> sig = sim_peak_oscillation(sig_ap, fs=fs, freq=20, bw=5, height=7)
"""

sig_len = len(sig_ap)
times = create_times(sig_len/fs, fs)

# Construct the aperiodic component and compute its Fourier transform. Only use the
# first half of the frequencies from the Fourier transform since the signal is real.
sig_ap_hat = np.fft.fft(sig_ap)[0:(sig_len//2+1)]

# Create the range of frequencies that appear in the power spectrum since these
# will be the frequencies in the cosines we sum below
freqs = np.linspace(0, fs/2, num=sig_len//2 + 1, endpoint=True)

# Construct the array of relative heights above the aperiodic power spectrum
rel_heights = np.array([height * np.exp(-(lin_freq - freq)**2/(2*bw**2)) for lin_freq in freqs])

# Build an array of the sum of squares of the cosines as they appear in the calculation of the
# amplitudes
cosine_norms = np.array(
[norm(np.cos(2*np.pi*lin_freq*times), 2)**2 for lin_freq in freqs]
)

# Build an array of the amplitude coefficients
cosine_coeffs = np.array(
[(-np.real(sig_ap_hat[ell]) + np.sqrt(np.real(sig_ap_hat[ell])**2 + \
(10**rel_heights[ell] - 1)*np.abs(sig_ap_hat[ell])**2))/cosine_norms[ell]
for ell in range(cosine_norms.shape[0])]
)

# Add cosines with the respective coefficients and with a random phase shift for each one
sig_periodic = np.sum(
np.array([cosine_coeffs[ell]*np.cos(2*np.pi*freqs[ell]*times + 2*np.pi*np.random.rand())
for ell in range(cosine_norms.shape[0])]),
axis=0
)

sig = sig_ap + sig_periodic

return sig
17 changes: 16 additions & 1 deletion neurodsp/tests/sim/test_combined.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

from pytest import raises

from neurodsp.tests.settings import FS, N_SECONDS, FREQ1, FREQ2
from neurodsp.tests.settings import FS, N_SECONDS, FREQ1, FREQ2, EXP1
from neurodsp.tests.tutils import check_sim_output

from neurodsp.sim.combined import *
from neurodsp.sim import sim_powerlaw
from neurodsp.spectral import compute_spectrum

###################################################################################################
###################################################################################################
Expand All @@ -29,3 +31,16 @@ def test_sim_combined():
variances = [0.5, 1]
with raises(ValueError):
out = sim_combined(N_SECONDS, FS, simulations, variances)

def test_sim_peak_oscillation():

sig_ap = sim_powerlaw(N_SECONDS, FS)
sig = sim_peak_oscillation(sig_ap, FS, FREQ1, bw=5, height=10)

check_sim_output(sig)

# Ensure the peak is at or close (+/- 5hz) to FREQ1
_, powers_ap = compute_spectrum(sig_ap, FS)
_, powers = compute_spectrum(sig, FS)

assert abs(np.argmax(powers-powers_ap) - FREQ1) < 5

0 comments on commit 4c561c9

Please sign in to comment.