Dynamically adding columns to a pyplot not working - python

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

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

How to get stable plot using matplotlib

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)

Python Plots - Plotting a subplots in a subplots

I want to plot a graph representing the changes as per the varying variables. The sample figure is shown below.
The idea is to plot subplot within a subplot. Note It is different from plotting a graph using subplot with a predefined number of rows and columns, i.e matplotlib.pyplot.subplots(nrows=2, ncols=2)
Can I plot such figures using matplotlib/seaborn?
I have drawn the frames and placed the axes inside the frames, everything is based on the no. of subplots/frame, the no. of rows and columns of the frames' grid and the physical dimensions of the different elements.
I imagine that most of the code is self explanatory, except the part where we place the axes in the precise locations, that's stolen from the Demo Fixed Size Axes, if you see points in need of elucidation please ask
import matplotlib
from mpl_toolkits.axes_grid1 import Divider, Size
from mpl_toolkits.axes_grid1.mpl_axes import Axes
import matplotlib.pyplot as plt
import numpy as np
from itertools import product
mm = lambda d: d/25.4
nplots = 2
wp, hp = mm(40), mm(28)
dxp, dyp = mm(16), mm(12)
nrows, ncols = 3, 2
wf, hf = nplots*(wp+dxp), hp+dyp
dxf, dyf = mm(10), mm(8)
xcorners, ycorners = (np.arange(dxf/2,ncols*(wf+dxf),wf+dxf),
np.arange(dyf/2,nrows*(hf+dyf),hf+dyf))
# plus 10 mm for suptitle
fig = plt.figure(figsize=(ncols*(wf+dxf), nrows*(hf+dyf)+mm(10)))
rect = lambda xy: plt.Rectangle(xy, wf, hf,
transform=fig.dpi_scale_trans,
figure=fig,
edgecolor='k', facecolor='none')
fig.patches.extend([rect(xy) for xy in product(xcorners, ycorners)])
t = np.linspace(0,3.14,315); s = np.sin(t)
for nframe, (y, x) in enumerate(product(ycorners, xcorners), 1):
for n in range(nplots):
divider = Divider(fig, (0.0, 0.0, 1., 1.),
[Size.Fixed(x+0.7*dxp+n*(wp+dxp)), Size.Fixed(wp)],
[Size.Fixed(y+0.7*dyp ), Size.Fixed(hp)],
aspect=False)
ax = Axes(fig, divider.get_position())
ax.set_axes_locator(divider.new_locator(nx=1, ny=1))
ax.plot(t, s)
fig.add_axes(ax)
fig.text(x, y, 'Frame %d'%nframe, transform=fig.dpi_scale_trans)
figsize = fig.get_size_inches()
width = figsize[0]*25.4 # mm
fig.suptitle('Original figure width is %.2f mm - everything is scaled'%width)
fig.savefig('pippo.png', dpi=118, facecolor='#f8f8f0')
You will need to use Matplotlib to plot these graphs
You can follow the following example to create your own figure with the graphs:
import matplotlib.pyplot as plt
plt.subplot(1, 2, 1) # Args ( Lines, Columns, Reference )
plt.plot(x, y, 'r') # Reference will say what graph we are modding
plt.subplot(1, 2, 2)
plt.plot(y, x, 'g')
plt.show()
The code will create one graph like this:
And you can use plt.xlabel('name'), plt.ylabel('name') and plt.title('name') to define the labels and the title of your figure
Note: The code above will create one image with 2 graphs, and you can use this code inside another block of code to create the image that you want.
You can also use the following code:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(nrows=5, ncols=5, figsize=(5, 5))
ax[0, 0].plot(x, y) # The method ax is now one array and is referred by indexes
ax[0, 0].set_title('Title')
ax[1, 1].plot(x, y)
ax[1, 1].set_title('Title')
plt.tight_layout() # It will separate the graphs to avoid overlays
plt.show()
It will create the following image:

Matplotlib boxplot + imageshow (subplots)

I'm doing some methods for data visualization, being one of which to show the data with the box plot for this data, as follows:
def generate_data_heat_map(data, x_axis_label, y_axis_label, plot_title, file_path, box_plot=False):
plt.figure()
plt.title(plot_title)
if box_plot:
plt.subplot(1, 2, 1)
plt.boxplot(data.data.flatten(), sym='r+')
plt.subplot(1, 2, 2)
fig = plt.imshow(data.data, extent=[0, data.cols, data.rows, 0])
plt.xlabel(x_axis_label)
plt.ylabel(y_axis_label)
plt.colorbar(fig)
plt.savefig(file_path + '.png')
plt.close()
With this code, this is the image that I get:
First of all, I didn't get why my fliers are not represented as red +, but with the standard pattern. Besides this, as I want to plot the box plot and the data side by side, I divided my plot area. But this space is equally divided, and the figure plot gets really bad. I would like that the box plot took some as 1/3 of the plot area, and the data 2/3.
Thank you in advance.
The error is a simple mistake with your matplotlib code. You are plotting over your own image.
Where you have:
if box_plot:
plt.subplot(1, 1, 1)
plt.boxplot(data.data)
plt.subplot(1, 2, 2)
you need to specify the two rows of your subplots in both calls to plt.subplots
This will work.
if box_plot:
plt.subplot(1, 2, 1)
plt.boxplot(data.data)
plt.subplot(1, 2, 2)
If you want to size the plots independently then you can use gridspec. You might want to plot them above one another like this...
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.gridspec as gridspec
def generate_data_heat_map(data, x_axis_label, y_axis_label, plot_title, file_path, box_plot=False):
plt.figure()
gs = gridspec.GridSpec(2, 1,height_ratios=[1,4])
if box_plot:
plt.subplot(gs[0])
plt.boxplot(data.data.flatten(), 0, 'rs', 0)
plt.subplot(gs[1])
plt.title(plot_title)
fig = plt.imshow(data.data, extent=[0, data.cols, data.rows, 0])
plt.xlabel(x_axis_label)
plt.ylabel(y_axis_label)
plt.colorbar(fig)
plt.savefig(file_path + '.png')
plt.close()
class Data(object):
def __init__(self, rows=200, cols=300):
# The data grid
self.cols = cols
self.rows = rows
# The 2D data structure
self.data = np.zeros((rows, cols), float)
def randomise(self):
self.data = np.random.rand(*self.data.shape)
data = Data()
data.randomise()
generate_data_heat_map(data, 'x', 'y', 'title', 'heat_map', box_plot=True)

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