How to get stable plot using matplotlib - python

I'm trying to plot 3D poses of hand using matplotlib. For every frame of video or we can say if data is changed then plot size(X, Y, Z) also changed with respect to object(hand poses) size and position. For more detail below are two screenshots with next and previous frame, in screenshots we can see that x, y and z axis are changed as hand pose is changed.
My question is that how to get a stable plot that it's size will not change even object position will be changed.
As a reference below is another image that shows a stable plot and I'm trying to get like this. As we can see that input frames and object size is changing but plot is with same size. No matter if object size will be changed in plot.
Below is my plotting code. Kindly take it just as plotting sample, I'm sharing only plotting code because problem is in plotting code.
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.view_init(elev=20, azim=75)
ax.set_xlabel('X Axis')
ax.set_ylabel('Y Axis')
ax.set_zlabel('Z Axis')
rgb_dict = get_keypoint_rgb(skeleton)
for i in range(len(skeleton)): # here is skeleton, just ignore it
joint_name = skeleton[i]['name']
pid = skeleton[i]['parent_id']
parent_joint_name = skeleton[pid]['name']
x = np.array([kps_3d[i, 0], kps_3d[pid, 0]]) # kps_3d is pose data
y = np.array([kps_3d[i, 1], kps_3d[pid, 1]])
z = np.array([kps_3d[i, 2], kps_3d[pid, 2]])
ax.plot(x, z, -y, c = np.array(rgb_dict[parent_joint_name])/255., linewidth = line_width)
ax.scatter(kps_3d[i, 0], kps_3d[i, 2], -kps_3d[i, 1], c = np.array(rgb_dict[joint_name]).reshape(1, 3)/255., marker='o')
ax.scatter(kps_3d[pid, 0], kps_3d[pid, 2], -kps_3d[pid, 1], c = np.array(rgb_dict[parent_joint_name]).reshape(1, 3)/255., marker='o')
As ax.scatter docs show that it is used to get a scatter plot of y vs. x with varying marker size and/or color.
Which alternative function I can use to get stable plots?
Looking for some valuable suggestions.

Try set the plot limits by these lines of code:
ax.set_xlim3d(-0.1, 0.1)
ax.set_ylim3d(-0.1, 0.1)
ax.set_zlim3d(-0.1, 0.1)
Adjust the numbers as needed. You may also need to adjust viewing angle:
ax.view_init(elev=28., azim=55)

Related

What's the meaning of clip_box in Matplotlib Artist?

