Efficient updates of image plots in Bokeh for interactive visualization

2024/9/28 8:03:57

I'm trying to create a smooth interactive visualization of different slices of a muldimensional array using Bokeh. The data in the slices changes according to the user interaction and thus has to be updated several times per second. I have written a Bokeh app with several small image plots (64x64 values) to show the contents of the slices, and a callback to update the ColumnDataSources when the user interacts with the app. Everything works as expected but I can't get more than 2 or 3 frames per second and I would like to get at least 10 frames.

Here is a simplified sample of my code using 16 images with a periodic callback every 100ms to simulate user interaction. Using Bokeh 0.12.3 and Python 2.7 on Mac and Linux I get almost exactly the same timings (~300ms per frame) on both machines.

from __future__ import print_function, division
from random import randint
from timeit import default_timer as timer
import numpy as npfrom bokeh.io import curdoc
from bokeh.layouts import gridplot
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure# Set up app and fake data
grid_size = 4
data_size = 64
names = ['d{}'.format(i) for i in range(grid_size)]
plots = [[None for _ in range(grid_size)] for _ in range(grid_size)]
sources = dict()num_images = 16
image_data = [[np.random.rand(data_size, data_size)] for i in range(num_images)]# Create plots and datasources
plot_size = 256
for row, row_name in enumerate(names):for col, c_name in enumerate(names):d_name = row_name + "_" + c_namesources[d_name] = ColumnDataSource({'value': image_data[randint(0, num_images - 1)]})plots[row][col] = figure(plot_width=plot_size,plot_height=plot_size,x_range=(0, data_size),y_range=(0, data_size))plots[row][col].image('value', source=sources[d_name],x=0, y=0, dw=data_size, dh=data_size,palette="Viridis256")# Updates
def update():global sourcesstart_update, end_update = [], []start_time = timer()for row, row_name in enumerate(names):for col, c_name in enumerate(names):d_name = row_name + "_" + c_namenew_data = dict()new_data['value'] = image_data[randint(0, num_images - 1)]start_update.append(timer())  # ----- TIMER ONsources[d_name].data = new_dataend_update.append(timer())    # ----- TIMER OFFprint("\n---- \tTotal update time (secs): {:07.5f}".format(timer() - start_time))print("+ \tSources update times (secs): {}".format(["{:07.5f}".format(end_update[i] - s) for i,s in enumerate(start_update)]))# Document
grid = gridplot(plots)
curdoc().add_root(grid)
curdoc().add_periodic_callback(update, 100)

I've tried using only one data source with different fields for each plot, and also updating the data with the stream() method (although it doesn't make sense since the whole image is being replaced) and I haven't achieved any performance gain. Does anyone know what could I do to improve the interactivity of this visualization? Am I doing something wrong to update the image data?

My guess is that the bottleneck is the overhead caused by the JSON encoding/decoding of the image data, which might improve in the future since it appears that Bokeh developers are aware of this problem and trying to solve it. Sadly, it does not look like the fix is coming soon.

https://github.com/bokeh/bokeh/issues/2204

https://github.com/bokeh/bokeh/pull/5429

Any other suggestions?

Answer

As others have mentioned, an efficient binary array protocol has been implemented. So the answer is to upgrade to recent versions.

For completeness, here is a comparison of results.

With 0.12.3 (the version from the original post — make sure to use tornado < 4.5):

---- Total update time (secs): 0.14389+ Sources update times (secs): ['0.00943', '0.00962', '0.01100', '0.00908', '0.00004', '0.00975', '0.00984', '0.00997', '0.00946', '0.00926', '0.00912', '0.00898', '0.00900', '0.00908', '0.00999', '0.01014']^C

With 0.12.13 (the most recent version as of this time):

---- Total update time (secs): 0.01999+ Sources update times (secs): ['0.00157', '0.00231', '0.00131', '0.00143', '0.00114', '0.00123', '0.00109', '0.00118', '0.00116', '0.00102', '0.00113', '0.00118', '0.00099', '0.00099', '0.00104', '0.00104']

There is probably even more marginal improvement possible if all of the images are stored in different (length 1) columns of a single ColumnDataSource and updated at once, rather than iterating over several different data sources.

https://en.xdnf.cn/q/71240.html

Related Q&A

AttributeError: module cv2.cv2 has no attribute TrackerMOSSE_create

As the Dans suggestion, i tried to edit this post Error occurred at setting up MOOSE tracker, I also dont know why this error happened because i installed the Opencv-contrib-python==4.5.1.48.However,af…

Python, Error audio Recording in 16000Hz using Pyaudio

I use Python 2.7.3, Mac OS 10.8.2 and Xcode 4.5.1I am trying to record sound using PyAudio following the instructions in http://people.csail.mit.edu/hubert/pyaudio/and using the program ""&qu…

FastAPI passing json in get request via TestClient

Im try to test the api I wrote with Fastapi. I have the following method in my router : @app.get(/webrecord/check_if_object_exist) async def check_if_object_exist(payload: WebRecord) -> bool:key = g…

Python TDD directory structure

Is there a particular directory structure used for TDD in Python?Tutorials talk about the content of the tests, but not where to place themFrom poking around Python Koans, suspect its something like:/…

Pillow was built without XCB support

Im working on a program that uses ImageGrab in Pillow. I am getting the error mentioned in the title. I notice in the documentation that it says the generic pip install Pillow doesnt come with libxcb. …

Set equal aspect in plot with colorbar

I need to generate a plot with equal aspect in both axis and a colorbar to the right. Ive tried setting aspect=auto, aspect=1, and aspect=equal with no good results. See below for examples and the MWE.…

How to emit dataChanged in PyQt5

The code below breaks on self.emit line. It works fine in PyQt4. How to fix this code so it works in PyQt5?from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtCore import QObject, pyqtSignalclass …

django: gettext and coercing to unicode

I have following code in my django application.class Status(object):def __init__(self, id, desc):self.id = idself.desc = descdef __unicode__(self):return self.descSTATUS = Status(0, _(u"Some text&…

Telegram bot api keyboard

I have problem with Telegram Bot Api and with "ReplyKeyboard". Im using Python 2.7 and I send post request:TelegramAPI.post(TELEGRAM_URL + "sendMessage", data=dict(chat_id=CHAT_ID, …

Use of torch.stack()

t1 = torch.tensor([1,2,3]) t2 = torch.tensor([4,5,6]) t3 = torch.tensor([7,8,9])torch.stack((t1,t2,t3),dim=1)When implementing the torch.stack(), I cant understand how stacking is done for different di…