Skip to content

Commit 7876e43

Browse files
authored
Merge pull request #165 from arokem/detect_lines_doc
DOC: Addresses #164
2 parents 80f9e38 + 0002370 commit 7876e43

File tree

2 files changed

+214
-210
lines changed

2 files changed

+214
-210
lines changed

nitime/algorithms/spectral.py

Lines changed: 1 addition & 195 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from nitime.lazy import scipy_fftpack as fftpack
1616

1717
import nitime.utils as utils
18+
from nitime.utils import tapered_spectra, dpss_windows
1819

1920
# To support older versions of numpy that don't have tril_indices:
2021
from nitime.index_utils import tril_indices, triu_indices
@@ -363,201 +364,6 @@ def periodogram_csd(s, Fs=2 * np.pi, Sk=None, NFFT=None, sides='default',
363364
return freqs, csd_mat
364365

365366

366-
def dpss_windows(N, NW, Kmax, interp_from=None, interp_kind='linear'):
367-
"""
368-
Returns the Discrete Prolate Spheroidal Sequences of orders [0,Kmax-1]
369-
for a given frequency-spacing multiple NW and sequence length N.
370-
371-
Parameters
372-
----------
373-
N : int
374-
sequence length
375-
NW : float, unitless
376-
standardized half bandwidth corresponding to 2NW = BW/f0 = BW*N*dt
377-
but with dt taken as 1
378-
Kmax : int
379-
number of DPSS windows to return is Kmax (orders 0 through Kmax-1)
380-
interp_from : int (optional)
381-
The dpss can be calculated using interpolation from a set of dpss
382-
with the same NW and Kmax, but shorter N. This is the length of this
383-
shorter set of dpss windows.
384-
interp_kind : str (optional)
385-
This input variable is passed to scipy.interpolate.interp1d and
386-
specifies the kind of interpolation as a string ('linear', 'nearest',
387-
'zero', 'slinear', 'quadratic, 'cubic') or as an integer specifying the
388-
order of the spline interpolator to use.
389-
390-
391-
Returns
392-
-------
393-
v, e : tuple,
394-
v is an array of DPSS windows shaped (Kmax, N)
395-
e are the eigenvalues
396-
397-
Notes
398-
-----
399-
Tridiagonal form of DPSS calculation from:
400-
401-
Slepian, D. Prolate spheroidal wave functions, Fourier analysis, and
402-
uncertainty V: The discrete case. Bell System Technical Journal,
403-
Volume 57 (1978), 1371430
404-
"""
405-
Kmax = int(Kmax)
406-
W = float(NW) / N
407-
nidx = np.arange(N, dtype='d')
408-
409-
# In this case, we create the dpss windows of the smaller size
410-
# (interp_from) and then interpolate to the larger size (N)
411-
if interp_from is not None:
412-
if interp_from > N:
413-
e_s = 'In dpss_windows, interp_from is: %s ' % interp_from
414-
e_s += 'and N is: %s. ' % N
415-
e_s += 'Please enter interp_from smaller than N.'
416-
raise ValueError(e_s)
417-
dpss = []
418-
d, e = dpss_windows(interp_from, NW, Kmax)
419-
for this_d in d:
420-
x = np.arange(this_d.shape[-1])
421-
I = interpolate.interp1d(x, this_d, kind=interp_kind)
422-
d_temp = I(np.linspace(0, this_d.shape[-1] - 1, N, endpoint=False))
423-
424-
# Rescale:
425-
d_temp = d_temp / np.sqrt(np.sum(d_temp ** 2))
426-
427-
dpss.append(d_temp)
428-
429-
dpss = np.array(dpss)
430-
431-
else:
432-
# here we want to set up an optimization problem to find a sequence
433-
# whose energy is maximally concentrated within band [-W,W].
434-
# Thus, the measure lambda(T,W) is the ratio between the energy within
435-
# that band, and the total energy. This leads to the eigen-system
436-
# (A - (l1)I)v = 0, where the eigenvector corresponding to the largest
437-
# eigenvalue is the sequence with maximally concentrated energy. The
438-
# collection of eigenvectors of this system are called Slepian
439-
# sequences, or discrete prolate spheroidal sequences (DPSS). Only the
440-
# first K, K = 2NW/dt orders of DPSS will exhibit good spectral
441-
# concentration
442-
# [see http://en.wikipedia.org/wiki/Spectral_concentration_problem]
443-
444-
# Here I set up an alternative symmetric tri-diagonal eigenvalue
445-
# problem such that
446-
# (B - (l2)I)v = 0, and v are our DPSS (but eigenvalues l2 != l1)
447-
# the main diagonal = ([N-1-2*t]/2)**2 cos(2PIW), t=[0,1,2,...,N-1]
448-
# and the first off-diagonal = t(N-t)/2, t=[1,2,...,N-1]
449-
# [see Percival and Walden, 1993]
450-
diagonal = ((N - 1 - 2 * nidx) / 2.) ** 2 * np.cos(2 * np.pi * W)
451-
off_diag = np.zeros_like(nidx)
452-
off_diag[:-1] = nidx[1:] * (N - nidx[1:]) / 2.
453-
# put the diagonals in LAPACK "packed" storage
454-
ab = np.zeros((2, N), 'd')
455-
ab[1] = diagonal
456-
ab[0, 1:] = off_diag[:-1]
457-
# only calculate the highest Kmax eigenvalues
458-
w = linalg.eigvals_banded(ab, select='i',
459-
select_range=(N - Kmax, N - 1))
460-
w = w[::-1]
461-
462-
# find the corresponding eigenvectors via inverse iteration
463-
t = np.linspace(0, np.pi, N)
464-
dpss = np.zeros((Kmax, N), 'd')
465-
for k in range(Kmax):
466-
dpss[k] = utils.tridi_inverse_iteration(
467-
diagonal, off_diag, w[k], x0=np.sin((k + 1) * t)
468-
)
469-
470-
# By convention (Percival and Walden, 1993 pg 379)
471-
# * symmetric tapers (k=0,2,4,...) should have a positive average.
472-
# * antisymmetric tapers should begin with a positive lobe
473-
fix_symmetric = (dpss[0::2].sum(axis=1) < 0)
474-
for i, f in enumerate(fix_symmetric):
475-
if f:
476-
dpss[2 * i] *= -1
477-
# rather than test the sign of one point, test the sign of the
478-
# linear slope up to the first (largest) peak
479-
pk = np.argmax(np.abs(dpss[1::2, :N//2]), axis=1)
480-
for i, p in enumerate(pk):
481-
if np.sum(dpss[2 * i + 1, :p]) < 0:
482-
dpss[2 * i + 1] *= -1
483-
484-
# Now find the eigenvalues of the original spectral concentration problem
485-
# Use the autocorr sequence technique from Percival and Walden, 1993 pg 390
486-
dpss_rxx = utils.autocorr(dpss) * N
487-
r = 4 * W * np.sinc(2 * W * nidx)
488-
r[0] = 2 * W
489-
eigvals = np.dot(dpss_rxx, r)
490-
491-
return dpss, eigvals
492-
493-
def tapered_spectra(s, tapers, NFFT=None, low_bias=True):
494-
"""
495-
Compute the tapered spectra of the rows of s.
496-
497-
Parameters
498-
----------
499-
500-
s : ndarray, (n_arr, n_pts)
501-
An array whose rows are timeseries.
502-
503-
tapers : ndarray or container
504-
Either the precomputed DPSS tapers, or the pair of parameters
505-
(NW, K) needed to compute K tapers of length n_pts.
506-
507-
NFFT : int
508-
Number of FFT bins to compute
509-
510-
low_bias : Boolean
511-
If compute DPSS, automatically select tapers corresponding to
512-
> 90% energy concentration.
513-
514-
Returns
515-
-------
516-
517-
t_spectra : ndarray, shaped (n_arr, K, NFFT)
518-
The FFT of the tapered sequences in s. First dimension is squeezed
519-
out if n_arr is 1.
520-
eigvals : ndarray
521-
The eigenvalues are also returned if DPSS are calculated here.
522-
523-
"""
524-
N = s.shape[-1]
525-
# XXX: don't allow NFFT < N -- not every implementation is so restrictive!
526-
if NFFT is None or NFFT < N:
527-
NFFT = N
528-
rest_of_dims = s.shape[:-1]
529-
M = int(np.product(rest_of_dims))
530-
531-
s = s.reshape(int(np.product(rest_of_dims)), N)
532-
# de-mean this sucker
533-
s = utils.remove_bias(s, axis=-1)
534-
535-
if not isinstance(tapers, np.ndarray):
536-
# then tapers is (NW, K)
537-
args = (N,) + tuple(tapers)
538-
dpss, eigvals = dpss_windows(*args)
539-
if low_bias:
540-
keepers = (eigvals > 0.9)
541-
dpss = dpss[keepers]
542-
eigvals = eigvals[keepers]
543-
tapers = dpss
544-
else:
545-
eigvals = None
546-
K = tapers.shape[0]
547-
sig_sl = [slice(None)] * len(s.shape)
548-
sig_sl.insert(len(s.shape) - 1, np.newaxis)
549-
550-
# tapered.shape is (M, Kmax, N)
551-
tapered = s[sig_sl] * tapers
552-
553-
# compute the y_{i,k}(f) -- full FFT takes ~1.5x longer, but unpacking
554-
# results of real-valued FFT eats up memory
555-
t_spectra = fftpack.fft(tapered, n=NFFT, axis=-1)
556-
t_spectra.shape = rest_of_dims + (K, NFFT)
557-
if eigvals is None:
558-
return t_spectra
559-
return t_spectra, eigvals
560-
561367
def mtm_cross_spectrum(tx, ty, weights, sides='twosided'):
562368
r"""
563369

0 commit comments

Comments
 (0)