Interactive pixel information of an image in Python? - 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

Related

How to fix BytesIO numpy image array returning blank?

I'm trying to save a Matplotlib plot to an array using BytesIO as suggested here: Matplotlib save plot to NumPy array. Here is my code
import lightkurve
import matplotlib.pyplot as plt
import numpy as np
import io
def download(search):
lc = search.download() # downloads lightcurve as lightcurve object
if lc is not None:
fig,ax = plt.subplots()
ax.scatter(lc.time.value.tolist(), lc.flux.value.tolist(), color='k')
ax.autoscale()
ax.set_xlabel('Time (BTJD)')
ax.set_ylabel('Flux')
fig.show()
io_buf = io.BytesIO()
fig.savefig(io_buf,format="raw")
io_buf.seek(0)
img_arr = np.frombuffer(io_buf.getvalue(),dtype=np.uint8)
io_buf.close()
return img_arr
For some reason, the returned image array only contains the repeated value 255 like so: [255 255 255 ... 255 255 255] suggesting a blank image. I've tried using plt instead of fig, autoscaling the axes in case they weren't showing, and plotting instead with the Lightkurve built-in plotting function lc.plot(ax=ax) but nothing has changed. Does anyone know how to fix this?
I couldn't reproduce your bug. In fact, I ran your code (with some modifications) and the resulting image was exactly like the original image. Did you thoroughly check if your img_arr had only 255s? (e.g., np.unique(img_arr), in my case, len(np.unique(imgarr)) == 231)
import lightkurve
import matplotlib.pyplot as plt
import numpy as np
import io
def download(search):
lc = search.download() # downloads lightcurve as lightcurve object
if lc is not None:
fig,ax = plt.subplots()
ax.scatter(lc.time.value.tolist(), lc.flux.value.tolist(), color='k')
ax.autoscale()
ax.set_xlabel('Time (BTJD)')
ax.set_ylabel('Flux')
fig.show()
io_buf = io.BytesIO()
fig.savefig(io_buf,format="raw")
fig.savefig('test.png') # So I could see the dimensions of the array
io_buf.seek(0)
img_arr = np.frombuffer(io_buf.getvalue(),dtype=np.uint8)
io_buf.close()
return img_arr
# I put something random -- Next time, provide this step so others can more easily debug your code. Never touched lightkurve before
search = lightkurve.search_lightcurve('KIC 757076', author="Kepler", quarter=3)
imgarr = download(search)
fig, ax = plt.subplots()
ax.imshow(imgarr.reshape(288, -1), aspect=4, cmap='gray') # Visualizing the image from the array. Got '288' from the dimensions of the png.
Original plot:
Reconstructed plot:

Matplotlib: Points do not show in SVG

I have a scatter plot that I'd like to output as SVG (Python 3.5). However, when used with agg as backend, some points are simply missing. See the data and the PNG and SVG output. Is this some kind of misconfiguration or a bug?
Code:
import matplotlig
matplotlib.use('agg')
import matplotlib.pyplot as plt
x = [22752.9597858324,33434.3100283611,None,None,3973.2239542398,None,None,None
,None,None,None,None,None,960.6513071797,None,None,None,None,None,None,None
,None,None,None,None,None,749470.931292081,None,None,None,None,None,None
,None,None,None,None,None,None,None,None,23045.262784499,None,None,None
,None,None,None,None,1390.8383822667,None,None,9802.5632611025
,3803.3240362092,None,None,None,None,None,2058.1191666219,None
,3777.5383953988,None,91224.0759036624,23296.1857550166,27956.249381887
,None,237247.707648005,None,None,None,None,None,None,None,None,None
,760.3493458787,None,321687.799104496,None,None,22339.5617383239,None,None
,None,None,None,28135.0261453192,None,None,None,None,None,None,None
,1687.4387356974,None,None,29037.8494868489,None,None,None,None,None,None
,None,3937.3066755226,None,None,None,None]
y = [63557.4319306279,None,None,None,9466.0204228915,None,None,None,None,None
,None,None,None,3080.3393940948,None,None,None,None,None,None,None,None
,None,None,None,None,592184.803802073,None,None,None,None,None,None,None
,None,None,None,None,None,None,None,18098.725166318,None,None,None,None
,None,None,None,789.2710621298,None,None,7450.9539135753,4251.6033622036
,None,None,None,None,None,1277.1691956597,None,4273.5950324508,None
,51861.5572682614,19415.3369388317,2117.2407148378,None,160776.887146683
,None,None,None,None,None,None,None,None,None,1550.3003177484,None
,402333.163939038,None,None,16604.3340243551,None,None,None,None,None
,32545.0784355136,None,None,None,None,None,None,None,2567.9264180605,None
,None,45786.935597305,None,None,None,None,None,None,None,5645.5218715636
,None,None,None,None]
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x, y, '.')
fig.savefig('/home/me/test_svg', format='svg')
fig.savefig('/home/me/test_png', format='png')
The result:
PNG:
SVG:
The problem seems to be related to the None values. Though there is simply no point included if no matching point exists, it seems to influence the rendering of the SVG. Removing both entries if at one or the other point is None fixes the issue.
data = np.array([x, y])
data = data.transpose()
# Filter out pairs of points of which at least one is None.
data = [pair for pair in data if pair[0] and pair[1]]
data = np.array(data).transpose()
x = data[0]
y = data[1]
ax.plot(x, y, '.')
fig.savefig('/home/me/test_svg', format='svg')
fig.savefig('/home/me/test_png', format='png')
Update
This looks like a bug that was fixed some time between matplotlib 2.0.0 and 3.1.1. Upgrading solved the problem for me.
Original Answer
I ran into the same problem, so I created a minimal example to reproduce it:
import numpy as np
from matplotlib import pyplot as plt
data = np.array([1.0, np.nan, 1.0])
plt.plot(data, 'o')
plt.savefig('example.svg')
plt.savefig('example.png')
It works fine as a PNG:
However, the left point is missing from the SVG.
Using your suggestion of removing invalid data, I used the numpy indexing features:
import numpy as np
from matplotlib import pyplot as plt
data = np.array([1.0, np.nan, 1.0])
indexes = np.arange(data.size)
is_valid = np.negative(np.isnan(data))
plt.plot(indexes[is_valid], data[is_valid], 'o')
plt.savefig('example.svg')
plt.savefig('example.png')
Now the PNG and the SVG display both points.

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)

