Ajay Ajay - 3 months ago 115
Python Question

PyQtGraph and numpy realtime plot

I am currently making a project that reads 8 sensors and plots real time graphs. I have used Matplotlib but it was slow so I switched to pyqtgraph. It is comparatively very fast. I have refered to the documentations and designed a simple code that plots live data.
The only problem I am facing is the diskspace and cpu usage increases drastically as i let it draw for 20minutes or so. Here is my code.

from tinkerforge.ip_connection import IPConnection
from tinkerforge.bricklet_ptc import BrickletPTC

from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg

win = pg.GraphicsWindow(title="Basic plotting examples")
win.resize(1280,720)
win.setWindowTitle('Live Temperature Data')
#Enable antialiasing for prettier plots
pg.setConfigOptions(antialias=True)


p1 = win.addPlot(title = 'Sensor1')
curve1 = p1.plot(pen = '#00A3E0')
p1.setLabel('left', "Temperature", units='°C')
p1.setLabel('bottom', "Time", units= 's')
p1.setDownsampling(auto=True,mode='peak')
p1.setClipToView(True)
p1.showGrid(x=True, y=True)

tempC1 = []

def updateSensor1():
global curve1, tempC1, indx1, p1

ipcon = IPConnection() # Create IP connection
ptc1 = BrickletPTC(UID1, ipcon) # S1
ipcon.connect(HOST, PORT) # Connect to brickd
temperature1 = ptc1.get_temperature()

dataArray1=str(temperature1/100).split(',')
temp1 = float(dataArray1[0])
tempC1.append(temp1)

curve1.setData(tempC1)
app.processEvents()


timer1 = QtCore.QTimer()
timer1.timeout.connect(updateSensor1)
timer1.start(1000)

if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_'):
QtGui.QApplication.instance().exec_()


I heard lists are much slower and numpy is really fast and much more compatible with pyqtgraph. Since I am new to this programming stuff I am unable to make a numpy array that takes these temperature readings and plots the graph. I have also referred to the documentation but it didn't help

P.s sicne I have 8 sensors I have no idea should I create 8 different numpy arrays or something like one multidimensional array that takes sensor values and a function that plots these values in real time

I'd be grateful if someone can help me creating numpy arrays instead of lists.

Answer

So if you want to replace your list with an np.array you will face one major issue: It is not directly possible to append data to an array the same way as with a list. One possibility is to use e.g. np.hstack, np.vstack, np.column_stack or np.row_stack. Here is an example how you could alter your updateSensor1 function using np.hstack:

tempC1 = np.array(())

def updateSensor1():
    global curve1, tempC1, indx1, p1

    ipcon = IPConnection() # Create IP connection
    ptc1 = BrickletPTC(UID1, ipcon) # S1 
    ipcon.connect(HOST, PORT) # Connect to brickd 
    temperature1 = ptc1.get_temperature()

    dataArray1=str(temperature1/100).split(',')
    temp1 = np.array((dataArray1[0])).astype(np.float32)
    tempC1 = np.hstack((tempC1,temp1))

    curve1.setData(tempC1)
    app.processEvents()

However, for arrays each "append" action requires short-term doubling of required memory, so you will probably find that using a list will be more efficient when you will deal with large arrays.

A better solution is to generate an array (e.g. np.zeros) that is as large as your desired final output, for example after 20 minutes of plotting. That way you can avoid array creation/destruction. Then you just need to create another count-variable or pass the time to your updateSensor1-function in order to update and plot the right data, e.g.:

tempC1 = np.zeros((1200))
count = 0

def updateSensor1():
    global curve1, tempC1, indx1, p1, count

    ipcon = IPConnection() # Create IP connection
    ptc1 = BrickletPTC(UID1, ipcon) # S1 
    ipcon.connect(HOST, PORT) # Connect to brickd 
    temperature1 = ptc1.get_temperature()

    dataArray1=str(temperature1/100).split(',')
    temp1 = float(dataArray1[0])
    tempC1[count] = temp1

    curve1.setData(tempC1[::count])
    count += 1
    app.processEvents()

For multiple sensors, you can just add new dimensions to your array, e.g. for 8 sensors, create a np.zeros((1200,8)) array. I hope this was helpful in some way...

EDIT:

If you pop the list each 300s or so and still want the x-axis to continue in time you should pass a second list or array with x/time values. I would suggest something like:

import time

times = []
t0 = time.time()

def updateSensor1():
    global curve1, tempC1, indx1, p1, times, t0

    ipcon = IPConnection() # Create IP connection
    ptc1 = BrickletPTC(UID1, ipcon) # S1 
    ipcon.connect(HOST, PORT) # Connect to brickd 
    temperature1 = ptc1.get_temperature()

    dataArray1=str(temperature1/100).split(',')
    temp1 = float(dataArray1[0])
    tempC1.append(temp1)
    times.append(time.time()-t0)

    curve1.setData(times,tempC1)
    app.processEvents()

Then you should just pop the times list the same time you pop your tempC1 list. Since the reference time t0 is not changed this should give you always the correct time.