Plot the sum of an image selection

I am starting with bokeh and I wonder if anyone could point me in the right direction.

I have an image (2D array). Using the gallery example:

import numpy as np

from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, RangeTool
from bokeh.layouts import column

x = np.linspace(0, 10, 300)
y = np.linspace(0, 10, 300)
xx, yy = np.meshgrid(x, y)
d = np.sin(xx) * np.cos(yy)

# Figures creation
im_fig = figure(width=400, height=400)

# Plotting the data
im_fig.image(image=[d], x=0, y=0, dw=10, dh=10, palette="Sunset11", level="image")
im_fig.grid.grid_line_width = 0.5

show(im_fig)

Which results in:

bokeh_plot

Now I would like to sum the data along a y selection and plot it in another figure. This seems to be the work of the RangeTool.

I create another figure to plot the summed data of the selection but I get an error while adding the initial range:

import numpy as np

from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, RangeTool
from bokeh.layouts import column

x = np.linspace(0, 10, 300)
y = np.linspace(0, 10, 300)
xx, yy = np.meshgrid(x, y)
d = np.sin(xx) * np.cos(yy)

# Figures creation
im_fig = figure(width=400, height=400)
sum_fig = figure(width=400, height=200)

# Plotting the data
im_fig.image(image=[d], x=0, y=0, dw=10, dh=10, palette="Sunset11", level="image")
im_fig.grid.grid_line_width = 0.5

# Adding the range tools
range_tool = RangeTool(y_range=im_fig.y_range)
range_tool.overlay.fill_color = "navy"
range_tool.overlay.fill_alpha = 0.2
im_fig.add_tools(range_tool)
im_fig.toolbar.active_multi = range_tool

show(column(im_fig, sum_fig))

This is the error:

failed to validate RangeTool(id='p1108', ...).y_range: expected either None or a value of type Instance(Range1d), got DataRange1d(id='p1003', ...)

My guess, this is happening because the Range tool is not compatible with the Image glyph. I wonder if anyone could please point me towards the right direction. Ty.

Well your thought that Range tool not being out of the box compatible with Image glyph I think is correct, but that’s not why you’re getting that error.

By default, when you create a figure without defining x/y ranges, a DataRange1d model is used, which is set to “follow” the values of all renderers (in your case it’ll follow your one renderer, the image). You need to initialize the range on your RangeTool with a Range1d model, which is pretty straightforward to do:

from bokeh.models import Range1d #...
#...
range_tool = RangeTool(x_range=Range1d(start=2,end=8))

That gets the RangeTool added to the plot, but to get from there to what you want happening on sum_fig you’ll have to elaborate about what you mean by “plotting the sum” before I can give any more advice.

Now I would like to sum the data along a y selection and plot it in another figure. This seems to be the work of the RangeTool

My guess, this is happening because the Range tool is not compatible with the Image glyph.

I think there is some confusion, so maybe the docs need some clarification. The RangeTool does not have anything to to with summing, or any other aggregations. It is also independent of any glyphs. It does not care what glyphs are part of the plot, or whether there are even any glyphs at all. It operates independently of glyphs.

The RangeTool is for allowing a user to drag an overlay across a plot, and record the stop/start of the drag area in a configured Range1d, full stop. If the configured range happens to be a range for another plot, that plot will update (because that is how Bokeh works in general). You would set things up like that if you wanted a range tool on one plot to be able to “zoom” another plot, but it does not sound like that is what you are after. So you would want to configure things as @gmerritt123 has shown above, with a new Range1d just for the tool, and then have JS or Python callbacks on the Range1d to do your aggeragations.

Thank you @gmerritt123 and @Bryan very much for your patience.

My apologies that the question was not very clear. With @gmerritt123 explanation I managed to initialize the RangeTool:

 import numpy as np

from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, RangeTool
from bokeh.layouts import column
from bokeh.models import Range1d

x = np.linspace(0, 20, 600)
y = np.linspace(0, 10, 300)
xx, yy = np.meshgrid(x, y)
d = np.sin(xx) * np.cos(yy)

# Figures creation
im_fig = figure(width=400, height=400)
sum_fig = figure(width=400, height=200)

# Plotting the data
im_fig.image(image=[d], x=0, y=0, dw=20, dh=10, palette="Sunset11", level="image")
im_fig.grid.grid_line_width = 0.5

# Adding the range tool
range_tool = RangeTool(y_range=Range1d(start=10, end=d.shape[0]/2))
range_tool.overlay.fill_color = "navy"
range_tool.overlay.fill_alpha = 0.2
im_fig.add_tools(range_tool)
im_fig.toolbar.active_multi = range_tool

# Plot the line and display the figures
sum_fig.line(x=np.arange(d.shape[1]), y=d.sum(axis=0), legend_label="Complete y range sum")
show(column(im_fig, sum_fig))

The lower plot shows the sum of the image data on the complete y interval. I would like to change this plot so the sum is limited to the user selected range. As @Bryan says, this might not be the best tool since what I want are the y coordinates on the selection and then recalculate the data to plot.

I wonder if you could recommend me another tool or some similar example to study.

Thanks again for all this work.

What you want to do is absolutely possible, but you need to do the custom-callback leg work yourself. The first step is probably to make a decision about whether to go the python-callback bokeh server route or the standalone CustomJS route. Ultimately you need a callback that will retrieve the start and end properties of the y_range attached to the RangeTool, then you will need to “slice” the image array to include only the rows within that range, then sum each column to assemble the sum line’s y values. That’s probably easiest via numpy’s array manipulation (i.e. python based bokeh server), but there are certainly ways to do it on the JS side too.

Thank you very much @gmerritt123. I am still very lost but I am glad it can be done.

I think the python callback would be the easier for me. However I wonder if you would have in mind any example or tutorial where I can get ideas.

Thanks again.