Interactive slider to vary slice used in Bokeh image plot - python

I'm interested in using Bokeh to put images inside IPython notebooks. In particular a datatype I often interact with is a multidimensional NumPy array with 3 or more dimensions. Take the example of a 3-dimensional array. An oft-encountered example is RGB images. The three dimensions are x, y, and color
I'm interested in using Bokeh to plot a single image channel in an IPython notebook. I'd like to provide an interactive slider that allows the user of the IPython notebook to click through each index of the 3rd dimension, in this example, color.
My code below (when run in an IPython notebook) successfully shows the plot of the first color channel. But I can't figure out what is causing the error in my call to interact. Do I have my ColumnDataSource defined correctly and referenced correctly in the Bokeh plot construction?
# imports
import numpy as np
from scipy.misc import imread
from bokeh.plotting import figure, show
from bokeh.io import output_notebook
from bokeh.models import ColumnDataSource
from bokeh.palettes import Greys9
from IPython.html.widgets import interact
# enable Bokeh to plot to the notebook
output_notebook()
# Make the Bokeh plot of the "first" layer of the 3D data
## This part works
TOOLS="pan, box_zoom, reset, save"
# The image from https://windycitizensports.files.wordpress.com/2011/10/baboon.jpg?w=595
RGB_image = imread('/Users/curt/Downloads/BaboonRGB.jpg')
nx, ny, n_colors = RGB_image.shape
source = ColumnDataSource(data={'image': RGB_image[:, :, 0]})
p = figure(title="ColorChannel",
tools=TOOLS,
x_range=[0, nx],
y_range=[0, ny],
)
p.image([source.data['image'][::-1, :]-1],
x=0,
y=0,
dh=[ny],
dw=[nx],
palette=Greys9,
source=source,
)
show(p)
# try to add interactive slider
## This part does not work & gives a JavaScript error
def update(idx=0):
global RGB_image
source.data['image'] = RGB_image[:, :, idx]
source.push_notebook()
interact(update, idx=(0, 2))
The Javascript error is:
Javascript error adding output!
TypeError: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The provided float value is non-finite.
See your browser Javascript console for more details.
I'm not sure how this can be. I tried coercing RGB_Image to a float by doing RGB_Image = RGB_Image.astype(float) immediately after defining it but I got the same error.

In essence, the data format is not consistent between your predefined image and what you are trying to update.
A workaround which does work:
def update(idx=0):
global RGB_image
source.data['image'] = RGB_image[:, :, idx]
p.image([source.data['image'][::-1, :]-1],
x=0,
y=0,
dh=[ny],
dw=[nx],
palette=Greys9,
source=source,
)
show(p)
interact(update, idx=(0, 2))
I'll acknowledge that defining the image over and over again is not the preferred way of doing this, but it should at least give you a handle as to where to look.

Related

pyplot pcolormesh set_facecolor not updating the plot in jupyter

