En el post sobre Gráficas en tiempo real con Python y Matplotlib vimos como hacer gráficas con pyplot. Sin embargo, estas gráficas tenían un problema si los datos que obteníamos llegaban a más de 10-15 muestras por segundo. Matplotlib es una librería orientada a hacer gráficos bonitos y de apariencia profesional, no a hacerlos interactivos o dinámicos. Para esta tarea existen otras librerías, hoy vamos a hablar sobre PyQtGraph(Web oficial)

Introducción rápida

En esta entrada vamos a ver como hacer una gráfica en tiempo real de manera muy rápida. No vamos a meternos en profundidad sobre como funciona, sino que vamos a implementar algo rápido y sencillo.Básicamente, pyQtGraph sirve para hacer gráficos en tiempo real que funcionan a velocidades muy altas. En nuestro ejemplo vamos a coger los datos de la IMU y vamos a representar alguno de ellos.

Lo primero que necesitamos es instalarnos el paquete de Python, de manera sencilla hacemos:

1
 pip install pyqtgraph

Y con esto tendríamos listo nuestro python para poder crear nuestras gráficas en tiempo real. Así que ahora a trabajar!

Cuerpo mínimo

Lo mínimo requerido para poder usar el pyQtGraph es incluir el paquete en nuestro script de python y crear la app.

1
2
3
4
5
6
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg

app = QtGui.QApplication([])

pg.QtGui.QApplication.exec_()

Si ejecutamos este código no veremos nada ya que hemos creado la aplicación pero no le hemos dicho que dibuje nada, ni quiera una ventana. Aun así, veremos que la consola se queda esperando a algo. Si queremos terminar la aplicación vamos a tener que pararla con el gestor de procesos.

Primera ventana y gráfica en PyQtGraph

Vamor a crear primero una ventana estándar con un título y vamos a añadir a esta ventana una gráfica(Plot). De momento vamos a darles solo un título, tanto a la ventana y a la gráfica.

1
2
3
4
5
6
7
8
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg

app = QtGui.QApplication([])
win = pg.GraphicsWindow(title="Grafica en tiempo real")
p = win.addPlot(title="Grafica tiempo real")

pg.QtGui.QApplication.exec_()

El resultado es la ventana con una gráfica ocupando todo el espacio. Ya tenemos la ventana más básica y útil preparada para empezar a pintar. El resultado de este script de aquí arriba es la siguiente figura. Como vemos pyQtGraph es una herramienta muy sencilla que con tan solo 4 líneas de código hemos preparado una ventana gráfica.

Ventana básica PyQtGraph con una gráfica vacía

Ventana básica PyQtGraph con una gráfica vacía

Añadimos curvas y preparamos nuestra gráfica

De momento lo que tenemos es más bien inútil si no tenemos nada para pintar, así que vamos a añadir una curva a nuestra gráfica. Este paso es muy sencillo y simplemente como tenemos que agregar a nuestro plot (‘p’ en el código) una línea. Aunque sea algo confuso hay que añadir plots al plot principal. Además, ya vamos a aprovechar para ponerle unos limites en el eje Y (en este ejemplo entre -120 y 120).

1
2
3
4
5
6
7
8
9
10
11
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg

app = QtGui.QApplication([])
win = pg.GraphicsWindow(title="Grafica en tiempo real")
p = win.addPlot(title="Grafica tiempo real")  

curva = p.plot(pen='y')
p.setRange(yRange=[-120, 120])

pg.QtGui.QApplication.exec_()

Pintamos algo ya, o qué?

El último paso (por fin!), para tener datos pintados, es añadirlos en la curva que hemos creado en el paso anterior (curva). El proceso es tan fácil como usar la función .setData(X_data, Y_data) sobre la curva que queremos.

El código de ejemplo nos quedaría:

1
2
3
4
5
6
7
8
9
10
11
12
13
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg

app = QtGui.QApplication([])
win = pg.GraphicsWindow(title="Grafica en tiempo real")
p = win.addPlot(title="Grafica tiempo real")  

curva= p.plot(pen='y')
p.setRange(yRange=[-120, 120])

curva.setData([0,20,40,80],[0,20,40,10])

