So here is the deal, I have a module which sends out data over the serial port at 9600 baud and I am using the matplotlib to plot that data in real time. I wrote this code
#! python
############ import section ###################
import serial
import struct
import numpy as np
import matplotlib.pyplot as plt
###############################################
############ Serial Configuration #############
com = serial.Serial(14,9600)
print ("Successfully opened " + com.portstr)
###############################################
def get_new_values():
s = com.read(20)
raw_data = struct.unpack('!BBBBBBBBBBBBBBBBBBBB', s)
j = 0;
norm_data = []
for i in range(0,20,2):
big_byte = raw_data[i+1]+(raw_data[i]*256)
norm_data.append(big_byte)
j = j+1
return norm_data
x = range(0,10,1)
y = get_new_values()
plt.ion()
line, = plt.plot(x,y)
while(1):
a = get_new_values()
line.set_ydata(a)
plt.draw()
com.close()
print ("Port Closed Successfully")
I get 20 bytes and then make 10 big bytes (2 byte data is split as two 1 byte values while transmitting). But I just noticed that I am not getting real time values from this. I am on windows 7 home basic if that matters.
Any clue why this is happening?
EDIT
Also, whenever I click on the plot it hangs.
I know the code below seems long and overly complex for your simple problem, but manual optimization is usually more code and more potential bugs. That's why premature optimization is almost always a mistake.
In __init__ it sets up the plot, setting references for the axis, canvas, line (it starts with a line drawn off screen), and the pre-animation background. Additionally __init__ registers callbacks to handle resizing and shutdown. The on_resize callback is needed to update the background (used for the blit) when the window is resized. The on_close callback uses a lock to update the running status. I haven't eliminated all race conditions with this, but this works to prevent a _tkinter.TclError caused by trying to blit to a terminated Tk app. I only tested with Tk, and on only one machine. YMMV, and I'm open to suggestions.
In the run method I've added a call to canvas.flush_events(). This should keep the plot window from hanging if you try to drag the window around and resize it. The while loop in this method calls self.get_new_values() to set the data in the plot. It then updates the plot using the configured method. If self.blit is true, it uses canvas.blit, else it uses pyplot.draw.
The variable spf (samples per frame) controls how many samples are plotted in a frame. You can use it in your implementation of get_new_values to determine the number of bytes to read (e.g. 2 * self.spf for 2 bytes per sample). I've set the default to 120, which is 5 frames per second if your data rate is 600 samples per second. You have to find the sweet spot that maximizes throughput vs time resolution in the graph while also keeping up with the incoming data.
Reading your data into a NumPy array instead of using a Python list will likely speed up processing. Also, it would give you easy access to tools to downsample and analyze the signal. You can read into a NumPy array directly from a byte string, but make sure you get the endianess right:
>>> data = b'\x01\xff' #big endian 256 + 255 = 511
>>> np.little_endian #my machine is little endian
True
>>> y = np.fromstring(data, dtype=np.uint16); y #so this is wrong
array([65281], dtype=uint16)
>>> if np.little_endian: y = y.byteswap()
>>> y #fixed
array([511], dtype=uint16)
Code:
from __future__ import division
from matplotlib import pyplot
import threading
class Main(object):
def __init__(self, samples_per_frame=120, blit=True):
self.blit = blit
self.spf = samples_per_frame
pyplot.ion()
self.ax = pyplot.subplot(111)
self.line, = self.ax.plot(range(self.spf), [-1] * self.spf)
self.ax.axis([0, self.spf, 0, 65536])
pyplot.draw()
self.canvas = self.ax.figure.canvas
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
self.canvas.mpl_connect('resize_event', self.on_resize)
self.canvas.mpl_connect('close_event', self.on_close)
self.lock = threading.Lock()
self.run()
def get_new_values(self):
import time
import random
#simulate receiving data at 9600 bps (no cntrl/crc)
time.sleep(2 * self.spf * 8 / 9600)
y = [random.randrange(65536) for i in range(self.spf)]
self.line.set_ydata(y)
def on_resize(self, event):
self.line.set_ydata([-1] * self.spf)
pyplot.draw()
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
def on_close(self, event):
with self.lock:
self.running = False
def run(self):
with self.lock:
self.running = True
while self.running:
self.canvas.flush_events()
with self.lock:
self.get_new_values()
if self.running:
if self.blit:
#blit for a higher frame rate
self.canvas.restore_region(self.background)
self.ax.draw_artist(self.line)
self.canvas.blit(self.ax.bbox)
else:
#versus a regular draw
pyplot.draw()
if __name__ == '__main__':
Main(samples_per_frame=120, blit=True)
Related
I'm working on a program that displays drawings, with the option of animating the drawing to show which order the lines should be drawn in. When I used plt.show() to display the drawing as a still image, everything works as expected: the code pauses, and then resumes again as soon as the popup window is closed. However, when I use the same function to display an animated drawing, the code remains frozen even after I close the popup window. The only way to get it unstuck is to fully restart the python shell - it doesn't respond to KeyboardInterrupt.
UPDATE:
When I set repeat to False in the FuncAnimation call, it behaves slightly differently. If I close the popup window while the animation is running, the glitch happens, locking up my program. However, if I close the popup after the animation has finished, the program continues as intended. It seems like the glitch here has something to do with closing the window before the animation is done.
UPDATE 2:
For some reason, replacing all of the plt.plot() calls in the animate_pattern function with ax.plot() fixes the issue. I have no idea why this works, because as far as I know the two functions do the same thing. However, the problem is solved.
Below is the code for the module that handles the animation. Some notes:
Normally, I create the animations by calling plot_animated() from a different module. However, the bug happens whether or not I create the animation that way or do it through the code in this module's if name == main statement.
convert_to_points() is a function from the main module that turns the data it's given into a list of x-values and a list of y-values to be plotted.
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation,PillowWriter
from functools import partial
from os.path import isfile
import json
end_marker = [None]
smooth = 40
def animate_pattern(f,anim_data):
x_anim,y_anim,scale = anim_data
global end_marker
# starting point
if x_anim[f] is None:
end_marker = plt.plot(x_anim[1],y_anim[1],marker='o',ms=1.8*scale,mew=0.4*scale,mec="black",c="#ff6bff")
# segment going into a point
if f%smooth == 1:
plt.plot(x_anim[f-1:f+1],y_anim[f-1:f+1],c="#ff6bff",lw=scale)
plt.plot(x_anim[f],y_anim[f],marker='o',ms=1.8*scale,mew=0.4*scale,mec="black",c="#ff6bff")
# segment coming out of a point
elif f%smooth in (2,4):
plt.plot(x_anim[f-1:f+1],y_anim[f-1:f+1],c="#ff6bff",lw=scale)
plt.plot(x_anim[f-f%smooth+1],y_anim[f-f%smooth+1],marker='o',ms=1.8*scale,mew=0.4*scale,mec="black",c="#ff6bff")
# all other segments
else:
plt.plot(x_anim[f-1:f+1],y_anim[f-1:f+1],c="#ff6bff",lw=scale)
# marker for current endpoint of animated line
if x_anim[f]:
end_marker[0].remove()
end_marker = plt.plot(x_anim[f],y_anim[f],marker='h',ms=2.4*scale,mew=0.5*scale,mec="#547dd6",c="#6bc9e8")
def init_pattern(plot_data,settings):
x_vals,y_vals,scale = plot_data[:3]
# clear the canvas
plt.cla()
plt.gca().axis("off")
# draw the full pattern in the background
for i in range(len(x_vals)-1):
plt.plot(x_vals[i:i+2],y_vals[i:i+2],color=settings["monochrome_color"],lw=scale)
plt.plot(x_vals[i],y_vals[i],'ko',ms=2*scale)
plt.plot(x_vals[-1],y_vals[-1],'ko',ms=2*scale)
def anim_interpolate(plot_data):
x_vals,y_vals,scale = plot_data[:3]
x_anim,y_anim = [None],[None]
# create five interpolated points after each point
for i in range(len(x_vals)-1):
x_dist = x_vals[i+1] - x_vals[i]
y_dist = y_vals[i+1] - y_vals[i]
x_anim += [x_vals[i]+x_dist*(1/smooth)*j for j in range(smooth)]
y_anim += [y_vals[i]+y_dist*(1/smooth)*j for j in range(smooth)]
# add the last point
x_anim.append(x_vals[-1])
y_anim.append(y_vals[-1])
return x_anim,y_anim,scale
def plot_animated(plot_data,settings,):
# convert basic pointlist into special version for animating
anim_data = anim_interpolate(plot_data)
# create animation object by repeatedly invoking animate_pattern()
ani = FuncAnimation(plt.gcf(),
func=animate_pattern,
fargs=[anim_data],
frames=len(anim_data[0]),
init_func=partial(init_pattern,plot_data,settings),
interval=1000/smooth,
repeat=True)
return ani
if __name__ == "__main__":
with open("settings.json",mode="r") as file:
settings = json.load(file)
from hex_draw import convert_to_points
print("Displaying test animation...")
plot_data = convert_to_points("qeewdweddw","northeast",settings)
ax = plt.figure(figsize=(4,4)).add_axes([0,0,1,1])
ax.set_aspect("equal")
ani = plot_animated(plot_data,settings)
plt.show()
I am quite fresh using Python. I need it to acquire serial datas from a force sensor plugged into a COMPORT (6). With the code below, I have no problem to store the datas in a list and save it afterwards. I can also print each data without noticing any lag.
However, when I try to implement a plot in my while loop, a rather annoying shift appears between the time I touch my sensor and the datas are written (from few to tens of seconds). I first thought it was because matplotlib is a memory consuming library, but even when i add the simple line "time.sleep(0.00001), which is a very short pause compared to the rate of acquisition (60 FPS), I get the same lags. I also tried to save my datas in a csv file and plot the datas in a different function by using multiprocess but even saving datas triggers the lag also.
This is problematic as visualizing my live datas is an important part of my experiment.
Could someone please help me with this particular issue ?
Thank you so much.
import serial
import struct
import platform
import multiprocessing
import time
import numpy as np
import csv
# from pylab import *
import matplotlib.pyplot as plt
class MeasurementConverter:
def convertValue(self, bytes):
pass
class ForceMeasurementConverterKG(MeasurementConverter):
def __init__(self, F_n, S_n, u_e):
self.F_n = F_n
self.S_n = S_n
self.u_e = u_e
def convertValue(self, value):
A = struct.unpack('>H', value)[0]
# return (A - 0x8000) * (self.F_n / self.S_n) * (self.u_e / 0x8000)
return self.F_n / self.S_n * ((A - 0x8000) / 0x8000) * self.u_e * 2
class GSV3USB:
def __init__(self, com_port, baudrate=38400):
com_path = f'/dev/ttyUSB{com_port}' if platform.system(
) == 'Linux' else f'COM{com_port}'
# print(f'Using COM: {com_path}')
self.sensor = serial.Serial(com_path, baudrate)
self.converter = ForceMeasurementConverterKG(10, 0.499552, 2)
def read_value(self):
self.sensor.read_until(b'\xA5')
read_val = self.sensor.read(2)
return self.converter.convertValue(read_val)
# initialization of datas
gsv_data=[]
temps=[]
t_0=time.time()
def data_gsv():
dev = GSV3USB(6)
# fig=plt.figure()
# ax = fig.add_subplot(111)
i=0
# line1, = ax.plot(temps, gsv_data)
try:
while True:
gsv_data.append(dev.read_value())
t1=time.time()-t_0
temps.append(t1)
# I can print the datas without noticing any lags
print(dev.read_value())
# I cannot plot the datas without noticing any lags
plt.plot(temps,gsv_data)
plt.draw ()
plt.axis([temps[i]-6,temps[i]+6,-2,10])
plt.pause(0.00001)
plt.clf()
i=i+1
# I cannot pause without noticing any lags
time.sleep(0.0001)
# I cannot save datas without noticing any lags
with open('student_gsv.csv', 'w') as f:
write = csv.writer(f)
write.writerow(gsv_data)
except KeyboardInterrupt:
print("Exiting")
return
if __name__ == "__main__":
data_gsv()```
Your main loop is constructed in a way that each time when you acquire a value with gsv_data.append(dev.read_value()), the plot is drawn again. That probably takes time, and if the data frequency with that the measuring device (GSV-3 USB) send measured data is quite high - let's say >10 frames/s - you'll get these "lags".
I would lower the plot update rate:
append the measured data to an array (as you already do)
Use a counter or a comparison of time difference to get a plot update rate of about 4 times per second (250ms). This is an update rate often used for being consumed by the human eye.
Inspired by the answer to this question, I have tried the following code:
import nidaqmx
from nidaqmx import stream_readers
from nidaqmx import constants
import time
sfreq = 1000
bufsize = 100
data = np.zeros((1, 1), dtype = np.float32) # initializes total data file
with nidaqmx.Task() as task:
task.ai_channels.add_ai_voltage_chan("cDAQ2Mod1/ai1")
task.timing.cfg_samp_clk_timing(rate = sfreq, sample_mode = constants.AcquisitionType.CONTINUOUS,
samps_per_chan = bufsize) # unclear samps_per_chan is needed or why it would be different than bufsize
stream = stream_readers.AnalogMultiChannelReader(task.in_stream)
def reading_task_callback(task_id, event_type, num_samples, callback_data=None): # num_samples is set to bufsize
buffer = np.zeros((1, num_samples), dtype = np.float32) # probably better to define it here inside the callback
stream.read_many_sample(buffer, num_samples, timeout = constants.WAIT_INFINITELY)
data = np.append(data, buffer, axis = 1) # hopping to retrieve this data after the read is stopped
task.register_every_n_samples_acquired_into_buffer_event(bufsize, reading_task_callback)
Expected behavior: it reads continuously from a channel. I am not even trying to get it to do something specific yet (such as plotting in real time), but I would expect the python console to run until one stops it, since the goal is to read continuously.
Observed behavior: running this code proceeds quickly and the console prompt is returned.
Problem: it seems to me this is not reading continuously at all. Furthermore, the data variable does not get appended like I would like it to (I know that retrieving a certain number of data samples does not require such convoluted code with nidaqmx; this is just one way I thought I could try and see if this is doing what I wanted, i.e. read continuously and continuously append the buffered sample values to data, so that I can then look at the total data acquired).
Any help would be appreciated. I am essentially certain the way to achieve this is by making use of these callbacks which are part of nidaqmx, but somehow I do not seem to manage them well. Note I have been able to read a predefined and finite amount of data samples from analog input channels by making use of read_many_sample.
Details: NI cDAQ 9178 with NI 9205 module inserted, on Lenovo laptop running Windows Home 10, python 3.7 and nidaqmx package for python.
EDIT: for anyone interested, I now have this working in the following way, with a live visual feedback using matplotlib, and - not 100% percent sure yet - it seems there no buffer problems even if one aims at long acquisitions (>10 minutes). Here is the code (not cleaned, sorry):
"""
Analog data acquisition for QuSpin's OPMs via National Instruments' cDAQ unit
The following assumes:
"""
# Imports
import matplotlib.pyplot as plt
import numpy as np
import nidaqmx
from nidaqmx.stream_readers import AnalogMultiChannelReader
from nidaqmx import constants
# from nidaqmx import stream_readers # not needed in this script
# from nidaqmx import stream_writers # not needed in this script
import threading
import pickle
from datetime import datetime
import scipy.io
# Parameters
sampling_freq_in = 1000 # in Hz
buffer_in_size = 100
bufsize_callback = buffer_in_size
buffer_in_size_cfg = round(buffer_in_size * 1) # clock configuration
chans_in = 3 # set to number of active OPMs (x2 if By and Bz are used, but that is not recommended)
refresh_rate_plot = 10 # in Hz
crop = 10 # number of seconds to drop at acquisition start before saving
my_filename = 'test_3_opms' # with full path if target folder different from current folder (do not leave trailing /)
# Initialize data placeholders
buffer_in = np.zeros((chans_in, buffer_in_size))
data = np.zeros((chans_in, 1)) # will contain a first column with zeros but that's fine
# Definitions of basic functions
def ask_user():
global running
input("Press ENTER/RETURN to stop acquisition and coil drivers.")
running = False
def cfg_read_task(acquisition): # uses above parameters
acquisition.ai_channels.add_ai_voltage_chan("cDAQ2Mod1/ai1:3") # has to match with chans_in
acquisition.timing.cfg_samp_clk_timing(rate=sampling_freq_in, sample_mode=constants.AcquisitionType.CONTINUOUS,
samps_per_chan=buffer_in_size_cfg)
def reading_task_callback(task_idx, event_type, num_samples, callback_data): # bufsize_callback is passed to num_samples
global data
global buffer_in
if running:
# It may be wiser to read slightly more than num_samples here, to make sure one does not miss any sample,
# see: https://documentation.help/NI-DAQmx-Key-Concepts/contCAcqGen.html
buffer_in = np.zeros((chans_in, num_samples)) # double definition ???
stream_in.read_many_sample(buffer_in, num_samples, timeout=constants.WAIT_INFINITELY)
data = np.append(data, buffer_in, axis=1) # appends buffered data to total variable data
return 0 # Absolutely needed for this callback to be well defined (see nidaqmx doc).
# Configure and setup the tasks
task_in = nidaqmx.Task()
cfg_read_task(task_in)
stream_in = AnalogMultiChannelReader(task_in.in_stream)
task_in.register_every_n_samples_acquired_into_buffer_event(bufsize_callback, reading_task_callback)
# Start threading to prompt user to stop
thread_user = threading.Thread(target=ask_user)
thread_user.start()
# Main loop
running = True
time_start = datetime.now()
task_in.start()
# Plot a visual feedback for the user's mental health
f, (ax1, ax2, ax3) = plt.subplots(3, 1, sharex='all', sharey='none')
while running: # make this adapt to number of channels automatically
ax1.clear()
ax2.clear()
ax3.clear()
ax1.plot(data[0, -sampling_freq_in * 5:].T) # 5 seconds rolling window
ax2.plot(data[1, -sampling_freq_in * 5:].T)
ax3.plot(data[2, -sampling_freq_in * 5:].T)
# Label and axis formatting
ax3.set_xlabel('time [s]')
ax1.set_ylabel('voltage [V]')
ax2.set_ylabel('voltage [V]')
ax3.set_ylabel('voltage [V]')
xticks = np.arange(0, data[0, -sampling_freq_in * 5:].size, sampling_freq_in)
xticklabels = np.arange(0, xticks.size, 1)
ax3.set_xticks(xticks)
ax3.set_xticklabels(xticklabels)
plt.pause(1/refresh_rate_plot) # required for dynamic plot to work (if too low, nulling performance bad)
# Close task to clear connection once done
task_in.close()
duration = datetime.now() - time_start
# Final save data and metadata ... first in python reloadable format:
filename = my_filename
with open(filename, 'wb') as f:
pickle.dump(data, f)
'''
Load this variable back with:
with open(name, 'rb') as f:
data_reloaded = pickle.load(f)
'''
# Human-readable text file:
extension = '.txt'
np.set_printoptions(threshold=np.inf, linewidth=np.inf) # turn off summarization, line-wrapping
with open(filename + extension, 'w') as f:
f.write(np.array2string(data.T, separator=', ')) # improve precision here!
# Now in matlab:
extension = '.mat'
scipy.io.savemat(filename + extension, {'data':data})
# Some messages at the end
num_samples_acquired = data[0,:].size
print("\n")
print("OPM acquisition ended.\n")
print("Acquisition duration: {}.".format(duration))
print("Acquired samples: {}.".format(num_samples_acquired - 1))
# Final plot of whole time course the acquisition
plt.close('all')
f_tot, (ax1, ax2, ax3) = plt.subplots(3, 1, sharex='all', sharey='none')
ax1.plot(data[0, 10:].T) # note the exclusion of the first 10 iterations (automatically zoomed in plot)
ax2.plot(data[1, 10:].T)
ax3.plot(data[2, 10:].T)
# Label formatting ...
ax3.set_xlabel('time [s]')
ax1.set_ylabel('voltage [V]')
ax2.set_ylabel('voltage [V]')
ax3.set_ylabel('voltage [V]')
xticks = np.arange(0, data[0, :].size, sampling_freq_in)
xticklabels = np.arange(0, xticks.size, 1)
ax3.set_xticks(xticks)
ax3.set_xticklabels(xticklabels)
plt.show()
Of course comments are appreciated. This is probably still suboptimal.
I am trying to make a simple graph from a data gathered from a continuous real-time data source.
My code for using matplotlib is below:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import time
from serialdata import SerialData
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
def animate(i):
xar = []
yar = []
#Open Serial Port and Receive Continuous Data
#in format of number,number
a = SerialData()
b = a.setSerial('COM3', 9600)
dataArray = a.getSerial(9999)
for eachLine in dataArray:
if len(eachLine)>1:
x,y = eachLine.split(',')
xar.append(int(x))
yar.append(int(y))
ax1.clear()
ax1.plot(xar,yar)
ani = animation.FuncAnimation(fig, animate, interval=1000)
plt.show()
The serialdata.py just yields the data every time it gets from the data source:
import serial
from time import sleep
class SerialData:
def __init__(self):
pass
def setSerial(self, port, baudrate):
self.port = port
self.baudrate = baudrate
print("Opening Serial Port...")
self.ser = serial.Serial(self.port, self.baudrate, timeout=1)
sleep(2)
print("Setup Successful")
def getSerial(self, read):
while True:
self.data = self.ser.read(read)
if len(self.data)>0:
yield self.data.decode('utf-8')
sleep(.1)
self.ser.close()
supposedly it should send data in form of:
1,2
2,5
3,7
4(autoincrement),5(random number)
and works just fine when I just make them print on the CLI.
However I cannot make it work with the matplotlib.
There is no specific error.
It just shows
Opening Serial Port...
Setup Successful
and... thats it. nothing happens then.
What is wrong with my code?
I did more research and found that I shouldn't be using show()
so I rewrote my code as following:
import time
import numpy as np
import matplotlib.pyplot as plt
from serialdata import SerialData
plt.axis([0, 1000, 0, 1])
plt.ion()
plt.show()
for i in range(1000):
# y = np.random.random()
a = SerialData()
b = a.setSerial('COM3', 9600)
dataArray = a.getSerial(9999)
print("Data Gathering...")
for eachLine in dataArray:
if len(eachLine)>1:
y = eachLine.split(',')[1]
plt.scatter(i, y)
plt.draw()
time.sleep(0.05)
But, the result is the same.
I don't have a serial port, but this is what I tried to do to figure it out:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import time
import random
from time import sleep
class MockData():
def __init__(self, n):
self.n = n #number of data points to return
def getData(self):
start = 0 #mock autoincrement
for i in range(self.n):
yield (start, random.randint(0, 100)) #yield a tuple, not a str (x(autoincrem),y(random))
start+=1
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
def animate(i):
xar = []
yar = []
#Open Serial Port and Receive Continuous Data
#in format of number,number
a = MockData(10)
dataArray = a.getData() #this is a generator that will yield (x,y) tuples 10 times.
for eachLine in dataArray:
x,y = eachLine
xar.append(int(x))
yar.append(int(y))
ax1.clear()
ax1.plot(xar,yar)
ani = animation.FuncAnimation(fig, animate, interval=1000)
plt.show()
This code will fail\be very slow:
If I request a large amount of data at a time: a=MockData(1000) will execute for ~2-3seconds per frame
If readout times are long i.e.
def getData(self):
start = 0
for i in range(self.n):
yield (start, random.randint(0, 100))
start+=1
time.sleep(1)
this will execute ~10 seconds per frame
Both
So as far as I can conclude issue is in your SerialData class, specifically in the getSerial method. I know that because that's the method that's responsible for retrieving the actual data. Since I don't have a serial port I can't exactly test where, but I can wager a few guesses.
def getSerial(self, read):
while True:
self.data = self.ser.read(read)
if len(self.data)>0:
yield self.data.decode('utf-8')
sleep(.1)
self.ser.close()
self.data = self.ser.read(read) Issue here is that you request 9999 bytes to be read. 9600 bauds is about 1200 bytes/s. To read 9 999 bytes it would take ~10seconds. And that's if there's actually 9999 new bytes to be read. If there weren't the read function would continue waiting until it read in that amount. This is equal to my 1) test case, except the waiting period would be sleep(10). The if check therefore is already in the read function so you don't have to check again.
self.data.decode('utf-8') Check out how long it takes to decode 9999 bytes:
>>> from timeit import Timer
>>> ts = Timer("s.decode('utf-8')", "s = b'1'*9999")
>>> ts.timeit()
2.524627058740407
Now, granted, this isn't the same conversion as yours, but since I don't have a serial port on my laptop I can't test it. Anyhow, it seems like it's very slow. Right now, you have my case 2) with sleep(12)
sleep(.1) this now just seems like adding an insult to injury.
Your code doesn't report an error because it works, it just takes over 3 minutes to read in the first set of data and plot it.
My recommendation is that you ignore this approach, read in data, almost literally, byte per byte, plot them as they come. Have an array in which you just append new bytes as they come and plot that. You could easily pull something like:
serialPort = serial.Serial(self.port, self.baudrate, timeout=1)
data = list() #some global list to append to
def animate():
d = serialPort.read(2) #this will hang until it completes
data.append(d) #does the data really have to be in string?
ax.plot(data)
ani = animation.FuncAnimation(fig, animate, interval=1000) #optionally send in repeat_delay?
plt.show()
though part about this is that I'm not sure how animation behaves if the data array starts getting really big, so you might consider shifting your x axis to the right, deleting your old data and creating a new array periodically.
Hopefully I was of some help in pointing out which things you need to test for. Start timing all your read-ins from serial port to get a feeling for how long do things take. Don't forget to make sure you're actually reading anything at all from the serial port also, if you're not, the serial.read will just wait there.
I've also never done anything like this, so it's possible I'm way off course. Last time I dealt with serial ports was robotics championship from elementary (rofl).
I'm trying to create a Matplotlib animation of my paw data, where you can see the pressure distribution on the entire pressure plate over time (256x64 sensors for 250 frames).
I found a working example on Matplotlib's own site and managed to get it working on my own data. However the 'animation' is awfully slow and I have no idea how to speed it up.
Here's an example of a gif Joe Kington made in another answer, which is about the speed with which it gets displayed. Considering the measurements are done at 125 Hz, this makes the measurements look awfully slow. If it ran at 30-60 fps, it could be run in 4 or 8 seconds rather than the current 20+.
I don't mind using whatever tool I need to get the job done, as long as there's some good documentation to figure out how to do it.
So my question is: how can I speed up these animations?
I've implemented Ignacio's suggestion to put in t.Start(1), however it only runs 'decently' when the Figure is this large:
class PlotFigure(Frame):
""" This class draws a window and updates it with data from DataCollect
"""
def __init__(self):
Frame.__init__(self, None, -1, "Test embedded wxFigure")
#Varying the size of Figure has a big influence on the speed
self.fig = Figure((3,3), 75)
self.canvas = FigureCanvasWxAgg(self, -1, self.fig)
EVT_TIMER(self, TIMER_ID, self.onTimer)
def init_plot_data(self):
self.datagen = DataCollect(array3d)
self.axes = self.fig.add_subplot(111)
self.axes.imshow(self.datagen.next().T)
def onTimer(self, evt):
self.data = self.datagen.next()
self.axes.imshow(self.datagen.next().T)
self.canvas.draw()
When I resize the window during the animation, it immediately slows down to a crawl. Which makes me suspect the delay isn't the only cause of the slow down. So any other suggestions? In case you're curious, here's a link to one of the ASCII files.
I found Joe Kington's answer that mentioned using Glumpy instead. At first I couldn't get it to work on my own data, but with some help on chat we managed to figure out how to adapt one of the Matplotlib examples that come with Glumpy to work on my data.
import numpy, glumpy
from glumpy.pylab import *
window = glumpy.Window(256,64)
Z = data.astype(numpy.float32)
t0, frames, t = 0,0,0
fig = plt.figure(figsize=(7,7))
ax = plt.subplot(111)
ax = imshow(Z[:,:,0], origin='lower', interpolation='bilinear')
show()
window = glumpy.active_window()
#window.event
def on_idle(dt):
global Z, t0, frames, t
t += dt
frames = frames + 1
if frames > 248:
fps = float(frames)/(t-t0)
print 'FPS: %.2f (%d frames in %.2f seconds)' % (fps, frames, t-t0)
frames,t0 = 0, t
for image, axis, alpha in items:
image.data[...] = Z[:,:,frames]
image.update()
window.draw()
window.mainloop()
The end result can be seen here, it doesn't matter how large I make the window, it will run at a very steady 58+ fps. So I must say, I'm very pleased with the end result!
The value passed to wx.Timer.Start() is the trigger rate in milliseconds. Pass a smaller value.
Use a Profiler to find the root cause, frame skipping might be useful too as a last resort.
Or switch to an alternative solution like Double Buffering using Device Contexts or PyOpenGL...