I have a program that connects to a bluetooth accelerometer and reads that data to determine motion in real time and I'm trying to figure out how to smooth out the noise so I can better represent the motion. I found a scipy function for a butterworth filter (pardon my ignorance on filters) but it seems like it only works when you have the whole plot as it looks at the points before and after to smooth noise. How can I smooth out noise dynamically? Here's my code:
def animator():
global xyz
fig = plt.figure()
xyz_mot = fig.add_subplot(111, projection = "3d")
xyz_mot.set_title("Motion")
xyz_mot.set_xlim3d(-100, 100)
xyz_mot.set_ylim3d(-100, 100)
xyz_mot.set_zlim3d(-100, 100)
xyz = xyz_mot.scatter(0,0,0)
ani = FuncAnimation(fig, updateAni, frames=2, interval=50)
fig.show()
def updateAni(i):
t = float(time_data[-1] / 1000)**2
xmot[0] = .5 * acceleration_data[-1].x * t
ymot[0] = .5 * acceleration_data[-1].y * t
zmot[0] = .5 * acceleration_data[-1].z * t
xyz._offsets3d = (xmot, ymot, zmot)
#print("X Motion: " + str(xmot) + ", Y Motion: " + str(ymot))
#print(time_data[-1])
The accelerometer data and time data are being added to the arrays acceleration_data and time_data from another thread. Is there a matplotlib/some other library function to smooth noise? Any help is appreciated
Look at running data through an exponential averaging or moving average filter. The exponential average allows you to trade off averaging verse speed with alpha parameter. Filter the raw data from the accelerometer before adding to the output array.
The following snippet implements simple exponential avererager
avg = alpha * x + (1 - alpha) * x_prev
x_prev = x
buffer.append(avg)
Instead of adding the raw acceleration data to acceleration_data from your secondary thread, I'd filter the data there by using a buffer to constantly take the average of the last several measurements. Without your code for the thread, I can only give you some pseudo-code, but this should give you an idea of how it would work.:
import collections
def thread_func():
buf = collections.deque(maxlen=10) #take the average of up to the last 10 samples
while True:
accel = bluetooth_device.read()
buf.append(accel)
if buf: #may want to make sure the buffer isn't empty first to prevent devide by 0
acceleration_data.append(sum(buf)/len(buf))
Related
I am working with the pyaudio and matplotlib packages for the first time and I am attempting to plot live audio data from microphone input, transform it to frequency domain information, and then output peaks with an input distance. This project is a modification of the three-part guide to build a spectrum analyzer found here.
Currently the code is formatted in a class as I have alternative methods that I am applying to the audio but I am only posting the class with the relevant methods as they don't make reference to each and are self-contained. Another quirk of the program is that it calls upon a local file though it only uses input from the user microphone; this is a leftover from the original functionality of plotting a sound file's intensity while it played and is no longer integral to the code.
import pyaudio
import wave
import struct
import pandas as pd
from scipy.fftpack import fft
from scipy.signal import find_peaks
import matplotlib.pyplot as plt
import numpy as np
class Wave:
def __init__(self, file) -> None:
self.CHUNK = 1024 * 4
self.obj = wave.open(file, "r")
self.callback_output = []
self.data = self.obj.readframes(self.CHUNK)
self.rate = 44100
# Initiate an instance of PyAudio
self.p = pyaudio.PyAudio()
# Open a stream with the file specifications
self.stream = self.p.open(format = pyaudio.paInt16,
channels = self.obj.getnchannels(),
rate = self.rate,
output = True,
input = True,
frames_per_buffer = self.CHUNK)
def fft_plot(self, distance: float):
x_fft = np.linspace(0, self.rate, self.CHUNK)
fig, ax = plt.subplots()
line_fft, = ax.semilogx(x_fft, np.random.rand(self.CHUNK), "-", lw = 2)
# Bind plot window sizes
ax.set_xlim(20, self.rate / 2)
plot_data = self.stream.read(self.CHUNK)
self.data_int = pd.DataFrame(struct.unpack(\
str(self.CHUNK * 2) + 'h', plot_data)).astype(dtype = "b")[::2]
y_fft = fft(self.data_int)
line_fft.set_ydata(np.abs(y_fft[0:self.CHUNK]) / (256 * self.CHUNK))
plt.show(block = False)
while True:
# Read incoming audio data
data = self.stream.read(self.CHUNK)
# Convert data to bits then to array
self.data_int = struct.unpack(str(4 * self.CHUNK) + 'B', data)
# Recompute FFT and update line
yf = fft(self.data_int)
line_data = np.abs(yf[0:self.CHUNK]) / (128 * self.CHUNK)
line_fft.set_ydata(line_data)
# Find all values above threshold
peaks, _ = find_peaks(line_data, distance = distance)
# Update the plot
plt.plot(peaks, line_data[peaks], "x")
fig.canvas.draw()
fig.canvas.flush_events()
# Exit program when plot window is closed
fig.canvas.mpl_connect('close_event', exit)
test_file = "C:/Users/Tam/Documents/VScode/Final Project/PrismGuitars.wav"
audio_test = Wave(test_file)
audio_test.fft_plot(2000)
The code does not throw any errors and runs fine with an okay framerate and only terminates when the plot window is closed, all of which is good. The issue I'm encountering is with the determination and plotting of the peaks of line_data as when I run this code the output over time looks like this matplotlib graph instance.
It seems that the peaks (or peak) are being found but at a lower frequency than the x of line_data and as such are shifted comparatively. The other, more minor, issue is that since this is a live plot I would like to clear the previous instance of the peak marker so that it only shows the current instance and not all of the ones plotted prior.
I have attempted in prior fixes to use the line_fft in the peak detection but as it is cast to a Line2D format the peak detection algorithm isn't able to deal with the data type. I have also tried implementing a list comprehension as seen in this post but the time to cast to list is prohibitively slow and did not return any peak markers when I ran it.
EDIT: Following Jody's input the program now returns the proper values as I was only printing an index for the x-coordinate of the peak marker. Nevertheless I would still appreciate some insight as to whether it is possible to update per marker rather than having all the previous ones constantly displayed.
As for the marker updating I have attempted to clear the plot in the while loop both before and after drawing the markers (in different tests of course) but I only ever end up with a completely blank graph.
Please let me know if there is anything I should clarify and thank you for your time.
As Jody pointed out the peaks variable contains indexes for the detected peaks that then need to be retrieved from x_fft and line_data in order to match up with the displayed data.
First we create a scatter plot:
scat = ax.scatter([], [], c = "purple", marker = "x")
This data can then be stacked using a container variable in the while loop as such:
array_peaks = np.c_[x_fft[peaks], line_data[peaks]]
and update the data in the while loop with:
scat.set_offsets(array_peaks)
I am a college student working on a Amplitude Modulator that switches modulation constants every so often. The problem I am running into is when I plot my carrier signal graph I get this weird line plotted on the graph that I am unsure of where it comes from.
Can anyone help me understand where this is coming from?
The code is calling 5 seconds of audio I have loaded on my computer, loading the data and sampling rate, taking said data and making an array of nparrays that are 8 bits long. I then am modulating the signal with a carrier frequency to have a Modulated signal at the end. Or this is what I think im doing, because I'm a novice python coder.
Also any suggestions on how I building my code, I am still learning python and consider myself a novice at best, any helpful hints, recommendations, or tips help me greatly. Thank you for the time and help you offer me.
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
import librosa as lib
from math import trunc
#rewriting pi into a easy to use variable
pi = np.pi
myaudio, sr = lib.load("C:/Users/kaczm/Documents/Downloads/summer-bensound-royalty-free-music-
no-copyright-music_UpIcRKDG.mp3")
duration = len(myaudio)/sr
#print(trunc(len(myaudio) / 8))
time = np.arange(0,duration,1/sr)
plt.plot(time,myaudio)
plt.title('Signal original')
plt.xlabel('signal in time domain')
plt.ylabel('amplitude of signal')
plt.show()
#____________________________________________________________________________________________
arr1 = []
for i in range(0, (8 - (len(myaudio) % 8))):
arr1.append([0])
#print(len(arr1))
#adds zeros to the end of myaudio,and time so it is easisly dvisble by 8 no matter the size
myaudio = np.append(myaudio,arr1)
time = np.append(time,arr1)
#variable for how many frames needed to be modulated, and spliting the exact time frames with
it
frame_num = trunc(len(myaudio) / 8)
#an array of numpy arrays of each individual byte in order
Split_audio = np.split(myaudio,frame_num)
Split_time = np.split(time,frame_num)
#___________________________________________________________________________________________
#A = int()
carrier = []
modulator = []
envelope = []
Mod_sig = []
#needs 8 different variables to work, designed this way for ease of coding
k = [1,.5,2,1.5,.3,2,1,2]
for x , y in zip(Split_audio,Split_time):
for Val, T, con in zip(x,y,k):
modulator.append(float(Val * np.cos((2 * pi * T * 5 / sr))))
carrier.append(float(signal.square(2 * pi * T )))
envelope.append(float((1 + con * np.cos(2 * pi * T ))))
# A = A + 1
# print(con)
# print(A)
for x,y in zip(envelope,modulator):
Mod_sig.append(float(x * y))
#plt.plot(time,modulator)
#plt.title('Signal modulator')
#plt.xlabel('signal in time domain')
#plt.ylabel('amplitude of signal')
#plt.show()
plt.plot(time,carrier)
plt.title('Signal carrier wave')
plt.xlabel('signal in time domain')
plt.ylabel('amplitude of signal')
plt.show()
plt.plot(time,Mod_sig)
plt.title('Signal Modulated signal')
plt.xlabel('signal in time domain')
plt.ylabel('amplitude of signal')
plt.show()
All the commented out lines of code are ways for me to test and view what is happening with the code. It looks like everything is going fine when referencing the plotted graphs but the carrier graph has that strange line going through it. I have a feeling it could be, because I am implementing the math wrong, but I am unsure. Any help given is always greatly appreciated
I'm trying to make a program in Python from which I can upload a music file and get notes from this file (on piano). I created a Spectrogram, and now how can I get a frequencies from it? How can I fix spectrogram (from half of spectrogram I have mirror reflection)? I need something like this. Here is my code.
import numpy as np
from matplotlib import pyplot as plt
import scipy.io.wavfile as wav
from numpy.lib import stride_tricks
""" short time fourier transform of audio signal """
def stft(sig, frameSize, overlapFac=0.5, window=np.hanning):
win = window(frameSize)
hopSize = int(frameSize - np.floor(overlapFac * frameSize))
# zeros at beginning (thus center of 1st window should be for sample nr. 0)
samples = np.append(np.zeros(np.floor(frameSize/2.0)), sig)
# cols for windowing
cols = np.ceil((len(samples) - frameSize) / float(hopSize)) + 1
# zeros at end (thus samples can be fully covered by frames)
samples = np.append(samples, np.zeros(frameSize))
frames = stride_tricks.as_strided(samples, shape=(cols, frameSize), strides=(samples.strides[0]*hopSize, samples.strides[0])).copy()
frames *= win
return np.fft.rfft(frames)
""" scale frequency axis logarithmically """
def logscale_spec(spec, sr=44100, factor=20.):
timebins, freqbins = np.shape(spec)
scale = np.linspace(0, 1, freqbins) ** factor
scale *= (freqbins-1)/max(scale)
scale = np.unique(np.round(scale))
# create spectrogram with new freq bins
newspec = np.complex128(np.zeros([timebins, len(scale)]))
for i in range(0, len(scale)):
if i == len(scale)-1:
newspec[:,i] = np.sum(spec[:,scale[i]:], axis=1)
else:
newspec[:,i] = np.sum(spec[:,scale[i]:scale[i+1]], axis=1)
# list center freq of bins
allfreqs = np.abs(np.fft.fftfreq(freqbins*2, 1./sr)[:freqbins+1])
freqs = []
for i in range(0, len(scale)):
if i == len(scale)-1:
freqs += [np.mean(allfreqs[scale[i]:])]
else:
freqs += [np.mean(allfreqs[scale[i]:scale[i+1]])]
return newspec, freqs
""" plot spectrogram"""
def plotstft(audiopath, binsize=2**10, plotpath=None, colormap="jet"):
samplerate, samples = wav.read(audiopath)
s = stft(samples, binsize)
sshow, freq = logscale_spec(s, factor=1.0, sr=samplerate)
ims = 20.*np.log10(np.abs(sshow)/10e-6) # amplitude to decibel
timebins, freqbins = np.shape(ims)
plt.figure(figsize=(15, 7.5))
plt.imshow(np.transpose(ims), origin="lower", aspect="auto", cmap=colormap, interpolation="none")
plt.colorbar()
plt.xlabel("time (s)")
plt.ylabel("frequency (Hz)")
plt.xlim([0, timebins-1])
plt.ylim([0, freqbins])
xlocs = np.float32(np.linspace(0, timebins-1, 5))
plt.xticks(xlocs, ["%.02f" % l for l in ((xlocs*len(samples)/timebins)+(0.5*binsize))/samplerate])
ylocs = np.int16(np.round(np.linspace(0, freqbins-1, 10)))
plt.yticks(ylocs, ["%.02f" % freq[i] for i in ylocs])
if plotpath:
plt.savefig(plotpath, bbox_inches="tight")
else:
plt.show()
plt.clf()
plotstft("Sound/piano2.wav")
The audio transcription problem you describe is a well know problem in the Music Information Retrieval (MIR) research community. It is not one that is easy to solve and consists of two aspects:
detecting pitch frequencies, which is often hard due to the occurrence of harmonics and the fact that notes are often glided into (C# can be detected instead of C), also due to tuning discrepancies.
beat detection: audio performances are often not played in time exactly, so finding the actual onsets can be tricky.
A promising novel approach is to use deep neural networks to solve this, e.g.:
Boulanger-Lewandowski, N., Bengio, Y., & Vincent, P. (2012). Modeling temporal dependencies in high-dimensional sequences: Application to polyphonic music generation and transcription. arXiv preprint arXiv:1206.6392.
More info:
Poliner, G. E., Ellis, D. P., Ehmann, A. F., Gómez, E., Streich, S., & Ong, B. (2007). Melody transcription from music audio: Approaches and evaluation. IEEE Transactions on Audio, Speech, and Language Processing, 15(4), 1247-1256.
I am working on a small project in the lab with an Arduino Mega 2560 board. I want to average the signal (voltage) of the positive-slope portion (rise) of a triangle wave to try to remove as much noise as possible. My frequency is 20Hz and I am working with a data rate of 115200 bits/second (fastest recommended by Arduino for data transfer to a computer).
The raw signal looks like this:
My data is stored in a text file, with each line corresponding to a data point. Since I do have thousands of data points, I expect that some averaging would smooth the way my signal looks and make a close-to-perfect straight line in this case. However, other experimental conditions might lead to a signal where I could have features along the positive-slope portion of the triangle wave, such as a negative peak, and I absolutely do need to be able to see this feature on my averaged signal.
I am a Python beginner so I might not have the ideal approach to do so and my code might look bad for most of you guys but I would still like to get your hints / ideas on how to improve my signal processing code to achieve a better noise removal by averaging the signal.
#!/usr/bin/python
import matplotlib.pyplot as plt
import math
# *** OPEN AND PLOT THE RAW DATA ***
data_filename = "My_File_Name"
filepath = "My_File_Path" + data_filename + ".txt"
# Open the Raw Data
with open(filepath, "r") as f:
rawdata = f.readlines()
# Remove the \n
rawdata = map(lambda s: s.strip(), rawdata)
# Plot the Raw Data
plt.plot(rawdata, 'r-')
plt.ylabel('Lightpower (V)')
plt.show()
# *** FIND THE LOCAL MAXIMUM AND MINIMUM
# Number of data points for each range
datarange = 15 # This number can be changed for better processing
max_i_range = int(math.floor(len(rawdata)/datarange))-3
#Declare an empty lists for the max and min
min_list = []
max_list = []
min_list_index = []
max_list_index = []
i=0
for i in range(0, max_i_range):
delimiter0 = i * datarange
delimiter1 = (i+1) * datarange
delimiter2 = (i+2) * datarange
delimiter3 = (i+3) * datarange
sumrange1 = sum(float(rawdata[i]) for i in range(delimiter0, delimiter1 + 1))
averagerange1 = sumrange1 / len(rawdata[delimiter0:delimiter1])
sumrange2 = sum(float(rawdata[i]) for i in range(delimiter1, delimiter2 + 1))
averagerange2 = sumrange2 / len(rawdata[delimiter1:delimiter2])
sumrange3 = sum(float(rawdata[i]) for i in range(delimiter2, delimiter3 + 1))
averagerange3 = sumrange3 / len(rawdata[delimiter2:delimiter3])
# Find if there is a minimum in range 2
if ((averagerange1 > averagerange2) and (averagerange2 < averagerange3)):
min_list.append(min(rawdata[delimiter1:delimiter2])) # Find the value of all the minimum
#Find the index of the minimum
min_index = delimiter1 + [k for k, j in enumerate(rawdata[delimiter1:delimiter2]) if j == min(rawdata[delimiter1:delimiter2])][0] # [0] To use the first index out of the possible values
min_list_index.append(min_index)
# Find if there is a maximum in range 2
if ((averagerange1 < averagerange2) and (averagerange2 > averagerange3)):
max_list.append(max(rawdata[delimiter1:delimiter2])) # Find the value of all the maximum
#Find the index of the maximum
max_index = delimiter1 + [k for k, j in enumerate(rawdata[delimiter1:delimiter2]) if j == max(rawdata[delimiter1:delimiter2])][0] # [0] To use the first index out of the possible values
max_list_index.append(max_index)
# *** PROCESS EACH RISE PATTERN ***
# One rise pattern goes from a min to a max
numb_of_rise_pattern = 50 # This number can be increased or lowered. This will average 50 rise patterns
max_min_diff_total = 0
for i in range(0, numb_of_rise_pattern):
max_min_diff_total = max_min_diff_total + (max_list_index[i]-min_list_index[i])
# Find the average number of points for each rise pattern
max_min_diff_avg = abs(max_min_diff_total / numb_of_rise_pattern)
# Find the average values for each of the rise pattern
avg_position_value_list = []
for i in range(0, max_min_diff_avg):
sum_position_value = 0
for j in range(0, numb_of_rise_pattern):
sum_position_value = sum_position_value + float( rawdata[ min_list_index[j] + i ] )
avg_position_value = sum_position_value / numb_of_rise_pattern
avg_position_value_list.append(avg_position_value)
#Plot the Processed Signal
plt.plot(avg_position_value_list, 'r-')
plt.title(data_filename)
plt.ylabel('Lightpower (V)')
plt.show()
At the end, the processed signal looks like this:
I would expect a straighter line, but I could be wrong. I believe that there are probably a lot of flaws in my code and there would certainly be better ways to achieve what I want. I have included a link to a text file with some raw data if any of you guys want to have fun with it.
http://www108.zippyshare.com/v/2iba0XMD/file.html
Simpler might be to use a smoothing function, such as a moving window average. This is pretty simple to implement using the rolling function from pandas.Series. (Only 501 points are shown.) Tweak the numerical argument (window size) to get different amounts of smoothing.
import pandas as pd
import matplotlib.pyplot as plt
# Plot the Raw Data
ts = rawdata[0:500]
plt.plot(ts, 'r-')
plt.ylabel('Lightpower (V)')
# previous version
# smooth_data = pd.rolling_mean(rawdata[0:500],5).plot(style='k')
# changes to pandas require a change to the code as follows:
smooth_data = pd.Series(ts).rolling(window=7).mean().plot(style='k')
plt.show()
Moving Average
A moving average is, basically, a low-pass filter. So, we could also implement a low-pass filter with functions from SciPy as follows:
import scipy.signal as signal
# First, design the Buterworth filter
N = 3 # Filter order
Wn = 0.1 # Cutoff frequency
B, A = signal.butter(N, Wn, output='ba')
smooth_data = signal.filtfilt(B,A, rawdata[0:500])
plt.plot(ts,'r-')
plt.plot(smooth_data[0:500],'b-')
plt.show()
Low-Pass Filter
The Butterworth filter method is from OceanPython.org, BTW.
I have a .wav file, I load it and I get the next spectrogram showing the spectrum in dB
http://i.stack.imgur.com/22TjY.png
Now I would like to know these values exactly because I want to compare with other wav file, for recognizing if these 4 values are there.
http://i.stack.imgur.com/Jun25.png
The source to generate that pictures (taken from other stackoverflow example)
## some stuff here
for i in range(0, int(RATE / CHUNK_SIZE * RECORD_SECONDS)):
# little endian, signed shortdata_chunk
data_chunk = array('h', stream.read(CHUNK_SIZE))
if byteorder == 'big':
data_chunk.byteswap()
data_all.extend(data_chunk)
## some stuff here
Fs = 16000
f = np.arange(1, 9) * 2000
t = np.arange(RECORD_SECONDS * Fs) / Fs
x = np.empty(t.shape)
for i in range(8):
x[i*Fs:(i+1)*Fs] = np.cos(2*np.pi * f[i] * t[i*Fs:(i+1)*Fs])
w = np.hamming(512)
Pxx, freqs, bins = mlab.specgram(data_all, NFFT=512, Fs=Fs, window=w,
noverlap=464)
#plot the spectrogram in dB
Pxx_dB = np.log10(Pxx)
pyplot.subplots_adjust(hspace=0.4)
pyplot.subplot(211)
ex1 = bins[0], bins[-1], freqs[0], freqs[-1]
pyplot.imshow(np.flipud(Pxx_dB), extent=ex1)
pyplot.axis('auto')
pyplot.axis(ex1)
pyplot.xlabel('time (s)')
pyplot.ylabel('freq (Hz)')
I "think" that the information is in Pxx but I don't know how to get it.
From the documentation, I gather that Pxx is a simple 2D numpy array.
You're interested in periodograms around 1s. Considering Pxx should have 512 columns and your sample is about 5s long, I'd take a slice somewhere around column 100:
periodogram_of_interest = Pxx[:, 100]
Then find the 4 maxima. Unfortunately, each of those 4 frequencies has a finite width, so simply looking for the top 4 maxima will nog be as easy. However, assuming your signal is quite clean, there's a function in scipy.signal that will list all local extrema: argrelmax. You could play with the order argument of that function to reduce your search space.
With the values returned from that function, you could get the frequencies like this: freqs[those_4_indices].