Move 3D plot to avoid clipping by margins

I'm trying to figure out how I can get the 3D matplotlib images below to plot higher on the canvas so it doesn't get clipped. Here is the code I'm using to create the plot. I couldn't find a way to attach the text file containing the Z elevations (referenced in the code below), but it is simply a 2D array containing a surface made up of values ranging between 0 and 1.
import os
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from mpl_toolkits.mplot3d import Axes3D
nrow=30
ncol=100
f = open(r'C:\temp\fracEvapCume_200.txt','r')
fracEvapTS = np.loadtxt(f)
f.close()
X, Y = np.meshgrid(ncol, nrow)
Y3d, X3d = np.mgrid[0:Y, 0:X]
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.auto_scale_xyz([0, 100], [0, 30], [0, 0.2])
Y3d, X3d = np.mgrid[0:Y, 0:X]
Z = fracEvapTS
surf = ax.plot_surface(X3d, Y3d, Z, cmap='autumn', cstride=2, rstride=2)
ax.set_xlabel("X-Label")
ax.set_ylabel("Y-Label")
ax.set_zlabel("Z-Label")
ax.pbaspect = [1., .33, 0.25]
ax.dist = 7
plt.tight_layout()
plt.savefig('clipped.png')
In order to get the ax.pbaspect=[1., .33, 0.25] line to work, changes to the get_proj function inside site-packages\mpl_toolkits\mplot3d\axes3d.py were made as suggested in this post. In order to get the figure to draw larger, I added ax.dist = 7 based on this post. Lastly, based on this post I was hoping that plt.tight_layout() would roll back the margins and prevent the red/yellow surface shown below from being clipped, but that didn't work either. I'm failing to find the command that will move the image up on the canvas, thereby avoiding all of the unnecessary white space at the top of the figure and preventing the red/yellow surface from getting clipped. Is there one line of Python that will accomplish this?
after adding the line plt.tight_layout(), it made matters worse:
The problem is that your modification to site-packages\mpl_toolkits\mplot3d\axes3d.py changes the projection matrix, without changing the center of the view, messing up the position of the scene once transfomed in camera coordinates.
So when the view is zoomed (with ax.dist) then moved, the plot sometimes gets out of the canvas.
You need to replace the following line to the get_proj function in axes3d.py :
# look into the middle of the new coordinates
R = np.array([0.5, 0.5, 0.5])
By :
# look into the middle of the new coordinates
try:
R = np.array(self.pbaspect)/2
except AttributeError:
R = np.array([0.5, 0.5, 0.5])
And this should work :
PS : Code used to make the figures :
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from mpl_toolkits.mplot3d import Axes3D
nrow=30
ncol=100
X, Y = np.meshgrid(ncol, nrow)
Y3d, X3d = np.mgrid[0:Y, 0:X]
Z = np.sin(Y3d/Y)*np.sin(X3d/X)
fig = plt.figure()
for i in range(4):
ax = fig.add_subplot(2,2,i,projection='3d')
ax.auto_scale_xyz([0, 100], [0, 30], [0, 0.2])
surf = ax.plot_surface(X3d, Y3d, Z, cmap='autumn', cstride=2, rstride=2)
ax.set_xlabel("X-Label")
ax.set_ylabel("Y-Label")
ax.set_zlabel("Z-Label")
ax.pbaspect = [1., .33, 0.25]
ax.dist = 7

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,)

Categories