aven
aven

Reputation: 11

Vispy plotting data

I followed this Add labels to real-time signal plotting with Vispy thread and tried using the example from https://vispy.org/gallery/scene/line_update.html#sphx-glr-gallery-scene-line-update-py . How can I hold the plotted information, I should be able to look at the past data as well by panning the view. Should I store the plotted data else where and retrieve that to look at the old data? I am trying to hold all information as new data gets added to the view. Any suggestions on how to do this will be really helpful.

Thanks.

Update 1:

'''

import sys
import numpy as np
from vispy import app, scene

# vertex positions of data to draw
N = 100
pos = np.zeros((N, 2), dtype=np.float32)
x_lim = [0, 10.]
y_lim = [-2., 2.]
pos[:, 0] = np.linspace(x_lim[0], x_lim[1], N)
pos[:, 1] = np.random.normal(size=N)

# color array
color = np.ones((N, 4), dtype=np.float32)
color[:, 0] = np.linspace(0, 1, N)
color[:, 1] = color[::-1, 0]

canvas = scene.SceneCanvas(keys='interactive', show=True)
grid = canvas.central_widget.add_grid(spacing=0)

viewbox = grid.add_view(row=0, col=1, camera='panzoom')

# add some axes
x_axis = scene.AxisWidget(orientation='bottom')
x_axis.stretch = (1, 0.1)
grid.add_widget(x_axis, row=1, col=1)
x_axis.link_view(viewbox)
y_axis = scene.AxisWidget(orientation='left')
y_axis.stretch = (0.1, 1)
grid.add_widget(y_axis, row=0, col=0)
y_axis.link_view(viewbox)

# add a line plot inside the viewbox
line = scene.Line(pos, color, parent=viewbox.scene)

# auto-scale to see the whole line.
viewbox.camera.set_range()
import time
t0 = time.time()
x_val = []
y_val= []



def update(ev):
    '''
     New x and y coordinates are generated and assigned to `pos`. 
     `datastore` variable holds all the data (past and current 
     generated data). Only `pos` data is used to update the plot in
     real time. 
    '''

    global pos, color, line, x_val, y_val,datastore
    
    
    pos[:, 0] = np.linspace(x_lim[0], x_lim[1], N)+time.time()-t0
    pos[:, 1] = np.sin(pos[:, 0])+0.2*np.random.normal(size=(N))
    
    
    x_val= np.append(x_val,pos[:,0])
    y_val= np.append(y_val,pos[:,1])
    datastore=np.stack((x_val, y_val),axis=1)
#     print(pos.size)
    
    color = np.roll(color, 1, axis=0)
    line.set_data(pos=pos)
    
    viewbox.camera.set_range(y=(pos[:, 1].max(), pos[:, 1].min()), x=(pos[:, 0].max(), pos[:, 0].min()))
    
    
def mouseMove(ev):
   '''
     `datastore` consists of all data. Based on the camera view, 
      data is extracted from `datastore`. this extracted data is 
      used to update the plot.
    '''
    
    
    print(viewbox.camera.get_state()['rect'])
    currentBounds= viewbox.camera.get_state()['rect']
    currentMin_x = currentBounds.right
    currentMax_x = currentBounds.left

    
    if currentMin_x < datastore[:,0].min():
        currentMin_x = datastore[:,0].min()
    if currentMax_x > datastore[:,0].max():
        currentMax_x = datastore[:,0].max()

    getData = datastore[(datastore[:,0]> currentMin_x) & (datastore[:,0]< currentMax_x)][:100] #Should be equal to N, for code to work
    
    print(getData.size)
    line.set_data(pos=getData, color=color) #Updates plot with the extracted data
    viewbox.camera.set_range(y=(getData[:, 1].max(), getData[:, 1].min()), x=(getData[:, 0].max(), getData[:, 0].min()))
    
    
print(viewbox.camera.get_state())
  
t = app.Timer()
t.connect(update)
t.start(iterations=60*5)
viewbox.events.mouse_release.connect(mouseMove)
t.events.stop.connect(lambda x: app.quit())

if __name__ == '__main__' and sys.flags.interactive == 0:
    app.run()

'''

Update 2

import sys
import numpy as np
from vispy import app, scene

# vertex positions of data to draw
N = 100
pos = np.zeros((N, 2), dtype=np.float32)
x_lim = [0, 10.]
y_lim = [-2., 2.]
pos[:, 0] = np.linspace(x_lim[0], x_lim[1], N)
pos[:, 1] = np.random.normal(size=N)



