Jupyter Lab interactive image display : issue with widgets arrangements using HBox - python

I am trying to change content of an image interactively using a slider (e.g. for applying a threshold operation with different values).
My code is as follows:
#%matplotlib ipympl
%matplotlib widget
import matplotlib.pyplot as plt
import cv2
import numpy as np
import ipywidgets as widgets
from ipywidgets import HBox, IntSlider
from IPython.display import Image
def update_lines(change):
ret,thresh2 = cv2.threshold(img_gray,change.new,255,cv2.THRESH_BINARY)
plt.imshow(thresh2)
fig.canvas.flush_events()
image = cv2.imread("Untitled.jpg")
img_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
ret,thresh2 = cv2.threshold(img_gray,30,255,cv2.THRESH_BINARY)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
slider = IntSlider(
orientation='vertical',
step=1,
value=127,
min=0,
max=255
)
display(HBox([slider, fig.canvas]))
slider.observe(update_lines, names='value')
When executing my code, I have an unexpected behavior: the figure is displayed twice, the first time when I do fig = plt.figure() and the second time when I do display(HBox([slider, fig.canvas])) => see The figure is displayed twice.
How can I display the image only into the HBox ?
When I change the value with the slider, I have the following result => After changing value

It seems that matplotlib cannot directly be persuaded to plot the figure at the figure() call, but it's possible to encapsulate it in an Output widget (taken from here):
output = widgets.Output()
with output:
fig = plt.figure()
# fill figure with content here
display(HBox([slider, output]))
That way, the plot is correctly displayed once.

Related

Image data in mha brain tumor file

I have an MHA file and when I write
from medpy.io import load
image_data, image_header = load("HG/0001/VSD.Brain.XX.O.MR_Flair/VSD.Brain.XX.O.MR_Flair.684.mha")
print(image_data.shape)
I get a tuple (160, 216, 176). What do these dimensions represent (for reference these are brain tumor images from BRATS 2013)? Your help is appreciated.
Edit: on Jupyter for the slider to work I did
import matplotlib.pyplot as plt
from ipywidgets import interact
import numpy as np
%matplotlib inline
#interact(x=(0, image_data.shape[2]))
def update(x):
plt.imshow(np.flip(image_data[x].T, 0))
but of course your code probably works on other editors
According to the documentation, load(image) "Loads the image and returns a ndarray with the image’s pixel content as well as a header object."
Further down in medpy.io.load it says that image_data is "The image data as numpy array with order x,y,z,c.".
Edit: Because I was kind of curious to see what is actually in this file, I put together a quick script (heavily based on the slider demo) to take a look. I'll leave it here just in case it may be useful to someone. (Click on the "Layer" slider to select the z-coordinate to be drawn.)
from medpy.io import load
image_data, image_header = load("/tmp/VSD.Brain.XX.O.MR_Flair.684.mha")
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)
axlayer = plt.axes([0.25, 0.1, 0.65, 0.03])
slider_layer = Slider(axlayer, 'Layer', 1, image_data.shape[2], valinit=1, valstep=1)
def update(val):
layer = slider_layer.val
ax.imshow(image_data[:,:,layer])
fig.canvas.draw_idle()
slider_layer.on_changed(update)
ax.imshow(image_data[:,:,0])
plt.show()
(This indirectly confirms that image_data holds a 3-D voxel image.)
Just to add on top the accepted answer, we can visualize the slices with subplots and animation too:
from medpy.io import load
image_data, image_header = load("VSD.Brain.XX.O.MR_Flair.684.mha")
image_data = image_data / image_data.max()
plt.figure(figsize=(20,32))
plt.gray()
plt.subplots_adjust(0,0,1,0.95,0.01,0.01)
for i in range(ct.shape[0]):
plt.subplot(16,10,i+1), plt.imshow(image_data[i]), plt.axis('off')
plt.suptitle('Brain-Tumor CT-scan mha (raw) files', size=15)
plt.show()

How to set matplotlib to show every image of an array?

