Updating ticks when using blit with Matplotlib - python

This is my first time asking a question on this platform. Sorry if I make any mistakes.
Some context: I have a figure with three axes. The first one holds the video sequence, the second, the estimation computed by my visual odometry system, the third, the ground truth. It looks something like this:
image1
To get this, I first create the subplots:
fig, (ax_image, ax_odom, ax_gt) = plt.subplots(1, 3)
line_image = ax_image.imshow(cv_image)
ax_image.set_title("Original sequence")
line_odom, = ax_odom.plot(estimated_x, estimated_y, animated=False, color='black', marker='o', markersize=3)
ax_odom.set_title("Estimation")
line_gt, = ax_gt.plot(gt_x, gt_y, animated=False, color='red', marker='o', markersize=3)
ax_gt.set_title("Ground truth")
fig.canvas.draw()
plt.show(block=False)
And, everytime I read an image from the bag file and its corresponding ground truth, I update the plots by calling this function.
def update_plot(fig, ax_image, line_image, ax_odom, line_odom, ax_gt, line_gt, cv_image, estimated_x, estimated_y, gt_x, gt_y):
ax_image.imshow(cv_image)
line_image.set_data(cv_image)
ax_image.draw_artist(ax_image.patch)
ax_image.draw_artist(line_image)
line_odom.set_data(estimated_x, estimated_y)
ax_odom.draw_artist(ax_odom.axes)
ax_odom.draw_artist(ax_odom.patch)
ax_odom.draw_artist(line_odom)
ax_odom.relim()
ax_odom.autoscale_view()
line_gt.set_data(gt_y, gt_x)
ax_gt.draw_artist(ax_gt.patch)
ax_gt.draw_artist(line_gt)
ax_gt.relim()
ax_gt.autoscale_view()
fig.canvas.blit(ax_image.bbox)
fig.canvas.blit(ax_odom.clipbox)
fig.canvas.blit(ax_gt.bbox)
fig.canvas.flush_events()
That works well so far. The problem comes when I try to update the axis ticks. I've read several questions/answers regarding this topic, also Matplotlib documentation but the solution evades me. Instead of replacing the ticks (xticks for the Estimation plot in this example), it gets overwritten.
image2
I avoid the calling of plt.show(), fig.canvas.draw() or plt.pause(x) because it reduces the frame rate significantly.
Any help will be appreciated.

Related

Problem updating imshow in loop and colormap after loop