pg.QtGui.QApplication.exec_()

El resultado como podemos ver es una línea que une los puntos que hemos definido en la función .setData(X_data, Y_data). En la siguiente imagen vemos como quedaría el resultado final.

pyQtGraph con una línea básica

pyQtGraph con una línea básica

Conectando datos

Ya sabemos como dibujar una línea (bravo…). Pero para que queremos esto, si con matplotlib nos queda más bonito y es más usado?. La respuesta está en el tiempo real y la adquisición de datos.

Como os he comentado al inicio, matplotlib es una librería pensada para realizar gráficas bonitas y más orientadas a imágenes estáticas. Y el hecho de intentar hacerla dinámica con una tasa de refresco mayor a 10-15 actualizaciones por segundo le hace sudar. Para corregir esto hemos hecho todo el camino hasta aquí, para poder actualizar la gráfica que hemos creado a por lo menos 100 muestras por segundo.

Para hacerlo más fácil de entender vamos a usar una función que se encargará de actualizar los datos que están en las curvas que hemos pintado. Vamos a llamar a esta función Update y va a acceder al resto de variables de manera global. Suponiendo que tenemos las variables definidas como teníamos hasta ahora el código nos quedaría:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg

app = QtGui.QApplication([])
win = pg.GraphicsWindow(title="Grafica en tiempo real")
p = win.addPlot(title="Grafica tiempo real")  

curva = p.plot(pen='y')
p.setRange(yRange=[-120, 120])

dataX = [] # Array's para guardar los datos
dataY = []
def Update():
   global curva, dataX, dataY

   # Actualizamos dataX y dataY

   curva.setData(dataX, dataY)
   QtGui.QApplication.processEvents()

while True: Update() #Actualizamos todo lo rápido que podamos.

pg.QtGui.QApplication.exec_()

Tiempo real y puerto Serie

Un caso práctico del punto anterior es cuando recibimos datos por el puerto serie, que queremos representar en una gráfica. Vamos a usar el modulo serial de python para conectarnos al puerto serie.

La conexión al puerto serie y su uso se puede ver en el post de matplotlib donde hacíamos lo mismo. Rápidamente:

1
2
3
4
5
6
7
import serial # Importamos el modulo

puerto = "COM6"
baudrate = 115200
ser = serial.Serial(portName, baudrate) # Creamos un Serial en COM6 a 115200

line = ser.readline() # Leemos una línea del serial (hasta el \n)

Si suponemos que tenemos un dato por línea en el serial, es decir el equivalente a hacer en arduino Serial.println(valor), el siguiente código recogería ese valor y lo añadiría a la gráfica de manera dinámica.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
import serial

puerto = "COM6"
baudrate = 115200
ser = serial.Serial(portName, baudrate)

app = QtGui.QApplication([])
win = pg.GraphicsWindow(title="Grafica en tiempo real")
p = win.addPlot(title="Grafica tiempo real")  

curva = p.plot(pen='y')
p.setRange(yRange=[-120, 120])

dataX = [] # Array's para guardar los datos
dataY = []
lastY = 0
def Update():
   global curva, dataX, dataY

   # Leemos la nueva línea
   line = ser.readline()
   nuevoDato = float(line.decode('utf-8'))

   #Agregamos los datos al array
   dataX.append(nuevoDato)
   dataY.append(lastY)
   lastY = lastY + 1

   # Limitamos a mostrar solo 300 muestras
   if len(dataX) > 300:
      dataX = dataX[:-1]
      dataY = dataY[:-1]

   #Actualizamos los datos y refrescamos la gráfica.
   curva.setData(dataX, dataY)
   QtGui.QApplication.processEvents()

while True: Update() #Actualizamos todo lo rápido que podamos.

pg.QtGui.QApplication.exec_()

Ya estaría todo! con esto tenemos nuestra gráfica en tiempo real, actualizada a través del puerto serie, y que soporta altas tasas de refresco!

Espero que os haya gustado, no olvidéis que cualquier duda la podéis dejar en los comentarios!


Gluón

Teleco con ganas de aprender más y compartirlo. Viajero empedernido y amante de la fotografía y la tecnología. Espero dejar mi granito de arena y que este pueda servir de ayuda.