How to set matplotlib to show every image of an array?
I want that everytime i click on the right arrow, it shows the next image and so on...
Is that possible?
width = 14
height = 14
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
data_images = X_train.reshape(X_train.shape[0],width,height)
print "Shape " ,data_images.shape #Shape (50000L, 14L, 14L)
plt.imshow(data_images[0])
plt.show()
I wanted to pass the "data_images" variable to plt.imshow and so everytime i clicked on next on the matplotlib, it would show the next image.
Working example with plt.connect().
You can change image by pressing any key.
import matplotlib.pyplot as plt
data_images = [
[[1,2,3],[1,2,3],[1,2,3]],
[[1,1,1],[2,2,2],[3,3,3]],
[[1,2,1],[2,2,2],[1,2,1]],
]
#----------------------------------
index = 0
def toggle_images(event):
global index
index += 1
if index < len(data_images):
plt.imshow(data_images[index])
plt.draw()
else:
plt.close()
#----------------------------------
plt.imshow(data_images[index])
plt.connect('key_press_event', toggle_images)
plt.show()
I would do this using ipywidgets within the IPython notebook. Here's an example:
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import interact
images = np.random.random((500, 14, 14))
def browse_images(images):
N = images.shape[0]
def view_image(i=0):
plt.imshow(images[i], cmap='gray', interpolation='nearest')
plt.title('Image {0}'.format(i))
interact(view_image, i=(0, N-1))
browse_images(images)
Edit: the result, in the notebook page, will look something like this:
You can press the left or right arrow to advance the slider and view the next image.
You can do a bit better in the notebook than using inline:
%matplotlib notebook
import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import interact
from IPython.display import display
images = np.random.random((500, 14, 14))
fig, ax = plt.subplots()
im = ax.imshow(images[0], cmap='gray', interpolation='nearest')
def browse_images(images):
N = images.shape[0]
def view_image(i=0):
im.set_data(images[i])
ax.set_title('Image {0}'.format(i))
fig.canvas.draw_idle()
interact(view_image, i=(0, N-1))
and then in the next cell
browse_images(images)
which will give you a pannable/zoom able figure. In mpl 1.5.0 you also get the pixel values under the cursor by default.
(I tested this on tmpnb.org)

Interactive pixel information of an image in Python?