I have two imshow() problems that I suspect are closely related.
First, I can't figure out how to use set_data() to update an image I've created with imshow().
Second, I can't figure out why the colorbar I add to the imshow() plot after I'm done updating the plot doesn't match the colorbar I add to an imshow() plot of the same data that I create from scratch after I'm done taking data. The colorbar of the second plot appears to be correct.
Background.
I'm collecting measurement data in two nested loops, with each loop controlling one of the measurement conditions. I'm using pyplot.imshow() to plot my results, and I'm updating the imshow() plot every time I take data in the inner loop.
What I have works in terms of updating the imshow() plot but it seems to be getting increasingly slower as I add more loop iterations, so it's not scaling well. (The program I've included in with this post creates a plot that is eight rows high and six columns wide. A "real" plot might be 10x or 20x this size, in both dimensions.)
I think what I want to do is use the image's set_data() method but I can't figure out how. What I've tried either throws an error or doesn't appear to have any effect on the imshow() plot.
Once I'm done with the "take the data" loops, I add a colorbar to the imshow() plot I've been updating. However, the colorbar scale is obviously bogus.
In contrast, if I take create an entirely new imshow() plot, using the data I took in the loops, and then add a colorbar, the colorbar appears to be correct.
My hunch is the problem is with the vmin and vmax values associated with the imshow() plot I'm updating in the loops but I can't figure out how to fix it.
I've already looked at several related StackOverflow posts. For example:
update a figure made with imshow(), contour() and quiver()
Update matplotlib image in a function
How to update matplotlib's imshow() window interactively?
These have helped, in that they've pointed me to set_data() and given me solutions to some other
problems I had, but I still have the two problems I mentioned at the start.
Here's a simplified version of my code. Note that there are repeated zero values on the X and Y axes. This is on purpose.
I'm running Python 3.5.1, matplotlib 1.5.1, and numpy 1.10.4. (Yes, some of these are quite old. Corporate IT reasons.)
import numpy as np
import matplotlib.pyplot as plt
import random
import time
import warnings
warnings.filterwarnings("ignore", ".*GUI is implemented.*") # Filter out bogus matplotlib warning.
# Create the simulated data for plotting
v_max = 120
v_step_size = 40
h_max = 50
h_step_size = 25
scale = 8
v_points = np.arange(-1*abs(v_max), 0, abs(v_step_size))
v_points = np.append(v_points, [-0.0])
reversed_v_points = -1 * v_points[::-1] # Not just reverse order, but reversed sign
v_points = np.append(v_points, reversed_v_points)
h_points = np.arange(-1*abs(h_max), 0, abs(h_step_size))
h_points = np.append(h_points, [-0.0])
reversed_h_points = -1 * h_points[::-1] # Not just reverse order, but reversed sign
h_points = np.append(h_points, reversed_h_points)
h = 0 # Initialize
v = 0 # Initialize
plt.ion() # Turn on interactive mode.
fig, ax = plt.subplots() # So I have access to the figure and the axes of the plot.
# Initialize the data_points
data_points = np.zeros((v_points.size, h_points.size))
im = ax.imshow(data_points, cmap='hot', interpolation='nearest') # Specify the color map and interpolation
ax.set_title('Dummy title for initial plot')
# Set up the X-axis ticks and label them
ax.set_xticks(np.arange(len(h_points)))
ax.set_xticklabels(h_points)
ax.set_xlabel('Horizontal axis measurement values')
# Set up the Y-axis ticks and label them
ax.set_yticks(np.arange(len(v_points)))
ax.set_yticklabels(v_points)
ax.set_ylabel('Vertical axis measurement values')
plt.pause(0.0001) # In interactive mode, need a small delay to get the plot to appear
plt.show()
for v, v_value in enumerate(v_points):
for h, h_value in enumerate(h_points):
# Measurement goes here.
time.sleep(0.1) # Simulate the measurement delay.
measured_value = scale * random.uniform(0.0, 1.0) # Create simulated data
data_points[v][h] = measured_value # Update data_points with the simulated data
# Update the heat map with the latest point.
# - I *think* I want to use im.set_data() here, not ax.imshow(), but how?
ax.imshow(data_points, cmap='hot', interpolation='nearest') # Specify the color map and interpolation
plt.pause(0.0001) # In interactive mode, need a small delay to get the plot to appear
plt.draw()
# Create a colorbar
# - Except the colorbar here is wrong. It goes from -0.10 to +0.10 instead
# of matching the colorbar in the second imshow() plot, which goes from
# 0.0 to "scale". Why?
cbar = ax.figure.colorbar(im, ax=ax)
cbar.ax.set_ylabel('Default heatmap colorbar label')
plt.pause(0.0001) # In interactive mode, need a small delay to get the colorbar to appear
plt.show()
fig2, ax2 = plt.subplots() # So I have access to the figure and the axes of the plot.
im = ax2.imshow(data_points, cmap='hot', interpolation='nearest') # Specify the color map and interpolation
ax2.set_title('Dummy title for plot with pseudo-data')
# Set up the X-axis ticks and label them
ax2.set_xticks(np.arange(len(h_points)))
ax2.set_xticklabels(h_points)
ax2.set_xlabel('Horizontal axis measurement values')
# Set up the Y-axis ticks and label them
ax2.set_yticks(np.arange(len(v_points)))
ax2.set_yticklabels(v_points)
ax2.set_ylabel('Vertical axis measurement values')
# Create a colorbar
cbar = ax2.figure.colorbar(im, ax=ax2)
cbar.ax.set_ylabel('Default heatmap colorbar label')
plt.pause(0.0001) # In interactive mode, need a small delay to get the plot to appear
plt.show()
dummy = input("In interactive mode, press the Enter key when you're done with the plots.")
OK, I Googled some more. More importantly, I Googled smarter and figured out my own answer.
To update my plot inside my nested loops, I was using the command:
ax.imshow(data_points, cmap='hot', interpolation='nearest') # Specify the color map and interpolation
What I tried using to update my plot more efficiently was:
im.set_data(data_points)
What I should have used was:
im.set_data(data_points)
im.autoscale()
This updates the pixel scaling, which fixed both my "plot doesn't update" problem and my "colorbar has the wrong scale" problem.

How can I return a matplotlib figure from a function?

I need to plot changing molecule numbers against time. But I'm also trying to investigate the effects of parallel processing so I'm trying to avoid writing to global variables. At the moment I have the following two numpy arrays tao_all, contains all the time points to be plotted on the x-axis and popul_num_all which contains the changing molecule numbers to be plotted on the y-axis.
The current code I've got for plotting is as follows:
for i, label in enumerate(['Enzyme', 'Substrate', 'Enzyme-Substrate complex', 'Product']):
figure1 = plt.plot(tao_all, popul_num_all[:, i], label=label)
plt.legend()
plt.tight_layout()
plt.show()
I need to encapsulate this in a function that takes the above arrays as the input and returns the graph. I've read a couple of other posts on here that say I should write my results to an axis and return the axis? But I can't quite get my head around applying that to my problem?
Cheers
def plot_func(x, y):
fig,ax = plt.subplots()
ax.plot(x, y)
return fig
Usage:
fig = plot_func([1,2], [3,4])
Alternatively you may want to return ax. For details about Figure and Axes see the docs. You can get the axes array from the figure by fig.axes and the figure from the axes by ax.get_figure().
In addition to above answer, I can suggest you to use matplotlib animation.FuncAnimation method if you are working with the time series and want to make your visualization better.
You can find the details here https://matplotlib.org/api/_as_gen/matplotlib.animation.FuncAnimation.html