I am testing the clip_box feature of Artist using the code snippet below:
import matplotlib.pyplot as plt
from matplotlib.transforms import Bbox
import numpy as np
fig = plt.figure()
ax = fig.subplots(1, 2)
x = [1, 2, 3, 4]
y = [3, 8, 5, 2]
line_a, = ax[0].plot(x, y, color='red', linewidth=3.0)
line_b, = ax[1].plot(x, y, color='red', linewidth=3.0)
boundingbox = Bbox(np.array([[0, 0], [3, 9]]))
line_b.set_clip_box(boundingbox)
line_b.set_clip_on(True)
plt.show()
What I expect is the last part of line_b will be cut out by the clip box, and line_b will be a bit shorter than line_a.
It turns out that there's nothing left on the second subplot. It's totally empty. Is my understanding of the clip_box wrong or are there some issues in the code snippet?
The "natural" clip box for the right hand side plot is ax[1].bbox. Finding its extent tells us what units should be used to specify the clip box Bbox.
Since we don't add the Bbox instance to any axes when we create, it could only be relative to the figure. When we print ax[1].bbox, we can see that its size is to be specified in pixels.
It's indeed much simpler to use a Rectangle or Polygon to specify the clip box because they can be added to axes. Using 'none' color for its facecolor could be more convenient because it's figure style-independent.
import matplotlib.pyplot as plt
from matplotlib.transforms import Bbox
fig = plt.figure(dpi=89)
ax = fig.subplots(1, 2)
x = [1, 2, 3, 4]
y = [3, 8, 5, 2]
line_a, = ax[0].plot(x, y, color='red', linewidth=3.0)
line_b, = ax[1].plot(x, y, color='red', linewidth=3.0)
print(ax[1].bbox, '\n', ax[1].bbox.extents)
# the line above prints
# TransformedBbox(
# Bbox(x0=0.5477272727272726, y0=0.10999999999999999, x1=0.8999999999999999, y1=0.88),
# BboxTransformTo(
# TransformedBbox(
# Bbox(x0=0.0, y0=0.0, x1=6.393258426966292, y1=4.797752808988764),
# Affine2D().scale(178.0))))
# [ 623.31363636 93.94 1024.2 751.52 ]
# 178.0 is 2 * dpi, I believe the doubling happens because of what screen I have got
boundingbox = Bbox.from_extents([623.31363636, 93.94, 900.2, 751.52])
print(boundingbox, '\n', boundingbox.extents)
# the line above prints
# Bbox(x0=623.31363636, y0=93.94, x1=900.2, y1=751.52)
# [623.31363636 93.94 900.2 751.52 ]
line_b.set_clip_box(boundingbox)
line_b.set_clip_on(True)
plt.show()
I've spent some time reading about Bboxes in Matplotlib and they are pretty complicated. The set_clip_box method you refer to has not got very helpful documentation, and the examples of its use both use the bbox of an Axes, which is a nested transformation; ie _, ax = plt.subplots(); ax.bbox is a TransformedBbox based on a linear transform of another TransformedBbox based on an Affine2D transform of a plain Bbox! (All of this explained in more detail here.)
It seems that these involve transformations between different sets of co-ordinates; in the case of a regular Axes it is between x- and y-values, pixels, and the specific adaptations to screen size. I would be happy to hear from someone who knows more about Bboxes why your Bbox acts the way it does. But what you want to achieve can be done much more easily, using a FancyBboxPatch (a Rectangle patch would work just as well):
from matplotlib.patches import FancyBboxPatch
f, ax = plt.subplots(1, 2)
x = [1, 2, 3, 4]
y = [3, 8, 5, 2]
line_a, = ax[0].plot(x, y, color='red', linewidth=3.0)
line_b, = ax[1].plot(x, y, color='red', linewidth=3.0)
bb = Bbox([[0, 0], [3, 9]])
ax[1].add_patch(FancyBboxPatch((bb.xmin, bb.ymin), bb.width, bb.height, boxstyle="square",
ec='white', fc='white', zorder=2.1))
(ec and fc are edge colour and fill colour; zorder determines the artist order. Lines are 2, so we just need out Bbox patch to be slightly higher.)

Dynamically adding columns to a pyplot not working

Not really a pressing issue but it's bugging me: I want to display several images side by side (i.e. several columns) but for some reason the following code (taken from python tutorial) only displays the images in one column. So what I want is a layout like this
X X X
but what I get is
X
X
X
code:
...
plt.ion()
...
fig = plt.figure()
sample = face_dataset[65] # <== this is a simple image of size 640x480
for i, tsfrm in enumerate([scale, crop, composed]):
transformed_sample = tsfrm(sample)
ax = plt.subplot(1, 3, i + 1)
plt.tight_layout()
ax.set_title(type(tsfrm).__name__)
show_landmarks(**transformed_sample)
plt.show()
...
Here is the show_landmarks function definition:
def show_landmarks(image, landmarks):
"""Show image with landmarks"""
plt.imshow(image)
plt.scatter(landmarks[:, 0], landmarks[:, 1], s=10, marker='.', c='r')
plt.pause(0.001) # pause a bit so that plots are updated
I do not think the different transforms (scale,crop, composed) matter so I left them out but they can be found under the link above.
If I write basically the same as a test code the columns show up fine:
fig = plt.figure()
for i in (0,1,2):
ax = plt.subplot(1, 3, i + 1)
plt.tight_layout()
ax.set_title(i)
plt.show()
So my guess is that somehow show_landmarks messes things up. Can anybody point me in the right direction as to why/how that is?
You need to modify show_landmarks so it is making calls to the current plotting axis, rather than to plt. Then pass the axis to the function as well as the other args.
def show_landmarks(ax, image, landmarks):
"""Show image with landmarks"""
ax.imshow(image)
ax.scatter(landmarks[:, 0], landmarks[:, 1], s=10, marker='.', c='r')
...
for i, tsfrm in enumerate([scale, crop, composed]):
transformed_sample = tsfrm(sample)
ax = plt.subplot(1, 3, i + 1)
ax.set_title(type(tsfrm).__name__)
show_landmarks(ax, **transformed_sample)
plt.tight_layout()
...

