I am trying to show a sequence of images using Bokeh and hope to interactively slide through or play the sequence. When I run my script it shows the first image but the image is not updating when I drag the slider or click the play button. My code is as following:
import numpy as np
import bokeh.io
from bokeh.plotting import figure
from bokeh.io import curdoc
from bokeh.layouts import layout
from bokeh.models import ColumnDataSource, HoverTool, SingleIntervalTicker, Slider, Button, Label
from bokeh.palettes import Spectral1
from skimage.external import tifffile as T
img=T.imread('C:/Users/UserXX/Desktop/Image_Sequence.tif')
sources={}
frames=list(range(0,img.shape[0]))
for frame in frames:
sources[frame]=ColumnDataSource(data=dict(image=[img[frame,:,:]]))
source1 = sources[0]
p_img = figure(plot_width=694, plot_height=520, x_range=[0,1388], y_range=[0, 1040])
label = Label(x=1.1, y=18, text=str(frames[0]), text_font_size='70pt', text_color='#eeeeee')
p_img.add_layout(label)
p_img.image(image='image', x=[0], y=[0], dw=[1388], dh=[1040],source=source1, palette="Spectral11")
slider = Slider(start=frames[0], end=frames[-1], value=frames[0],step=1, title="Frame")
def animate_update():
frame = slider.value + 1
if frame > frames[-1]:
frame = frames[0]
slider.value = frame
def slider_update(attr, old, new):
global source
global sources
frame = slider.value
label.text = str(frame)
source= sources[frame]
slider.on_change('value', slider_update)
def animate():
if button.label == '► Play':
button.label = '❚❚ Pause'
curdoc().add_periodic_callback(animate_update, 200)
else:
button.label = '► Play'
curdoc().remove_periodic_callback(animate_update)
button = Button(label='► Play', width=60)
button.on_click(animate)
l = layout([[p_img],[slider, button],], sizing_mode='scale_width')
curdoc().add_root(l)
curdoc().title = "Image_Sequence"
I am using this as an example: https://github.com/bokeh/bokeh/blob/master/examples/app/gapminder/main.py I am not sure if the way that I am pass new image data to the source is correct or not. Any suggestion?
There's a few different things that need fixing or improving in your code, so it's probably just easier to study and emulate this simplified complete working example:
import numpy as np
from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, Slider
from bokeh.plotting import figure
N = 100
x_ = np.linspace(0, 10, 200)
y_ = np.linspace(0, 10, 200)
z_ = np.linspace(0, 10, N)
x, y, z = np.meshgrid(x_, y_, z_, indexing='xy')
data = np.sin(x+z)*np.cos(y)
source = ColumnDataSource(data=dict(image=[data[:, :, 0]]))
p = figure(x_range=(0, 10), y_range=(0, 10))
p.image(image='image', x=0, y=0, dw=10, dh=10, source=source, palette="Spectral11")
slider = Slider(start=0, end=(N-1), value=0, step=1, title="Frame")
def update(attr, old, new):
source.data = dict(image=[data[:, :, slider.value]])
slider.on_change('value', update)
curdoc().add_root(column(p, slider))
Please note that updates happen on every slider change, and even for 200x200 things can start to get slightly laggy (at least on my system). So you may also want to consider the technique described in Throttling in Bokeh application
Base on @bigreddot's example. I got my code work to update images when dragging the slider or click button. Though for images with larger size, they are updating very slowly.
import numpy as np
from bokeh.plotting import figure,ColumnDataSource
from bokeh.io import curdoc
from bokeh.layouts import layout
from bokeh.models import Slider, Button, Label
from bokeh.palettes import Spectral11
from skimage.external import tifffile as T
img_o=T.imread('C:/Users/UserXX/Desktop/Image_Sequence.tif')
frames=list(range(0,img_o.shape[0]))
img=np.flip(img_o,1)
source = ColumnDataSource(data=dict(image=[img[0,:,:]]))
p_img = figure(x_range=(0,1388), y_range=(0, 1040))
label = Label(x=1.1, y=18, text=str(frames[0]), text_font_size='70pt', text_color='#eeeeee')
p_img.add_layout(label)
im=p_img.image(image='image', x=0, y=0, dw=1388, dh=1040, source=source, palette="Spectral11")
slider = Slider(start=frames[0], end=frames[-1], value=frames[0],step=1, title="Frame")
def animate_update():
frame = slider.value + 1
if frame > frames[-1]:
frame = frames[0]
slider.value = frame
ds=im.data_source
def slider_update(attr, old, new):
new_data=dict()
frame = slider.value
label.text = str(frame)
new_data['image']=[img[frame,:,:]]
ds.data= new_data
slider.on_change('value', slider_update)
def animate():
if button.label == '► Play':
button.label = '❚❚ Pause'
curdoc().add_periodic_callback(animate_update, 200)
else:
button.label = '► Play'
curdoc().remove_periodic_callback(animate_update)
button = Button(label='► Play', width=60)
button.on_click(animate)
l = layout([[p_img],[slider,button],], sizing_mode='scale_width')
curdoc().add_root(l)
curdoc().title = "Image_sequence"
I mainly change the way how the data is passed to the source. Another thing is this line would not work (the first frame is plotted but not updated)
im=p_img.image(image='image', x=[0], y=[0], dw=[1388], dh=[1040], source=source, palette="Spectral11")
After I change to:
im=p_img.image(image='image', x=0, y=0, dw=1388, dh=1040, source=source, palette="Spectral11")
The images can be updated. The only difference is the [ ] is get rid off from the parameter x, y, dw, dh. But I don't know why it will cause a problem.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With