Pyplot 3d scatter: points at the back overlap points at the front

I am preparing 3d plots with matplotlib and I am having a really weird behaviour with multiple datasets. I have two datasets that describe basically two shells in 3d: one inner shell and one outer shell. To plot them in 3d I do:
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(outer_z[:n], outer_x[:n], outer_y[:n], c='black', marker='.', lw=0)
ax.scatter(inner_z[:n], inner_x[:n], inner_y[:n], c='red', marker='.', lw=0)
ax.set_xlabel("Z")
ax.set_ylabel("X")
ax.set_zlabel("Y")
ax.set_xlim([-5,5])
ax.set_ylim([5,-5])
ax.set_zlim([-5,5])
(the order of the axes are just for perspective purposes). When I save the figure, however, I don't get two shells:
I get one layer over the other, with the points that are clearly in the back appearing in front. You can see on the pictures that some points of the outer shell that should be behind the inner shell are plotted in front of the inner shell. This is really annoying, because it does not pursue the "plot in 3d" purpose. Does any one have an idea on why is this happening and how could this be solved?
Many thanks!
I know that this isn't a solution to your problem, but perhaps an explanation for why it's behaving the way it is.
This has to do with the fact that Matplotlib does not actually have a 3D engine. Mplot3D takes your points and projects them to what it would look like on a 2D plot (for each object), and then Matplotlib draws each object one at a time; Matplotlib is a 2D drawing framework and Mplot3D is kind of a little hack to get some 3D functionality working without needing to write an full-blown 3D engine for Matplotlib.
This means the order in which you draw your different plots (in this case your red and black dots) matters, and if you draw your black dots after your red dots, they will appear to be in front of the red dots, regardless of their position.
Let me illustrate this with another example.
theta = np.linspace(0, 2*np.pi, 100, endpoint=True)
helix_x = np.cos(3*theta)
helix_y = np.sin(3*theta)
helix_z = theta
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
line_x = np.zeros(100)
line_y = np.zeros(100)
ax.plot(line_x, line_y, theta, lw='3', color='r')
ax.plot(helix_x, helix_y, helix_z, lw='2', color='k')
ax.set_xlabel("Z")
ax.set_ylabel("X")
ax.set_zlabel("Y")
ax.set_xlim([-1.5,1.5])
ax.set_ylim([-1.5,1.5])
ax.set_zlim([0,2*np.pi])
This gives:
But from the top view you can see that the line is inside the helix:
However if you swap the order in which you plot these lines:
ax.plot(line_x, line_y, theta, lw='3', color='r')
ax.plot(helix_x, helix_y, helix_z, lw='2', color='k')
You then see the line drawn after the helix:
Ultimately this means that you will have to manually determine which points will be in front of the other points. Then you can use the zorder argument to determine which objects will be in front of the others. But you would have to do this for each perspective (angle, elevation). In this case you would probably have to break up the inside line into "infront_of_helix" and "behind_helix" parts and then draw them in front and behind the helix respectively.
I hope someone comes along with more elaboration on the matter though, as I'm interested in the topic myself. I know that mplot3d has some elementary methods for making sure the front points show first, I believe, when it's using the shading algorithms but I'm not exactly sure.
thanks you so much for your explanation :) I thought it could be something like that indeed. But I forgot to say in my question that the same thing happened no matter the order of the ax.scatter commands, what is pretty weird. I found out before reading your answer that that does not happen with the ax.plot command. Therefore, I replaced:
ax.scatter(outer_z[:n], outer_x[:n], outer_y[:n], c='black', marker='.', lw=0)
ax.scatter(inner_z[:n], inner_x[:n], inner_y[:n], c='red', marker='.', lw=0)
by
ax.plot(outer_z[:n], outer_x[:n], outer_y[:n], '.', markersize=1, color='black')
ax.plot(inner_z[:n], inner_x[:n], inner_y[:n], '.', markersize=1, color='red')
And I got the following picture:
which works for me. I know, however, that if I change the point of view I will have the red shell appearing on top of the black one. One problem I found later was that the .plot function does not have vmin and vmax arguments (as the .scatter one), which makes it harder to define the color as a gradient starting in vmin and vmax...

remove colorbars from plot

