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

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...

Related

Updating ticks when using blit with Matplotlib

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.

How to add annotation arrows to legend

I have a trajectory of some robot, together with some arrows describing parameters of the robot. Just like so:
Robot Trajectory with annotation arrows
(because of my lack of reputation, I cannot add the picture directly)
The question is: How can I display the annotation arrows together with the lines in the legend?
I use annotation arrows in order to draw the arrows as per this answer in a loop to draw arrows for every point.
Here is the code for one of my annotations:
an = ax.annotate('', xy=xy_tuple, xytext=xy_texttuple, label=labelString, arrowprops=dict(color=arrowcolor, arrowstyle=aStyle))
And just as a reference, I use the plotting function like so:
# plot Local x-y axis
fig, ax1 = plt.subplots()
ln1 = ax1.plot(x, y, '-o', label='Location of the last 48h')
ax1.set_ylabel('Local North (m)')
ax1.set_xlabel('Local East (m)')
ax1.grid()
fig.gca().set_aspect('equal', adjustable='box')
lns = ln1
labs = [l.get_label() for l in lns]
ax1.legend(lns, labs, loc='best')
So how can I add the annotate labels (given by labelString) to my legend?
As pointed out by #ImportanceOfBeingErnest, this is an open issue which was found with matplotlib. The developers wanted to fix this issue, but according to this discussion on the matplotlib developerboard, an solution was implemented but did not pass to the final version and is now closed (although it is a pretty cool solution imho).
Give an line and two annotations, my solution now looks like so. Of course this is not nearly as nice:
ax1.legend([ln1, an1.arrow_patch, an2.arrow_patch], (ln1.get_label(), an1.get_label(), an2.get_label()))
Resulting in the following example

How can I adjust Axes sizes in matplotlib polar plots? [duplicate]

