oscilloscope-like realtime/streaming display from a sensor?

Hello,

I’m new to Bokeh, but I like the promise of it and I’m looking forward to using it more in my every day work.

yesterday, I spent a lot of time trying to figure out how to make a bokeh plot that would stream data from a bluetooth enabled sensor and show how the values changed over time. Sorta like what might happen if I were to hook up the device I’m sensing to an osilloscope.

To achieve this, my idea is to have two callbacks – one slow callback to get new data, and a much faster one that will “advance” the data across the screen. At the moment, I’m using a dictionary of lists, but using two fixed length deques might be a better idea. However, I belive I am misunderstanding how .sourcedata works; I’d like to update the list associated with both X and Y. I would greatly appreciate any help with the code I’ve pasted below.

note: in the following code, get_next_sample() is
just a loop (with a very small time delay inside each loop) that polls
the object (connected to the port) for y values and returns the index of said loop for the x values. get_next_sample()returns two lists.

Thank you in advance.

···

import numpy as np

from bokeh.client import push_session
from bokeh.plotting import figure, curdoc
from bokeh.models import ColumnDataSource

dd = {‘x’:,‘y’:}

x,y = get_next_sample()
dd = update_dict(dd,x,y)

yls =
yls.extend(y)

source = ColumnDataSource(data=dict(x=x, y=y))

p = figure(x_range=(0, 1000), y_range=(0, 100))
r1 = p.line(dd[‘x’],dd[‘y’], color=“firebrick”)

open a session to keep our local document in sync with server

session = push_session(curdoc())

def get_new_data(source=source):

data = source.data

x,y = get_next_sample()



r1.data_source.data["y"] =  data["y"].extend(y)[-1000:]
r1.data_source.data["x"] =  data["x"].extend(y)[-1000:]

def advance_data():

r1.data_source.data["y"] = r1.data_source.data['y'][10:]
r1.data_source.data["x"] = r1.data_source.data['x'][10:]

curdoc().add_periodic_callback(advance_data, 50)
curdoc().add_periodic_callback(get_new_data, 300)

session.show(p) # open the document in a browser

session.loop_until_closed() # run forever

Hi,

This would be a neat example, but is also somewhat sophisticated. A few general comments:

It's almost always preferable to write "bokeh serve myapp.py" style apps, instead of using bokeh.client and push_session, etc. All of the examples under examples/app demonstrate this sort of "full" bokeh app. See the discussion here:

  Bokeh server — Bokeh 3.3.2 Documentation

Next, it's *always better* to update .data in "one go":

  source.data = dict(...)

and NOT do:

     source.data['x'] = ...
  source.data['y'] = ...

The latter way results in two independent update events, which can result flicker for the instant the two columns are out of sync, or much worse problems if you change the lengths and break the bedrock "columns *always* have the same length" assumption.

If you are streaming new data, you probably want to use the .stream method on column data sources, since that will send only the new data, and not re-send the entire data source. Updating and sending only the marginal new data will be much more efficient.

You might want to check out the existing spectrogram example:

  https://github.com/bokeh/bokeh/tree/master/examples/app/spectrogram

Running it requires being able to install pyaudio, which is problematic on some platforms. But you can see it demonstrated in this talk:

  https://youtu.be/xqwCxuEBpxk?t=30m28s

Finally, regarding other advanced techniques that might be relevant, like embedding a Bokeh server directly in your own Tornado ioloop, or responding to async events (zeromq messages, etc) there have been previous discussions in this mailing list. The Dask dashboard is probably also the most sophisticated streaming Bokeh app I know of, showing live performance stats collected across an entire cluster. You might also look to it for inspiration.

Thanks,

Bryan

···

On Dec 11, 2016, at 2:24 PM, [email protected] wrote:

Hello,

I'm new to Bokeh, but I like the promise of it and I'm looking forward to using it more in my every day work.

yesterday, I spent a lot of time trying to figure out how to make a bokeh plot that would stream data from a bluetooth enabled sensor and show how the values changed over time. Sorta like what might happen if I were to hook up the device I'm sensing to an osilloscope.

To achieve this, my idea is to have two callbacks -- one slow callback to get new data, and a much faster one that will "advance" the data across the screen. At the moment, I'm using a dictionary of lists, but using two fixed length deques might be a better idea. However, I belive I am misunderstanding how .sourcedata works; I'd like to update the list associated with both X and Y. I would greatly appreciate any help with the code I've pasted below.

note: in the following code, get_next_sample() is just a loop (with a very small time delay inside each loop) that polls the object (connected to the port) for y values and returns the index of said loop for the x values. get_next_sample()returns two lists.

Thank you in advance.

--------------------------------------------

import numpy as np

from bokeh.client import push_session
from bokeh.plotting import figure, curdoc
from bokeh.models import ColumnDataSource

dd = {'x':,'y':}

x,y = get_next_sample()
dd = update_dict(dd,x,y)

yls =
yls.extend(y)

source = ColumnDataSource(data=dict(x=x, y=y))

p = figure(x_range=(0, 1000), y_range=(0, 100))
r1 = p.line(dd['x'],dd['y'], color="firebrick")

