pceccon
pceccon

Reputation: 9844

Filter image with slider in Bokeh

I'm trying to filter a RGBA image based on its alpha value, for example, using Bokeh callbacks. I've been studying this library for less than one week so my knowledge about it is really raw. From the API example I didn't quite get how to work with these callbacks. My approach to achieve what I wanted was the following:

### RGBA Image
N = 20
img = np.empty((N,N, 4), dtype=np.uint8)
for i in range(N):
    for j in range(N):
        img[i, j, 0] = int(i/N*255)
        img[i, j, 1] = 158
        img[i, j, 2] = int(j/N*255)
        img[i, j, 3] = np.random.randint(1, 255)

mask = img[:, :, 3]
img = np.squeeze(img.view(np.uint32))
source = ColumnDataSource(data=(dict(image=[img],
                                    x=[0],
                                    y=[0],
                                    dw=[10],
                                    dh=[10])))

p = figure(x_range=(0,10), y_range=(0,10))
p.image_rgba(source=source, image='image', x='x', y='y', dw='dw', dh='dh')

### Threshold Slider
def slider_callback(source=source):
    data = source.data
    img = data['image']
    img = img * (mask > cb_obj.value).astype(int)
    source.change.emit();

t_slider = Slider(start=0, end=255, value=255, step=1,
                  title="Threshold", width=140, 
                  callback=CustomJS.from_py_func(slider_callback))

l = layout([t_slider, p])
curdoc().add_root(l)
show(l)

As I don't see any change in the plot while changing the slider value, I think I didn't get how to use this callback.

Upvotes: 0

Views: 416

Answers (1)

bigreddot
bigreddot

Reputation: 34568

First, as a gentle suggestion: please do not omit imports in your example code. The fastest way for others to help is to be able to run example code immediately, directly, as-is, which is not possible with incomplete code

There are several different issues with this code, I will try to untangle them:

  • CustomJS.from_py_func is deprecated and will be removed in the future, it should not be used

  • Even if this was not the case, from_py_func ultimately produces JavaScript code that runs in your brower. It can only convert simple plain Python dode, and can't convert any python code that relies on real Python libraries like Numpy or Pandas. Your call to astype and all the fancy slicing are Numpy functions that the browser knows nothing at all about, so this approach cannot possibly work.

  • So to be able to run real Python code in callbacks, you will have to make a Bokeh Server application Just to be clear, Bokeh server apps have to be run with the bokeh server, i.e. similar to

    bokeh serve --show myapp.py
    
  • The logic of the callback is also not right. It assigns a new value to a local variable img and then throws it away. It does not uopdate the value of source.data which is what would trigger Bokeh to update the plot based on new data. You would need a callback and hookup more like:

    t_slider = Slider(start=0, end=255, value=255, step=1,
                      title="Threshold", width=140)
    
    def slider_callback(attr, old, new):
        source.data['image'] = [(mask > t_slider.value).astype(int)]
    
    t_slider.on_change('value', slider_callback)
    

    Note also the list value for source.data['image'] The value needs to be a list/array of images (since image can display multiple images at once), so the list is relevant.

If if make the changes above and run your code with bokeh serve, then things "work" in the sense that the plot updates when the slider is scrubbed. But the callback logic sets most of the image array to zero, resulting in a blank plot. Without knowing more about what you are trying to actually accomplish, it's not possible to help more with the callback logic.

Edit: If your intent is to use the mask to update the image shown somwehow, you should also be aware that you will have to copy the original image each time in the callback. Otherwise you will update based on the original the first time the callback runs, but then subsequent callbacks will be modifying the already modified version, forever. I.e. you'll need something like this:

def slider_callback(attr, old, new):
    newimg = img.copy()
    newimg[(mask > t_slider.value).astype(int)] = 0
    source.data['image'] = [newimg]

Upvotes: 2

Related Questions