I need to plot in realtime a series floating point numbers from the serial port. These values are sepparated by the '\n' character, so the data sequence is something like this:
x1
x2
x3
...
How would you plot the data?
I am using an Arduino board, the data rate is 200 samples/s, and my PC is running on Windows7 64 bits.
I think a good choice is use the pyqtgraph library. I started to use the Plotting.py example in pyqtgraph (plenty more examples available after installing pyqtgraph and then running python3 -m pyqtgraph.examples), but I don't know how to adapt this code for my needs (see below).
Thank you very much in advance.
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
# Set graphical window, its title and size
win = pg.GraphicsWindow(title="Sample process")
win.resize(1000,600)
win.setWindowTitle('pyqtgraph example')
# Enable antialiasing for prettier plots
pg.setConfigOptions(antialias=True)
# Random data process
p6 = win.addPlot(title="Updating plot")
curve = p6.plot(pen='y')
data = np.random.normal(size=(10,1000)) # If the Gaussian distribution shape is, (m, n, k), then m * n * k samples are drawn.
# plot counter
ptr = 0
# Function for updating data display
def update():
global curve, data, ptr, p6
curve.setData(data[ptr%10])
if ptr == 0:
p6.enableAutoRange('xy', False) ## stop auto-scaling after the first data set is plotted
ptr += 1
# Update data display
timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(50)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Here is the code that works fine. The main process is contained in the update() function. It reads the input value from the serial port, updates the array Xm (that contains the input values) and then updates its associated curve.
This code was posted for the sake of simplicity and works just for low data rates (less than 100 samples/s). For higher data rates, it should be modified inside the update() function as follows. A set of values (instead of a single one) should be read from the serial port. Then, such set should be appended to the array Xm
I hope this answer is useful for you, and thank you very much for your help!
# Import libraries
from numpy import *
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
import serial
# Create object serial port
portName = "COM12" # replace this port name by yours!
baudrate = 9600
ser = serial.Serial(portName,baudrate)
### START QtApp #####
app = QtGui.QApplication([]) # you MUST do this once (initialize things)
####################
win = pg.GraphicsWindow(title="Signal from serial port") # creates a window
p = win.addPlot(title="Realtime plot") # creates empty space for the plot in the window
curve = p.plot() # create an empty "plot" (a curve to plot)
windowWidth = 500 # width of the window displaying the curve
Xm = linspace(0,0,windowWidth) # create array that will contain the relevant time series
ptr = -windowWidth # set first x position
# Realtime data plot. Each time this function is called, the data display is updated
def update():
global curve, ptr, Xm
Xm[:-1] = Xm[1:] # shift data in the temporal mean 1 sample left
value = ser.readline() # read line (single value) from the serial port
Xm[-1] = float(value) # vector containing the instantaneous values
ptr += 1 # update x position for displaying the curve
curve.setData(Xm) # set the curve with this data
curve.setPos(ptr,0) # set x position in the graph to 0
QtGui.QApplication.processEvents() # you MUST process the plot now
### MAIN PROGRAM #####
# this is a brutal infinite loop calling your realtime data plot
while True: update()
### END QtApp ####
pg.QtGui.QApplication.exec_() # you MUST put this at the end
##################
The best way to deal with this may be to run a separate "worker" thread to process your data and then update the graph. I believe you can do it with Qthread.
I don't know the exact reason why, but apparently .processEvents() is not the best way to solve this problem.
Related
Using the below python(ubuntu) code and rtlsdr, I am able to plot a graph. Can anyone please tell me how to modify this code to plot the graph continuously in real time?
from pylab import *
from rtlsdr import *
sdr = RtlSdr()
sdr.sample_rate = 2.4e6
sdr.center_freq = 93.5e6
sdr.gain = 50
samples = sdr.read_samples(256*1024)
sdr.close()
psd(samples.real, NFFT=1024, Fs=sdr.sample_rate/1e6, Fc=sdr.center_freq/1e6)
xlabel('Frequency (MHz)')
ylabel('Relative power (dB)')
show()
Normally you can update a pyplot-generated plot by calling plot.set_xdata(), plot.set_ydata(), and plot.draw() (Dynamically updating plot in matplotlib), without having to recreate the entire plot. However, this only works for plots that directly draw a data series. The plot instance cannot automatically recalculate the spectral density calculated by psd().
You'll therefore need to call psd() again when you want to update the plot - depending on how long it takes to draw, you could do this in regular intervals of a second or less.
This might work:
from pylab import *
from rtlsdr import *
from time import sleep
sdr = RtlSdr()
sdr.sample_rate = 2.4e6
sdr.center_freq = 93.5e6
sdr.gain = 50
try:
while True: # run until interrupted
samples = sdr.read_samples(256*1024)
clf()
psd(samples.real, NFFT=1024, Fs=sdr.sample_rate/1e6, Fc=sdr.center_freq/1e6)
xlabel('Frequency (MHz)')
ylabel('Relative power (dB)')
show()
sleep(1) # sleep for 1s
except:
pass
sdr.close()
Edit: Of course, I'm not sure how read_samples runs; my example here assumed that it returns almost immediately. If it blocks for a long time while waiting for data, you may want to read less data at a time, and discard old data as you do so:
from collections import deque
max_size = 256*1024
chunk_size = 1024
samples = deque([], max_size)
while True:
samples.extend(sdr.read_samples(chunk_size))
# draw plot
I want to create a real-time, point plotting GUI. I am using the Scanse Sweep LiDAR, and at each sweep of this LiDAR (working between 1 - 10Hz) I receive approximately 1000 points (x, y) describing the LiDARs surrounding. This is a 2D LiDAR.
I have looked everywhere and tried countless of code snippets for pyqtgraph, but either it crashes, is super slow or doesn't work at all.
Is there a straight-forward way of creating a plotter window and upon each new scan/data delivery, push those points to the plotter window?
Thankful for any kind of help
It is unclear to me what exactly you want to do, so I assume that you want to make a scatter plot with a 1000 points that are refreshed 10 times a second. Next time please include your code so that we can reproduce your issues and see what you want to achieve.
In my experience PyQtGraph is the fastest option in Python. It can easily plot a 1000 points at 10 Hz. See the example below.
#!/usr/bin/env python
from PyQt5 import QtCore, QtWidgets
import pyqtgraph as pg
import numpy as np
class MyWidget(pg.GraphicsWindow):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.mainLayout = QtWidgets.QVBoxLayout()
self.setLayout(self.mainLayout)
self.timer = QtCore.QTimer(self)
self.timer.setInterval(100) # in milliseconds
self.timer.start()
self.timer.timeout.connect(self.onNewData)
self.plotItem = self.addPlot(title="Lidar points")
self.plotDataItem = self.plotItem.plot([], pen=None,
symbolBrush=(255,0,0), symbolSize=5, symbolPen=None)
def setData(self, x, y):
self.plotDataItem.setData(x, y)
def onNewData(self):
numPoints = 1000
x = np.random.normal(size=numPoints)
y = np.random.normal(size=numPoints)
self.setData(x, y)
def main():
app = QtWidgets.QApplication([])
pg.setConfigOptions(antialias=False) # True seems to work as well
win = MyWidget()
win.show()
win.resize(800,600)
win.raise_()
app.exec_()
if __name__ == "__main__":
main()
The way it works is as follows. By plotting an empty list a PlotDataItem is created. This represents a collection of points. When new data points arrive, the setData method is used to set them as the data of the PlotDataItem, which removes the old points.
What is the fastest Python mechanism for getting data read off of a serial port, to a separate process which is plotting that data?
I am plotting eeg data in real-time that I read off of a serial port. The serial port reading and packet unpacking code works fine, in that if I read and store the data, then later plot the stored data, it looks great. Like this:
note: device generates test sine wave for debugging
I am using pyQtGraph for the plotting. Updating the plot in the same process that I read the serial data in is not an option because the slight delay between serial read() calls causes the serial buffer to overflow and bad check-sums ensue. pyQtGraph has provisions for rendering the graph on a separate process, which is great, but the bottle-neck seems to be in the inter-process communication. I have tried various configuration of Pipe() and Queue(), all of which result in laggy, flickering graph updates. So far, the smoothest, most consistent method of getting new values from the serial port to the graph seems to be through shared memory, like so:
from pyqtgraph.Qt import QtGui
import pyqtgraph as pg
from multiprocessing import Process, Array, Value, Pipe
from serial_interface import EEG64Board
from collections import deque
def serialLoop(arr):
eeg = EEG64Board(port='/dev/ttyACM0')
eeg.openSerial()
eeg.sendTest('1') #Tells the eeg device to start sending data
while True:
data = eeg.readEEG() #Returns an array of the 8 latest values, one per channel
if data != False: #Returns False if bad checksum
val.value = data[7]
val = Value('d',0.0)
q = deque([],500)
def graphLoop():
global val,q
plt = pg.plot(q)
while True:
q.append(val.value)
plt.plot(q,clear=True)
QtGui.QApplication.processEvents()
serial_proc = Process(target=serialLoop, args=(val,), name='serial_proc')
serial_proc.start()
try:
while True:
graphLoop()
except KeyboardInterrupt:
print('interrupted')
The above code performs real-time plotting by simply pulling the latest value recorded by the serialLoop and appending it to a deque. While the plot updates smoothly, it is only grabbing about 1 in 4 values, as seen in the resulting plot:
So, what multi-process or thread structure would you recommend, and then what form of IPC should be used between them?
Update:
I am receiving 2,000 samples per second. I am thinking that if I update the display at 100 fps and add 20 new samples per frame then I should be good. What is the best Python multithreading mechanism for implementing this?
This may not be the most efficient, but the following code achieves 100 fps for one plot, or 20 fps for 8 plots. The idea is very simple: share an array, index, and lock. Serial fills array and increments index while is has lock, plotting process periodically grabs all of the new values from the array and decrements index, again, under lock.
from pyqtgraph.Qt import QtGui
import pyqtgraph as pg
from multiprocessing import Process, Array, Value, Lock
from serial_interface import EEG64Board
from collections import deque
def serialLoop(arr,idx,lock):
eeg = EEG64Board(port='/dev/ttyACM0')
eeg.openSerial()
eeg.sendTest('1') #Tells the eeg device to start sending data
while True:
data = eeg.readEEG() #Returns an array of the 8 latest values, one per channel
if data != False: #Returns False if bad checksum
lock.acquire()
for i in range(8):
arr[i][idx.value] = data[i]
idx.value += 1
lock.release()
eeg.sendTest('2')
arr = [Array('d',range(1024)) for i in range(8)]
idx = Value('i', 0)
q = [deque([],500) for i in range(8)]
iq = deque([],500)
lock = Lock()
lastUpdate = pg.ptime.time()
avgFps = 0.0
def graphLoop():
global val,q,lock,arr,iq, lastUpdate, avgFps
win = pg.GraphicsWindow()
plt = list()
for i in range(8):
plt += [win.addPlot(row=(i+1), col=0, colspan=3)]
#iplt = pg.plot(iq)
counter = 0
while True:
lock.acquire()
#time.sleep(.01)
for i in range(idx.value):
for j in range(8):
q[j].append(arr[j][i])
idx.value = 0
lock.release()
for i in range(8):
plt[i].plot(q[i],clear=True)
QtGui.QApplication.processEvents()
counter += 1
now = pg.ptime.time()
fps = 1.0 / (now - lastUpdate)
lastUpdate = now
avgFps = avgFps * 0.8 + fps * 0.2
serial_proc = Process(target=serialLoop, args=(arr,idx,lock), name='serial_proc')
serial_proc.start()
graphLoop()
serial_proc.terminate()
I've written a simple GUI in python using pylabs and tkinter based on an example found here:
http://hardsoftlucid.wordpress.com/various-stuff/realtime-plotting/
used for sine wave generation.
Except I tweaked it to pull data through suds from a server on the internet. It's not working as I exactly anticipated as the GUI is somewhat slow. I think its due to the timer. I just started learning how to use matplotlib functions yesterday so I'm not aware of how every function works.
How can I speed it up? Right now the data comes in at 2-3 seconds which is fine, but I just want to increase the GUI responsiveness.
Here is my code:
import numpy as np
from matplotlib import pyplot as plt
plt.ion() # set plot to animated
url = "http://10.217.247.36/WSDL/v4.0/iLON100.WSDL"
client = Client(url, username='ilon', password='ilon', location = 'http://10.217.247.36/WSDL/iLON100.WSDL')
read = client.factory.create('ns0:E_xSelect')
read['xSelect'] = """//Item[starts-with(UCPTname, "Net/MB485/MAIN POWER/Fb/PowerSum")]"""
ydata = [0] * 50
ax1=plt.axes()
# make plot
line, = plt.plot(ydata)
plt.ylim([10,40])
# start data collection
while True:
x = client.service.Read(read).Item[0].UCPTvalue[0].value #data stream
x = float(x)
ymin = float(min(ydata))-10
ymax = float(max(ydata))+10
plt.ylim([ymin,ymax])
ydata.append(x)
del ydata[0]
line.set_xdata(np.arange(len(ydata)))
line.set_ydata(ydata) # update the data
plt.draw() # update the plot
I want to implement a fast scrolling timetrace tool in python. The timetrace data is already all in memory in a numpy array and is big (>1e6 samples). I need a tool for quick visual inspection.
I already tried using Matplotlib+PySide but the update speed is not fast enough.
Can you reproduce the Matplotlib+Pyside demo in another toolkit like pygraphqt/chaco/quiqwt? I don't know any of them and I'm willing to learn the one that perform better in this application.
To be useful in my workflow, the chosen framework should allow to run the plot from an interactive ipython session and should be fast and extensible (eventually I will need several plots scrolled in sync on the same windows). In principle pyqtgraph, guiqwt or chaco all seem good candidates. But let judge on a real example.
Thanks.
Here's the pyqtgraph version. I tried to keep the code as similar as I could to the original demo. On my system, pyqtgraph only runs about 5x faster than matplotlib, and is still pretty slow (~1fps) when all of the data is visible. The major performance differences between matplotlib and pyqtgraph are in throughput--how rapidly new data can be plotted.
For better performance, I'd recommend looking at some of the GPU-based plotting libraries like visvis or galry. Pyqtgraph will be adding GPU support in the future, but it's not there yet. There are some efforts to bring matplotlib to the GPU as well, but I haven't seen any results from that yet..
## adapted from http://stackoverflow.com/questions/16824718/python-matplotlib-pyside-fast-timetrace-scrolling
from PySide import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
N_SAMPLES = 1e6
def test_plot():
time = np.arange(N_SAMPLES)*1e-3
sample = np.random.randn(N_SAMPLES)
plt = pg.PlotWidget(title="Use the slider to scroll and the spin-box to set the width")
plt.addLegend()
plt.plot(time, sample, name="Gaussian noise")
q = ScrollingToolQT(plt)
return q # WARNING: it's important to return this object otherwise
# python will delete the reference and the GUI will not respond!
class ScrollingToolQT(object):
def __init__(self, fig):
# Setup data range variables for scrolling
self.fig = fig
self.xmin, self.xmax = fig.plotItem.vb.childrenBounds()[0]
self.step = 1 # axis units
self.scale = 1e3 # conversion betweeen scrolling units and axis units
# Retrive the QMainWindow used by current figure and add a toolbar
# to host the new widgets
self.win = QtGui.QMainWindow()
self.win.show()
self.win.resize(800,600)
self.win.setCentralWidget(fig)
self.toolbar = QtGui.QToolBar()
self.win.addToolBar(QtCore.Qt.BottomToolBarArea, self.toolbar)
# Create the slider and spinbox for x-axis scrolling in toolbar
self.set_slider(self.toolbar)
self.set_spinbox(self.toolbar)
# Set the initial xlimits coherently with values in slider and spinbox
self.set_xlim = self.fig.setXRange
self.set_xlim(0, self.step)
def set_slider(self, parent):
# Slider only support integer ranges so use ms as base unit
smin, smax = self.xmin*self.scale, self.xmax*self.scale
self.slider = QtGui.QSlider(QtCore.Qt.Horizontal, parent=parent)
self.slider.setTickPosition(QtGui.QSlider.TicksAbove)
self.slider.setTickInterval((smax-smin)/10.)
self.slider.setMinimum(smin)
self.slider.setMaximum(smax-self.step*self.scale)
self.slider.setSingleStep(self.step*self.scale/5.)
self.slider.setPageStep(self.step*self.scale)
self.slider.setValue(0) # set the initial position
self.slider.valueChanged.connect(self.xpos_changed)
parent.addWidget(self.slider)
def set_spinbox(self, parent):
self.spinb = QtGui.QDoubleSpinBox(parent=parent)
self.spinb.setDecimals(3)
self.spinb.setRange(0.001, 3600.)
self.spinb.setSuffix(" s")
self.spinb.setValue(self.step) # set the initial width
self.spinb.valueChanged.connect(self.xwidth_changed)
parent.addWidget(self.spinb)
def xpos_changed(self, pos):
#pprint("Position (in scroll units) %f\n" %pos)
# self.pos = pos/self.scale
pos /= self.scale
self.set_xlim(pos, pos + self.step, padding=0)
def xwidth_changed(self, xwidth):
#pprint("Width (axis units) %f\n" % step)
if xwidth <= 0: return
self.step = xwidth
self.slider.setSingleStep(self.step*self.scale/5.)
self.slider.setPageStep(self.step*self.scale)
old_xlim = self.fig.plotItem.vb.viewRange()[0]
self.xpos_changed(old_xlim[0] * self.scale)
if __name__ == "__main__":
app = pg.mkQApp()
q = test_plot()
app.exec_()