예제 #1
0
def test_spect_slope():
    """Test for :func:`compute_powercurve_deviation`.

    We impose the power to be written as power(f) = k1/f**theta with noise
    and derive a signal candidate, to which we apply the function
    compute_powercurve_deviation and check whether the k1 and theta estimates
    are correct.
    """
    # Support of the spectrum
    freqs = np.fft.fftfreq(n=int(sfreq), d=1. / sfreq)

    # parameters
    k1 = 5.
    theta = 3.

    # Define the magnitude of the spectrum
    # such that power(f) = k1/f**a with noise
    mag = np.zeros((freqs.shape[0], ))
    mag[0] = 0
    noise = rng.uniform(low=-0.01, high=0.01, size=127)
    pos_freqs = np.arange(1, 128)
    mag[pos_freqs] = (np.sqrt(k1) + noise) / (pos_freqs**(theta / 2))
    mag[-pos_freqs] = mag[1:128]

    # From the magnitude, we get the spectrum, choosing a random phase per bin.
    spect = np.zeros((freqs.shape[0], ), dtype=np.complex64)
    spect[0] = 0
    phase = rng.uniform(low=-np.pi, high=np.pi, size=127)
    spect[pos_freqs] = mag[pos_freqs] * np.exp(phase * 1j)
    spect[-pos_freqs] = np.conj(spect[pos_freqs])

    # Take the inverse FFT to go back to the time domain.
    # The imaginary part of _sig is numerically close to 0
    # by hermitian symmetry. So we obtain a real signal.
    _sig = np.fft.ifft(spect)
    sig = _sig.real
    n_times = sig.shape[0]

    # We test our estimates
    intercept, slope, mse, r2 = \
        compute_spect_slope(sfreq=sfreq, data=sig.reshape(1, -1),
                            with_intercept=True, psd_method='fft')

    # obtained by the expression ps[f] = 2 * [ (spect[f]^2) / (n_times^2) ]
    # and plug-in: power(f) = k1/f**theta with noise
    k1_estimate = 10**(intercept - np.log10(2) + 2 * np.log10(n_times))
    theta_estimate = -slope

    np.testing.assert_almost_equal(k1, k1_estimate, decimal=1)
    np.testing.assert_almost_equal(theta, theta_estimate, decimal=1)
    assert r2 > 0.95, "Explained variance is not high enough."
    assert mse < 0.5, "Residual has too large standard deviation."
# Compute the (one-sided) PSD using FFT. The ``mask`` variable allows to
# select only the part of the PSD which corresponds to frequencies between
# 0.1Hz and 40Hz (the data used in this example is already low-pass filtered
# at 40Hz).
psd, freqs = power_spectrum(sfreq, data)
mask = np.logical_and(0.1 <= freqs, freqs <= 40)
psd, freqs = psd[0, mask], freqs[mask]

# Estimate the slope (and the intercept) of the PSD. The function
# :func:`compute_spect_slope` assumes that the PSD of the signal is of the
# form: ``psd[f] = b / (f ** a)``. The coefficients a and b are respectively
# called *slope* and *intercept* of the Power Spectral Density. The values of
# the variables ``slope`` and ``intercept`` differ from the values returned
# by ``compute_spect_slope`` because, in the feature function, the linear
# regression fit is done in the log10-log10 scale.
intercept, slope, _, _ = compute_spect_slope(sfreq, data, fmin=1., fmax=40.)
print('The estimated slope (respectively intercept) is: %1.2f (resp. %1.3e)' %
      (slope, intercept))

# Plot the PSD together with the ``b / (f ** a)`` curve (estimated decay of
# the PSD with frequency).
plt.figure()
plt.semilogx(freqs, np.log10(psd), '-b', lw=2, label='PSD')
plt.semilogx(freqs,
             intercept + slope * np.log10(freqs),
             '-r',
             lw=2,
             label='b / (f ** a)')
plt.xlabel('Frequency (Hz)')
plt.ylabel('PSD (dB)')
plt.xlim([1, 40])