plotting an mXnXk matrix as a 3d model in python

I have a matrix generated by parsing a file the numpy array is the size 101X101X41 and each entry has a value which represents the magnitude at each point.
Now what I want to do is to plot it in a 3d plot where the 4th dimension will be represented by color. so that I will be able to see the shape of the data points (represent molecular orbitals) and deduce its magnitude at that point.
If I plot each slice of data I get the desired outcome, but in a 2d with the 3rd dimension as the color.
Is there a way to plot this model in python using Matplotlib or equivalent library
Thanks
EDIT:
Im trying to get the question clearer to what I desire.
Ive tried the solution suggested but ive received the following plot:
as one can see, due to the fact the the mesh has lots of zeros in it it "hide" the 3d orbitals. in the following plot one can see a slice of the data, where I get the following plot:
So as you can see I have a certain structure I desire to show in the plot.
my question is, is there a way to plot only the structure and ignore the zeroes such that they won't "hide" the structure.
the code I used to generate the plots:
x = np.linspase(1,101,101)
y = np.linspase(1,101,101)
z = np.linspase(1,101,101)
xx,yy,zz = np.meshgrid(x,y,z)
fig=plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(xx, yy, zz, c=cube.calc_data.flatten())
plt.show()
plt.imshow(cube.calc_data[:,:,11],cmap='jet')
plt.show()
Hope that now the question is much clearer, and that you'd appreciate the question enough now to upvote
Thanks.
you can perform the following:
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
epsilon = 2.5e-2 # threshold
height, width, depth = data.shape
global_min = np.inf
global_max = -np.inf
for d in range(depth):
slice = data[:, :, d]
minima = slice.min()
if (minima < global_min): global_min = minima
maxima = slice.max()
if (maxima>global_max): global_max=maxima
norm = colors.Normalize(vmin=minima, vmax=maxima, clip=True)
mapper = cm.ScalarMappable(norm=norm, cmap=cm.jet)
points_gt_epsilon = np.where(slice >= epsilon)
ax.scatter(points_gt_epsilon[0], points_gt_epsilon[1], d,
c=mapper.to_rgba(data[points_gt_epsilon[0],points_gt_epsilon[1],d]), alpha=0.015, cmap=cm.jet)
points_lt_epsilon = np.where(slice <= -epsilon)
ax.scatter(points_lt_epsilon[0], points_lt_epsilon[1], d,
c=mapper.to_rgba(data[points_lt_epsilon[0], points_lt_epsilon[1], d]), alpha=0.015, cmap=cm.jet)
ax.set_xlabel('X Label')
ax.set_ylabel('Y Label')
ax.set_zlabel('Z Label')
plt.title('Electron Density Prob.')
norm = colors.Normalize(vmin=global_min, vmax=global_max, clip=True)
cax, _ = colorbar.make_axes(ax)
colorbar.ColorbarBase(cax, cmap=cm.jet,norm=norm)
plt.savefig('test.png')
plt.clf()
What this piece of code does is going slice by slice from the data matrix and for each scatter plot only the points desired (depend on epsilon).
in this case you avoid plotting a lot of zeros that 'hide' your model, using your words.
Hope this helps
You can adjust the color and size of the markers for the scatter. So for example you can filter out all markers below a certain threshold by putting their size to 0. You can also make the size of the marker adaptive to the field strength.
As an example:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
f = lambda x,y,z: np.exp(-(x-3)**2-(y-3)**2-(z-1)**2) - \
np.exp(-(x+3)**2-(y+3)**2-(z+1)**2)
t1 = np.linspace(-6,6,101)
t2 = np.linspace(-3,3,41)
# Data of shape 101,101,41
data = f(*np.meshgrid(t1,t1,t2))
print(data.shape)
# Coordinates
x = np.linspace(1,101,101)
y = np.linspace(1,101,101)
z = np.linspace(1,101,41)
xx,yy,zz = np.meshgrid(x,y,z)
fig=plt.figure()
ax = fig.add_subplot(111, projection='3d')
s = np.abs(data/data.max())**2*25
s[np.abs(data) < 0.05] = 0
ax.scatter(xx, yy, zz, s=s, c=data.flatten(), linewidth=0, cmap="jet", alpha=.5)
plt.show()