Short version: is there a Python method for displaying an image which shows, in real time, the pixel indices and intensities? So that as I move the cursor over the image, I have a continually updated display such as pixel[103,214] = 198 (for grayscale) or pixel[103,214] = (138,24,211) for rgb?
Long version:
Suppose I open a grayscale image saved as an ndarray im and display it with imshow from matplotlib:
im = plt.imread('image.png')
plt.imshow(im,cm.gray)
What I get is the image, and in the bottom right of the window frame, an interactive display of the pixel indices. Except that they're not quite, as the values are not integers: x=134.64 y=129.169 for example.
If I set the display with correct resolution:
plt.axis('equal')
the x and y values are still not integers.
The imshow method from the spectral package does a better job:
import spectral as spc
spc.imshow(im)
Then in the bottom right I now have pixel=[103,152] for example.
However, none of these methods also shows the pixel values. So I have two questions:
Can the imshow from matplotlib (and the imshow from scikit-image) be coerced into showing the correct (integer) pixel indices?
Can any of these methods be extended to show the pixel values as well?
There a couple of different ways to go about this.
You can monkey-patch ax.format_coord, similar to this official example. I'm going to use a slightly more "pythonic" approach here that doesn't rely on global variables. (Note that I'm assuming no extent kwarg was specified, similar to the matplotlib example. To be fully general, you need to do a touch more work.)
import numpy as np
import matplotlib.pyplot as plt
class Formatter(object):
def __init__(self, im):
self.im = im
def __call__(self, x, y):
z = self.im.get_array()[int(y), int(x)]
return 'x={:.01f}, y={:.01f}, z={:.01f}'.format(x, y, z)
data = np.random.random((10,10))
fig, ax = plt.subplots()
im = ax.imshow(data, interpolation='none')
ax.format_coord = Formatter(im)
plt.show()
Alternatively, just to plug one of my own projects, you can use mpldatacursor for this. If you specify hover=True, the box will pop up whenever you hover over an enabled artist. (By default it only pops up when clicked.) Note that mpldatacursor does handle the extent and origin kwargs to imshow correctly.
import numpy as np
import matplotlib.pyplot as plt
import mpldatacursor
data = np.random.random((10,10))
fig, ax = plt.subplots()
ax.imshow(data, interpolation='none')
mpldatacursor.datacursor(hover=True, bbox=dict(alpha=1, fc='w'))
plt.show()
Also, I forgot to mention how to show the pixel indices. In the first example, it's just assuming that i, j = int(y), int(x). You can add those in place of x and y, if you'd prefer.
With mpldatacursor, you can specify them with a custom formatter. The i and j arguments are the correct pixel indices, regardless of the extent and origin of the image plotted.
For example (note the extent of the image vs. the i,j coordinates displayed):
import numpy as np
import matplotlib.pyplot as plt
import mpldatacursor
data = np.random.random((10,10))
fig, ax = plt.subplots()
ax.imshow(data, interpolation='none', extent=[0, 1.5*np.pi, 0, np.pi])
mpldatacursor.datacursor(hover=True, bbox=dict(alpha=1, fc='w'),
formatter='i, j = {i}, {j}\nz = {z:.02g}'.format)
plt.show()
An absolute bare-bones "one-liner" to do this: (without relying on datacursor)
def val_shower(im):
return lambda x,y: '%dx%d = %d' % (x,y,im[int(y+.5),int(x+.5)])
plt.imshow(image)
plt.gca().format_coord = val_shower(ims)
It puts the image in closure so makes sure if you have multiple images each will display its own values.
All of the examples that I have seen only work if your x and y extents start from 0. Here is code that uses your image extents to find the z value.
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
d = np.array([[i+j for i in range(-5, 6)] for j in range(-5, 6)])
im = ax.imshow(d)
im.set_extent((-5, 5, -5, 5))
def format_coord(x, y):
"""Format the x and y string display."""
imgs = ax.get_images()
if len(imgs) > 0:
for img in imgs:
try:
array = img.get_array()
extent = img.get_extent()
# Get the x and y index spacing
x_space = np.linspace(extent[0], extent[1], array.shape[1])
y_space = np.linspace(extent[3], extent[2], array.shape[0])
# Find the closest index
x_idx= (np.abs(x_space - x)).argmin()
y_idx= (np.abs(y_space - y)).argmin()
# Grab z
z = array[y_idx, x_idx]
return 'x={:1.4f}, y={:1.4f}, z={:1.4f}'.format(x, y, z)
except (TypeError, ValueError):
pass
return 'x={:1.4f}, y={:1.4f}, z={:1.4f}'.format(x, y, 0)
return 'x={:1.4f}, y={:1.4f}'.format(x, y)
# end format_coord
ax.format_coord = format_coord
If you are using PySide/PyQT here is an example to have a mouse hover tooltip for the data
import matplotlib
matplotlib.use("Qt4Agg")
matplotlib.rcParams["backend.qt4"] = "PySide"
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
# Mouse tooltip
from PySide import QtGui, QtCore
mouse_tooltip = QtGui.QLabel()
mouse_tooltip.setFrameShape(QtGui.QFrame.StyledPanel)
mouse_tooltip.setWindowFlags(QtCore.Qt.ToolTip)
mouse_tooltip.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
mouse_tooltip.show()
def show_tooltip(msg):
msg = msg.replace(', ', '\n')
mouse_tooltip.setText(msg)
pos = QtGui.QCursor.pos()
mouse_tooltip.move(pos.x()+20, pos.y()+15)
mouse_tooltip.adjustSize()
fig.canvas.toolbar.message.connect(show_tooltip)
# Show the plot
plt.show()
with Jupyter you can do so either with datacursor(myax)or by ax.format_coord.
Sample code:
%matplotlib nbagg
import numpy as np
import matplotlib.pyplot as plt
X = 10*np.random.rand(5,3)
fig,ax = plt.subplots()
myax = ax.imshow(X, cmap=cm.jet,interpolation='nearest')
ax.set_title('hover over the image')
datacursor(myax)
plt.show()
the datacursor(myax) can also be replaced with ax.format_coord = lambda x,y : "x=%g y=%g" % (x, y)
In case you, like me, work on Google Colab, this solutions do not work as Colab disabled interactive feature of images for matplotlib.
Then you might simply use Plotly:
https://plotly.com/python/imshow/
import plotly.express as px
import numpy as np
img_rgb = np.array([[[255, 0, 0], [0, 255, 0], [0, 0, 255]],
[[0, 255, 0], [0, 0, 255], [255, 0, 0]]
], dtype=np.uint8)
fig = px.imshow(img_rgb)
fig.show()
Matplotlib has built-in interactive plot which logs pixel values at the corner of the screen.
To setup first install pip install ipympl
Then use either %matplotlib notebook or %matplotlib widget instead of %matplotlib inline
The drawback with plotly or Bokeh is that they don't work on Pycharm.
For more information take a look at the doc
To get interactive pixel information of an image use the module imagetoolbox
To download the module open the command prompt and write
pip install imagetoolbox
Write the given code to get interactive pixel information of an image
enter image description here
Output:enter image description here

Matplotlib : What is the function of cmap in imshow?

I'm trying to learn opencv using python and came across this code below:
import cv2
import numpy as np
from matplotlib import pyplot as plt
BLUE = [255,0,0]
img1 = cv2.imread('opencv_logo.png')
replicate = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REPLICATE)
reflect = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REFLECT)
reflect101 = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REFLECT_101)
wrap = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_WRAP)
constant= cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_CONSTANT,value=BLUE)
plt.subplot(231),plt.imshow(img1,'gray'),plt.title('ORIGINAL')
plt.subplot(232),plt.imshow(replicate,'gray'),plt.title('REPLICATE')
plt.subplot(233),plt.imshow(reflect,'gray'),plt.title('REFLECT')
plt.subplot(234),plt.imshow(reflect101,'gray'),plt.title('REFLECT_101')
plt.subplot(235),plt.imshow(wrap,'gray'),plt.title('WRAP')
plt.subplot(236),plt.imshow(constant,'gray'),plt.title('CONSTANT')
plt.show()
source : http://docs.opencv.org/master/doc/py_tutorials/py_core/py_basic_ops/py_basic_ops.html#exercises
What does plt.imshow(img1, 'gray') do? I tried searching Google and all I could understand was that the 'gray' argument was a Color map. But my image (pic is there on the site. see link) is not displayed in grayscale. I tried removing the second argument. So the code was like plt.imshow(img1). It executes. The image remains same as before. Then what does the second argument 'gray' do? Can someone explain all this to me? Any help appreciated. Thanks.
PS. I'm totally new to Matplotlib
When img1 has shape (M,N,3) or (M,N,4), the values in img1 are interpreted as RGB or RGBA values. In this case the cmap is ignored. Per the help(plt.imshow) docstring:
cmap : ~matplotlib.colors.Colormap, optional, default: None
If None, default to rc image.cmap value. cmap is ignored when
X has RGB(A) information
However, if img were an array of shape (M,N), then the cmap controls the colormap used to display the values.
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.axes_grid1 as axes_grid1
np.random.seed(1)
data = np.random.randn(10, 10)
fig = plt.figure()
grid = axes_grid1.AxesGrid(
fig, 111, nrows_ncols=(1, 2), axes_pad = 0.5, cbar_location = "right",
cbar_mode="each", cbar_size="15%", cbar_pad="5%",)
im0 = grid[0].imshow(data, cmap='gray', interpolation='nearest')
grid.cbar_axes[0].colorbar(im0)
im1 = grid[1].imshow(data, cmap='jet', interpolation='nearest')
grid.cbar_axes[1].colorbar(im1)
plt.savefig('/tmp/test.png', bbox_inches='tight', pad_inches=0.0, dpi=200,)

