Creating bounding box ground truth tool with Bokeh
Imports and user defined functions
In [11]:
from bokeh.models import CustomJS, ColumnDataSource, BoxSelectTool, Button, Range1d, Rect, ImageURL
from bokeh.plotting import figure, output_notebook, show, output_file
from bokeh.layouts import gridplot
from bokeh.resources import INLINE
from bokeh.embed import components
from jinja2 import Template
import os
def save_or_show_plot(layout, filename='', title=''):
if filename:
output_file(filename, title=title, mode='inline')
show(layout)
else:
output_notebook(INLINE)
show(layout)
Loading the data
In [12]:
#with open('imagelinks.txt') as f:
# content = f.readlines()
img_list = ['https://raw.githubusercontent.com/ghandic/GroundTruth/master/Images/' + x for x in os.listdir('../Images')]
#img_list = [x.strip() for x in content]
source = ColumnDataSource(data=dict(x=[], y=[], width=[], height=[]))
out_source = ColumnDataSource(data=dict(x=[], y=[], width=[], height=[]))
img_source = ColumnDataSource(data=dict(url=img_list))
shown_img_source = ColumnDataSource(data=dict(x=[0], y=[1], width=[1], height=[1], url=[img_list[0]], counter=[0]))
Creating custom JavaScript callbacks
In [13]:
reset_callback = CustomJS(args=dict(source=source),
code="""
var data = source.data;
data['x'] = [];
data['y'] = [];
data['width'] = [];
data['height'] = [];
source.change.emit();
""")
download_callback = CustomJS(args=dict(out_source=out_source, img_source=img_source),
code="""
var outdata = out_source.data;
var img_data = img_source.data;
var filetext = "img,x,y,width,height\\n";
for (i=0; i < outdata['x'].length; i++) {
if (outdata['x'][i]){
var currRow = ['"' + img_data['url'][i].toString() + '"',
outdata['x'][i].toString(),
outdata['y'][i].toString(),
outdata['width'][i].toString(),
outdata['height'][i].toString().concat('\\n')];
var joined = currRow.join();
filetext = filetext.concat(joined);
}
}
var filename = 'data_result.csv';
var blob = new Blob([filetext], { type: 'text/csv;charset=utf-8;' });
if (navigator.msSaveBlob) {
navigator.msSaveBlob(blob, filename);
}
else {
var link = document.createElement("a");
link = document.createElement('a')
link.href = URL.createObjectURL(blob);
link.download = filename
link.target = "_blank";
link.style.visibility = 'hidden';
link.dispatchEvent(new MouseEvent('click'))
}
""")
submit_callback = CustomJS(args=dict(source=source, out_source=out_source,
img_source=img_source, shown_img_source=shown_img_source),
code="""
var data = source.data;
var outdata = out_source.data
var img_data = img_source.data;
var shown_img_data = shown_img_source.data;
var last_img = img_data['url'].length;
outdata['x'].push(data['x'][0]);
outdata['y'].push(data['y'][0]);
outdata['width'].push(data['width'][0]);
outdata['height'].push(data['height'][0]);
counter = Number(shown_img_data['counter']) + 1;
if (counter >= last_img) {
var filetext = "img,x,y,width,height\\n";
for (i=0; i < outdata['x'].length; i++) {
if (outdata['x'][i]){
var currRow = [img_data['url'][i].toString(),
outdata['x'][i].toString(),
outdata['y'][i].toString(),
outdata['width'][i].toString(),
outdata['height'][i].toString().concat('\\n')];
var joined = currRow.join();
filetext = filetext.concat(joined);
}
}
var filename = 'bokeh_result.csv';
var blob = new Blob([filetext], { type: 'text/csv;charset=utf-8;' });
navigator.msSaveBlob(blob, filename);
if (navigator.msSaveBlob) {
navigator.msSaveBlob(blob, filename);
}
else {
var link = document.createElement("a");
link = document.createElement('a')
link.href = URL.createObjectURL(blob);
link.download = filename
link.target = "_blank";
link.style.visibility = 'hidden';
link.dispatchEvent(new MouseEvent('click'))
}
} else {
console.log(img_data['url'][counter]);
shown_img_data['url'] = [img_data['url'][counter]];
shown_img_data['counter'] = counter
shown_img_source.change.emit();
}
data['x'] = [];
data['y'] = [];
data['width'] = [];
data['height'] = [];
source.change.emit();
out_source.change.emit();
""")
get_box_callback = CustomJS(args=dict(source=source), code="""
// get data source from Callback args
var data = source.data;
/// get BoxSelectTool dimensions from cb_data parameter of Callback
console.log(cb_data)
var geometry = cb_data['geometry'];
/// calculate Rect attributes
var width = geometry['x1'] - geometry['x0'];
var height = geometry['y1'] - geometry['y0'];
var x = geometry['x0'] + width/2;
var y = geometry['y0'] + height/2;
/// update data source with new Rect attributes
data['x'] = [x];
data['y'] = [y];
data['width'] = [width];
data['height'] = [height];
// emit update of data source
source.change.emit();
""")
Creating the figure and widgets
In [14]:
box_select = BoxSelectTool(callback=get_box_callback)
p = figure(plot_width=600,
plot_height=400,
tools=[box_select, 'tap'],
title="Select the cats!",
x_range=Range1d(start=0.0, end=1.0),
y_range=Range1d(start=0.0, end=1.0))
rect = Rect(x='x',
y='y',
width='width',
height='height',
fill_alpha=0.2,
fill_color='#009933')
img = ImageURL(url='url', x='x', y='y', w='width', h='height')
p.add_glyph(shown_img_source, img)
p.add_glyph(source, rect, selection_glyph=rect)
p.js_on_event('tap', reset_callback)
download_button = Button(label="Download", callback = download_callback)
submit_button = Button(label="Submit", button_type="success",
callback=submit_callback,width=580)
layout_rect = gridplot([[p], [submit_button], [download_button]])
p.xgrid.grid_line_color = None
p.ygrid.grid_line_color = None
p.xaxis.visible = False
p.yaxis.visible = False
p.outline_line_color = None
p.title.align = "center"
p.title.text_font_size = '12pt'
save_or_show_plot(layout_rect)
Centering the app for html page and removing toolbar
In [15]:
script, div = components(layout_rect)
js_resources = INLINE.render_js()
css_resources = INLINE.render_css()
newdiv = '<center>' + div +'\n</center>'
template = Template('''<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Boundingbox - Ground truth tool</title>
{{ js_resources }}
{{ css_resources }}
{{ script }}
<style>
.wrapper {
width: 800px;
background-color: white;
margin: 0 auto;
}
.plotdiv {
margin: 0 auto;
}
.bk-toolbar-box {
visibility: hidden;
}
</style>
</head>
<body>
<div class='wrapper'>
{{ div }}
</div>
</body>
</html>
''')
html = template.render(js_resources=js_resources,
css_resources=css_resources,
script=script,
div=newdiv)
Final output
In [16]:
with open('../Finished tools/Bokeh_ground_truth_tool.html', 'w') as file:
file.write(html)
In [ ]: