How do I work with images in Bokeh (Python) - python

For example you can plot an image in matplotlib using this code:
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
img=mpimg.imread('image.png')
plt.imshow(img)
Is something like this possible with Bokeh(0.10)?

You can use the ImageURL glyph (image_url plot method)to load images locally or from the web.
from bokeh.plotting import figure, show, output_file
output_file('image.html')
p = figure(x_range=(0,1), y_range=(0,1))
p.image_url(url=['tree.png'], x=0, y=1, w=0.8, h=0.6)
## could also leave out keywords
# p.image_url(['tree.png'], 0, 1, 0.8, h=0.6)
show(p)
One gotcha - if you graph only an image (and no other data), you'll have to explicitly set the plot ranges.
Here's the docs:
http://docs.bokeh.org/en/latest/docs/reference/models/glyphs.html#bokeh.models.glyphs.ImageURL

The earlier answer was helpful. However, I wanted an image only option without any additional object. So, adding the answer for Bokeh version 0.12.0 and removed all the grids, axes and toolbar.
from bokeh.plotting import figure, curdoc
from bokeh.models import ColumnDataSource, Range1d
bosch_logo = "static/tree.jpg"
logo_src = ColumnDataSource(dict(url = [bosch_logo]))
page_logo = figure(plot_width = 500, plot_height = 500, title="")
page_logo.toolbar.logo = None
page_logo.toolbar_location = None
page_logo.x_range=Range1d(start=0, end=1)
page_logo.y_range=Range1d(start=0, end=1)
page_logo.xaxis.visible = None
page_logo.yaxis.visible = None
page_logo.xgrid.grid_line_color = None
page_logo.ygrid.grid_line_color = None
page_logo.image_url(url='url', x=0.05, y = 0.85, h=0.7, w=0.9, source=logo_src)
page_logo.outline_line_alpha = 0
curdoc().add_root(page_logo)

Another option is to display the image in a div.:
from bokeh.io import output_notebook, show
from bokeh.models.widgets import Div
output_notebook()
div_image = Div(text="""<img src="https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/7.png" alt="div_image">""", width=150, height=150)
show(div_image)
ImageURL can't get updated dynamically with a callback. However, using a div, you can do so by treating the div_image.text as a regular Python string, for example:
from ipywidgets import interact
from bokeh.io import output_notebook, show, push_notebook
from bokeh.models.widgets import Div
output_notebook()
div_image = Div(text="""<img src="https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/1.png" alt="div_image">""", width=100, height=100)
def update(pokemon_number=1):
div_image.text = """<img src="https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/{}.png" alt="div_image">""".format(pokemon_number)
push_notebook()
show(div_image, notebook_handle=True)
interact(update, pokemon_number=[1, 4, 7])
Of course, the image source can also point to a local file.
(Tested in Python 3.7.3 and bokeh 1.2.0)

Running this example using bokeh serve is a bit more tricky. I suggest to setup working directory properly:
server_folder/
+main.py
+static/
+logo.png
.. and run bokeh serve command from directory ABOVE server_folder
bokeh serve server_folder --show
Then this code works for me
#main.py file
from bokeh.plotting import figure, curdoc
x_range = (-20,-10) # could be anything - e.g.(0,1)
y_range = (20,30)
p = figure(x_range=x_range, y_range=y_range)
#img_path = 'https://docs.bokeh.org/en/latest/_static/images/logo.png'
img_path = 'server_folder/static/logo.png'
p.image_url(url=[img_path],x=x_range[0],y=y_range[1],w=x_range[1]-x_range[0],h=y_range[1]-y_range[0])
doc = curdoc()
doc.add_root(p)

Here is a simple example that works, almost the same simple format as you requested:
from PIL import Image
import numpy as np
from bokeh.plotting import figure, output_notebook, show
output_notebook()
#load image
im = Image.open('Segment_image.png') # just replace any image that you want here
p = figure()
imarray = np.array(im.convert("RGBA"))
plotted_image = p.image_rgba(image=[imarray.view("uint32").reshape(imarray.shape[:2])], x=0, y=0, dw=imarray.shape[0], dh=imarray.shape[1])
show(p)

Related

Holoviews Bars Ignoring .opts()

