Python, QT and matplotlib scatter plots with blitting - python

I am trying to animate a scatter plot (it needs to be a scatter plot as I want to vary the circle sizes). I have gotten the matplotlib documentation tutorial matplotlib documentation tutorial to work in my PyQT application, but would like to introduce blitting into the equation as my application will likely run on slower machines where the animation may not be as smooth.
I have had a look at many examples of animations with blitting, but none ever use a scatter plot (they use plot or lines) and so I am really struggling to figure out how to initialise the animation (the bits that don't get re-rendered every time) and the ones that do. I have tried quite a few things, and seem to be getting nowhere (and I am sure they would cause more confusion than help!). I assume that I have missed something fairly fundamental. Has anyone done this before? Could anyone help me out splitting the figure into the parts that need to be initiated and the ones that get updates?
The code below works, but does not blit. Appending
blit=True
to the end of the animation call yields the following error:
RuntimeError: The animation function must return a sequence of Artist objects.
Any help would be great.
Regards
FP
import numpy as np
from PyQt4 import QtGui, uic
import sys
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupAnim()
self.show()
def setupAnim(self):
self.fig = plt.figure(figsize=(7, 7))
self.ax = self.fig.add_axes([0, 0, 1, 1], frameon=False)
self.ax.set_xlim(0, 1), self.ax.set_xticks([])
self.ax.set_ylim(0, 1), self.ax.set_yticks([])
# Create rain data
self.n_drops = 50
self.rain_drops = np.zeros(self.n_drops, dtype=[('position', float, 2),
('size', float, 1),
('growth', float, 1),
('color', float, 4)])
# Initialize the raindrops in random positions and with
# random growth rates.
self.rain_drops['position'] = np.random.uniform(0, 1, (self.n_drops, 2))
self.rain_drops['growth'] = np.random.uniform(50, 200, self.n_drops)
# Construct the scatter which we will update during animation
# as the raindrops develop.
self.scat = self.ax.scatter(self.rain_drops['position'][:, 0], self.rain_drops['position'][:, 1],
s=self.rain_drops['size'], lw=0.5, edgecolors=self.rain_drops['color'],
facecolors='none')
self.animation = FuncAnimation(self.fig, self.update, interval=10)
plt.show()
def update(self, frame_number):
# Get an index which we can use to re-spawn the oldest raindrop.
self.current_index = frame_number % self.n_drops
# Make all colors more transparent as time progresses.
self.rain_drops['color'][:, 3] -= 1.0/len(self.rain_drops)
self.rain_drops['color'][:, 3] = np.clip(self.rain_drops['color'][:, 3], 0, 1)
# Make all circles bigger.
self.rain_drops['size'] += self.rain_drops['growth']
# Pick a new position for oldest rain drop, resetting its size,
# color and growth factor.
self.rain_drops['position'][self.current_index] = np.random.uniform(0, 1, 2)
self.rain_drops['size'][self.current_index] = 5
self.rain_drops['color'][self.current_index] = (0, 0, 0, 1)
self.rain_drops['growth'][self.current_index] = np.random.uniform(50, 200)
# Update the scatter collection, with the new colors, sizes and positions.
self.scat.set_edgecolors(self.rain_drops['color'])
self.scat.set_sizes(self.rain_drops['size'])
self.scat.set_offsets(self.rain_drops['position'])
if __name__== '__main__':
app = QtGui.QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())

