I'm fairly new to python programming and I'm struggling with the Vispy Library.
Basically, I have a Raspberry pi connected to 2 Arduinos accelerometers sensors. The raspberry is sending the X, Y and Z values from both of the sensors through UDP to my computer. Then my computer has to displays 9 graphs : 6 for the evolutions of x, y and z for both sensors and 3 for the differences between them (X1-X2, Y1-Y2 and Z1-Z2) and finally, it must be in real-time.
I wanted to use the Vispy library for that last point. After reading the documentation, I came up with the following code :
#!/usr/bin/env python3
import numpy as np
from vispy import app
from vispy import gloo
import socket
from itertools import count
# init x, y arrays
x1_vals = []
time_vals = []
#UDP connection from Raspberry pi
UDP_IP = ""
UDP_PORT = 5005
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))
# Initialize the index and set it to 1
index = count()
next(index)
# Initialize the Canvas c
c = app.Canvas(keys='interactive')
vertex = """
attribute vec2 a_position;
void main (void)
{
gl_Position = vec4(a_position, 0.0, 1.0);
}
"""
fragment = """
void main()
{
gl_FragColor = vec4(0.0, 0.0, 15.0, 10.0);
}
"""
program = gloo.Program(vertex, fragment)
@c.connect
def on_resize(event):
gloo.set_viewport(0, 0, *event.size)
@c.connect
def on_draw(event):
gloo.clear((1,1,1,1))
program.draw('line_strip')
def on_timer(event):
# next index
cpt = next(index)
# Get data from UDP
recv, addr = sock.recvfrom(1024)
data = recv.decode('UTF-8').split(';')
# We want to display only 100 samples so the graph still readable.
# So we delete the first value of the x array if there are more than 100 values
if (cpt > 100):
del x1_vals[0]
time_vals = np.linspace(-1.0, +1.0, 100)
else:
time_vals = np.linspace(-1.0, +1.0, cpt)
# The values must be bound between -1.0 and 1.0
tmp = float(data[0])*0.5
if (tmp >= 1):
tmp = float(0.99)
elif (tmp <= -1):
tmp = float(-0.99)
x1_vals.append(tmp)
# Then we concatenate the arrays of x and y
program['a_position'] = np.c_[time_vals, x1_vals].astype(np.float32)
c.update()
c.timer = app.Timer('auto', connect=on_timer, start=True)
c.show()
app.run()
So as the comments describe it, it firstly intitializes the UDP connection and the canvas, then for each values received it updates the canvas with the newly added value. If the number of values exceed 100, the first value of the array is deleted to keep a constant number of samples.
It works well when I want to display only the X1 accelerometer sensors evolution. So now I picked the code from the Vispy documentation which demonstrates how to show multiple graphs, but the code is a bit too complex for my level.
Basically, in my code I receive all the sensors values in the data
array. I pick the first value [0] (X1), but the complete data looks like this : [x1, y1, z1, dx, dy, dz, x2, y2, z2]
where dx = x1 - x2, dy = y1 - y2 and dz = z1 - z2. (the difference has to be directly calculated on the raspberry).
So I tried to modify the code from the documentation as following :
# Number of cols and rows in the table.
nrows = 3
ncols = 3
# Number of signals.
m = nrows*ncols
# Number of samples per signal.
n = 100
Because I want 9 graphs and only 100 samples per graph.
I ignored the index, the color and deleted the amplitude has it is not required in my case. Basically, I almost kept the original code for the whole setting part, then I replaced the def on_timer
with mine.
Now I'm trying to feed the a_position
array from GLSL with my own data. But I'm not sure how to prepare the data to make it works properly with this code. I'm struggling to understand what does these lines do :
# GLSL C code
VERT_SHADER = """
// Compute the x coordinate from the time index.
float x = -1 + 2*a_index.z / (u_n-1);
vec2 position = vec2(x - (1 - 1 / u_scale.x), a_position);
// Find the affine transformation for the subplots.
vec2 a = vec2(1./ncols, 1./nrows)*.9;
vec2 b = vec2(-1 + 2*(a_index.x+.5) / ncols,
-1 + 2*(a_index.y+.5) / nrows);
// Apply the static subplot transformation + scaling.
gl_Position = vec4(a*u_scale*position+b, 0.0, 1.0);
"""
# Python code
def __init__(self):
self.program['a_position'] = y.reshape(-1, 1)
def on_timer(self, event):
k = 10
y[:, :-k] = y[:, k:]
y[:, -k:] = amplitudes * np.random.randn(m, k)
self.program['a_position'].set_data(y.ravel().astype(np.float32))
I deleted the surrounding code that I think I'm understanding.
Note that even if I'm starting with python, I'm aware that they are using a class definition for the Canvas when I'm using the bare object in my code. I understand the use of self
and others.
How can I adapt the code from the realtime_signals documentation to my case ?