I am trying to update the color of the faces of a pcolormesh using set_facecolor. The purpose of this operation is to "bleach" (emulate transparency by whitening the facecolor) some regions of the plot -- see this post. Although I am pretty sure I have been getting this to work in the past, for some reason it does not work anymore in a jupyter lab or jupyter notebook environment (not tested in a script yet). Unexpectedly, calling fig.canvas.draw() seems to restore the initial face colors. Here is a minimal example reproducing the error:
from matplotlib import pyplot as plt
import numpy as np
## Generate data and show initial plot
xx = np.linspace(-1, 1, 5)
xx, yy = np.meshgrid(xx, xx)
data = np.exp( -(xx**2+yy**2) )
### Generate plot
fig, ax = plt.subplots(1, 1)
hpc = ax.pcolormesh(data)
plt.colorbar(hpc)
ax.set_aspect(1)
fig.canvas.draw() # this is required to generate the facecolor array
colors = hpc.get_facecolors() # for checking: this contains the RGBA array
newc = np.ones((colors.shape[0], 3))/2. # uniform grey
hpc.set_facecolor(newc) # this should update the facecolor of the plot
newc = hpc.get_facecolors() # indeed, the facecolor seems to have been updated
fig.canvas.draw() # re-generating the plot
### On my machine: the plot is unchanged and the new facecolors are back to the initial values
print(newc[0,:], hpc.get_facecolors()[0,:])
Configuration:
backend: %matplotlib inline or %matplotlib widget both produce the error
python 3.9.13 (macOS Big Sur) or python 3.8.11 (Linux on a cluster -- maybe Fedora)
matplotlib 3.5.1 or 3.5.2 (resp), jupyterlab 3.4.4 or 3.4.0 (resp.)
Does anyone have an idea of what is going on? Am I doing something wrong? Thanks a lot in advance
OK, I found a "trick" based on this matplotlib issue (that I am sure was not necessary with a previous configuration): inserting hpc.set_array(None) before calling set_facecolor enables achieving the desired result.

Upgrading from bokeh==2.1.1 to bokeh==2.2.0 breaks figure.image_rgba()

This snippet of code renders an image (albeit upside-down) when using bokeh==2.1.1:
from PIL import Image
import numpy as np
import requests
from bokeh.plotting import figure
from bokeh.io import output_notebook
from bokeh.plotting import show
output_notebook()
response = requests.get('https://upload.wikimedia.org/wikipedia/en/a/a9/Example.jpg', stream=True)
rgba_img = Image.open(response.raw).convert("RGBA")
numpy_img = np.array(rgba_img)
fig = figure()
plotted_image = fig.image_rgba(image=[np_img], x=50, y=50, dw=50, dh=50)
show(fig)
Running the exact same code in bokeh==2.2.0 (as well as later versions) outputs nothing, and doesn't raise any errors.
bokeh's release notes does not mention any changes to image_rgba()
The error is in the JS console in the browser (because the error actually occurs in the browser, not on the "python side"):
Unhandled Promise Rejection: Error: expected a 2D array
And if you look at np_img, you can see it is not a 2D array:
In [3]: np_img.shape
Out[3]: (297, 275, 4)
In [4]: np_img.dtype
Out[4]: dtype('uint8')
Bokeh expects a 2D array of uint32, not a 3D array of uint8, and this has always been the case, and what has been demonstrated in the docs. It's possible this a 3D array was accepted accidentally or unintentionally in the past (which is why no change would be noted in the release notes), or it's possible that something has changed on either the NumPy or PIL side with conversions to NumPy arrays. Regardless, you will need to create a 2D view:
np_img = np.array(rgba_img)
np_img2d = np_img.view("uint32").reshape(np_img.shape[:2])
fig = figure()
plotted_image = fig.image_rgba(image=[np_img2d], x=50, y=50, dw=50, dh=50)

Plots and widgets not showing up in bokeh serve