# open a session to keep our local document in sync with server
session = push_session(curdoc())

def get_new_data(source=source):

    data = source.data
    
    x,y = get_next_sample()

    r1.data_source.data["y"] = data["y"].extend(y)[-1000:]
    r1.data_source.data["x"] = data["x"].extend(y)[-1000:]

def advance_data():

    r1.data_source.data["y"] = r1.data_source.data['y'][10:]
    r1.data_source.data["x"] = r1.data_source.data['x'][10:]
   
curdoc().add_periodic_callback(advance_data, 50)
curdoc().add_periodic_callback(get_new_data, 300)

session.show(p) # open the document in a browser

session.loop_until_closed() # run forever

--
You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/2abb323d-4ede-4d93-b145-029fcc76f595%40continuum.io\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.

For a simpler streaming example you might first look at the OHLC example, which you can certainly run locally as well:

  https://github.com/bokeh/bokeh/tree/master/examples/app/ohlc

Note: I gave links pointing to master, please take care to look on GitHub at tags for older versions if you are using older versions of Bokeh, to ensure the code matches your version.

Thanks,

Bryan

···

On Dec 11, 2016, at 5:20 PM, Bryan Van de Ven <[email protected]> wrote:

Hi,

This would be a neat example, but is also somewhat sophisticated. A few general comments:

It's almost always preferable to write "bokeh serve myapp.py" style apps, instead of using bokeh.client and push_session, etc. All of the examples under examples/app demonstrate this sort of "full" bokeh app. See the discussion here:

  Bokeh server — Bokeh 3.3.2 Documentation

Next, it's *always better* to update .data in "one go":

  source.data = dict(...)

and NOT do:

    source.data['x'] = ...
  source.data['y'] = ...

The latter way results in two independent update events, which can result flicker for the instant the two columns are out of sync, or much worse problems if you change the lengths and break the bedrock "columns *always* have the same length" assumption.

If you are streaming new data, you probably want to use the .stream method on column data sources, since that will send only the new data, and not re-send the entire data source. Updating and sending only the marginal new data will be much more efficient.

You might want to check out the existing spectrogram example:

  https://github.com/bokeh/bokeh/tree/master/examples/app/spectrogram

Running it requires being able to install pyaudio, which is problematic on some platforms. But you can see it demonstrated in this talk:

  https://youtu.be/xqwCxuEBpxk?t=30m28s

Finally, regarding other advanced techniques that might be relevant, like embedding a Bokeh server directly in your own Tornado ioloop, or responding to async events (zeromq messages, etc) there have been previous discussions in this mailing list. The Dask dashboard is probably also the most sophisticated streaming Bokeh app I know of, showing live performance stats collected across an entire cluster. You might also look to it for inspiration.

Thanks,

Bryan

On Dec 11, 2016, at 2:24 PM, [email protected] wrote:

Hello,

I'm new to Bokeh, but I like the promise of it and I'm looking forward to using it more in my every day work.

yesterday, I spent a lot of time trying to figure out how to make a bokeh plot that would stream data from a bluetooth enabled sensor and show how the values changed over time. Sorta like what might happen if I were to hook up the device I'm sensing to an osilloscope.

To achieve this, my idea is to have two callbacks -- one slow callback to get new data, and a much faster one that will "advance" the data across the screen. At the moment, I'm using a dictionary of lists, but using two fixed length deques might be a better idea. However, I belive I am misunderstanding how .sourcedata works; I'd like to update the list associated with both X and Y. I would greatly appreciate any help with the code I've pasted below.

note: in the following code, get_next_sample() is just a loop (with a very small time delay inside each loop) that polls the object (connected to the port) for y values and returns the index of said loop for the x values. get_next_sample()returns two lists.

Thank you in advance.

--------------------------------------------

import numpy as np

from bokeh.client import push_session
from bokeh.plotting import figure, curdoc
from bokeh.models import ColumnDataSource

dd = {'x':,'y':}

x,y = get_next_sample()
dd = update_dict(dd,x,y)

yls =
yls.extend(y)

source = ColumnDataSource(data=dict(x=x, y=y))

p = figure(x_range=(0, 1000), y_range=(0, 100))
r1 = p.line(dd['x'],dd['y'], color="firebrick")

# open a session to keep our local document in sync with server
session = push_session(curdoc())

def get_new_data(source=source):

   data = source.data

   x,y = get_next_sample()

   r1.data_source.data["y"] = data["y"].extend(y)[-1000:]
   r1.data_source.data["x"] = data["x"].extend(y)[-1000:]

def advance_data():

   r1.data_source.data["y"] = r1.data_source.data['y'][10:]
   r1.data_source.data["x"] = r1.data_source.data['x'][10:]

curdoc().add_periodic_callback(advance_data, 50)
curdoc().add_periodic_callback(get_new_data, 300)

session.show(p) # open the document in a browser

session.loop_until_closed() # run forever

--
You received this message because you are subscribed to the Google Groups "Bokeh Discussion - Public" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/2abb323d-4ede-4d93-b145-029fcc76f595%40continuum.io\.
For more options, visit https://groups.google.com/a/continuum.io/d/optout\.