I am trying to pass .opts() arguments to style a holoviews .Bars graph, but all arguments are being ignored when passed. This results in the following error:
WARNING:param.Bars02978: Use of call to set options will be deprecated in the next major release (1.14.0). Use the equivalent .opts method instead.
I am on version 1.14.8.
How can I pass the .opts() so my graph can be styled appropriately?
# https://www.youtube.com/watch?v=ZWP_h6WV8h0 Connecting multiple plots
from collections import defaultdict
import panel as pn
import holoviews as hv
from holoviews import opts
import pandas as pd
import numpy as np
import pandas_bokeh
import bokeh
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, TableColumn, DataTable, DateEditor, IntEditor\
, DateFormatter, NumeralTickFormatter, CustomJS, DatePicker, widgets
from bokeh.models import Panel, Tabs, Row
from bokeh.io import show
from bokeh.layouts import Column, layout
import hvplot.pandas
hv.extension('bokeh', 'matplotlib')
renderer = hv.renderer('bokeh')
renderer = renderer.instance(mode='server')
data = {
"calories": [420],
"duration": [50],
"ggg": [60]
}
#load data into a DataFrame object:
df = pd.DataFrame(data)
print(df)
df2 = pd.DataFrame({'Missing Data': df[['calories']].sum(axis=1), 'Delayed Data': df[['duration']].sum(axis=1)
, 'Duplicate Data': df[['ggg']].sum(axis=1)})
print(df2)
bar_opts = opts.Bars(height=400, width=600, tools=["hover"], bgcolor="grey", xlabel="Wine Class", ylabel="Malic Acid", ylim=(0.0, 3.5))
bars = hv.Bars(pd.melt(df2.reset_index(), ['index']), ['index', 'variable'], 'value', label='dl_refined_analytics').opts(bar_opts)
bars.opts(height=400, width=600, tools=["hover"], bgcolor="grey", xlabel="Wine Class", ylabel="Malic Acid", ylim=(0.0, 3.5))
dmap = hv.DynamicMap(bars)
app = pn.Row(dmap)
app.show() ```
What was the intent behind hv.DynamicMap(bars)? I'm not sure why you'd want to pass the bars object to a DynamicMap constructor, and if you just use app = pn.Row(bars) instead of app = pn.Row(dmap) it should work fine.

How to update a python bokeh plot as soon as data is available? [duplicate]

I want Bokeh to update periodically and arbitrarily when the results from a separate algorithm running in python returns results, not based on any input from the Bokeh interface.
I've tried various solutions but they all depend on a callback to a some UI event or a periodic callback as in the code below.
import numpy as np
from bokeh.plotting import figure, curdoc
from bokeh.models import ColumnDataSource, Plot, LinearAxis, Grid
from bokeh.models.glyphs import MultiLine
from time import sleep
from random import randint
def getData(): # simulate data acquisition
# run slow algorith
sleep(randint(2,7)) #simulate slowness of algorithm
return dict(xs=np.random.rand(50, 2).tolist(), ys=np.random.rand(50, 2).tolist())
# init plot
source = ColumnDataSource(data=getData())
plot = Plot(
title=None, plot_width=600, plot_height=600,
min_border=0, toolbar_location=None)
glyph = MultiLine(xs="xs", ys="ys", line_color="#8073ac", line_width=0.1)
plot.add_glyph(source, glyph)
xaxis = LinearAxis()
plot.add_layout(xaxis, 'below')
yaxis = LinearAxis()
plot.add_layout(yaxis, 'left')
plot.add_layout(Grid(dimension=0, ticker=xaxis.ticker))
plot.add_layout(Grid(dimension=1, ticker=yaxis.ticker))
curdoc().add_root(plot)
# update plot
def update():
bokeh_source = getData()
source.stream(bokeh_source, rollover=50)
curdoc().add_periodic_callback(update, 100)
This does seem to work, but is this the best way to go about things? Rather than having Bokeh try to update every 100 milliseconds can I just push new data to it when it becomes available?
Thanks
You can use zmq and asyncio to do it. Here is the code for the bokeh server, it wait data in an async coroutine:
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, curdoc
from functools import partial
from tornado.ioloop import IOLoop
import zmq.asyncio
doc = curdoc()
context = zmq.asyncio.Context.instance()
socket = context.socket(zmq.SUB)
socket.connect("tcp://127.0.0.1:1234")
socket.setsockopt(zmq.SUBSCRIBE, b"")
def update(new_data):
source.stream(new_data, rollover=50)
async def loop():
while True:
new_data = await socket.recv_pyobj()
doc.add_next_tick_callback(partial(update, new_data))
source = ColumnDataSource(data=dict(x=[0], y=[0]))
plot = figure(height=300)
plot.line(x='x', y='y', source=source)
doc.add_root(plot)
IOLoop.current().spawn_callback(loop)
to send the data just run following code in another python process:
import time
import random
import zmq
context = zmq.Context.instance()
pub_socket = context.socket(zmq.PUB)
pub_socket.bind("tcp://127.0.0.1:1234")
t = 0
y = 0
while True:
time.sleep(1.0)
t += 1
y += random.normalvariate(0, 1)
pub_socket.send_pyobj(dict(x=[t], y=[y]))

How do I show two Bokeh objects at once?

I am trying to use Bokeh vbar with RangeTool. I tried many different lines of code but this is still not working. I don't understand the error with 'browser'
The same code work with line or scatter
This is the error:
TypeError: argument of type 'Figure' is not iterable
This is the code below:
from bokeh.io import show
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, RangeTool
from bokeh.plotting import figure
from bokeh.models import Range1d
x = [1,2,3,4,5,6,7,8]
wdth = [0.5 for x in range(0,8)]
top = [8,7,6,5,4,3,2,1]
sourceRT5 = ColumnDataSource(data=dict(x=x, width=wdth, top=top))
p = figure(plot_height=300, plot_width=800,
tools="", toolbar_location=None,
x_axis_type="linear")
p.vbar('x', 'width', 'top', bottom=0, line_color='dodgerblue',
fill_color="dodgerblue", legend='New Words',
source=sourceRT5)
p.yaxis.axis_label = 'Price'
select = figure(plot_height=50, plot_width=800, y_range=(0,10),
x_axis_type="linear", y_axis_type=None,
tools="", toolbar_location=None)
range_rool = RangeTool(x_range=Range1d(3,8))
range_rool.overlay.fill_color = "navy"
range_rool.overlay.fill_alpha = 0.2
select.vbar('x', 'width', 'top', source=sourceRT5)
select.ygrid.grid_line_color = None
select.add_tools(range_rool)
select.toolbar.active_multi = range_rool
show(p,select)
More on the error:
File "FromInt.py", line 6433, in testrangeVBar
show(p,select)
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/bokeh/io/showing.py", line 137, in show
return _show_with_state(obj, state, browser, new, notebook_handle=notebook_handle)
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/bokeh/io/showing.py", line 165, in _show_with_state
controller = get_browser_controller(browser=browser)
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/bokeh/util/browser.py", line 47, in get_browser_controller
controller = webbrowser.get(browser)
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/webbrowser.py", line 34, in get
if '%s' in browser:
TypeError: argument of type 'Figure' is not iterable
It seems you are trying to display multiple figures. show takes a single object which it should display, the second argument is which browser it is supposed to use for that (e.g. firefox, chrome, IE, vivaldi, ...). While you wrote it worked with scatter I guess that it did not throw an error but did not actually work.
Bokeh has some documentation on displaying multiple plots. Short version is you need to tell it in which way it should layout it. A very general function is layout :
from bokeh.layouts import layout
... # your code without show(...)
show(layout([p,select]))

Select columns (not rows) in Bokeh Table Widget?

I have a bokeh table that is linked to a plot, and is working as intended. Selecting a row in the table mutes all the non-selected rows in the plot display.
However, what if someone wants to select a column, and hide all other columns in the plot? Is this possible using a bokeh widget? Or does some custom code need to be written for this feature? I have attached to code used to produce the widget table on the bokeh website, as it is the most simple example I can think of (and quickest).
from datetime import date
from random import randint
from bokeh.io import output_file, show
from bokeh.layouts import widgetbox
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import DataTable, DateFormatter, TableColumn
output_file("data_table.html")
data = dict(
dates=[date(2014, 3, i+1) for i in range(10)],
downloads=[randint(0, 100) for i in range(10)],
)
source = ColumnDataSource(data)
columns = [
TableColumn(field="dates", title="Date", formatter=DateFormatter()),
TableColumn(field="downloads", title="Downloads"),
]
data_table = DataTable(source=source, columns=columns, width=400, height=280)
show(widgetbox(data_table))
Here is a code with a JS callback that allows you to know the selected row and column.
from bokeh.io import show
from bokeh.layouts import widgetbox
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.models.widgets import DataTable,TableColumn
column_list = ['col1','col2','col3']
source = ColumnDataSource(data = {key:range(10) for key in column_list})
columns = [TableColumn(field=col, title=col) for col in column_list]
data_table = DataTable(source=source, columns=columns, width=400, height=280,selectable=True)
source_code = """
var grid = document.getElementsByClassName('grid-canvas')[0].children;
var row = '';
var col = '';
for (var i=0,max=grid.length;i<max;i++){
if (grid[i].outerHTML.includes('active')){
row=i;
for (var j=0,jmax=grid[i].children.length;j<jmax;j++){
if(grid[i].children[j].outerHTML.includes('active')){col=j}
}
}
}
console.log('row',row);
console.log('col',col);
cb_obj.selected['1d'].indices = [];
"""
source.callback = CustomJS(code= source_code)
show(widgetbox(data_table))
The line cb_obj.selected['1d'].indices = []; just resets the selected indices so that the callback can trigger even if the same cell is clicked multiple times
You can then do what you want with the row/column index
If you need to you can also "transfer" the values back to python by updating a ColumnDatasource with the row and column values.
I use bokeh 0.12.10 so this may require some change with the latest version
EDIT: tested with 0.12.16 and it still works
EDIT: update for bokeh 1.1.0
from bokeh.io import show
from bokeh.layouts import widgetbox
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.models.widgets import DataTable,TableColumn
column_list = ['col1','col2','col3']
source = ColumnDataSource(data = {key:range(20) for key in column_list})
columns = [TableColumn(field=col, title=col) for col in column_list]
data_table = DataTable(source=source, columns=columns, width=400, height=280,selectable=True)
source_code = """
var grid = document.getElementsByClassName('grid-canvas')[0];
var active_row = grid.querySelectorAll('.active')[0];
if (active_row!=undefined){
var active_row_ID = Number(active_row.children[0].innerText);
for (var i=1, imax=active_row.children.length; i<imax; i++){
if (active_row.children[i].className.includes('active')){
var active_col_ID = i-1;
}
}
console.log('row',active_row_ID);
console.log('col',active_col_ID);
var active_cells = grid.querySelectorAll('.active');
for (i=0, imax=active_cells.length;i<imax;i++){
active_cells[i].classList.remove('active');
}
cb_obj.indices = [];
}
"""
source.selected.js_on_change('indices', CustomJS(args={'source':source},code= source_code) )
show(widgetbox(data_table))
As of Bokeh 0.12.16 the built-in DataTable does not support any sort of column selection or click events. Looking at the descriptions of Grid Events for the underlying SlickGrid implementation, it appears that onClick and onHeaderClick are supported at the low level. So the immediately accessible option would be Extending Bokeh with a custom DataTable subclass. Otherwise you can submit a GitHub feature request issue to discuss exposing these events somehow in the built in table.

hide annotations in bokeh

I have a Bokeh plot where I add some data in the form of a LabelSet and BoxAnnotation as overlay, but I want to be able to dynamically enable/disable this overlay.
I can enable/hide some of the lines in the plot already, but the system for the Annotations seems to be different. I've got so far already
Initialising
from ipywidgets import interact
from bokeh.plotting import figure as bf
from bokeh.layouts import layout as bl
from bokeh.models import Toggle, BoxAnnotation, CustomJS
from bokeh.io import push_notebook, show, output_notebook
output_notebook()
Widget generation
p = bf(title='test', x_range=(0,1), y_range=(0,1))
x = [1/3, 2/3]
y=[1/3, 2/3]
p.circle(x=x, y=y, size=15)
box = BoxAnnotation(left=None, right=0.5, fill_color='red', fill_alpha=0.1)
p.add_layout(box)
Interactivity
code = '''\
if toggle.active
box.visible = true
console.log 'enabling box'
else
box.visible = false
console.log 'disabling box'
'''
callback = CustomJS.from_coffeescript(code=code, args={})
toggle = Toggle(label="Red Box", button_type="success", callback=callback)
callback.args = {'toggle': toggle, 'box': box}
layout = bl([p], [toggle])
show(layout)
When I check the JS console, the if/else clauses get triggered as expected, so the Toggle works but the red box stays in place, both in Firefox as in IE
I think there might be some plumbing that is not hooked up on the BokehJS side to respond to visible. If so, that's a bug. Please make an issue with all this information in the Project Issue Tracker.
In the mean time, you can accomplish the same visual effect by manipulating the alpha values instead:
code = '''\
if toggle.active
box.fill_alpha = 0.1
box.line_alpha = 1
console.log 'enabling box'
else
box.fill_alpha = 0
box.line_alpha = 0
console.log 'disabling box'
'''
callback = CustomJS.from_coffeescript(code=code, args={})
toggle = Toggle(label="Red Box", button_type="success", callback=callback)
callback.args = {'toggle': toggle, 'box': box}
layout = bl([p], [toggle])
show(layout)

Categories