Invertible STFT and ISTFT in Python

匿名 (未验证) 提交于 2019-12-03 02:12:02

问题:

Is there any general-purpose form of short-time Fourier transform with corresponding inverse transform built into SciPy or NumPy or whatever?

There's the pyplot specgram function in matplotlib, which calls ax.specgram(), which calls mlab.specgram(), which calls _spectral_helper():

#The checks for if y is x are so that we can use the same function to #implement the core of psd(), csd(), and spectrogram() without doing #extra calculations.  We return the unaveraged Pxy, freqs, and t. 

but

This is a helper function that implements the commonality between the 204 #psd, csd, and spectrogram. It is NOT meant to be used outside of mlab

I'm not sure if this can be used to do an STFT and ISTFT, though. Is there anything else, or should I translate something like these MATLAB functions?

I know how to write my own ad-hoc implementation; I'm just looking for something full-featured, which can handle different windowing functions (but has a sane default), is fully invertible with COLA windows (istft(stft(x))==x), tested by multiple people, no off-by-one errors, handles the ends and zero padding well, fast RFFT implementation for real input, etc.

回答1:

I'm a little late to this, but realised scipy has inbuilt istft function as of 0.19.0



回答2:

Here is my Python code, simplified for this answer:

import scipy, pylab  def stft(x, fs, framesz, hop):     framesamp = int(framesz*fs)     hopsamp = int(hop*fs)     w = scipy.hanning(framesamp)     X = scipy.array([scipy.fft(w*x[i:i+framesamp])                       for i in range(0, len(x)-framesamp, hopsamp)])     return X  def istft(X, fs, T, hop):     x = scipy.zeros(T*fs)     framesamp = X.shape[1]     hopsamp = int(hop*fs)     for n,i in enumerate(range(0, len(x)-framesamp, hopsamp)):         x[i:i+framesamp] += scipy.real(scipy.ifft(X[n]))     return x 

Notes:

  1. The list comprehension is a little trick I like to use to simulate block processing of signals in numpy/scipy. It's like blkproc in Matlab. Instead of a for loop, I apply a command (e.g., fft) to each frame of the signal inside a list comprehension, and then scipy.array casts it to a 2D-array. I use this to make spectrograms, chromagrams, MFCC-grams, and much more.
  2. For this example, I use a naive overlap-and-add method in istft. In order to reconstruct the original signal the sum of the sequential window functions must be constant, preferably equal to unity (1.0). In this case, I've chosen the Hann (or hanning) window and a 50% overlap which works perfectly. See this discussion for more information.
  3. There are probably more principled ways of computing the ISTFT. This example is mainly meant to be educational.

A test:

if __name__ == '__main__':     f0 = 440         # Compute the STFT of a 440 Hz sinusoid     fs = 8000        # sampled at 8 kHz     T = 5            # lasting 5 seconds     framesz = 0.050  # with a frame size of 50 milliseconds     hop = 0.025      # and hop size of 25 milliseconds.      # Create test signal and STFT.     t = scipy.linspace(0, T, T*fs, endpoint=False)     x = scipy.sin(2*scipy.pi*f0*t)     X = stft(x, fs, framesz, hop)      # Plot the magnitude spectrogram.     pylab.figure()     pylab.imshow(scipy.absolute(X.T), origin='lower', aspect='auto',                  interpolation='nearest')     pylab.xlabel('Time')     pylab.ylabel('Frequency')     pylab.show()      # Compute the ISTFT.     xhat = istft(X, fs, T, hop)      # Plot the input and output signals over 0.1 seconds.     T1 = int(0.1*fs)      pylab.figure()     pylab.plot(t[:T1], x[:T1], t[:T1], xhat[:T1])     pylab.xlabel('Time (seconds)')      pylab.figure()     pylab.plot(t[-T1:], x[-T1:], t[-T1:], xhat[-T1:])     pylab.xlabel('Time (seconds)') 



回答3:

Here is the STFT code that I use. STFT + ISTFT here gives perfect reconstruction (even for the first frames). I slightly modified the code given here by Steve Tjoa : here the magnitude of the reconstructed signal is the same as that of the input signal.

import scipy, numpy as np  def stft(x, fftsize=1024, overlap=4):        hop = fftsize / overlap     w = scipy.hanning(fftsize+1)[:-1]      # better reconstruction with this trick +1)[:-1]       return np.array([np.fft.rfft(w*x[i:i+fftsize]) for i in range(0, len(x)-fftsize, hop)])  def istft(X, overlap=4):        fftsize=(X.shape[1]-1)*2     hop = fftsize / overlap     w = scipy.hanning(fftsize+1)[:-1]     x = scipy.zeros(X.shape[0]*hop)     wsum = scipy.zeros(X.shape[0]*hop)      for n,i in enumerate(range(0, len(x)-fftsize, hop)):          x[i:i+fftsize] += scipy.real(np.fft.irfft(X[n])) * w   # overlap-add         wsum[i:i+fftsize] += w ** 2.     pos = wsum != 0     x[pos] /= wsum[pos]     return x 


回答4:

librosa.core.stft and istft look pretty similar to what I was looking for, though they didn't exist at the time:

librosa.core.stft(y, n_fft=2048, hop_length=None, win_length=None, window=None, center=True, dtype=<type 'numpy.complex64'>)

They don't invert exactly, though; the ends are tapered.



回答5:

Found another STFT, but no corresponding inverse function:

http://code.google.com/p/pytfd/source/browse/trunk/pytfd/stft.py

def stft(x, w, L=None):     ...     return X_stft 
  • w is a window function as an array
  • L is the overlap, in samples


回答6:

Neither of the above answers worked well OOTB for me. So I modified Steve Tjoa's.

import scipy, pylab import numpy as np  def stft(x, fs, framesz, hop):     """      x - signal      fs - sample rate      framesz - frame size      hop - hop size (frame size = overlap + hop size)     """     framesamp = int(framesz*fs)     hopsamp = int(hop*fs)     w = scipy.hamming(framesamp)     X = scipy.array([scipy.fft(w*x[i:i+framesamp])                       for i in range(0, len(x)-framesamp, hopsamp)])     return X  def istft(X, fs, T, hop):     """ T - signal length """     length = T*fs     x = scipy.zeros(T*fs)     framesamp = X.shape[1]     hopsamp = int(hop*fs)     for n,i in enumerate(range(0, len(x)-framesamp, hopsamp)):         x[i:i+framesamp] += scipy.real(scipy.ifft(X[n]))     # calculate the inverse envelope to scale results at the ends.     env = scipy.zeros(T*fs)     w = scipy.hamming(framesamp)     for i in range(0, len(x)-framesamp, hopsamp):         env[i:i+framesamp] += w     env[-(length%hopsamp):] += w[-(length%hopsamp):]     env = np.maximum(env, .01)     return x/env # right side is still a little messed up... 


回答7:

I also found this on GitHub, but it seems to operate on pipelines instead of normal arrays:

http://github.com/ronw/frontend/blob/master/basic.py#LID281

def STFT(nfft, nwin=None, nhop=None, winfun=np.hanning):     ...     return dataprocessor.Pipeline(Framer(nwin, nhop), Window(winfun),                                   RFFT(nfft))   def ISTFT(nfft, nwin=None, nhop=None, winfun=np.hanning):     ...     return dataprocessor.Pipeline(IRFFT(nfft), Window(winfun),                                   OverlapAdd(nwin, nhop)) 


回答8:

I think scipy.signal has what you are looking for. It has reasonable defaults, supports multiple window types, etc...

http://docs.scipy.org/doc/scipy-0.17.0/reference/generated/scipy.signal.spectrogram.html

from scipy.signal import spectrogram freq, time, Spec = spectrogram(signal) 


回答9:

If you have access to a C binary library that does what you want, then use http://code.google.com/p/ctypesgen/ to generate a Python interface to that library.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!