I wrote some code to create a png of a raster object (self[:] = a np array).
it's supposed to be a method, to easily make a plot
Problem with the code is that it runs fine the first time,
but when i run this method multiple times i get a picture with multiple legends.
I tried to get rid of it with delaxes, but this legend is really stubborn.
Any Idea's how to solve this are welcome
Here's the code:
def plot(self,image_out,dpi=150, rotate = 60):
xur = self.xur()
xll = self.xll()
yur = self.yur()
yll = self.yll()
fig = plt.figure()
#tmp = range(len(fig.axes))
#tmp = tmp[::-1]
#for x in tmp:
# fig.delaxes(fig.axes[x])
ax = fig.add_subplot(111)
cax = ax.imshow(self[:],cmap='jet', extent = [yll,yur,xll,xur],
interpolation = 'nearest')
cbar = fig.colorbar()
plt.xticks(rotation=70)
plt.tight_layout(pad = 0.25)
plt.savefig(image_out,dpi=dpi)
return
You need to close the plot. I had this same problem
After plt.savefig, add plt.close()
A better option is to specify to colorbar which axes you would like to see it render into, see the example here.
I encountered the same problem and the answers in another post solved it
remove colorbar from figure in matplotlib
Please refer to the second answer
I had a similar problem and played around a little bit. I came up with two solutions which might be slightly more elegant:
Clear the whole figure and add the subplot (+colorbar if wanted) again.
If there's always a colorbar, you can simply update the axes with autoscale which also updates the colorbar.
I've tried this with imshow, but I guess it works similar for other plotting methods.
In particular, I used the first approach, which is to clear the figure by clf() and then re-add the axis each time.
You can remove the colorbar by its .remove() method:
cbar = fig.colorbar()
...
cbar.remove()

matplotlib colorbar in subplots: labels are vanishing

I am developing some code to produce an arbitrary number of 2D plots (maps and simple contour plots) on a figure. The matplotlib subplots routine works great for this. In the simplified example below, everything works as it should. However, in my real application - which uses the exact same commands for subplots, contourf and colorbar, only that these are dispersed across several routines - the labels on the colorbars are not showing up (the color patches seem to be ok though). Even after hours of reading documentation and searching the web, I don't even have a clue where I could start looking for what the problem is. If I have my colorbar instance (cbar), I should be able to find out if the ticklabel position makes sense, if the ticklabels are set to visible, if my font settings make sense, etc.... But how do I actually check these properties? Has anyone encountered similar problems already? (and even better: found a solution?) Oh yes: if I manually create a new figure and axes in the actual plotting routine (where the contourf command is issued), then it will work again. But that means losing all control over the figure layout etc. Could it be that I am not passing my axes instance correctly? Here is what I do:
fig, ax = plt.subplots(nrows, ncols)
row, col = getCurrent(...)
plotMap(x, y, data, ax=ax[row,col], ...)
Then, inside plotMap:
c = ax.contourf(x, y, data, ...)
ax.figure.colorbar(c, ax=ax, orientation="horizontal", shrink=0.8)
As said above, the example below with simplified plots and artificial data works fine:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0.,360.,5.)*np.pi/180.
y = np.arange(0.,360.,5.)*np.pi/180.
data = np.zeros((y.size, x.size))
for i in range(x.size):
data[:,i] = np.sin(x[i]**2*y**2)
fig, ax = plt.subplots(2,1)
contour = ax[0].contourf(x, y, data)
cbar = ax[0].figure.colorbar(contour, ax=ax[0], orientation='horizontal', shrink=0.8)
contour = ax[1].contourf(x, y, data, levels=[0.01,0.05,0.1,0.05])
cbar = ax[1].figure.colorbar(contour, ax=ax[1], orientation='horizontal', shrink=0.8)
plt.show()
Thanks for any help!
Addition after some further poking around:
for t in cbar.ax.get_xticklabels():
print t.get_position(), t.get_text(), t.get_visible()
shows me the correct text and visible=True, but all positions are (0.,0.). Could this be a problem?
BTW: axis labels are also missing sometimes... and I am using matplotlib version 1.1.1 with python 2.7.3 on windows.
OK - I could track it down: matplotlib is working as it should!
The error was embedded in a utility routine that adds some finishing touches to each page (=figure) once the given number of plot panels has been produced. In this routine I wanted to hide empty plot panels (i.e. on the last page) and I did this with
ax = fig.axes
for i in range(axCurrent, len(ax)):
ax[i].set_axis_off()
However, axCurrent was already reset to zero when the program entered this routine for any page but the last, hence the axes were switched off for all axes in figure. Adding
if axCurrent > 0:
before the for i... solves the problem.
Sorry if I stole anyone's time. Thanks anyway to everyone who was considering to help!

Categories