canvas = scene.SceneCanvas(keys='interactive', show=True)
grid = canvas.central_widget.add_grid(spacing=0)

viewbox = grid.add_view(row=0, col=1, camera='panzoom')

# add some axes
x_axis = scene.AxisWidget(orientation='bottom')
x_axis.stretch = (1, 0.1)
grid.add_widget(x_axis, row=1, col=1)
x_axis.link_view(viewbox)
y_axis = scene.AxisWidget(orientation='left')
y_axis.stretch = (0.1, 1)
grid.add_widget(y_axis, row=0, col=0)
y_axis.link_view(viewbox)

# add a line plot inside the viewbox
line = scene.Line(pos,  parent=viewbox.scene)

# auto-scale to see the whole line.
viewbox.camera.set_range()
import time
t0 = time.time()
x_val = []
y_val= []



def update(ev):
    global pos, line, x_val, y_val,datastore
    
    
    pos[:, 0] = np.linspace(x_lim[0], x_lim[1], N)+time.time()-t0
    pos[:, 1] = np.sin(pos[:, 0])+0.2*np.random.normal(size=(N))
    
    
    x_val= np.append(x_val,pos[:,0])
    y_val= np.append(y_val,pos[:,1])
    datastore=np.stack((x_val, y_val),axis=1)
#     print(pos.size)
    
    line.set_data(pos=pos)
    
    viewbox.camera.set_range(x=(pos[:, 0].min(),pos[:, 0].max()),y=(pos[:, 1].min(),pos[:, 1].max()))
    
    
def mouseMove(ev):
    
    
    print(viewbox.camera.get_state()['rect'])
    currentBounds= viewbox.camera.get_state()['rect']
    currentMin_x = currentBounds.left
    currentMax_x = currentBounds.right


    print(currentMin_x, currentMax_x)
    getData = datastore[(datastore[:,0]>currentMin_x) & (datastore[:,0]< currentMax_x)] #Should be equal to N, for code to work
    print(getData)
    print(getData.size)
    line.set_data(pos=getData)
    viewbox.camera.set_range( x=( getData[:, 0].min(),getData[:, 0].max()),y=(getData[:, 1].min(),getData[:, 1].max()))
    
    
print(viewbox.camera.get_state())
  
t = app.Timer()
t.connect(update)
t.start(iterations=60*15)
viewbox.events.mouse_release.connect(mouseMove)
t.events.stop.connect(lambda x: app.quit())

if __name__ == '__main__' and sys.flags.interactive == 0:
    app.run()

Upvotes: 0

Views: 1110

Answers (1)

djhoese
djhoese

Reputation: 3667

Overall I think this depends how you want your application to function as far as user experience and what kind of toll you want to have on the user's system. You have a couple choices:

  1. Hold on to all data in the GPU. Due to the way OpenGL and VisPy work this means you'd also have to have all that data on the CPU so you can update the overall array when new data comes in and then upload the whole array to the GPU. There are ways to only upload a portion of the data you want to send, but I can't remember if they work out of the box for the LineVisual used here or for the use case you have in mind (new data taking the place of old data).
  2. Hold on to all the data in the CPU and only a subset in the GPU, automatically updating the GPU data as the user interacts. This would require hooking pieces together so you know when the camera panned around and updating the data on the GPU. Not terrible, but a little complicated.
  3. Same as 2 but force the user to manually update data ranges with other UI elements (ex. Qt dropdown menus, buttons, etc). This way you don't have to hook things up for interactivity, you just update the data that you need.

This also leads me to ask about one concern I always have when people want applications like yours: do you need to show all the data? Would showing the last N records (even if only by default) be good enough? This makes it much easier on you, the application developer, at the cost of flexibility for the user that they probably won't even notice in most cases.

I should point out in the example you linked to, if it wasn't clear already, these are the lines where the data array are being updated with new data and then .set_data is called to upload the data to the GPU on the next draw:

    pos[:, 1] = np.random.normal(size=N)
    color = np.roll(color, 1, axis=0)
    line.set_data(pos=pos, color=color)

The color is just a fake modification of the array to change the color. The random data is just generating fake data and only updating the Y coordinates. You would obviously have real data that you'd need to update, but if you only show the last N records then updating only the Y positions in this matter would be an easy and performant way to do it. There are plenty of tricks that can be done with numpy arrays to reduce the amount of copies of the data you need to make and get the best performance. Let me know if you have any questions.

Upvotes: 0

Related Questions