timestamp of peak frequency python - python

I have used this code how to extract frequency associated with fft values in python and added a firwin filter to detect a short high frequency signal. The signal is 1 second long and occurs at random in a wav audio file of x-seconds. My code looks as follows:
from scipy import signal
from scipy.io import wavfile
from scipy.fftpack import fft, ifft,fftfreq
import matplotlib.pyplot as plt
import wave
import numpy as np
import sys
import struct
frate,data = wavfile.read('SoundPeep.wav')
#print(frate)
b = signal.firwin(101, cutoff=900, fs= frate, pass_zero=False)
data = signal.lfilter(b, [1.0], data)
w = np.fft.fft(data)
freqs = np.fft.fftfreq(len(w))
#print(len(w)/frate)
#print(w.min(), w.max())
# (-0.5, 0.499975)
# Find the peak in the coefficients
idx = np.argmax(np.abs(w))
winner = np.argwhere(np.abs(w) == np.amax(np.abs(w)))
freq = freqs[idx]
freq_in_hertz = abs(freq * frate)
print("HZ")
print(freq_in_hertz)
occurence = idx/frate
print(occurence)
The code works well at detecting the peak HZ frequency. My problem is that I want to calculate where the high frequency signal begins in the audio-file. I thought it could be done simply by dividing the index(idx) by the framerate of the recording but this does not seem to work.

you could make a sonogram by using a short time fourier transform to see how frequency changes over time. the stft returns 3 values: timestamp, amplitude, and frequency. If the amplitude isnt relevant, you can just use peak detection from there.

Related

How to plot frequency data from a .wav file in Python?