Multiple matplotlib plots in reportlab

I'm trying to put a matplotlib graph onto a reportlab canvas. I can do a simple graph with the code from this question: How to drawImage a matplotlib figure in a reportlab canvas?
But when I try to use subplots or use multiple plots it will not work properly. Doing it this way causes the same image to be plotted twice even when I added things like imgdata.close() or deleting the figure:
from matplotlib.figure import Figure
import cStringIO
from reportlab.pdfgen import canvas
from reportlab.lib.utils import ImageReader
can = canvas.Canvas()
self.f = Figure()
plot(x,y)
xlabel(xlbl)
ylabel(ylbl)
imgdata=cStringIO.StringIO()
savefig(imgdata,format='png')
imgdata.seek(0)
Image = ImageReader(imgdata)
can.drawImage(Image,100,250, width=400,height=350)
self.g = Figure()
plot(x,y)
xlabel(xlbl)
ylabel(ylbl)
secondimgdata = cStringIO.StringIO()
savefig(secondimgdata,format='png')
secondimgdata.seek(0)
Image2 = ImageReader(secondimgdata)
can.drawImage(Image2,100,150, width=400,height=350)
When trying with subplots it simply produces a blank graph and I did not know where to go with it:
self.f = Figure()
self.a = self.f.add_subplot(111)
self.a.plot(x,y)
self.a2 =self.a.twinx()
self.a2.plot(x,y2,'r')
self.a2.set_ylabel(ylbl2)
self.a.set_xlabel(xlbl)
self.a.set_ylabel(ylbl)
Any solution or advice to this problem would be very much appreciated.
The key is that you must use plt.close() after you're done adding images. Here's a quick example that works for me using seaborn and barplot. Assume I have a dataframe with different data that I want plotted over a few figures.
import matplotlib.pyplot as plt
import seaborn as sns
import cStringIO
from reportlab.platypus import Image
my_df = <some dataframe>
cols_to_plot = <[specific columns to plot]>
plots = []
def create_barplot(col):
sns_plot = sns.barplot(x='col1', y=col, hue='col2', data=my_df)
imgdata = cStringIO.StringIO()
sns_plot.figure.savefig(imgdata, format='png')
imgdata.seek(0)
plots.append(Image(imgdata))
plt.close() # This is the key!!!
for col in cols_to_plot:
create_barplot(col)
for barplot in plots:
story.append(barplot)
This isn't an ideal solution as it has to save the file as an image instead of using StringIO but it works.
import Image as image
from matplotlib.pyplot import figure
from reportlab.pdfgen import canvas
from reportlab.lib.utils import ImageReader
can = canvas.Canvas()
self.f = figure()
self.a = self.f.add_subplot(2,1,1)
self.a.plot(x,y)
self.a2 =self.a.twinx()
self.a2.plot(x,y2,'r')
self.a2.set_ylabel(ylbl2,color='r')
self.a.set_xlabel(xlbl)
self.a.set_ylabel(ylbl,color='b')
self.f.savefig('plot.png',format='png')
image.open('plot.png').save('plot.png','PNG')
can.drawImage('plot.png',100,250, width=400,height=350)

Categories