I am starting to play around with creating polar plots in Matplotlib that do NOT encompass an entire circle - i.e. a "wedge" plot - by setting the thetamin and thetamax properties. This is something I was waiting for for a long time, and I am glad they have it done :)
However, I have noticed that the figure location inside the axes seem to change in a strange manner when using this feature; depending on the wedge angular aperture, it can be difficult to fine tune the figure so it looks nice.
Here's an example:
import numpy as np
import matplotlib.pyplot as plt
# get 4 polar axes in a row
fig, axes = plt.subplots(2, 2, subplot_kw={'projection': 'polar'},
figsize=(8, 8))
# set facecolor to better display the boundaries
# (as suggested by ImportanceOfBeingErnest)
fig.set_facecolor('paleturquoise')
for i, theta_max in enumerate([2*np.pi, np.pi, 2*np.pi/3, np.pi/3]):
# define theta vector with varying end point and some data to plot
theta = np.linspace(0, theta_max, 181)
data = (1/6)*np.abs(np.sin(3*theta)/np.sin(theta/2))
# set 'thetamin' and 'thetamax' according to data
axes[i//2, i%2].set_thetamin(0)
axes[i//2, i%2].set_thetamax(theta_max*180/np.pi)
# actually plot the data, fine tune radius limits and add labels
axes[i//2, i%2].plot(theta, data)
axes[i//2, i%2].set_ylim([0, 1])
axes[i//2, i%2].set_xlabel('Magnitude', fontsize=15)
axes[i//2, i%2].set_ylabel('Angles', fontsize=15)
fig.set_tight_layout(True)
#fig.savefig('fig.png', facecolor='skyblue')
The labels are in awkward locations and over the tick labels, but can be moved closer or further away from the axes by adding an extra labelpad parameter to set_xlabel, set_ylabel commands, so it's not a big issue.
Unfortunately, I have the impression that the plot is adjusted to fit inside the existing axes dimensions, which in turn lead to a very awkward white space above and below the half circle plot (which of course is the one I need to use).
It sounds like something that should be reasonably easy to get rid of - I mean, the wedge plots are doing it automatically - but I can't seem to figure it out how to do it for the half circle. Can anyone shed a light on this?
EDIT: Apologies, my question was not very clear; I want to create a half circle polar plot, but it seems that using set_thetamin() you end up with large amounts of white space around the image (especially above and below) which I would rather have removed, if possible.
It's the kind of stuff that normally tight_layout() takes care of, but it doesn't seem to be doing the trick here. I tried manually changing the figure window size after plotting, but the white space simply scales with the changes. Below is a minimum working example; I can get the xlabel closer to the image if I want to, but saved image file still contains tons of white space around it.
Does anyone knows how to remove this white space?
import numpy as np
import matplotlib.pyplot as plt
# get a half circle polar plot
fig1, ax1 = plt.subplots(1, 1, subplot_kw={'projection': 'polar'})
# set facecolor to better display the boundaries
# (as suggested by ImportanceOfBeingErnest)
fig1.set_facecolor('skyblue')
theta_min = 0
theta_max = np.pi
theta = np.linspace(theta_min, theta_max, 181)
data = (1/6)*np.abs(np.sin(3*theta)/np.sin(theta/2))
# set 'thetamin' and 'thetamax' according to data
ax1.set_thetamin(0)
ax1.set_thetamax(theta_max*180/np.pi)
# actually plot the data, fine tune radius limits and add labels
ax1.plot(theta, data)
ax1.set_ylim([0, 1])
ax1.set_xlabel('Magnitude', fontsize=15)
ax1.set_ylabel('Angles', fontsize=15)
fig1.set_tight_layout(True)
#fig1.savefig('fig1.png', facecolor='skyblue')
EDIT 2: Added background color to figures to better show the boundaries, as suggested in ImportanteOfBeingErnest's answer.
It seems the wedge of the "truncated" polar axes is placed such that it sits in the middle of the original axes. There seems so be some constructs called LockedBBox and _WedgeBbox in the game, which I have never seen before and do not fully understand. Those seem to be created at draw time, such that manipulating them from the outside seems somewhere between hard and impossible.
One hack can be to manipulate the original axes such that the resulting wedge turns up at the desired position. This is not really deterministic, but rather looking for some good values by trial and error.
The parameters to adjust in this case are the figure size (figsize), the padding of the labels (labelpad, as already pointed out in the question) and finally the axes' position (ax.set_position([left, bottom, width, height])).
The result could then look like
import numpy as np
import matplotlib.pyplot as plt
# get a half circle polar plot
fig1, ax1 = plt.subplots(1, 1, figsize=(6,3.4), subplot_kw={'projection': 'polar'})
theta_min = 1.e-9
theta_max = np.pi
theta = np.linspace(theta_min, theta_max, 181)
data = (1/6.)*np.abs(np.sin(3*theta)/np.sin(theta/2.))
# set 'thetamin' and 'thetamax' according to data
ax1.set_thetamin(0)
ax1.set_thetamax(theta_max*180./np.pi)
# actually plot the data, fine tune radius limits and add labels
ax1.plot(theta, data)
ax1.set_ylim([0, 1])
ax1.set_xlabel('Magnitude', fontsize=15, labelpad=-60)
ax1.set_ylabel('Angles', fontsize=15)
ax1.set_position( [0.1, -0.45, 0.8, 2])
plt.show()
Here I've set some color to the background of the figure to better see the boundary.

Drawing a Go-Board with Matplotlib

Might drawing a Go-Board be possible within matplotlib?
I will not show you my horrible tries (it included some working with patches) as long as you do not ask for them, I hope you come up with better ideas.
Or even better: there is a library for this, or someone already programmed it?
That would be nice!
(Why would someone need a GO Board within matplotlib? there are plenty of reasons. My AI works with python/C++ anyway as well as some visualizations of the performance, which is plotted in matplotlib. It is now possible to export/import to .sgf, but this includes an external viewer which is slow if there is the need for many plots. )
Sure. Anything can be drawn, it is just a matter of amount of code required...
import matplotlib.pyplot as plt
# create a 8" x 8" board
fig = plt.figure(figsize=[8,8])
fig.patch.set_facecolor((1,1,.8))
ax = fig.add_subplot(111)
# draw the grid
for x in range(19):
ax.plot([x, x], [0,18], 'k')
for y in range(19):
ax.plot([0, 18], [y,y], 'k')
# scale the axis area to fill the whole figure
ax.set_position([0,0,1,1])
# get rid of axes and everything (the figure background will show through)
ax.set_axis_off()
# scale the plot area conveniently (the board is in 0,0..18,18)
ax.set_xlim(-1,19)
ax.set_ylim(-1,19)
# draw Go stones at (10,10) and (13,16)
s1, = ax.plot(10,10,'o',markersize=30, markeredgecolor=(0,0,0), markerfacecolor='w', markeredgewidth=2)
s2, = ax.plot(13,16,'o',markersize=30, markeredgecolor=(.5,.5,.5), markerfacecolor='k', markeredgewidth=2)
Gives this:
If you do not like the background, you may even put there some nice photograph of a go board or whatever you need by using imshow.
One nice thing is that if you take the objects returned by ax.plot, you can remove them and replot the board without doing a lot of work.
ax.lines.remove(s1)
or simply
s1.remove()
The first one shows what is going on; the line object is removed from the line list, the second one is faster to type, as the line object knows its parent.
Either of this, and it's gone. (You may need to call draw to see the changes.)
There are many ways to do things in python, and matplotlib is no exception. As per tcaswell's suggestions the the lines are replaced by the grid, and the circular markers with circle patches. Also, now black and white stones are created from prototypes.
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import copy
fig = plt.figure(figsize=[8,8], facecolor=(1,1,.8))
ax = fig.add_subplot(111, xticks=range(19), yticks=range(19), axis_bgcolor='none', position=[.1,.1,.8,.8])
ax.grid(color='k', linestyle='-', linewidth=1)
ax.xaxis.set_tick_params(bottom='off', top='off', labelbottom='off')
ax.yaxis.set_tick_params(left='off', right='off', labelleft='off')
black_stone = mpatches.Circle((0,0), .45, facecolor='k', edgecolor=(.8,.8,.8, 1), linewidth = 2, clip_on=False, zorder=10)
white_stone = copy.copy(black_stone)
white_stone.set_facecolor((.9, .9, .9))
white_stone.set_edgecolor((.5, .5, .5))
s1 = copy.copy(black_stone)
s1.center = (18,18)
ax.add_patch(s1)
s2 = copy.copy(white_stone)
s2.center = (6,10)
ax.add_patch(s2)
The result is essentially the same.

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