Holoviews streaming visualization from a separate process

I’m trying to visualize data from pyBullet using Holoview’s streaming functions. I want the streams to be updated in real time and to be rendered in a web browser window.
From another thread, I found that I need to use the command panel serve --show filename.py to open a new browser window for streaming. So here’s my code so far:

import pybullet as p
import time
import pybullet_data
import pandas as pd
from multiprocessing import Process, Queue
import streamz
import holoviews as hv
from holoviews.streams import Pipe
import time
import numpy as np
import panel as pn

# -------------holoviews---------------
def holoviews_test(q):
    point_source = streamz.Stream()
    pipe = Pipe(data=pd.DataFrame({'x': [], 'y': []}))
    point_source.sliding_window(200).map(pd.concat).sink(pipe.send)  # Connect streamz to the Pipe
    plot = hv.DynamicMap(hv.Curve, streams=[pipe])

    def update_stream():
        while True:
            df = pd.DataFrame({'x': np.random.uniform(low=0, high=1.0, size=100), 'y': np.random.uniform(low=0, high=1.0, size=100)},
                              columns=['x', 'y'])
            point_source.emit(df)
            # Not visualizing the data from the queue, right now the goal is to get some streaming running!
            print(q.get())

    pn.state.onload(update_stream)
    pn.panel(plot).servable()
# -------------holoviews---------------

  physicsClient = p.connect(p.GUI)#or p.DIRECT for non-graphical version
  p.setAdditionalSearchPath(pybullet_data.getDataPath()) #optionally
  p.setGravity(0,0,-10)
  planeId = p.loadURDF("plane.urdf")
  startPos = [0,0,1]
  startOrientation = p.getQuaternionFromEuler([0,0,0])
  boxId = p.loadURDF("r2d2.urdf",startPos, startOrientation)

  # -------------holoviews---------------
  queue = Queue()
  vis_process = Process(target=holoviews_test, args=(queue,))
  vis_process.start()
  # -------------holoviews---------------

  for i in range (10000):
      p.stepSimulation()
      time.sleep(1./240.)

      # -------------holoviews---------------
      x = p.getBaseVelocity(bodyUniqueId=1)[0]
      queue.put(x)
      # -------------holoviews---------------

  cubePos, cubeOrn = p.getBasePositionAndOrientation(boxId)
  print(cubePos,cubeOrn)
  p.disconnect()

I am trying to plot velocity data and have marked the holoviews portions with comments. When I use panel serve --show filename.py, I get the following exception:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "C:\Users\m84241864\Miniconda3\lib\multiprocessing\spawn.py", line 116, in spawn_main
    exitcode = _main(fd, parent_sentinel)
  File "C:\Users\m84241864\Miniconda3\lib\multiprocessing\spawn.py", line 126, in _main
    self = reduction.pickle.load(from_parent)
ModuleNotFoundError: No module named 'bokeh_app_e1635060259a45a9bd31e818ab3f0d84'

I am not really sure how panel is creating a bokeh app under the hood, so I’m confused how to fix this.

I’m also aware that the above approach is quite hacky by launching a new process from a script submitted to Panel. So I am open to other approaches to get to my goal. Would it be possible to do the following:

  • Emit some data from pybullet.py. I run this like a normal python process (python pybullet.py)
  • Launch the bokeh/panel server as a separate process using panel serve --show .\viz.py. This script will keep scanning for emitted data, and will update the stream when it arrives.

Thank you for your time!

Ah I managed to stream data easily from separate processes using multiprocessing.connection.

3 Likes

Hi @manas96

Happy you found a solution. Would it be possible for you to share a working code example? Adding a video would even better.

It would help the community. Thanks.

1 Like

Sorry for the late reply, I work on this part-time. Here’s the code:

from multiprocessing.connection import Listener
import pybullet as p
import time
import pybullet_data

# PyBullet
physicsClient = p.connect(p.GUI)#or p.DIRECT for non-graphical version
p.setAdditionalSearchPath(pybullet_data.getDataPath()) #optionally
p.setGravity(0,0,-10)
planeId = p.loadURDF("plane.urdf")
startPos = [0,0,1]
startOrientation = p.getQuaternionFromEuler([0,0,0])
boxId = p.loadURDF("r2d2.urdf",startPos, startOrientation)

# Server communication
listener = Listener(('localhost', 2000), authkey=b'secret password')
conn = listener.accept()
print('connection accepted from', listener.last_accepted)

for i in range (1000000):
    p.stepSimulation()
    time.sleep(1./240.)
    x = p.getBaseVelocity(bodyUniqueId=1)[0]
    conn.send({'vel': list(x), 'timestep': i})
    print(f'sending{x[2]}')
    pass

p.disconnect()
# listener.close()
from multiprocessing.connection import Client
import streamz
import holoviews as hv
from holoviews.streams import Buffer, Pipe
import pandas as pd
import numpy as np
hv.extension('bokeh')
import panel as pn

point_source = streamz.Stream()
pipe = Pipe(data=pd.DataFrame({'x': [], 'y': []}))
point_source.sliding_window(200).map(pd.concat).sink(pipe.send) # Connect streamz to the Pipe
plot = hv.DynamicMap(hv.Curve, streams=[pipe])

conn = Client(('localhost', 2000), authkey=b'secret password')

plot.opts(show_grid=True, width=500, ylim=(0, 1.0))

def update_stream():
    xs = []
    ys = []
    while True:
        msg = conn.recv()
        timestep = msg['timestep']
        velocity = msg['vel'][2]

        xs.append(timestep)
        ys.append(velocity)
        print(f'timestep: {timestep} and velocity: {velocity}')
        if len(xs) == 100:
            df = pd.DataFrame({'x': xs, 'y': ys}, columns=['x', 'y'])
            xs = []
            ys = []
            point_source.emit(df)

# update_stream()
pn.state.onload(update_stream)
pn.panel(plot).servable()

I’ll upload the video soon, have some recording issues. However, the stream does not auto-scroll and scale. I’m not sure how to enable those features… any suggestions?

1 Like

Thanks. Where would I find the two .urdf files?

Aha! I fixed the auto-scrolling problem as well by using PeriodicDataFrames from streams.
The .urdf files should be installed automatically when you execute pip install pybullet (they will be located in the site-packages folder of your python installation).

Here’s the improved code for the client:

from multiprocessing.connection import Client
from streamz.dataframe import PeriodicDataFrame
import holoviews as hv
from holoviews.streams import Buffer
import pandas as pd
hv.extension('bokeh')
import panel as pn

DATA_BUFFER_SIZE = 100
REFRESH_RATE = '200ms'

conn = Client(('localhost', 2000), authkey=b'secret password')


def update_stream(**kwargs):
    velocities = []
    times = []
    ts = []
    while len(velocities) < DATA_BUFFER_SIZE:
        msg = conn.recv()
        timestep = msg['timestep']
        velocity = msg['vel'][2]
        velocities.append(velocity)
        times.append(pd.Timestamp.now())
        ts.append(timestep)

    return pd.DataFrame({'x': velocities}, index=ts)


df = PeriodicDataFrame(update_stream, interval=REFRESH_RATE)
plot = hv.DynamicMap(hv.Curve, streams=[Buffer(df.x)]).opts(width=1000, show_grid=True)

pn.panel(plot).servable()
1 Like

Also, unfortunately, I don’t think I will be able to record a video due to security policies. Instead, I’m trying to upload a screenshot here, but that’s also giving me an error!
The above two scripts should work after installing pybullet. Let me know if you face any issues, happy to help.

1 Like

looks like this should get reclassified as a showcase