I'm trying to plot the frequencies that make up the first 1 second of a voice recording.
My approach was to:
Read the .wav file as a numpy array containing time series data
Slice the array from [0:sample_rate-1], given that the sample rate has units of [samples/1 second], which implies that sample_rate [samples/seconds] * 1 [seconds] = sample_rate [samples]
Perform a fast fourier transform (fft) on the time series array in order to get the frequencies that make up that time-series sample.
Plot the the frequencies on the x-axis, and amplitude on the y-axis. The frequency domain would range from 0:(sample_rate/2) since the Nyquist Sampling Theorem tells us that the recording captured frequencies of at least two times the maximum frequency, i.e 2*max(frequency). I'll also slice the frequency output array in half since the output frequency data is symmetrical
Here is my implementation
import matplotlib.pyplot as plt
import numpy as np
from scipy.fftpack import fft
from scipy.io import wavfile
sample_rate, audio_time_series = wavfile.read(audio_path)
single_sample_data = audio_time_series[:sample_rate]
def fft_plot(audio, sample_rate):
N = len(audio) # Number of samples
T = 1/sample_rate # Period
y_freq = fft(audio)
domain = len(y_freq) // 2
x_freq = np.linspace(0, sample_rate//2, N//2)
plt.plot(x_freq, abs(y_freq[:domain]))
plt.xlabel("Frequency [Hz]")
plt.ylabel("Frequency Amplitude |X(t)|")
return plt.show()
fft_plot(single_sample_data, sample_rate)
This is the plot that it generated
However, this is incorrect, my spectrogram tells me I should have frequency peaks below the 5kHz range:
In fact, what this plot is actually showing, is the first second of my time series data:
Which I was able to debug by removing the absolute value function from y_freq when I plot it, and entering the entire audio signal into my fft_plot function:
...
sample_rate, audio_time_series = wavfile.read(audio_path)
single_sample_data = audio_time_series[:sample_rate]
def fft_plot(audio, sample_rate):
N = len(audio) # Number of samples
y_freq = fft(audio)
domain = len(y_freq) // 2
x_freq = np.linspace(0, sample_rate//2, N//2)
# Changed from abs(y_freq[:domain]) -> y_freq[:domain]
plt.plot(x_freq, y_freq[:domain])
plt.xlabel("Frequency [Hz]")
plt.ylabel("Frequency Amplitude |X(t)|")
return plt.show()
# Changed from single_sample_data -> audio_time_series
fft_plot(audio_time_series, sample_rate)
The code sample above produced, this plot:
Therefore, I think one of two things is going on:
The fft() function is not actually performing an fft on the time series data it is being given
The .wav file does not contain time series data to begin with
What could be the issue? Has anyone else experienced this?
I have replicated, essentially replicated, the code in the question and I don't see the problem the OP has described.
In [172]: %reset -f
...: import matplotlib.pyplot as plt
...: import numpy as np
...: from scipy.fftpack import fft
...: from scipy.io import wavfile
...:
...: sr, data = wavfile.read('sample.wav')
...: print(data.shape, sr)
...: signal = data[:sr,0]
...: Signal = fft(signal)
...: fig, (axt, axf) = plt.subplots(2, 1,
...: constrained_layout=1,
...: figsize=(11.8,3))
...: axt.plot(signal, lw=0.15) ; axt.grid(1)
...: axf.plot(np.abs(Signal[:sr//2]), lw=0.15) ; axf.grid(1)
...: plt.show()
sr, data = wavfile.read('sample.wav')
(268237, 2) 8000
Hence, I'm voting for closing the question because it is "Not reproducible or was caused by a typo".

How to find dominant frequency from FFT?

The peak suggestion, in the comments, does not find the peak that occurs most often. I need to find the frequency that occurs most often.
I need to find the dominant frequency in my Coefficient of Lift data. The frequency I am getting with the following code is quite large and not the dominant frequency. I know because the 2-D analysis is easy to analyze with a graph. It is sinusoidal. By dominant frequency, I mean the frequency of the signal with the most repeats.
#1/usr/bin/env python
import sys
import numpy
from numpy import sin
from math import pi
print("Hello World!")
data = numpy.loadtxt('CoefficientLiftData.dat', usecols= (0,3))
n = data.size
timestep = 0.000005
The peak data suggestion, in the comments, does not provide a count method to find how often a frequency occurs.
print(data)
fourier = numpy.fft.fft(data)
frequencies = numpy.fft.fftfreq(n, d=timestep)
positive_frequencies = frequencies[numpy.where(frequencies >= 0)]
magnitudes = abs(fourier[numpy.where(frequencies >= 0)])
peak_frequency = numpy.argmax(magnitudes)
print(peak_frequency)
Here is how to extract the dominate frequency from Coefficient of Lift data from, as an example, OpenFOAM.
#1/usr/bin/env python
import sys
import numpy as np
import scipy.fftpack as fftpack
from numpy import sin
from math import pi
import matplotlib.pyplot as plt
print("Hello World!")
N = 2500
Nev = 1000
data = np.loadtxt('CoefficientLiftData.dat', usecols= (0,3))
times = data[:,0]
length = int(len(times)/2)
forcez= data[:,1]
t = np.linspace(times[length], times[-1], 2500)
forcezint = np.interp(t, times, forcez)
fourier = fftpack.fft(forcezint[Nev-1:N-1])
frequencies = fftpack.fftfreq(forcezint[Nev-1:N-1].size, d=t[1]-t[0])
#print(frequencies)
freq = frequencies[np.argmax(np.abs(fourier))]
print(freq)

FFT spectrogram in python

I have python 3.4.
I transmitted a 2MHz (for example) frequency and received the cavitation over the time (until I stopped the measurement).
I want to get a spectrogram (cavitation vs frequency) and more interesting is a spectrogram of cavitation over the time of the sub-harmonic (1MHz) frequency.
The data is saved in sdataA (=cavitation), and t (=measurement time)
I tried to save fft in FFTA
FFTA = np.array([])
FFTA = np.fft.fft(dataA)
FFTA = np.append(FFTA, dataA)
I got real and complex numbers
Then I took only half (from 0 to 1MHz) and save the real and complex data.
nA = int(len(FFTA)/2)
yAre = FFTA[range(nA)].real
yAim = FFTA[range(nA)].imag
I tried to get the frequencies by:
FFTAfreqs = np.fft.fftfreq(len(yAre))
But it is totally wrong (I printed the data by print (FFTAfreqs))
I also plotted the data and again it's wrong:
plt.plot(t, FFTA[range(n)].real, 'b-', t, FFTA[range(n)].imag, 'r--')
plt.legend(('real', 'imaginary'))
plt.show()
How can I output a spectrogram of cavitation over the time of the sub-harmonic (1MHz) frequency?
EDIT:
Data example:
see a sample of 'dataA' and 'time':
dataA = [6.08E-04,2.78E-04,3.64E-04,3.64E-04,4.37E-04,4.09E-04,4.49E-04,4.09E-04,3.52E-04,3.24E-04,3.92E-04,3.24E-04,2.67E-04,3.24E-04,2.95E-04,2.95E-04,4.94E-04,4.09E-04,3.64E-04,3.07E-04]
time = [0.00E+00,4.96E-07,9.92E-07,1.49E-06,1.98E-06,2.48E-06,2.98E-06,3.47E-06,3.97E-06,4.46E-06,4.96E-06,5.46E-06,5.95E-06,6.45E-06,6.94E-06,7.44E-06,7.94E-06,8.43E-06,8.93E-06,9.42E-06]
EDIT II:
From #Martin example I tried the following code, please let me know if I did it right.
In the case that dataA and Time are saved as h5 files (or the data that I posted already)
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
dfdata = pd.read_hdf("C:\\data_python\\DataA.h5")
dft = pd.read_hdf("C:\\data_python\\time.h5")
dft_cor = int((len(dft)-2)*4.96E-6) # calculating the measured time
fs = 2000000 #sampling frequency 2MHz
CHUNK = 10000
signal_time = dft_cor # seconds
def sine(freq,fs,secs):
data=dfdata
wave = np.sin(freq*2*np.pi*data)
return wave
a1 = sine(fs,fs,120)
a2 = sine(fs/2,fs,120)
signal = a1+a2
afft = np.abs(np.fft.fft(signal[0:CHUNK]))
freqs = np.linspace(0,fs,CHUNK)[0:int(fs/2)]
spectrogram_chunk = freqs/np.amax(freqs*1.0)
# Plot spectral analysis
plt.plot(freqs[0:1000000],afft[0:1000000]) # 0-1MHz
plt.show()
number_of_chunks = 1000
# Empty spectrogram
Spectrogram = np.zeros(shape = [CHUNK,number_of_chunks])
for i in range(number_of_chunks):
afft = np.abs(np.fft.fft(signal[i*CHUNK:(1+i)*CHUNK]))
freqs = np.linspace(0,fs,CHUNK)[0:int(fs/2)]
spectrogram_chunk = afft/np.amax(afft*1.0)
try:
Spectrogram[:,i]=spectrogram_chunk
except:
break
import cv2
Spectrogram = Spectrogram[0:1000000,:]
cv2.imshow('spectrogram',np.uint8(255*Spectrogram/np.amax(Spectrogram)))
cv2.waitKey()
cv2.destroyAllWindows()
It seems your problem is not in Python but in understanding what is Spectrogram.
Spectrogram is sequences of spectral analysis of a signal.
1) You need to cut your signal in CHUNKS.
2) Do spectral analysis of these CHUNKS and stick it together.
Example:
You have 1 second of audio recoding (44100 HZ sampling). That means the recording will have 1s * 44100 -> 44100 samples. You define CHUNK size = 1024 (for example).
For each chunk you will do FFT, and stick it together into 2D matrix (X axis - FFT of the CHUNK, Y axis - CHUNK number,). 44100 samples / CHUNK ~ 44 FFTs, each of the FFT covers 1024/44100~0.023 seconds of the signal
The bigger the CHUNK, the more accurate Spectrogram is, but less 'realtime'.
The smaller the CHUNK is, the less acurate is the Spectrogram, but you have more measurements as you measure frequencies 'more often'.
If you need 1MHZ - actually you cannot use anything higher than 1MHZ, you just take half of the resulting FFT array - and it doesnt matter which half, because 1MHZ is just the half of your sampling frequency, and the FFT is mirroring anything that is higher than 1/2 of sampling frequency.
About FFT, you dont want complex numbers. You want to do
FFT = np.abs(FFT) # Edit - I just noticed you use '.real', but I will keep it here
because you want real numbers.
Preparation for Spectrogram - example of Spectrogram
Audio Signal with 150HZ wave and 300HZ Wave
import numpy as np
import matplotlib.pyplot as plt
fs = 44100#sampling frequency
CHUNK = 10000
signal_time = 20 # seconds
def sine(freq,fs,secs):
data=np.arange(fs*secs)/(fs*1.0)
wave = np.sin(freq*2*np.pi*data)
return wave
a1 = sine(150,fs,120)
a2 = sine(300,fs,120)
signal = a1+a2
afft = np.abs(np.fft.fft(signal[0:CHUNK]))
freqs = np.linspace(0,fs,CHUNK)[0:int(fs/2)]
spectrogram_chunk = freqs/np.amax(freqs*1.0)
# Plot spectral analysis
plt.plot(freqs[0:250],afft[0:250])
plt.show()
number_of_chunks = 1000
# Empty spectrogram
Spectrogram = np.zeros(shape = [CHUNK,number_of_chunks])
for i in range(number_of_chunks):
afft = np.abs(np.fft.fft(signal[i*CHUNK:(1+i)*CHUNK]))
freqs = np.linspace(0,fs,CHUNK)[0:int(fs/2)]
#plt.plot(spectrogram_chunk[0:250],afft[0:250])
#plt.show()
spectrogram_chunk = afft/np.amax(afft*1.0)
#print(signal[i*CHUNK:(1+i)*CHUNK].shape)
try:
Spectrogram[:,i]=spectrogram_chunk
except:
break
import cv2
Spectrogram = Spectrogram[0:250,:]
cv2.imshow('spectrogram',np.uint8(255*Spectrogram/np.amax(Spectrogram)))
cv2.waitKey()
cv2.destroyAllWindows()
Spectral analysis of single CHUNK
Spectrogram

Why is there a difference in magnitude response between scipy.filtfilt and scipy.lfilter?

I was trying to filter a signal using the scipy module of python and I wanted to see which of lfilter or filtfilt is better.
I tried to compare them and I got the following plot from my mwe
import numpy as np
import scipy.signal as sp
import matplotlib.pyplot as plt
frequency = 100. #cycles/second
samplingFrequency = 2500. #samples/second
amplitude = 16384
signalDuration = 2.3
cycles = frequency*signalDuration
time = np.linspace(0, 2*np.pi*cycles, signalDuration*samplingFrequency)
freq = np.fft.fftfreq(time.shape[-1])
inputSine = amplitude*np.sin(time)
#Create IIR Filter
b, a = sp.iirfilter(1, 0.3, btype = 'lowpass')
#Apply filter to input
filteredSignal = sp.filtfilt(b, a, inputSine)
filteredSignalInFrequency = np.fft.fft(filteredSignal)
filteredSignal2 = sp.lfilter(b, a, inputSine)
filteredSignal2InFrequency = np.fft.fft(filteredSignal2)
plt.close('all')
plt.figure(1)
plt.title('Sine filtered with filtfilt')
plt.plot(freq, abs(filteredSignalInFrequency))
plt.subplot(122)
plt.title('Sine filtered with lfilter')
plt.plot(freq, abs(filteredSignal2InFrequency))
print max(abs(filteredSignalInFrequency))
print max(abs(filteredSignal2InFrequency))
plt.show()
Can someone please explain why there is a difference in the magnitude response?
Thanks a lot for your help.
Looking at your graphs shows that the signal filtered with filtfilt has a peak magnitude of 4.43x107 in the frequency domain compared with 4.56x107 for the signal filtered with lfilter. In other words, the signal filtered with filtfilt has an peak magnitude that is 0.97 that when filtering with
Now we should note that scipy.signal.filtfilt applies the filter twice, whereas scipy.signal.lfilter only applies it once. As a result, input signals get attenuated twice as much. To confirm this we can have a look at the frequency response of the Butterworth filter you have used (obtained with iirfilter) around the input tone's normalized frequency of 100/2500 = 0.04:
which indeed shows an that the application of this filter does causes an attenuation of ~0.97 at a frequency of 0.04.

Python Scipy FFT wav files

I have a handful of wav files. I'd like to use SciPy FFT to plot the frequency spectrum of these wav files. How would I go about doing this?
Python provides several api to do this fairly quickly. I download the sheep-bleats wav file from this link. You can save it on the desktop and cd there within terminal. These lines in the python prompt should be enough: (omit >>>)
import matplotlib.pyplot as plt
from scipy.fftpack import fft
from scipy.io import wavfile # get the api
fs, data = wavfile.read('test.wav') # load the data
a = data.T[0] # this is a two channel soundtrack, I get the first track
b=[(ele/2**8.)*2-1 for ele in a] # this is 8-bit track, b is now normalized on [-1,1)
c = fft(b) # calculate fourier transform (complex numbers list)
d = len(c)/2 # you only need half of the fft list (real signal symmetry)
plt.plot(abs(c[:(d-1)]),'r')
plt.show()
Here is a plot for the input signal:
Here is the spectrum
For the correct output, you will have to convert the xlabelto the frequency for the spectrum plot.
k = arange(len(data))
T = len(data)/fs # where fs is the sampling frequency
frqLabel = k/T
If you are have to deal with a bunch of files, you can implement this as a function:
put these lines in the test2.py:
import matplotlib.pyplot as plt
from scipy.io import wavfile # get the api
from scipy.fftpack import fft
from pylab import *
def f(filename):
fs, data = wavfile.read(filename) # load the data
a = data.T[0] # this is a two channel soundtrack, I get the first track
b=[(ele/2**8.)*2-1 for ele in a] # this is 8-bit track, b is now normalized on [-1,1)
c = fft(b) # create a list of complex number
d = len(c)/2 # you only need half of the fft list
plt.plot(abs(c[:(d-1)]),'r')
savefig(filename+'.png',bbox_inches='tight')
Say, I have test.wav and test2.wav in the current working dir, the following command in python prompt interface is sufficient:
import test2
map(test2.f, ['test.wav','test2.wav'])
Assuming you have 100 such files and you do not want to type their names individually, you need the glob package:
import glob
import test2
files = glob.glob('./*.wav')
for ele in files:
f(ele)
quit()
You will need to add getparams in the test2.f if your .wav files are not of the same bit.
You could use the following code to do the transform:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import scipy.io.wavfile as wavfile
import scipy
import scipy.fftpack
import numpy as np
from matplotlib import pyplot as plt
fs_rate, signal = wavfile.read("output.wav")
print ("Frequency sampling", fs_rate)
l_audio = len(signal.shape)
print ("Channels", l_audio)
if l_audio == 2:
signal = signal.sum(axis=1) / 2
N = signal.shape[0]
print ("Complete Samplings N", N)
secs = N / float(fs_rate)
print ("secs", secs)
Ts = 1.0/fs_rate # sampling interval in time
print ("Timestep between samples Ts", Ts)
t = scipy.arange(0, secs, Ts) # time vector as scipy arange field / numpy.ndarray
FFT = abs(scipy.fft(signal))
FFT_side = FFT[range(N/2)] # one side FFT range
freqs = scipy.fftpack.fftfreq(signal.size, t[1]-t[0])
fft_freqs = np.array(freqs)
freqs_side = freqs[range(N/2)] # one side frequency range
fft_freqs_side = np.array(freqs_side)
plt.subplot(311)
p1 = plt.plot(t, signal, "g") # plotting the signal
plt.xlabel('Time')
plt.ylabel('Amplitude')
plt.subplot(312)
p2 = plt.plot(freqs, FFT, "r") # plotting the complete fft spectrum
plt.xlabel('Frequency (Hz)')
plt.ylabel('Count dbl-sided')
plt.subplot(313)
p3 = plt.plot(freqs_side, abs(FFT_side), "b") # plotting the positive fft spectrum
plt.xlabel('Frequency (Hz)')
plt.ylabel('Count single-sided')
plt.show()

Categories

Resources