Search code examples
pythonimageplotbokeh

How to create Bokeh plots with image in background?


I want to create a circle plot with Bokeh, with an image in background, see the code bellow. I can't figure out why the image is not displayed. Is there something wrong in my code, or the method I use is not the proper one?

import pandas as pd
import cv2

from bokeh.models import ImageURL, ColumnDataSource
from bokeh.plotting import figure
from bokeh.models.ranges import DataRange1d
from bokeh.io import show

df = pd.DataFrame({'Time': [2586, 2836, 2986, 3269, 3702],
                   'X': [120, 210, 80, 98, 40],
                   'Y': [230, 40, 33, 122, 10],
                   'User': ['u1', 'u1', 'u2', 'u2', 'u2']})

source = ColumnDataSource(data=dict(time=df['Time'], x=df['X'], y=df['Y'], user=df['User']))

#read image
img_url = 'tree.jpg'
image=cv2.imread(img_url)

sx = image.shape[1]
sy = image.shape[0]
x_range = DataRange1d(start = 0, end = sx, bounds = (0, sx), range_padding = 5, range_padding_units = 'percent')
y_range = DataRange1d(start = sy, end = 0, bounds = (0, sy), range_padding = 5, range_padding_units = 'percent')
pw = 600
ph = int(pw * sy / sx)

# Create the figure: p
p = figure(x_axis_label='X', y_axis_label='Y', plot_width = pw, plot_height = ph, 
           x_range=x_range, y_range=y_range, match_aspect=True)
p.image_url(url=[img_url], x = 0, y = 0, w = sx, h = sy, anchor="top_right")
p.circle(x='x', y='y', source=source, size=10, color='blue', fill_color='white', alpha=0.8)

show(p)

Image enter image description here


Solution

  • img_url needs to be a proper URL that the resulting page is able to access. A local file system path to a file is not a URL. Unfortunately, you cannot use image_url with a local file without using some sort of a web server because web pages cannot access local files for security reasons. Two feasible options that I can think of:

    • Use bokeh serve with a directory-based app. It will allow you to serve arbitrary files via the static directory
    • Don't use image_url, use image_rgba. You already read the whole image, so you can just serve it as data instead of serving it as a URL

    If you can't make image_url work, try playing with its anchor and make sure that the url parameter starts with the name of the directory. I could display the image by creating a directory named test_app:

    test_app
    ├── main.py
    └── static
        └── tree.png
    

    where main.py has only

    from bokeh.io import curdoc
    from bokeh.plotting import figure
    
    p = figure()
    p.image_url(url=['/test_app/static/tree.png'],
                x=[0], y=[0], w=[1], h=[1], anchor="bottom_left")
    
    curdoc().add_root(p)
    

    It should be run as bokeh serve test_app.