6

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_()
Nav
  • 19,885
  • 27
  • 92
  • 135
fmarengo
  • 351
  • 1
  • 2
  • 7
  • Your problem is not well enough defined. The code you present plots a list of floats contained in an object named `data`. That's a part of your task, of course, but what about the data stream from the Arduino? How is it formatted and how do you propose to parse it into numbers? Until you address that, there is very little that anyone can say. – Paul Cornelius Jul 12 '17 at 01:03
  • My arduino board is transmitting a series of float values followed by '\n' characters: x1 x2 x3 ... I want to plot these values on realtime. How can I do it, please? Thanks for your time. – fmarengo Jul 13 '17 at 19:09
  • Here's what I would do: get the Python package to access serial ports (PySerial). Install it. Write a script to open COM10 with the correct baud rate and other port parameters. In a tight loop gather the data from the Adruino. Convert each string to a float and print it on the console. Make sure there are no errors. Only after you've got that working should you tackle the problem of real-time plotting. If you need help getting that data acquisition loop to work, post a different question here on SO and you should get the help you need. – Paul Cornelius Jul 13 '17 at 21:44

2 Answers2

18

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
##################
fmarengo
  • 351
  • 1
  • 2
  • 7
  • 4
    For anyone who doesn't want to use a com port just to try the example, you can comment out `import serial` and the `ser = serial.Serial(portName,baudrate)` line. Then `from random import randrange, uniform` and substitute `value = ser.readline()` with `value = uniform(0, 10)`. Good example though. – Nav Jul 23 '19 at 06:46
  • Thanks @DennisJensen . I would like very much to get your code. Could you share it with me, please? fmarengorodriguez(at)gmail(dot)com . Thanks! – fmarengo Oct 28 '19 at 14:31
  • setData() repaints the whole series each time new data are appended. In fact if you have multiple series in the widget it will repaint those too. Every single time. – mhrvth Mar 21 '21 at 23:07
0

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.

mrwonderfulness
  • 361
  • 2
  • 7