You need to add return self.scat, at the end of the update method if you want to use FuncAnimation with blit=True. See also this nice StackOverflow post that presents an example of a scatter plot animation with matplotlib using blit.
As a side-note, if you wish to embed a mpl figure in a Qt application, it is better to avoid using the pyplot interface and to use instead the Object Oriented API of mpl as suggested in the matplotlib documentation.
This could be achieved, for example, as below, where mplWidget can be embedded as any other Qt widget in your main application. Note that I renamed the update method to update_plot to avoid conflict with the already existing method of the FigureCanvasQTAgg class.
import numpy as np
from PyQt4 import QtGui
import sys
import matplotlib as mpl
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
class mplWidget(FigureCanvasQTAgg):
def __init__(self):
super(mplWidget, self).__init__(mpl.figure.Figure(figsize=(7, 7)))
self.setupAnim()
self.show()
def setupAnim(self):
ax = self.figure.add_axes([0, 0, 1, 1], frameon=False)
ax.axis([0, 1, 0, 1])
ax.axis('off')
# Create rain data
self.n_drops = 50
self.rain_drops = np.zeros(self.n_drops, dtype=[('position', float, 2),
('size', float, 1),
('growth', float, 1),
('color', float, 4)
])
# Initialize the raindrops in random positions and with
# random growth rates.
self.rain_drops['position'] = np.random.uniform(0, 1, (self.n_drops, 2))
self.rain_drops['growth'] = np.random.uniform(50, 200, self.n_drops)
# Construct the scatter which we will update during animation
# as the raindrops develop.
self.scat = ax.scatter(self.rain_drops['position'][:, 0],
self.rain_drops['position'][:, 1],
s=self.rain_drops['size'],
lw=0.5, facecolors='none',
edgecolors=self.rain_drops['color'])
self.animation = FuncAnimation(self.figure, self.update_plot,
interval=10, blit=True)
def update_plot(self, frame_number):
# Get an index which we can use to re-spawn the oldest raindrop.
indx = frame_number % self.n_drops
# Make all colors more transparent as time progresses.
self.rain_drops['color'][:, 3] -= 1./len(self.rain_drops)
self.rain_drops['color'][:, 3] = np.clip(self.rain_drops['color'][:, 3], 0, 1)
# Make all circles bigger.
self.rain_drops['size'] += self.rain_drops['growth']
# Pick a new position for oldest rain drop, resetting its size,
# color and growth factor.
self.rain_drops['position'][indx] = np.random.uniform(0, 1, 2)
self.rain_drops['size'][indx] = 5
self.rain_drops['color'][indx] = (0, 0, 0, 1)
self.rain_drops['growth'][indx] = np.random.uniform(50, 200)
# Update the scatter collection, with the new colors,
# sizes and positions.
self.scat.set_edgecolors(self.rain_drops['color'])
self.scat.set_sizes(self.rain_drops['size'])
self.scat.set_offsets(self.rain_drops['position'])
return self.scat,
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = mplWidget()
sys.exit(app.exec_())

Related

Problem animating matplotlib pcolor plot with polar projection using object

I am trying to create a plotting object that produces an animated matplotlib pcolor plot with a polar projection. Currently the object can either create a set of polar plots or try to create an animation of those plots.
When creating the set of polar plots (but not the animation) the object works as planned.
The animation portion of the object is based on this example, which works on my system. Unfortunately the animation as implemented in my object is not working. There is a figure and an MP4 file produced for the animation but both the figure and the too-short animation both show just some mis-shaped axes.
Does anyone have a suggestion of how to capture this figure series in an animation when embedded in an object?
I am using python 3.7, matplotlib 3.03 on a windows 10 machine
The code for the object and the code to run its instantiation are given below.
class Polar_smudge(object):
# object for creating polar contour plots
def __init__(self, azimuth_grid, range_grid):
import numpy as np
self.azimuth_grid = np.deg2rad(azimuth_grid)
self.range_grid = range_grid
self.fig = None
self.ax = None
self.images = []
#------------------------------------------------------------------
def add_data(self, value_grid):
import numpy as np
self.value_grid = value_grid
self.value_grid[self.value_grid<=0] = np.nan
#------------------------------------------------------------------
def add_figure(self, value_grid):
import matplotlib.pyplot as plt
# make and set-up figure
fig, ax = plt.subplots(subplot_kw=dict(projection='polar'))
ax.set_theta_zero_location("N")
ax.set_theta_direction(-1)
ax.set_rlim([0,10])
# make plot
cax = ax.pcolor(self.azimuth_grid, self.range_grid, value_grid, cmap=plt.cm.viridis_r)
ax.grid()
plt.show()
#------------------------------------------------------------------
def start_figure(self):
import matplotlib.pyplot as plt
# make and set-up figure
if self.fig is None :
self.fig, self.ax = plt.subplots(111, subplot_kw=dict(projection='polar'))
self.ax[0].set_theta_zero_location("N")
self.ax[0].set_theta_direction(-1)
def update_figure(self, value_grid):
import matplotlib.pyplot as plt
# make figure and add to image list
self.images.append((self.ax[0].pcolor(self.azimuth_grid, self.range_grid, value_grid, cmap=plt.cm.viridis_r),))
def end_figure(self):
import matplotlib.animation as animation
# animate the figure list
im_ani = animation.ArtistAnimation(self.fig, self.images, interval=50, repeat_delay=3000,blit=True)
im_ani.save('smudge.mp4')
#============This runs the object ====================================
import numpy as np
azimuth_bins = np.linspace(0, 360, 360)
range_bins = np.linspace(0, 10, 30)
# make plotting azim range grids
range_grid, azimuth_grid = np.meshgrid(range_bins, azimuth_bins)
# this works but isnt what I want
good_smudge = Polar_smudge(azimuth_grid,range_grid)
for ix in range(3):
val_grid = np.random.randn(360,30)
good_smudge.add_figure(val_grid)
# this doesnt work
bad_smudge = Polar_smudge(azimuth_grid,range_grid)
bad_smudge.start_figure()
for ix in range(3):
val_grid = np.random.randn(360,30)
bad_smudge.update_figure(val_grid)
bad_smudge.end_figure()
In response to the comment from Earnest, I did some further refinement and it appears that the problem is not linked to being embedded in an object, and also that increasing the number of frames (to eg. 30) does not solve the problem. The code snippet below provides a more concise demonstration of the problem (but lacks the correctly produced figure output option).
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
azimuth_bins = np.linspace(0, 360, 60)
range_bins = np.linspace(0, 10, 30)
images = []
# make plotting azim range grids
range_grid, azimuth_grid = np.meshgrid(range_bins, azimuth_bins)
fig,ax = plt.subplots(111, subplot_kw=dict(projection='polar'))
ax[0].set_theta_zero_location("N")
ax[0].set_theta_direction(-1)
for ix in range(30):
val_grid = np.random.randn(60,30)
images.append((ax[0].pcolor(azimuth_grid, range_grid, val_grid, cmap=plt.cm.viridis_r),))
# animate the figure list
im_ani = animation.ArtistAnimation(fig, images, interval=50, repeat_delay=3000,blit=False)
im_ani.save('smudge2.mp4')

