9

I am using scipy.fft on a signal, with a moving window to plot the amplitudes of frequencies changing with time (here is an example, time is on X, frequency on Y, and amplitude is the color).

However, only a few frequencies interest me (~3, 4 frequencies only). With FFTs it seems like I can't select only the frequencies I want (cause apparently the range of frequencies is determined by the algorithm), so I calculate a lot of useless stuff, and my program even crashes with a MemoryError if the signal is too long.

What should I do ? Do I have to use a custom Fourier transform - in which case, links of good implementations are welcome - , or is there a scipy way ?


EDIT

After @jfaller answer, I decided to (try to) implement the Goertzel algorithm. I came up with this : https://gist.github.com/4128537 but it doesn't work (frequency 440 doesn't appear, nevermind the peaks, I didn't bother to apply a proper window). Any help !? I am bad with DSP.

sebpiq
  • 7,540
  • 9
  • 52
  • 69
  • Actually, Short-Time Fourier Transform, see this question http://stackoverflow.com/questions/2459295/stft-and-istft-in-python might be what I need ... – sebpiq Nov 21 '12 at 19:08
  • Isn't that what you said you're doing? i.e. a DFT of a sliding window. – Eryk Sun Nov 21 '12 at 19:10
  • Oh ... True, I am using `scipy.fft`, and the guy is using the same. So there's the same problem. – sebpiq Nov 21 '12 at 19:14
  • The signal needs to be approximately stationary over the window. That's an upper limit on the window size. A lower limit is the required frequency resolution. Hopefully they're consistent. – Eryk Sun Nov 21 '12 at 19:17
  • The [wikipedia article about STFT] says "If only a small number of ω are desired, then the STFT may be more efficiently evaluated using a sliding DFT algorithm.", so I guess that's what I should be lookign for ... – sebpiq Nov 21 '12 at 19:18
  • If your window is a reasonable size (say 4096 samples), I can't imagine the fft is a bottleneck (it's O(nlogn)) or would cause a `MemoryError`. – Eryk Sun Nov 21 '12 at 19:26
  • Yes ... but I still have the feeling that it is overkill : there is more efficient methods for the case you don't need all the frequencies [see this article](http://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=0CCAQFjAA&url=http%3A%2F%2Fwww.comm.utoronto.ca%2F~dimitris%2Fece431%2Fslidingdft.pdf&ei=ICmtUPL6McjltQad7oC4Ag&usg=AFQjCNEYMufNx8jtpiTF1ahuHYmDoC-OhQ&cad=rja). I just have to find an implementation, or roll my own !!! – sebpiq Nov 21 '12 at 19:28
  • 2
    In general, calculating the DFT on one frequency naively is an order-N operation. If you want to calculate the DFT coefficients of M frequencies over N points, and M < log(N), then just use the naive formula c_k = sum(x[n] * exp(-j*2*pi*k*n)) for the k's that interest you. – mtrw Nov 21 '12 at 19:34
  • @mtrw The answer about the Goertzel algorithm was good, but you should make your comment about direct DFT computation an answer -- that can be actually be more efficient in some circumstances. – wjl Nov 21 '12 at 22:38
  • @wjl: I'm dubious if we're talking about using Python bytecode, even with the help of Numpy arrays, not unless the OP is planning to write an extension in C or Cython. With a reasonable window size based on the limit of stationarity in the signal and the required frequency resolution, the fft should be trivial compared to the rest of the code (e.g. animating that spectrogram). The fact that `MemoryError` has come up implies an unreasonably long window. – Eryk Sun Nov 22 '12 at 03:04
  • @eryksu by "some circumstances" I was thinking of circumstances not in Python. (Scipy/Numpy are often used to prototype DSP algorithms that are intended to actually go in embedded systems software or FPGA firmware). – wjl Nov 22 '12 at 04:30
  • My goal is to implement an app which recognizes some sounds in realtime and reacts when recognized. I analysed those sounds, and found out that they have some kind of frequency signature (~ 6 frequencies seem to be enough to differentiate them). So now - @eryksun you were right :) - I am prototyping the algorithm in Python, and testing with recorded samples. – sebpiq Nov 22 '12 at 07:40
  • Considering that `scipy.signal.freqz` accepts `worN` you could bin the frequency bands, normalize them between 0 and `2*\pi` and use it. It should be faster than a for-loop implementation – dashesy Jul 16 '13 at 16:21

2 Answers2

9

You're really looking to use the Goertzel Algorithm: http://en.wikipedia.org/wiki/Goertzel_algorithm. Basically, it's an FFT at a single point, and efficient if you only need a limited number of frequencies in a signal. If you have trouble pulling apart the algorithm from Wikipedia, ping back, and I'll help you. Also if you google a few resources, there exist DTMF decoders (touch tone phone decoders) written in python. You could check out how they do it.