I am trying to build an interactive data visualization tool using bokeh layouts, but I am running into issues when generating and visualizing the plots. When running bokeh serve --show MWE1.py, I get the following error message "Only LayoutDOM items can be inserted into a column. Tried to insert: None of type " and no plots are generated in my browser window.
When running the code from the command python MWE1.py a plot is generated in a browser window, but no slider bar is present. I have also tried to remove the column layout tool from curdoc() but this didn't seem to help. Is there an issue passing functions that generate plots through curdoc(), and if so, is there an alternative solution?
(As an aside, I have also tried several of the tutorials and examples available online, all of which have worked as intended)
See MWE below:
import bokeh
from bokeh.io import curdoc
from bokeh import layouts
from bokeh.layouts import column,row,gridplot
from bokeh.models import ColumnDataSource, Slider
from bokeh.io import output_file
from bokeh.plotting import figure,show
x=[1, 2, 3]
y=[4, 5, 6]
def p(x,y):
p = figure()
p.line(x,y)
show(p)
q = p(x,y)
freq = Slider(title="frequency", value=1.0, start=0.1, end=5.1, step=0.1)
curdoc().add_root(column(freq,q))'''
You function p is wrong:
It doesn't return the plot
It calls show which doesn't work with bokeh serve
Try this instead
def p(x,y):
p = figure()
p.line(x,y)
return p

I'm confused about what the second parameter to matplotlib.axes does [duplicate]

I am using ipython-notebook a lot at the moment for numerical analysis and plotting of data. In the process of preparing publication quality plots there is a lot of tweaking to get the layout just right, however I can't get ipython/matplotlib to show me what I will be saving in the browser. Making the process more painful than it should be because I have to keep opening the new output file to check it.
Is there a way to get the image that is displayed inline to be the same as the image that is saved?
Example as follows, facecolor='gray' for clarity:
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
fig = plt.figure(figsize=(6,4),facecolor='gray')
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])
x = np.linspace(0,2*np.pi,1000)
y = np.sin(x)
ax.plot(x,y,label=r'$\sin(x)$')
ax.set_xlim(0,2*np.pi)
ax.set_ylim(-1.2,1.2)
ax.set_xlabel(r'$x$')
ax.set_ylabel(r'$y$')
ax.legend(loc='upper right', frameon=False)
fig.savefig('mypath.png',dpi=300, facecolor='gray')
plt.show()
Note here I have explicity chosen my axes dimensions so that they are equidistant from the two sides of the resulting image. This is respected in the saved image, but ignored in the image shown in the notebook:
Notebook displayed image:
Savefig image:
As noted by #andrew, the ipython magics are enforcing bbox_inches='tight' by default. This can be overridden using other magics as explained in the ipython documentation:
%matplotlib inline
%config InlineBackend.print_figure_kwargs = {'bbox_inches':None}
produces an inline image identical to that produced by savefig.
The behavior is due to the fact that the magic %matplotlib inline defaults to using the
bbox_inches='tight' when rendering inline.
I know you asked about changing the behavior of plt.show(), but alternatively, you could change the behavior of savefig() to use the same settings as the notbeook.
fig.savefig('mypath.png',dpi=300, facecolor='gray', bbox_inches='tight')
New 'savefig' image:

Cursor location and pixel value in a Jupyter notebook inline image

I am using Python 2.7.x with a Jupyter Notebook, matplotlib and %pylab backend with the inline flag
%pylab inline
to print images below active cells. I would like to be able to move my cursor over an image and know it's location and pixel value An example could be:
(x,y,val) = (123,285,230)
but I am not particular about any of the specifics of this example.
The %matplotlib inline backend displays the plot outputs as png images. It may be possible to write some JavaScript for the Jupyter notebook to obtain the color and pixel on mouse over an image in the cell output.
However it may be much easier to just use the %matplotlib notebook backend, which keeps the matplotlib figure alive when plotting it to the output and therefore the usual built-in mouseover functionality is readily available.
Note the picker in the lower right corner of the image, which displays x,y and the value of the current pixel.
To expand on ImportanceOfBeingErnest's answer, you can use mpl_connect to provide a callback on your clicks and ipywidgets to show an output of your callback. If needed, you can break up the code in different cells.
%matplotlib notebook
import matplotlib.pyplot as plt
import numpy as np
import ipywidgets as wdg # Using the ipython notebook widgets
# Create a random image
a = np.random.poisson(size=(12,15))
fig = plt.figure()
plt.imshow(a)
# Create and display textarea widget
txt = wdg.Textarea(
value='',
placeholder='',
description='event:',
disabled=False
)
display(txt)
# Define a callback function that will update the textarea
def onclick(event):
txt.value = str(event) # Dynamically update the text box above
# Create an hard reference to the callback not to be cleared by the garbage collector
ka = fig.canvas.mpl_connect('button_press_event', onclick)

Categories