Consecutive matplotlib animation in same figure

I have an algorithm which consists of two distinct parts which I want to visualize one after another (while possibly keeping the final state of animation 1 on the screen when animation 2 starts).
I can visualize both parts individually by calling animation.FuncAnimation and plt.show(). Since both parts have set number of frames and their very own behaviour, I would like to keep their implementations apart in two different classes and then do a wrapper around them which plays them in sequence.
However, is it possible to have two (or more) animation objects to be displayed one after another in the same figure?
Many thanks,
Matt
Thanks to the hint of ImportanceOfBeingErnest, I came up with a solution which updates only certain elements of my animator state depending on the current time step. Here is a small example illustrating this approach:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from math import sin, radians
class AnimationHandler:
def __init__(self, ax):
self.ax = ax
self.lines = [self.ax.plot([], []), self.ax.plot([], [])]
self.colors = ['cyan', 'red']
self.n_steps = [360, 360]
self.step = 0
def init_animation(self):
for anim_idx in [0, 1]:
self.lines[anim_idx], = self.ax.plot([0, 10], [0, 0], c=self.colors[anim_idx], linewidth=2)
self.ax.set_ylim([-2, 2])
self.ax.axis('off')
return tuple(self.lines)
def update_slope(self, step, anim_idx):
self.lines[anim_idx].set_data([0, 10], [0, sin(radians(step))])
def animate(self, step):
# animation 1
if 0 < step < self.n_steps[0]:
self.update_slope(step, anim_idx=0)
# animation 2
if self.n_steps[0] < step < sum(self.n_steps):
self.update_slope(step - self.n_steps[0], anim_idx=1)
return tuple(self.lines)
if __name__ == '__main__':
fig, axes = plt.subplots()
animator = AnimationHandler(ax=axes)
my_animation = animation.FuncAnimation(fig,
animator.animate,
frames=sum(animator.n_steps),
interval=10,
blit=True,
init_func=animator.init_animation,
repeat=False)
Writer = animation.writers['ffmpeg']
writer = Writer(fps=24, metadata=dict(artist='Me'), bitrate=1800)
my_animation.save('./anim_test.mp4', writer=writer)
plt.show()
I used this approach to visualize/debug an algorithm which has different elements with varying runtimes. Approach is the same: You know the number of steps of each subsequence and adjust the state accordingly.