ax.scatter making background figure come to foreground in 3D plot?

I've been working on this complicated plot of trajectories of rockets and maps and things... I came to the point that I needed to include markers on specific places of my map:
Figure without the marker
It's a long code, that requires a lot of data to reproduce, but when I include this line:
ax.scatter(-147.338786 65.32957 85.001453, c='aqua', marker='o', s = 100, label = 'PMC - Water release'
,edgecolors = 'black')
This is the result:
Figure with the marker
I'm using a figure generated with another code to generate the map as a png and add that to the 3D plot like so:
fig = plt.figure(figsize=(16,14))
ax = fig.gca(projection='3d')
ax.w_xaxis.set_pane_color((1.0, 1.0, 1.0, 1.0))
ax.w_yaxis.set_pane_color((1.0, 1.0, 1.0, 1.0))
k=.01
xx, yy = np.meshgrid(np.linspace(xlims[0],xlims[1],377), np.linspace(ylims[0]+k,ylims[1]+k,317))
# create vertices for a rotated mesh (3D rotation matrix)
X = xx#np.cos(theta)
Y = yy#np.sin(theta)
Z = yy*.0-2.
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, facecolors=plt.imread(myfolder +
'basemap.png'), shade=False)
basemap.png is the name of the map (sized 377x317).
Does anybody know how to override the figure coming to the foreground with the marker? I don't know why it would do that, but that line (ax.scatter) is the only difference between figure 1 and figure 2.
edit: I did change the order of plot calls and so on, with no positive results

Pyplot, plot 2 dataset into one figure, skip part of the y-axis

