def notch(ts, freq_hz, bandwidth_hz=1.0): """notch filter to remove remove a particular frequency Adapted from code by Sturla Molden """ orig_ndim = ts.ndim if ts.ndim is 1: ts = ts[:, np.newaxis] channels = ts.shape[1] fs = (len(ts) - 1.0) / (ts.tspan[-1] - ts.tspan[0]) nyq = 0.5 * fs freq = freq_hz / nyq bandwidth = bandwidth_hz / nyq R = 1.0 - 3.0 * (bandwidth / 2.0) K = ((1.0 - 2.0 * R * np.cos(np.pi * freq) + R**2) / (2.0 - 2.0 * np.cos(np.pi * freq))) b, a = np.zeros(3), np.zeros(3) a[0] = 1.0 a[1] = -2.0 * R * np.cos(np.pi * freq) a[2] = R**2 b[0] = K b[1] = -2 * K * np.cos(np.pi * freq) b[2] = K if not np.all(np.abs(np.roots(a)) < 1.0): raise ValueError('Filter will not be stable with these values.') dtype = ts.dtype output = np.zeros((len(ts), channels), dtype) for i in range(channels): output[:, i] = signal.filtfilt(b, a, ts[:, i]) if orig_ndim is 1: output = output[:, 0] return Timeseries(output, ts.tspan, labels=ts.labels)
def autocorrelation(ts, normalized=False, unbiased=False): """ Returns the discrete, linear convolution of a time series with itself, optionally using unbiased normalization. N.B. Autocorrelation estimates are necessarily inaccurate for longer lags, as there are less pairs of points to convolve separated by that lag. Therefore best to throw out the results except for shorter lags, e.g. keep lags from tau=0 up to one quarter of the total time series length. Args: normalized (boolean): If True, the time series will first be normalized to a mean of 0 and variance of 1. This gives autocorrelation 1 at zero lag. unbiased (boolean): If True, the result at each lag m will be scaled by 1/(N-m). This gives an unbiased estimation of the autocorrelation of a stationary process from a finite length sample. Ref: S. J. Orfanidis (1996) "Optimum Signal Processing", 2nd Ed. """ ts = np.squeeze(ts) if ts.ndim <= 1: if normalized: ts = (ts - ts.mean()) / ts.std() N = ts.shape[0] ar = np.asarray(ts) acf = np.correlate(ar, ar, mode='full') outlen = (acf.shape[0] + 1) / 2 acf = acf[(outlen - 1):] if unbiased: factor = np.array([1.0 / (N - m) for m in range(0, outlen)]) acf = acf * factor dt = (ts.tspan[-1] - ts.tspan[0]) / (len(ts) - 1.0) lags = np.arange(outlen) * dt return Timeseries(acf, tspan=lags, labels=ts.labels) else: # recursively handle arrays of dimension > 1 lastaxis = ts.ndim - 1 m = ts.shape[lastaxis] acfs = [ ts[..., i].autocorrelation(normalized, unbiased)[..., np.newaxis] for i in range(m) ] res = distob.concatenate(acfs, axis=lastaxis) res.labels[lastaxis] = ts.labels[lastaxis] return res
def highpass(ts, cutoff_hz, order=3): """forward-backward butterworth high-pass filter""" orig_ndim = ts.ndim if ts.ndim is 1: ts = ts[:, np.newaxis] channels = ts.shape[1] fs = (len(ts) - 1.0) / (ts.tspan[-1] - ts.tspan[0]) nyq = 0.5 * fs cutoff = cutoff_hz / nyq b, a = signal.butter(order, cutoff, btype='highpass') if not np.all(np.abs(np.roots(a)) < 1.0): raise ValueError('Filter will not be stable with these values.') dtype = ts.dtype output = np.zeros((len(ts), channels), dtype) for i in range(channels): output[:, i] = signal.filtfilt(b, a, ts[:, i]) if orig_ndim is 1: output = output[:, 0] return Timeseries(output, ts.tspan, labels=ts.labels)
def hilbert_phase(ts): """Phase of the analytic signal, using the Hilbert transform""" output = np.angle(signal.hilbert(signal.detrend(ts, axis=0), axis=0)) return Timeseries(output, ts.tspan, labels=ts.labels)
def hilbert_amplitude(ts): """Amplitude of the analytic signal, using the Hilbert transform""" output = np.abs(signal.hilbert(signal.detrend(ts, axis=0), axis=0)) return Timeseries(output, ts.tspan, labels=ts.labels)
def hilbert(ts): """Analytic signal, using the Hilbert transform""" output = signal.hilbert(signal.detrend(ts, axis=0), axis=0) return Timeseries(output, ts.tspan, labels=ts.labels)
def variability_fp(ts, freqs=None, ncycles=6, plot=True): """Example variability function. Gives two continuous, time-resolved measures of the variability of a time series, ranging between -1 and 1. The two measures are based on variance of the centroid frequency and variance of the height of the spectral peak, respectively. (Centroid frequency meaning the power-weighted average frequency) These measures are calculated over sliding time windows of variable size. See also: Blenkinsop et al. (2012) The dynamic evolution of focal-onset epilepsies - combining theoretical and clinical observations Args: ts Timeseries of m variables, shape (n, m). Assumed constant timestep. freqs (optional) List of frequencies to examine. If None, defaults to 50 frequency bands ranging 1Hz to 60Hz, logarithmically spaced. ncycles Window size, in number of cycles of the centroid frequency. plot bool Whether to display the output Returns: variability Timeseries of shape (n, m, 2) variability[:, :, 0] gives a measure of variability between -1 and 1 based on variance of centroid frequency. variability[:, :, 1] gives a measure of variability between -1 and 1 based on variance of maximum power. """ if freqs is None: freqs = np.logspace(np.log10(1.0), np.log10(60.0), 50) else: freqs = np.array(freqs) orig_ndim = ts.ndim if ts.ndim is 1: ts = ts[:, np.newaxis] channels = ts.shape[1] n = len(ts) dt = (1.0 * ts.tspan[-1] - ts.tspan[0]) / (n - 1) fs = 1.0 / dt dtype = ts.dtype # Estimate time-resolved power spectra using continuous wavelet transform coefs = ts.cwt(freqs, wavelet=cwtmorlet, plot=False) # this is a huge array so try to do operations in place powers = np.square(np.abs(coefs, coefs), coefs).real.astype(dtype, copy=False) del coefs max_power = np.max(powers, axis=1) total_power = np.sum(powers, axis=1, keepdims=True) rel_power = np.divide(powers, total_power, powers) del powers centroid_freq = np.tensordot(freqs, rel_power, axes=(0, 1)) # shape (n, m) del rel_power # hw is half window size (in number of samples) hw = np.int64(np.ceil(0.5 * ncycles * fs / centroid_freq)) # shape (n, m) allchannels_variability = np.zeros((n, channels, 2), dtype) # output array for i in range(channels): logvar_centfreq = np.zeros(n, dtype) logvar_maxpower = np.zeros(n, dtype) for j in range(n): # compute variance of two chosen signal properties over a # window of 2*hw+1 samples centered on sample number j wstart = j - hw[j, i] wend = j + hw[j, i] if wstart >= 0 and wend < n: logvar_centfreq[j] = np.log(centroid_freq[wstart:wend + 1].var()) logvar_maxpower[j] = np.log(max_power[wstart:wend + 1].var()) else: logvar_centfreq[j] = np.nan logvar_maxpower[j] = np.nan allchannels_variability[:, i, 0] = _rescale(logvar_centfreq) allchannels_variability[:, i, 1] = _rescale(logvar_maxpower) allchannels_variability = Timeseries(allchannels_variability, ts.tspan, labels=ts.labels) if plot: _plot_variability(ts, allchannels_variability) return allchannels_variability