Python: Animated 3D Scatterplot gets slow

My program plots the positions of particles in my file for every time step. Unfortunately it gets slower and slower although I used matplotlib.animation. Where is the bottleneck?
My data file for two particles looks like the following:
# x y z
# t1 1 2 4
# 4 1 3
# t2 4 0 4
# 3 2 9
# t3 ...
My script:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d.axes3d as p3
import matplotlib.animation as animation
# Number of particles
numP = 2
# Dimensions
DIM = 3
timesteps = 2000
with open('//home//data.dat', 'r') as fp:
particleData = []
for line in fp:
line = line.split()
particleData.append(line)
x = [float(item[0]) for item in particleData]
y = [float(item[1]) for item in particleData]
z = [float(item[2]) for item in particleData]
# Attaching 3D axis to the figure
fig = plt.figure()
ax = p3.Axes3D(fig)
# Setting the axes properties
border = 1
ax.set_xlim3d([-border, border])
ax.set_ylim3d([-border, border])
ax.set_zlim3d([-border, border])
def animate(i):
global x, y, z, numP
#ax.clear()
ax.set_xlim3d([-border, border])
ax.set_ylim3d([-border, border])
ax.set_zlim3d([-border, border])
idx0 = i*numP
idx1 = numP*(i+1)
ax.scatter(x[idx0:idx1],y[idx0:idx1],z[idx0:idx1])
ani = animation.FuncAnimation(fig, animate, frames=timesteps, interval=1, blit=False, repeat=False)
plt.show()
I would suggest to use pyqtgraph in this case. Citation from the docs:
Its primary goals are 1) to provide fast, interactive graphics for
displaying data (plots, video, etc.) and 2) to provide tools to aid in
rapid application development (for example, property trees such as
used in Qt Designer).
You can check out some examples after the installation:
import pyqtgraph.examples
pyqtgraph.examples.run()
This small code snippet generates 1000 random points and displays them in a 3D scatter plot by constantly updating the opacity, similar to the 3D scatter plot example in pyqtgraph.examples:
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import numpy as np
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.show()
g = gl.GLGridItem()
w.addItem(g)
#generate random points from -10 to 10, z-axis positive
pos = np.random.randint(-10,10,size=(1000,3))
pos[:,2] = np.abs(pos[:,2])
sp2 = gl.GLScatterPlotItem(pos=pos)
w.addItem(sp2)
#generate a color opacity gradient
color = np.zeros((pos.shape[0],4), dtype=np.float32)
color[:,0] = 1
color[:,1] = 0
color[:,2] = 0.5
color[0:100,3] = np.arange(0,100)/100.
def update():
## update volume colors
global color
color = np.roll(color,1, axis=0)
sp2.setData(color=color)
t = QtCore.QTimer()
t.timeout.connect(update)
t.start(50)
## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Small gif to give you an idea of the performance:
EDIT:
Displaying multiple points at every single time step is a little bit tricky since the gl.GLScatterPlotItem takes only (N,3)-arrays as point locations, see here. You could try to make a dictionary of ScatterPlotItems where each of them includes all time steps for a specific point. Then one would need to adapt the update function accordingly. You can find an example below where pos is an (100,10,3)-array representing 100 time steps for each point. I reduced the update time to 1000 ms for a slower animation.
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import numpy as np
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.show()
g = gl.GLGridItem()
w.addItem(g)
pos = np.random.randint(-10,10,size=(100,10,3))
pos[:,:,2] = np.abs(pos[:,:,2])
ScatterPlotItems = {}
for point in np.arange(10):
ScatterPlotItems[point] = gl.GLScatterPlotItem(pos=pos[:,point,:])
w.addItem(ScatterPlotItems[point])
color = np.zeros((pos.shape[0],10,4), dtype=np.float32)
color[:,:,0] = 1
color[:,:,1] = 0
color[:,:,2] = 0.5
color[0:5,:,3] = np.tile(np.arange(1,6)/5., (10,1)).T
def update():
## update volume colors
global color
for point in np.arange(10):
ScatterPlotItems[point].setData(color=color[:,point,:])
color = np.roll(color,1, axis=0)
t = QtCore.QTimer()
t.timeout.connect(update)
t.start(1000)
## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Keep in mind that in this examples, all points are shown in the scatter plot, however, the color opacity (4th dimension in the color array) is updated in every time step to get an animation. You could also try to update the points instead of the color to get better performance...
I would guess your bottleneck is calling ax.scatter and ax.set_xlim3d and similar in every frame in the animation.
Ideally, you should make a call to scatter once, then use the object returned by scatter and its set_... properties in the animate function (more details here).
I can't figure out how to do it with scatter, but if you use ax.plot(x, y, z, 'o') instead, you can then follow the demo method here.
Using some random data for x, y, z. It would work like this
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d.axes3d as p3
import matplotlib.animation as animation
from numpy.random import random
# Number of particles
numP = 2
# Dimensions
DIM = 3
timesteps = 2000
x, y, z = random(timesteps), random(timesteps), random(timesteps)
# Attaching 3D axis to the figure
fig = plt.figure()
ax = p3.Axes3D(fig)
# Setting the axes properties
border = 1
ax.set_xlim3d([-border, border])
ax.set_ylim3d([-border, border])
ax.set_zlim3d([-border, border])
line = ax.plot(x[:1], y[:1], z[:1], 'o')[0]
def animate(i):
global x, y, z, numP
idx1 = numP*(i+1)
# join x and y into single 2 x N array
xy_data = np.c_[x[:idx1], y[:idx1]].T
line.set_data(xy_data)
line.set_3d_properties(z[:idx1])
ani = animation.FuncAnimation(fig, animate, frames=timesteps, interval=1, blit=False, repeat=False)
plt.show()