I am plotting to different datasets into one graph with pylab.plot(), which works great. But one dataset has values between 0% an 25% and the other has values between 75% and 100%. I want to skip 30% to 70% on the y-axis to save some space. Do you have any suggestions how this might be work with pyplot?
EDIT:
For clearness I added the following graphic. I want to skip 30% to 60% on the y axis, so that the red line and the green line come closer together.
The solution is based on Space_C0wb0ys post.
fig = pylab.figure()
ax = fig.add_subplot(111)
ax.plot( range(1,10), camean - 25, 'ro-' )
ax.plot( range(1,10), oemean , 'go-' )
ax.plot( range(1,10), hlmean , 'bo-' )
ax.set_yticks(range(5, 60, 5))
ax.set_yticklabels(["5","10","15","20","25","30","...","65","70","75"])
ax.legend(('ClassificationAccuracy','One-Error','HammingLoss'),loc='upper right')
pylab.show()
This code creates the following graphic.
You could subtract 40 from the x-values for your second functions to make the range of x-values continuous. This would give you a range from 0% to 70%. Then you can make set the tics and labes of the x-axis as follows:
x_ticks = range(71, 0, 10)
a.set_xticks(x_ticks)
a.set_xticklabels([str(x) for x in [0, 10, 20, 30, 70, 80, 90, 100]])
Where a is the current axes. So basically, you plot your functions in the range from 0% to 70%, but label the axis with a gap.
To illustrate - the following script:
from numpy import arange
import matplotlib.pyplot as plt
x1 = arange(0, 26) # first function
y1 = x1**2
x2 = arange(75, 100) # second function
y2 = x2*4 + 10
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(x1, y1)
ax.plot(x2 - 40, y2) # shift second function 40 to left
ax.set_xticks(range(0, 61, 5)) # set custom x-ticks
# set labels for x-ticks - labels have the gap we want
ax.set_xticklabels([str(x) for x in range(0, 26, 5) + range(70, 101, 5)])
plt.show()
Produces the following plot (note the x-labels):
The matplotlib documentation actually has an example of how to do this.
The basic idea is to break up the plotting into two subplots, putting the same graph on each plot, then change the axes for each one to only show the specific part, then make it look nicer.
So, let's apply this. Imagine this is your starting code:
import matplotlib.pyplot as plt
import random, math
# Generates data
i = range(10)
x = [math.floor(random.random() * 5) + 67 for i in range(10)]
y = [math.floor(random.random() * 5) + 22 for i in range(10)]
z = [math.floor(random.random() * 5) + 13 for i in range(10)]
# Original plot
fig, ax = plt.subplots()
ax.plot(i, x, 'ro-')
ax.plot(i, y, 'go-')
ax.plot(i, z, 'bo-')
plt.show()
And we went to make it so that x is shown split off from the rest.
First, we want to plot the same graph twice, one on top of the other. To do this, the plotting function needs to be generic. Now it should look something like this:
# Plotting function
def plot(ax):
ax.plot(i, x, 'ro-')
ax.plot(i, y, 'go-')
ax.plot(i, z, 'bo-')
# Draw the graph on two subplots
fig, (ax1, ax2) = plt.subplots(2, 1)
plot(ax1)
plot(ax2)
Now this seems worse, but we can change the range for each axis to focus on what we want. For now I'm just choosing easy ranges that I know will capture all the data, but I'll focus on making the axes equal later.
# Changes graph axes
ax1.set_ylim(65, 75) # Top graph
ax2.set_ylim(5, 30) # Bottom graph
This is getting closer to what we're looking for. Now we need to just make it look a little nicer:
# Hides the spines between the axes
ax1.spines.bottom.set_visible(False)
ax2.spines.top.set_visible(False)
ax1.xaxis.tick_top()
ax1.tick_params(labeltop=False) # Don't put tick labels at the top
ax2.xaxis.tick_bottom()
# Adds slanted lines to axes
d = .5 # proportion of vertical to horizontal extent of the slanted line
kwargs = dict(
marker=[(-1, -d), (1, d)],
markersize=12,
linestyle='none',
color='k',
mec='k',
mew=1,
clip_on=False
)
ax1.plot([0, 1], [0, 0], transform=ax1.transAxes, **kwargs)
ax2.plot([0, 1], [1, 1], transform=ax2.transAxes, **kwargs)
Finally, let's fix the axes. Here you need to do a little math and decide more on the layout. For instance, maybe we want to make the top graph smaller, since the bottom graph has two lines. To do that, we need to change the height ratios for the subplots, like so:
# Draw the graph on two subplots
# Bottom graph is twice the size of the top one
fig, (ax1, ax2) = plt.subplots(2, 1, gridspec_kw={'height_ratios': [1, 2]})
Finally, It's a good idea to make the axes match. In this case, because the bottom image is twice the size of the top one, we need to change the axes of one to reflect that. I've chosen to modify the top one in this time. The bottom graph covers a range of 25, which means the top one should cover a range of 12.5.
# Changes graph axes
ax1.set_ylim(60.5, 73) # Top graph
ax2.set_ylim(5, 30) # Bottom graph
This looks good enough to me. You can play around more with the axes or tick labels if you don't want the ticks to overlap with the broken lines.
Final code:
import matplotlib.pyplot as plt
import random, math
# Generates data
i = range(10)
x = [math.floor(random.random() * 5) + 67 for i in range(10)]
y = [math.floor(random.random() * 5) + 22 for i in range(10)]
z = [math.floor(random.random() * 5) + 13 for i in range(10)]
# Plotting function
def plot(ax):
ax.plot(i, x, 'ro-')
ax.plot(i, y, 'go-')
ax.plot(i, z, 'bo-')
# Draw the graph on two subplots
# Bottom graph is twice the size of the top one
fig, (ax1, ax2) = plt.subplots(2, 1, gridspec_kw={'height_ratios': [1, 2]})
plot(ax1)
plot(ax2)
# Changes graph axes
ax1.set_ylim(60.5, 73) # Top graph
ax2.set_ylim(5, 30) # Bottom graph
# Hides the spines between the axes
ax1.spines.bottom.set_visible(False)
ax2.spines.top.set_visible(False)
ax1.xaxis.tick_top()
ax1.tick_params(labeltop=False) # Don't put tick labels at the top
ax2.xaxis.tick_bottom()
# Adds slanted lines to axes
d = .5 # proportion of vertical to horizontal extent of the slanted line
kwargs = dict(
marker=[(-1, -d), (1, d)],
markersize=12,
linestyle='none',
color='k',
mec='k',
mew=1,
clip_on=False
)
ax1.plot([0, 1], [0, 0], transform=ax1.transAxes, **kwargs)
ax2.plot([0, 1], [1, 1], transform=ax2.transAxes, **kwargs)
plt.show()

Categories

Resources