How to plot frequency band using `matplotlib.pyplot.specgram` - python

I can plot a spectrogram (in a Jupyter notebook) thus:
fs = 48000
noverlap = (fftFrameSamps*3) // 4
spectrum2d, freqs, timePoints, image = \
plt.specgram( wav, NFFT=fftFrameSamps, Fs=fs, noverlap=noverlap )
plt.show()
However, I am only interested in the 15-20 kHz range.
How can I plot only this range?
I can see that the function returns image, so maybe I could convert the image to a matrix and take an appropriate slice from the matrix...?
I can see that the function accepts vmin and vmax but these appear to be undocumented and playing with them doesn't yield a valid result.

You can modify the limits of the axis as you would normally with set_ylim() and set_xlim(). In this case
plt.ylim([15000, 20000])
should restrict your plot to the 15-20 kHz range. For a complete example drawing from the Spectrogram Demo:
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(19680801)
dt = 0.0005
t = np.arange(0.0, 20.0, dt)
s1 = np.sin(2 * np.pi * 100 * t)
s2 = 2 * np.sin(2 * np.pi * 400 * t)
# create a transient "chirp"
s2[t <= 10] = s2[12 <= t] = 0
# add some noise into the mix
nse = 0.01 * np.random.random(size=len(t))
x = s1 + s2 + nse # the signal
NFFT = 1024 # the length of the windowing segments
Fs = int(1.0 / dt) # the sampling frequency
fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(14, 7))
ax1.specgram(x, NFFT=NFFT, Fs=Fs, noverlap=900)
ax2.specgram(x, NFFT=NFFT, Fs=Fs, noverlap=900)
ax2.set_ylim([50, 500])
plt.show()

Related

How to obtain frequencies in Non-Uniform DFFT?

I have code that looks like this:
import matplotlib.pyplot as plt
import numpy as np
from nfft import nfft
# number of sample points
N = 400
# Simulated non-uniform data
x = np.linspace(0.0, 1 / 2, N) + np.random.random((N)) * 0.001
y = np.sin(50.0 * 2.0 * np.pi * x) + 0.5 * np.sin(80.0 * 2.0 * np.pi * x)
yf = np.abs(nfft(x, y))
fig, axs = plt.subplots(1)
fig_f, axs_f = plt.subplots(1)
axs.plot(x, y, '.', color='red')
axs_f.plot(x, yf, color='red')
How do I convert the values on the second graph to represent frequency?
The use of the nfft module is not required, answers using pynfft or scipy will be greatly appreciated.
See also:
How do I obtain the frequencies of each value in an FFT?
The following seems to work. Notice the line inserted before graphing the Fourier transform, to generate the frequencies, and that we graph N/2 of the data.
import matplotlib.pyplot as plt
import numpy as np
from nfft import nfft
# number of sample points
N = 400
# Simulated non-uniform data
x = np.linspace(0.0,0.5-0.02, N) + np.random.random((N)) * 0.001
print(x)
print( 'random' )
print( np.random.random((N)) * 0.001 )
y = np.sin(50.0 * 2.0 * np.pi * x) + 0.5 * np.sin(80.0 * 2.0 * np.pi * x)
yf = np.abs(nfft(x, y))
fig, axs = plt.subplots(1)
fig_f, axs_f = plt.subplots(1)
axs.plot(x, y, '.', color='red')
xf = np.fft.fftfreq(N,1./N)
axs_f.plot(xf[:int(N/2)], yf[:int(N/2)], color='red')
plt.show()
Output:

Filter out range of frequencies using band stop filter in Python and confirm it using Fourier Transform FFT

