1

I am trying to acquire and display to signals on real time using Vispy. I acquire signals over serial port by pyserial.

I have modified the realtime_signals example (https://github.com/vispy/vispy/blob/master/examples/demo/gloo/realtime_signals.py) and have been able to display each signal individually. I also can display 3 or 5 plots of the same signal. But I could not manage to diplay both signals (each from a different serial port) in the same canvas or in two different canvases inserted into PyQt5 application. I have no experience on OpenGL and shaders.

The serial port code and modified scene timer code is below.

#import multiprocessing
from PyQt5.QtWidgets import *
import vispy.app
import sys


from vispy import gloo
from vispy import app
import numpy as np
import math
import serial

datam=0.0
seri=serial.Serial("/dev/ttyACM0", baudrate=115200, timeout=1)
class Seriport(serial.Serial):
    def oku():
        global datam

        while seri.isOpen:
            dataGet=''
            serialData = seri.read().decode('ascii')
            if serialData == '$':
                while not serialData == ';':
                    serialData = seri.read().decode('ascii')
                    if not serialData == ';':
                        dataGet += serialData
            if not '$' in dataGet:
                dataList = dataGet.split(',')
                if len(dataList) == 3:
                    datam=float(dataList[0])-125000

            print(datam)
            return datam


# Number of cols and rows in the table.
nrows = 3
ncols = 1

# Number of signals.x = np.arange(sample)
m = nrows*ncols

# Number of samples per signal.
n = 1000

# Various signal amplitudes.

amplitudes = .1 + .2 * np.random.rand(m, 1).astype(np.float32)

# Generate the signals as a (m, n) array.
# y = amplitudes * np.random.randn(m, n).astype(np.float32)

y = np.zeros([m, n], dtype = np.float32) 

# Color of each vertex (TODO: make it more efficient by using a GLSL-based
# color map and the index).

color = np.repeat(np.random.uniform(size=(m, 3), low=.5, high=.9),
                n, axis=0).astype(np.float32)

# Signal 2D index of each vertex (row and col) and x-index (sample index
# within each signal).
index = np.c_[np.repeat(np.repeat(np.arange(ncols), nrows), n),
              np.repeat(np.tile(np.arange(nrows), ncols), n),
              np.tile(np.arange(n), m)].astype(np.float32)

VERT_SHADER = """
#version 120
// y coordinate of the position.
attribute float a_position;
// row, col, and time index.
attribute vec3 a_index;
varying vec3 v_index;
// 2D scaling factor (zooming).
uniform vec2 u_scale;
// Size of the table.
uniform vec2 u_size;
// Number of samples per signal.
uniform float u_n;
// Color.
attribute vec3 a_color;
varying vec4 v_color;
// Varying variables used for clipping in the fragment shader.
varying vec2 v_position;
varying vec4 v_ab;
void main() {
    float nrows = u_size.x;
    float ncols = u_size.y;
    // 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);
    v_color = vec4(a_color, 1.);
    v_index = a_index;
    // For clipping test in the fragment shader.
    v_position = gl_Position.xy;
    v_ab = vec4(a, b);
}
"""

FRAG_SHADER = """
#version 120
varying vec4 v_color;
varying vec3 v_index;
varying vec2 v_position;
varying vec4 v_ab;
void main() {
    gl_FragColor = v_color;
    // Discard the fragments between the signals (emulate glMultiDrawArrays).
    if ((fract(v_index.x) > 0.) || (fract(v_index.y) > 0.))
        discard;
    // Clipping test.
    vec2 test = abs((v_position.xy-v_ab.zw)/v_ab.xy);
    if ((test.x > 1) || (test.y > 1))
        discard;
}
"""



class Canvas(app.Canvas):

    def __init__(self):
        app.Canvas.__init__(self, title='Use your wheel to zoom!',
                            keys='interactive')
        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
        self.program['a_position'] = y.reshape(-1, 1)
        self.program['a_color'] = color
        self.program['a_index'] = index
        self.program['u_scale'] = (1., 1.)
        self.program['u_size'] = (nrows, ncols)
        self.program['u_n'] = n

        gloo.set_viewport(0, 0, *self.physical_size)

        self._timer = app.Timer(0.001, connect=self.on_timer, start=True)

        gloo.set_state(clear_color='black', blend=True,
                       blend_func=('src_alpha', 'one_minus_src_alpha'))

        self.show()

    def on_resize(self, event):
        gloo.set_viewport(0, 0, *event.physical_size)

    def on_mouse_wheel(self, event):
        dx = np.sign(event.delta[1]) * .05
        scale_x, scale_y = self.program['u_scale']
        scale_x_new, scale_y_new = (scale_x * math.exp(2.5*dx),
                                    scale_y * math.exp(0.0*dx))
        self.program['u_scale'] = (max(1, scale_x_new), max(1, scale_y_new))
        self.update()

    def on_timer(self, event):
        """Add some data at the end of each signal (real-time signals)."""
        global datam
        k = 1
        y[:, :-k] = y[:, k:]
        # y[:, -k:] = datam/50000

        y[:, -k:] = (Seriport.oku()/5000)

        # y[:, -k:] = amplitudes * np.random.randn(m, k)

        self.program['a_position'].set_data(y.ravel().astype(np.float32))
        self.update()

    def on_draw(self, event):
        gloo.clear()
        self.program.draw('line_strip')


canvas = Canvas()

w = QMainWindow()

widget = QWidget()
frame = QFrame()

w.setCentralWidget(widget)
widget.setLayout(QHBoxLayout())

frame.setLayout(QVBoxLayout())

widget.layout().addWidget(canvas.native)
#widget.layout().addWidget(canvas2.native)

widget.layout().addWidget(frame)
frame.layout().addWidget(QPushButton())
frame.layout().addWidget(QPushButton())
frame.layout().addWidget(QPushButton())
frame.layout().addWidget(QPushButton())
frame.layout().addWidget(QPushButton())
w.show()

vispy.app.run()
def on_timer(self, event):
        """Add some data at the end of each signal (real-time signals)."""
        global datam
        k = 1
        y[:, :-k] = y[:, k:]
        # y[:, -k:] = datam/50000

        y[:, -k:] = (Seriport.oku()/5000)

        # y[:, -k:] = amplitudes * np.random.randn(m, k)

        self.program['a_position'].set_data(y.ravel().astype(np.float32))
        self.update()

Any help would be appreciated.

bilgitay
  • 11
  • 1
  • Embedded code for serial port is pc.printf("$%d,%d,%d;", a,b,c); – bilgitay Aug 05 '19 at 22:04
  • you reader class is tied to a particular serial port `/dev/ttyACM0`. If you do that how do you change to use two ports? – Marcos G. Aug 06 '19 at 08:57
  • I have tried to use two canvases, each having a bound method for reading a serial port (/dev/ttyACM0 and /dev/ttyACM1) in their on_timer method. The major problem with this approach is loss of data. I will try threading or multiprocessing approaches. – bilgitay Aug 07 '19 at 22:07
  • @bilgitay If you still need help with this let me know. If you can both streams of data into the same Canvas class then the rest should be relatively simple. The easiest thing would be to switch to something like the SceneCanvas and using the existing Visuals in VisPy to save yourself from dealing with your own shaders too much. That is, unless you think it is necessary for the performance you are trying to achieve. – djhoese Oct 03 '20 at 11:57

0 Answers0