Skip to content

Commit 8ebf2dc

Browse files
linted
1 parent 0a57674 commit 8ebf2dc

File tree

1 file changed

+85
-2
lines changed

1 file changed

+85
-2
lines changed

docs/examples/plot_1D_basis_function.py

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,10 @@
6565
# -----------------
6666
# Each basis type may necessitate specific hyperparameters for instantiation. For a comprehensive description,
6767
# please refer to the [Code References](../../../reference/neurostatslib/basis). After instantiation, all classes
68-
# share the same syntax for basis evaluation. The following is an example of how to instantiate and
69-
# evaluate a log-spaced cosine raised function basis.
68+
# share the same syntax for basis evaluation.
69+
#
70+
# ### The Log-Spaced Raised Cosine Basis
71+
# The following is an example of how to instantiate and evaluate a log-spaced cosine raised function basis.
7072

7173
# Instantiate the basis noting that the `RaisedCosineBasisLog` does not require an `order` parameter
7274
raised_cosine_log = nsl.basis.RaisedCosineBasisLog(n_basis_funcs=10)
@@ -81,3 +83,84 @@
8183
plt.plot(samples, eval_basis)
8284
plt.show()
8385

86+
# %%
87+
# ### The Fourier Basis
88+
# Another type of basis available is the Fourier Basis. Fourier basis are ideal to capture periodic and
89+
# quasi-periodic patterns. Such oscillatory, rhythmic behavior is a common signature of many neural signals.
90+
# Additionally, the Fourier basis has the advantage of being orthogonal, which simplifies the estimation and
91+
# interpretation of the model parameters, each of which will represent the relative contribution of a specific
92+
# oscillation frequency to the overall signal.
93+
94+
95+
# A Fourier basis can be instantiated with the usual syntax.
96+
# The user can pass the desired frequencies for the basis or
97+
# the frequencies will be set to np.arange(n_basis_funcs//2).
98+
# The number of basis function is required to be even.
99+
fourier_basis = nsl.basis.FourierBasis(n_freqs=4)
100+
101+
# evaluate on equi-spaced samples
102+
samples, eval_basis = fourier_basis.evaluate_on_grid(1000)
103+
104+
# plot the `sin` and `cos` separately
105+
plt.figure(figsize=(6, 3))
106+
plt.subplot(121)
107+
plt.title("Cos")
108+
plt.plot(samples, eval_basis[:, :4])
109+
plt.subplot(122)
110+
plt.title("Sin")
111+
plt.plot(samples, eval_basis[:, 4:])
112+
plt.tight_layout()
113+
114+
# %%
115+
# !!! note "Fourier basis convolution and Fourier transform"
116+
# The Fourier transform of a signal $ s(t) $ restricted to a temporal window $ [t_0,\;t_1] $ is
117+
# $$ \\hat{x}(\\omega) = \\int_{t_0}^{t_1} s(\\tau) e^{-j\\omega \\tau} d\\tau. $$
118+
# where $ e^{-j\\omega \\tau} = \\cos(\\omega \\tau) - j \\sin (\\omega \\tau) $.
119+
#
120+
# When computing the cross-correlation of a signal with the Fourier basis functions,
121+
# we essentially measure how well the signal correlates with sinusoids of different frequencies,
122+
# within a specified temporal window. This process mirrors the operation performed by the Fourier transform.
123+
# Therefore, it becomes clear that computing the cross-correlation of a signal with the Fourier basis defined here
124+
# is equivalent to computing the discrete Fourier transform on a sliding window of the same size
125+
# as that of the basis.
126+
127+
n_samples = 1000
128+
n_freqs = 20
129+
130+
# define a signal
131+
signal = np.random.normal(size=n_samples)
132+
133+
# evaluate the basis
134+
_, eval_basis = nsl.basis.FourierBasis(n_freqs=n_freqs).evaluate_on_grid(n_samples)
135+
136+
# compute the cross-corr with the signal and the basis
137+
# Note that we are inverting the time axis of the basis because we are aiming
138+
# for a cross-correlation, while np.convolve compute a convolution which would flip the time axis.
139+
xcorr = np.array(
140+
[
141+
np.convolve(eval_basis[::-1, k], signal, mode="valid")[0]
142+
for k in range(2 * n_freqs - 1)
143+
]
144+
)
145+
146+
# compute the power (add back sin(0 * t) = 0)
147+
fft_complex = np.fft.fft(signal)
148+
fft_amplitude = np.abs(fft_complex[:n_freqs])
149+
fft_phase = np.angle(fft_complex[:n_freqs])
150+
# compute the phase and amplitude from the convolution
151+
xcorr_phase = np.arctan2(np.hstack([[0], xcorr[n_freqs:]]), xcorr[:n_freqs])
152+
xcorr_aplitude = np.sqrt(xcorr[:n_freqs] ** 2 + np.hstack([[0], xcorr[n_freqs:]]) ** 2)
153+
154+
fig, ax = plt.subplots(1, 2)
155+
ax[0].set_aspect("equal")
156+
ax[0].set_title("Signal amplitude")
157+
ax[0].scatter(fft_amplitude, xcorr_aplitude)
158+
ax[0].set_xlabel("FFT")
159+
ax[0].set_ylabel("cross-correlation")
160+
161+
ax[1].set_aspect("equal")
162+
ax[1].set_title("Signal phase")
163+
ax[1].scatter(fft_phase, xcorr_phase)
164+
ax[1].set_xlabel("FFT")
165+
ax[1].set_ylabel("cross-correlation")
166+
plt.tight_layout()

0 commit comments

Comments
 (0)