Supposing that I have following signal:
y = np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(100.0 * 2.0*np.pi*x) + 0.2*np.sin(200 * 2.0*np.pi*x)
how can I filter out in example 100Hz using Band-stop filter in Python? In this signal there are peaks at 50Hz, 100Hz and 200Hz. It would be helpful it it could be visualized using FFT in order to confirm that this frequency has been filtered correctly.
Basing on answers from:
Plotting a Fast Fourier Transform in Python
and:
Bandstop filter
I wrote following code:
import pandas as pd
import time
from scipy.signal import lfilter
import matplotlib.pyplot as plt
import scipy
import numpy as np
# In the below lines data are being filtered using Bandstop filter
print("Filtering using Bandstop filter...")
start_filtering_bandstop = time.time()
# Define filtering parameters:
order = 2
fs = 800.0 # sample rate, Hz
lowcut = 90 # desired cutoff frequency of the filter, Hz
highcut = 110
# Define plots
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 7))
# Number of samplepoints
N = 600
# sample spacing
T = 1.0 / fs
x = np.linspace(0.0, N*T, N)
y = np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(100.0 * 2.0*np.pi*x) + 0.2*np.sin(200 * 2.0*np.pi*x) # You can put there pandas series too...
ax1.plot(x, y, label='Signal before filtering')
print("Calculating FFT, please wait...")
yf = scipy.fftpack.fft(y)
xf = np.linspace(0.0, 1.0/(2.0*T), N/2)
ax1.set_title('Signal')
ax2.set_title('FFT')
ax2.plot(xf, 2.0/N * np.abs(yf[:N//2]), label='Before filtering')
def butter_bandstop_filter(data, lowcut, highcut, fs, order):
nyq = 0.5 * fs
low = lowcut / nyq
high = highcut / nyq
b, a = scipy.signal.butter(order, [low, high], btype='bandstop')#, fs, )
y = lfilter(b, a, data)
return y
print("Filtering signal, please wait...")
signal_filtered = butter_bandstop_filter(y, lowcut, highcut, fs, order)
ax1.plot(x, signal_filtered, label='Signal after filtering')
ax1.set(xlabel='X', ylabel='Signal values')
ax1.legend() # Don't forget to show the legend
ax1.set_xlim([0,0.8])
ax1.set_ylim([-1.5,2])
# Number of samplepoints
N = len(signal_filtered)
# sample spacing
T = 1.0 / fs
x = np.linspace(0.0, N*T, N)
y = signal_filtered
print("Calculating FFT after filtering, please wait...")
yf = scipy.fftpack.fft(y)
xf = np.linspace(0.0, 1.0/(2.0*T), N/2)
# Plot axes...
ax2.plot(xf, 2.0/N * np.abs(yf[:N//2]), label='After filtering')
ax2.set(xlabel='Frequency [Hz]', ylabel='Magnitude')
ax2.legend() # Don't forget to show the legend
plt.savefig('FFT_after_bandstop_filtering.png', bbox_inches='tight', dpi=300) # If dpi isn't set, the script execution will be faster
# Alternatively for immediate showing of plot:
# plt.show()
plt.close()
end_filtering_bandstop = time.time()
print("Data filtered using Bandstop filter in",round(end_filtering_bandstop - start_filtering_bandstop,2),"seconds!")
and obtained following plots:
As we can see, the 100Hz has been filtered out using band-stop filter.
Why magnitude for frequency 50 Hz decreased from 1 to 0.7 after Fast Fourier Transform?

How to remove frequency from signal

I want to remove one frequency (one peak) from signal and plot my function without it. After fft I found frequency and amplitude and I am not sure what I need to do now. For example I want to remove my highest peak (marked with red dot on plot).
import numpy as np
import matplotlib.pyplot as plt
# create data
N = 4097
T = 100.0
t = np.linspace(-T/2,T/2,N)
f = np.sin(50.0 * 2.0*np.pi*t) + 0.5*np.sin(80.0 * 2.0*np.pi*t)
#plot function
plt.plot(t,f,'r')
plt.show()
# perform FT and multiply by dt
dt = t[1]-t[0]
ft = np.fft.fft(f) * dt
freq = np.fft.fftfreq(N, dt)
freq = freq[:N/2+1]
amplitude = np.abs(ft[:N/2+1])
# plot results
plt.plot(freq, amplitude,'o-')
plt.legend(('numpy fft * dt'), loc='upper right')
plt.xlabel('f')
plt.ylabel('amplitude')
#plt.xlim([0, 1.4])
plt.plot(freq[np.argmax(amplitude)], max(amplitude), 'ro')
print "Amplitude: " + str(max(amplitude)) + " Frequency: " + str(freq[np.argmax(amplitude)])
plt.show()
One option is to transform the signal to the frequency domain then remove the selected frequency.
import numpy as np
import matplotlib.pyplot as plt
from scipy.fftpack import rfft, irfft, fftfreq, fft
# Number of samplepoints
N = 500
# sample spacing
T = 0.1
x = np.linspace(0.0, (N-1)*T, N)
# x = np.arange(0.0, N*T, T) # alternate way to define x
y = 5*np.sin(x) + np.cos(2*np.pi*x)
yf = fft(y)
xf = np.linspace(0.0, 1.0/(2.0*T), N//2)
#fft end
f_signal = rfft(y)
W = fftfreq(y.size, d=x[1]-x[0])
cut_f_signal = f_signal.copy()
cut_f_signal[(W>0.6)] = 0 # filter all frequencies above 0.6
cut_signal = irfft(cut_f_signal)
# plot results
f, axarr = plt.subplots(1, 3, figsize=(9, 3))
axarr[0].plot(x, y)
axarr[0].plot(x,5*np.sin(x),'g')
axarr[1].plot(xf, 2.0/N * np.abs(yf[:N//2]))
axarr[1].legend(('numpy fft * dt'), loc='upper right')
axarr[1].set_xlabel("f")
axarr[1].set_ylabel("amplitude")
axarr[2].plot(x,cut_signal)
axarr[2].plot(x,5*np.sin(x),'g')
plt.show()
You can design a bandstop filter:
from scipy import signal
wc = freq[np.argmax(amplitude)] / (0.5 / dt)
wp = [wc * 0.9, wc / 0.9]
ws = [wc * 0.95, wc / 0.95]
b, a = signal.iirdesign(wp, ws, 1, 40)
f = signal.filtfilt(b, a, f)

FFT coefficients using python

I am a newbie in Signal Processing. In here, I want to ask how to get FFT coeffients from FFT from in python. This is the example of my code:
from scipy.fftpack import fft
# Number of samplepoints
N = 600
# sample spacing
T = 1.0 / 800.0
x = np.linspace(0.0, N*T, N)
y = np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x)
yf = fft(y)
xf = np.linspace(0.0, 1.0/(2.0*T), N/2)
import matplotlib.pyplot as plt
plt.plot(xf, 2.0/N * np.abs(yf[0:N/2]))
plt.grid()
plt.show()
Hmm I don't really know about signal processing either but maybe this works:
from scipy.signal import argrelmax
f = xf[scipy.signal.argrelmax(yf[0:N/2])]
Af = np.abs(yf[argrelmax(yf[0:N/2])])
Quoting #hotpaw, in this similar answer:
"The real and imaginary arrays, when put together, can represent a complex array. Every complex element of the complex array in the frequency domain can be considered a frequency coefficient, and has a magnitude sqrt(RR + II))".
So, the coefficients are the complex elements in the array returned by the fft function. Also, it is important to play with the size (the number) of the bins for the FFT function. It would make sense to test a bunch of values and pick the one that makes more sense to your application. Often, it is in the same magnitude of the number of samples. This was as assumed by most of the answers given, and produces great and reasonable results. In case one wants to explore that, here is my code version:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import scipy.fftpack
fig = plt.figure(figsize=[14,4])
N = 600 # Number of samplepoints
Fs = 800.0
T = 1.0 / Fs # N_samps*T (#samples x sample period) is the sample spacing.
N_fft = 80 # Number of bins (chooses granularity)
x = np.linspace(0, N*T, N) # the interval
y = np.sin(50.0 * 2.0*np.pi*x) + 0.5*np.sin(80.0 * 2.0*np.pi*x) # the signal
# removing the mean of the signal
mean_removed = np.ones_like(y)*np.mean(y)
y = y - mean_removed
# Compute the fft.
yf = scipy.fftpack.fft(y,n=N_fft)
xf = np.arange(0,Fs,Fs/N_fft)
##### Plot the fft #####
ax = plt.subplot(121)
pt, = ax.plot(xf,np.abs(yf), lw=2.0, c='b')
p = plt.Rectangle((Fs/2, 0), Fs/2, ax.get_ylim()[1], facecolor="grey", fill=True, alpha=0.75, hatch="/", zorder=3)
ax.add_patch(p)
ax.set_xlim((ax.get_xlim()[0],Fs))
ax.set_title('FFT', fontsize= 16, fontweight="bold")
ax.set_ylabel('FFT magnitude (power)')
ax.set_xlabel('Frequency (Hz)')
plt.legend((p,), ('mirrowed',))
ax.grid()
##### Close up on the graph of fft#######
# This is the same histogram above, but truncated at the max frequence + an offset.
offset = 1 # just to help the visualization. Nothing important.
ax2 = fig.add_subplot(122)
ax2.plot(xf,np.abs(yf), lw=2.0, c='b')
ax2.set_xticks(xf)
ax2.set_xlim(-1,int(Fs/6)+offset)
ax2.set_title('FFT close-up', fontsize= 16, fontweight="bold")
ax2.set_ylabel('FFT magnitude (power) - log')
ax2.set_xlabel('Frequency (Hz)')
ax2.hold(True)
ax2.grid()
plt.yscale('log')
Output:

Cutting of unused frequencies in specgram matplotlib

I have a signal with sampling rate 16e3, its frequency range is from 125 to 1000 Hz.
So if i plot a specgram i get a pretty small colorrange because of all the unused frequencys.
ive tried to fix it with setting ax limits but that does not work.
is there any way to cut off unused frequencys or replace them with NaNs?
Resampling the Data to 2e3 won't work because there are still some not used frequencys below 125 Hz.
Thanks for your help.
specgram() is doing all the work for you. If you look in axes.py at the specgram function you can see how it works. The original function is in Python27\Lib\site-packages\matplotlib\axes.py on my computer.
<snip>
Pxx, freqs, bins = mlab.specgram(x, NFFT, Fs, detrend,
window, noverlap, pad_to, sides, scale_by_freq)
Z = 10. * np.log10(Pxx)
Z = np.flipud(Z)
if xextent is None: xextent = 0, np.amax(bins)
xmin, xmax = xextent
freqs += Fc
extent = xmin, xmax, freqs[0], freqs[-1]
im = self.imshow(Z, cmap, extent=extent, **kwargs)
self.axis('auto')
return Pxx, freqs, bins, im
You might have to make your own function modeled on this and clip the Pxx data to suit your needs.
Pxx, freqs, bins = mlab.specgram(x, NFFT, Fs, detrend,
window, noverlap, pad_to, sides, scale_by_freq)
# ****************
# create a new limited Pxx and freqs
#
# ****************
Z = 10. * np.log10(Pxx)
Z = np.flipud(Z)
Pxx is a 2d array with a shape of (len(freqs),len(bins)
>>> Pxx.shape
(129, 311)
>>> freqs.shape
(129,)
>>> bins.shape
(311,)
>>>
This will limit Pxx and freqs
Pxx = Pxx[(freqs >= 125) & (freqs <= 1000)]
freqs = freqs[(freqs >= 125) & (freqs <= 1000)]
Here is a complete solution - my_specgram() - used with the specgram_demo from the gallery.
from pylab import *
from matplotlib import *
# 100, 400 and 200 Hz sine 'wave'
dt = 0.0005
t = arange(0.0, 20.0, dt)
s1 = sin(2*pi*100*t)
s2 = 2*sin(2*pi*400*t)
s3 = 2*sin(2*pi*200*t)
# create a transient "chirp"
mask = where(logical_and(t>10, t<12), 1.0, 0.0)
s2 = s2 * mask
# add some noise into the mix
nse = 0.01*randn(len(t))
x = s1 + s2 + +s3 + nse # the signal
NFFT = 1024 # the length of the windowing segments
Fs = int(1.0/dt) # the sampling frequency
# modified specgram()
def my_specgram(x, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
window=mlab.window_hanning, noverlap=128,
cmap=None, xextent=None, pad_to=None, sides='default',
scale_by_freq=None, minfreq = None, maxfreq = None, **kwargs):
"""
call signature::
specgram(x, NFFT=256, Fs=2, Fc=0, detrend=mlab.detrend_none,
window=mlab.window_hanning, noverlap=128,
cmap=None, xextent=None, pad_to=None, sides='default',
scale_by_freq=None, minfreq = None, maxfreq = None, **kwargs)
Compute a spectrogram of data in *x*. Data are split into
*NFFT* length segments and the PSD of each section is
computed. The windowing function *window* is applied to each
segment, and the amount of overlap of each segment is
specified with *noverlap*.
%(PSD)s
*Fc*: integer
The center frequency of *x* (defaults to 0), which offsets
the y extents of the plot to reflect the frequency range used
when a signal is acquired and then filtered and downsampled to
baseband.
*cmap*:
A :class:`matplotlib.cm.Colormap` instance; if *None* use
default determined by rc
*xextent*:
The image extent along the x-axis. xextent = (xmin,xmax)
The default is (0,max(bins)), where bins is the return
value from :func:`mlab.specgram`
*minfreq, maxfreq*
Limits y-axis. Both required
*kwargs*:
Additional kwargs are passed on to imshow which makes the
specgram image
Return value is (*Pxx*, *freqs*, *bins*, *im*):
- *bins* are the time points the spectrogram is calculated over
- *freqs* is an array of frequencies
- *Pxx* is a len(times) x len(freqs) array of power
- *im* is a :class:`matplotlib.image.AxesImage` instance
Note: If *x* is real (i.e. non-complex), only the positive
spectrum is shown. If *x* is complex, both positive and
negative parts of the spectrum are shown. This can be
overridden using the *sides* keyword argument.
**Example:**
.. plot:: mpl_examples/pylab_examples/specgram_demo.py
"""
#####################################
# modified axes.specgram() to limit
# the frequencies plotted
#####################################
# this will fail if there isn't a current axis in the global scope
ax = gca()
Pxx, freqs, bins = mlab.specgram(x, NFFT, Fs, detrend,
window, noverlap, pad_to, sides, scale_by_freq)
# modified here
#####################################
if minfreq is not None and maxfreq is not None:
Pxx = Pxx[(freqs >= minfreq) & (freqs <= maxfreq)]
freqs = freqs[(freqs >= minfreq) & (freqs <= maxfreq)]
#####################################
Z = 10. * np.log10(Pxx)
Z = np.flipud(Z)
if xextent is None: xextent = 0, np.amax(bins)
xmin, xmax = xextent
freqs += Fc
extent = xmin, xmax, freqs[0], freqs[-1]
im = ax.imshow(Z, cmap, extent=extent, **kwargs)
ax.axis('auto')
return Pxx, freqs, bins, im
# plot
ax1 = subplot(211)
plot(t, x)
subplot(212, sharex=ax1)
# the minfreq and maxfreq args will limit the frequencies
Pxx, freqs, bins, im = my_specgram(x, NFFT=NFFT, Fs=Fs, noverlap=900,
cmap=cm.Accent, minfreq = 180, maxfreq = 220)
show()
close()
These days, there's an easier way to do this than when the question was asked: you can use matplotlib.pyplot.axis to set ymin and ymax to your desired frequencies. It's quite easy; here's a snippet from my program:
plt.specgram(xmit, NFFT=65536, Fs=Fs)
plt.axis(ymin=Fc-Fa*10, ymax=Fc+Fa*10)
plt.show()
specgram() returns (Pxx, freqs, bins, im), where im is AxesImage
instance [1]. You could use it to set the limits of your plot:
Pxx, freqs, bins, im = plt.specgram(signal, Fs=fs)
im.set_ylim((125,1000))
[1] http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.specgram
Here is an adapted version of this: http://matplotlib.org/examples/pylab_examples/specgram_demo.html
which changes the range of frequencies that are plotted.
#!/usr/bin/env python
#### from the example
####
from pylab import *
dt = 0.0005
t = arange(0.0, 20.0, dt)
s1 = sin(2*pi*100*t)
s2 = 2*sin(2*pi*400*t)
mask = where(logical_and(t>10, t<12), 1.0, 0.0)
s2 = s2 * mask
nse = 0.01*randn(len(t))
x = s1 + s2 + nse # the signal
NFFT = 1024 # the length of the windowing segments
Fs = int(1.0/dt) # the sampling frequency
ax1 = subplot(211)
plot(t, x)
subplot(212, sharex=ax1)
Pxx, freqs, bins, im = specgram(x, NFFT=NFFT, Fs=Fs, noverlap=900,
cmap=cm.gist_heat)
#### edited from the example
####
# here we get access to the axes
x1,x2,y1,y2 = axis()
# leave x range the same, change y (frequency) range
axis((x1,x2,25,500))
show()

Categories