I need to plot a profile of an image, which is, to plot values of a matrix column.
And to implement it as a drag tool, which would automatically update the lower plot based on cursor position over the upper plot:
Based on "A New Custom Tool" example from the docs I've written a code which works fine but has several problems:
import numpy as np
import bokeh.plotting as bp
from bokeh.models import CustomJS
from bokeh.layouts import layout, column, row
from bokeh.io import reset_output
from PIL import Image
im = Image.open(r'C:\Documents\image1.jpg')
z = np.array(im)[:,:,0]
from bokeh.core.properties import Instance, Float
from bokeh.io import output_file, show, output_notebook
from bokeh.models import ColumnDataSource, Tool
from bokeh.plotting import figure
from bokeh.util.compiler import TypeScript
from bokeh.layouts import layout, column, row
#output_file("a.html")
#reset_output()
# image vertical profile tool
TS_CODE = """
import {GestureTool, GestureToolView} from "models/tools/gestures/gesture_tool"
import {ColumnDataSource} from "models/sources/column_data_source"
import {GestureEvent} from "core/ui_events"
import * as p from "core/properties"
export class DrawToolView extends GestureToolView {
model: DrawTool
//this is executed when the pan/drag event starts
_pan_start(_ev: GestureEvent): void {
this.model.source.data = {x: [], y: []}
}
//this is executed on subsequent mouse/touch moves
_pan(ev: GestureEvent): void {
const {frame} = this.plot_view
const {sx, sy} = ev
if (!frame.bbox.contains(sx, sy))
return
const x = frame.xscales.default.invert(sx)
const y = frame.yscales.default.invert(sy)
var res = Array(128);
var rx = Math.round(x);
for(var i=0; i<128; i++) res[i] = this.model.zz.data["z"][i*225+rx];
this.model.source.data = {
x: Array(128).fill(0).map(Number.call, Number),
y: res
};
this.model.source.change.emit()
}
// this is executed then the pan/drag ends
_pan_end(_ev: GestureEvent): void {}
}
export namespace DrawTool {
export type Attrs = p.AttrsOf<Props>
export type Props = GestureTool.Props & {
source: p.Property<ColumnDataSource>,
zz: p.Property<ColumnDataSource>,
width: p.Float
}
}
export interface DrawTool extends DrawTool.Attrs {}
export class DrawTool extends GestureTool {
properties: DrawTool.Props
constructor(attrs?: Partial<DrawTool.Attrs>) {
super(attrs)
}
tool_name = "Drag Span"
icon = "bk-tool-icon-lasso-select"
event_type = "pan" as "pan"
default_order = 12
static initClass(): void {
this.prototype.type = "DrawTool"
this.prototype.default_view = DrawToolView
this.define<DrawTool.Props>({
source: [ p.Instance ],
zz: [ p.Instance ],
width: [ p.Float ]
})
}
}
DrawTool.initClass()
"""
class DrawTool(Tool):
__implementation__ = TypeScript(TS_CODE)
source = Instance(ColumnDataSource)
zz = Instance(ColumnDataSource)
width = Float()
output_notebook()
source = ColumnDataSource(data=dict(x=[], y=[]))
zz = ColumnDataSource(data=dict(z=z.flatten()))
p1 = figure(plot_width=600, plot_height=200, x_range=(0, 225), y_range=(0, 128),
tools=[DrawTool(source=source, zz=zz, width=225)])
im = p1.image(image=[np.flipud(z)], x=0, y=0, dw=225,
dh=128, palette='Greys256')
p2 = figure(plot_width=600, plot_height=200)
p2.line('x', 'y', source=source)
bp.show(column(p1, p2))
1) Image dimensions are hard-coded now: how do I feed image dimensions from python to js?
2) The image is transferred to the client twice: first as an argument to image()
, then as source for the button plot. How to access the image "source" from the DrawTool?
3) If (all this code being in one jupyter cell) I run it the second time it refuses to plot anything with a javascript error in console Model 'DrawTool' does not exist.
Running it the third time, fourth time and further on works fine. What exactly is bokeh trying to tell me in this error message?