jfaller
  • 508
  • 2
  • 6
  • Arrr ... I can't say that I didn't try (https://gist.github.com/4128537). But there's definitely something wrong. I am not so good with DSP :( any hint would be great ! – sebpiq Nov 21 '12 at 23:32
  • Okay, I'm logging out for the evening, but I'll take a look tonight or tomorrow, and update my comment. In the meantime, the C code linked from Wikipedia's pretty good: http://netwerkt.wordpress.com/2011/08/25/goertzel-filter/ – jfaller Nov 22 '12 at 00:08
  • @sebpiq: Have you looked into using `scipy.signal.lfilter` and `lfiltic`? That would be a fast way to apply the cascaded IIR, FIR filter. – Eryk Sun Nov 22 '12 at 03:10
  • jfaller: this article looks good. I'm taking a look. @eryksun : thanks for the tip, I'll look into that as an optimization. – sebpiq Nov 22 '12 at 07:41
9

With great help from @jfaller, @eryksun, ... I implemented a simple Goertzel algorithm in Python : https://gist.github.com/4128537. I'll re-paste it here. As @eryksun mentioned, it might be possible to optimize it with scipy.signal.lfilter and lfiltic, but for the moment I'll stick with this as I might want to implement it in another language :

import math

def goertzel(samples, sample_rate, *freqs):
    """
    Implementation of the Goertzel algorithm, useful for calculating individual
    terms of a discrete Fourier transform.

    `samples` is a windowed one-dimensional signal originally sampled at `sample_rate`.

    The function returns 2 arrays, one containing the actual frequencies calculated,
    the second the coefficients `(real part, imag part, power)` for each of those frequencies.
    For simple spectral analysis, the power is usually enough.

    Example of usage : 

        # calculating frequencies in ranges [400, 500] and [1000, 1100]
        # of a windowed signal sampled at 44100 Hz

        freqs, results = goertzel(some_samples, 44100, (400, 500), (1000, 1100))
    """
    window_size = len(samples)
    f_step = sample_rate / float(window_size)
    f_step_normalized = 1.0 / window_size

    # Calculate all the DFT bins we have to compute to include frequencies
    # in `freqs`.
    bins = set()
    for f_range in freqs:
        f_start, f_end = f_range
        k_start = int(math.floor(f_start / f_step))
        k_end = int(math.ceil(f_end / f_step))

        if k_end > window_size - 1: raise ValueError('frequency out of range %s' % k_end)
        bins = bins.union(range(k_start, k_end))

    # For all the bins, calculate the DFT term
    n_range = range(0, window_size)
    freqs = []
    results = []
    for k in bins:

        # Bin frequency and coefficients for the computation
        f = k * f_step_normalized
        w_real = 2.0 * math.cos(2.0 * math.pi * f)
        w_imag = math.sin(2.0 * math.pi * f)

        # Doing the calculation on the whole sample
        d1, d2 = 0.0, 0.0
        for n in n_range:
            y  = samples[n] + w_real * d1 - d2
            d2, d1 = d1, y

        # Storing results `(real part, imag part, power)`
        results.append((
            0.5 * w_real * d1 - d2, w_imag * d1,
            d2**2 + d1**2 - w_real * d1 * d2)
        )
        freqs.append(f * sample_rate)
    return freqs, results


if __name__ == '__main__':
    # quick test
    import numpy as np
    import pylab

    # generating test signals
    SAMPLE_RATE = 44100
    WINDOW_SIZE = 1024
    t = np.linspace(0, 1, SAMPLE_RATE)[:WINDOW_SIZE]
    sine_wave = np.sin(2*np.pi*440*t) + np.sin(2*np.pi*1020*t)
    sine_wave = sine_wave * np.hamming(WINDOW_SIZE)
    sine_wave2 = np.sin(2*np.pi*880*t) + np.sin(2*np.pi*1500*t)
    sine_wave2 = sine_wave2 * np.hamming(WINDOW_SIZE)

    # applying Goertzel on those signals, and plotting results
    freqs, results = goertzel(sine_wave, SAMPLE_RATE, (400, 500),  (1000, 1100))

    pylab.subplot(2, 2, 1)
    pylab.title('(1) Sine wave 440Hz + 1020Hz')
    pylab.plot(t, sine_wave)

    pylab.subplot(2, 2, 3)
    pylab.title('(1) Goertzel Algo, freqency ranges : [400, 500] and [1000, 1100]')
    pylab.plot(freqs, np.array(results)[:,2], 'o')
    pylab.ylim([0,100000])

    freqs, results = goertzel(sine_wave2, SAMPLE_RATE, (400, 500),  (1000, 1100))

    pylab.subplot(2, 2, 2)
    pylab.title('(2) Sine wave 660Hz + 1200Hz')
    pylab.plot(t, sine_wave2)

    pylab.subplot(2, 2, 4)
    pylab.title('(2) Goertzel Algo, freqency ranges : [400, 500] and [1000, 1100]')
    pylab.plot(freqs, np.array(results)[:,2], 'o')
    pylab.ylim([0,100000])

    pylab.show()
sebpiq
  • 7,540
  • 9
  • 52
  • 69