Change format string of axis picker in matplotlib [duplicate]

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

Pyqtgraph interactivity and axis scale

Hi I am having trouble working out which functions I need to use with Pyqtgraph.
Pyqtgraph automatically computes the axis and rescales upon zooming, and this is fine. However I have two axes, frequency and hour. Frequency can take any value between 0-100 and hour can take any value between 0-39. How can I limit the axis to these upper/lower bounds so that when the user zooms or pans they cannot go outside of these values?
I wish to add functionality such that the user can draw a rectangle over a selection of lines and the graph will refresh such that the lines within the rectangle keep their respective colour and any lines outside turn grey?
How can I add another graph to the same window which shows a zoomed in view of the region selected by the rectangle in 2. ?
My code is as follows, and currently zooms on the drawing of a user defined rectangle over the lines, (for 3 lines, my actual code will plot a lot more):
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')
from random import randint
class CustomViewBox(pg.ViewBox):
def __init__(self, *args, **kwds):
pg.ViewBox.__init__(self, *args, **kwds)
self.setMouseMode(self.RectMode)
## reimplement right-click to zoom out
def mouseClickEvent(self, ev):
if ev.button() == QtCore.Qt.RightButton:
#self.autoRange()
self.setXRange(0,5)
self.setYRange(0,10)
def mouseDragEvent(self, ev):
if ev.button() == QtCore.Qt.RightButton:
ev.ignore()
else:
pg.ViewBox.mouseDragEvent(self, ev)
app = pg.mkQApp()
vb = CustomViewBox()
graph = pg.PlotWidget(viewBox=vb, enableMenu=False)
colour = []
for i in range(0,3):
colourvalue = [randint(0,255), randint(0,255), randint(0,255)]
tuple(colourvalue)
colour.append(colourvalue)
y_data = [
[['a',0],['b',1],['c',None],['d',6],['e',7]],
[['a',5],['b',2],['c',1],['d',None],['e',1]],
[['a',3],['b',None],['c',4],['d',9],['e',None]],
]
x_data = [0, 1, 2, 3, 4]
for i in range(3):
xv = []
yv = []
for j, v in enumerate(row[i][1] for row in y_data):
if v is not None:
xv.append(int(j))
yv.append(float(v))
graph.plot(xv, yv, pen = colour[i], name=y_data[0][i][0])
graph.show()
graph.setWindowTitle('Hourly Frequency Graph')
graph.setXRange(0,5)
graph.setYRange(0,10)
graph.setLabel('left', "Frequency", units='%')
graph.setLabel('bottom', "Hour")
graph.showGrid(x=True, y=True)
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Thanks in advance for any help and advice!
I would also like to know why this code always give a segmentation fault: 11 when I close the window.

Categories

Resources