from scipy.signal import get_window
import dascore as dc
from dascore.units import second, percent
patch = dc.get_example_patch("chirp", channel_count=2)
# Simple stft with 10 second window and 4 seconds overlap
pa1 = patch.stft(time=10*second, overlap=4*second)
# Same as above, but using a boxcar window and 10% overlap.
pa2 = patch.stft(time=10*second, taper_window="boxcar", overlap=10*percent)
# Using a custom window array and specifying window/overlap in samples.
window = get_window(("tukey", 0.1), 1000)
pa2 = patch.stft(time=1000, taper_window=window, overlap=100, samples=True)
# Zero pad each 1000 sample window to a 4096 point FFT.
pa3 = patch.stft(time=1000, samples=True, nfft=4096)
# Local f-k spectra: windows of 32 channels by 64 samples.
fk = dc.get_example_patch().stft(distance=32, time=64, samples=True)stft
stft(
self ,
taper_window: str | numpy.ndarray | tuple[str, ndarray, tuple[str | Any[str, Any], …]] = hann,
overlap: pint.registry.Quantity | int | None[Quantity, int, None] = 50 %,
samples: bool = False,
detrend: bool = False,
nfft: int | pint.registry.Quantity | numpy.timedelta64 | collections.abc.Mapping[int, Quantity, timedelta64, collections.abc.Mapping[str, Any], None] = None,
**kwargs ,
)-> ‘Self’
Perform a short-time fourier transform.
Parameters
| Parameter | Description |
|---|---|
| taper_window |
The taper each window is multiplied by before its transform: a name, an array, or a (name, parameter) tuple get_window knows, forevery windowed dimension, or a list with one per dimension. |
| overlap |
The overlap between windows. Can be a number (assumed to be in units of the transformed dimension if samples==False), a percent, or None for0 overlap. |
| samples |
If True, the window length (provided in kwargs) and overlap parameters are in samples (or explicit units). |
| detrend |
If True, detrend each time window before performing fourier transform. This can lead to nicer looking spectrograms, but means the istft is no longer possible. |
| nfft |
The length of the FFT taken of each window, in samples, or as a quantity or timedelta in the transformed dimension’s units; a mapping gives each dimension its own. None, the default, is the window length. A longer FFT zero pads each window, which samples the same spectrum at more, closer frequencies; it adds no resolution, since the window holds no more data. Must be at least the window length. |
| **kwargs |
The dimensions to window and the window along each, in coordinate units or, with samples, in samples. More than one dimension giveseach window’s spectrum over all of them: distance=32, time=64is a local frequency-wavenumber spectrum. |
Examples
Note
- The output is scaled the same as Patch.dft. For a given sliding window, Parseval’s theorem doesn’t hold exactly (unless a boxcar window is used) because the taper window changes the time series signal before the transformation.
- An array passed for taper_window must have as many samples as the window; one of another length is refused. To zero pad each window’s FFT, give
nfft. - Real data is transformed one-sided along the last windowed dimension and centred along the others, as Patch.dft with
real=Trueis; complex data is centred along every one. - Data that single precision holds (eg float32, complex64, int16) gives a complex64 output; wider data gives complex128.
- The output is a stack of windows as Patch.tile_apply makes one, transformed along the window: the transformed dimension becomes the window centres,
{dim}_startand{dim}_stopgive physical cell edges in the dimension’s units, the frequencies sit where the dimension was and the centres come last. Private_tile_index_{dim}coordinates retain sample indices, and the coordinates the stack carries for Patch.reassemble are what Patch.istft blends the windows back with. Non-dimensional coordinates along the transformed dimension travel with the stack and come back on the inverse. - Named taper windows are symmetric:
"hann"isscipy.signal.get_window("hann", n, fftbins=False), whereasscipy.signal.stftuses the periodic one. - A window or overlap in coordinate units becomes a sample count rounded up:
time=2.0at 645.16 Hz is 1291 samples. - Windows of
nsamples advance byhop = n - overlapfrom the first sample, and every window reaching the data is kept, zero padded at both ends. The first window starts(ceil(n / hop) - 1) * hopsamples before the data: none when windows abut,overlapat 50% overlap with an evenn. - The transformed coordinate is each window’s centre, its sample
n // 2, and each spectrum’s phase is referred to that centre. Centres fall on samples 0, hop, 2 * hop, … only whenn // 2is a multiple ofhop, as at 50% overlap with an evenn. - Spectra are scaled by the product of the windowed dimensions’ sample spacings (
dtalong time alone), as dft’s are, rather than by1 / sum(window)asscipy.signal.stftscales them.
Along time, with the default taper, no detrend and no nfft, one channel’s stft of real data equals this scipy.signal.stft call, with x the channel’s data, fs = 1 / dt and overlap in samples (0 for None); complex spectra are also centred, as noted above:
import numpy as np
from scipy.signal import get_window, stft
hop = n - overlap
margin = (-(-n // hop) - 1) * hop
w = get_window("hann", n, fftbins=False)
f, _, z = stft(
np.pad(x, (margin, n)), fs, window=w, nperseg=n,
noverlap=overlap, boundary=None, padded=False,
)
count = (margin + len(x) - 1) // hop + 1
phase = np.exp(2j * np.pi * f / fs * (n // 2))
out = z[:, :count] * w.sum() / fs * phase[:, None]