|
15 | 15 | from nitime.lazy import scipy_fftpack as fftpack
|
16 | 16 |
|
17 | 17 | import nitime.utils as utils
|
| 18 | +from nitime.utils import tapered_spectra, dpss_windows |
18 | 19 |
|
19 | 20 | # To support older versions of numpy that don't have tril_indices:
|
20 | 21 | 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',
|
363 | 364 | return freqs, csd_mat
|
364 | 365 |
|
365 | 366 |
|
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 |
| - |
561 | 367 | def mtm_cross_spectrum(tx, ty, weights, sides='twosided'):
|
562 | 368 | r"""
|
563 | 369 |
|
|
0 commit comments