I am trying to make an interactive script in jupyter notebook that will draw different images in cycle and asks for user decision. I came to the point where I can redraw figure with 3 subplots, but have no idea how to configure the size of interactive figure to make all subplots visible. As you can see only part of the second subplot peeps out from the right.
I will be grateful for any help.
Here is my code:
import cv2
import matplotlib.pyplot as plt
import pandas as pd
% matplotlib notebook
fig, [ax1, ax2, ax3] = plt.subplots(1,3, figsize=(5, 5))
ax1.set_xlabel("src")
ax2.set_xlabel("rgb")
ax3.set_xlabel("hsv")
ax2.set_xlim([-1,257])
ax3.set_xlim([-1,257])
plt.legend(loc='upper right')
color = ('r','g','b') # HSV colors
labels = ('h', 's', 'v')
plt.subplots_adjust(right = 2.3)
for i in range(0,3):
im = cv2.imread("image.png")
im_rgb = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)
im_hsv = cv2.cvtColor(im, cv2.COLOR_BGR2HSV)
ax1.imshow(im_rgb, cmap='gray')
for i,col in enumerate(color):
histr = cv2.calcHist([im_rgb],[i],None,[256],[0,256])
ax2.plot(histr, color = col, label=col)
for i,col in enumerate(color):
histr = cv2.calcHist([im_hsv],[i],None,[256],[0,256])
ax3.plot(histr, color = col, label=labels[i])
fig.canvas.draw()
answer = input("Next? " )
It might not be obvious from the subplots_adjust documentation, but the parameters for top and right need to be smaller than 1 to have the axes stay within the figure boundaries, as those parameters are the fraction of figure height and width at which the figure margin starts.
If you provide right=2.3 it will mean that the last subplot's right edge ends at 2.3 times the figure width - which is most probably undesired.
So keep right below 1 and to make all plots fit inside the figure, you may want to adjust the figure size instead, e.g. figsize=(13,5).
Related
I would like to use matplotlib flexible text abilities on an existing image in PNG format with 300 dpi resolution.
I must preserve size and resolution.
what I tried :
from pylab import *
background = imread('Invitation.png')
imshow(background)
text(500,100, "n° 00001", size=20, rotation=30,
ha="right", va="top",
bbox=dict(boxstyle="round",
ec=(1., 0.5, 0.5),
fc=(1., 0.8, 0.8),
))
axis(off)
savefig('Invitation00001.png',dpi=300)
close()
But I encounter bounding box problems and dpi loss (left is before, right is after):
What is the good way to keep the original image features in the result?
Is there an alternative (at image level ?) for slanted text box ?
Thanks for any advice.
Generate a figure with correct size and axes that occupy the whole figure:
import matplotlib.pyplot as plt
im = plt.imread('Invitation.png')
f = plt.figure(figsize = (im.shape[1]/300, im.shape[0]/300) #figure with correct aspect ratio
ax = plt.axes((0,0,1,1)) #axes over whole figure
ax.imshow(im)
ax.text(...) #whatever text arguments
ax.axis('off')
f.savefig('Invitation00001.png',dpi=300)
I'm trying to create imshow subplots with the same pixel size without having the figure height automatically scaled, but I haven't been able to figure out how.
Ideally, I'm looking for a plot similar to the second picture, without the extra white space (ylim going from -0.5 to 4.5) and maybe centered vertically. My pictures will always have the same width, so maybe if I could fix the subplot width instead of the height that would help. Does anyone have any ideas?
close('all')
f,ax=subplots(1,2)
ax[0].imshow(random.rand(30,4),interpolation='nearest')
ax[1].imshow(random.rand(4,4),interpolation='nearest')
tight_layout()
f,ax=subplots(1,2)
ax[0].imshow(random.rand(30,4),interpolation='nearest')
ax[1].imshow(random.rand(4,4),interpolation='nearest')
ax[1].set_ylim((29.5,-0.5))
tight_layout()
Plot without ylim adjustment:
Plot with ylim adjustment:
In principle you can just make the figure size small enough in width, such that it constrains the widths of the subplots. E.g. figsize=(2,7) would work here.
For an automated solution, you may adjust the subplot parameters, such that the left and right margin constrain the subplot width. This is shown in the code below.
It assumes that there is one row of subplots, and that all images have the same pixel number in horizontal direction.
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(1,2)
im1 = ax[0].imshow(np.random.rand(30,4))
im2 = ax[1].imshow(np.random.rand(4,4))
def adjustw(images, wspace="auto"):
fig = images[0].axes.figure
if wspace=="auto":
wspace = fig.subplotpars.wspace
top = fig.subplotpars.top
bottom = fig.subplotpars.bottom
shapes = np.array([im.get_array().shape for im in images])
w,h = fig.get_size_inches()
imw = (top-bottom)*h/shapes[:,0].max()*shapes[0,1] #inch
n = len(shapes)
left = -((n+(n-1)*wspace)*imw/w - 1)/2.
right = 1.-left
fig.subplots_adjust(left=left, right=right, wspace=wspace)
adjustw([im1, im2], wspace=1)
plt.show()
If you need to use tight_layout(), do so before calling the function. Also you would then definitely need to set the only free parameter here, wspace to something other than "auto". wspace=1 means to have as much space between the plots as their width.
The result is a figure where the subplots have the same size in width.
I am working with wordcloud module in python3 and trying to save a figure that should only give me the image of the wordcloud without any whitespaces around the cloud. I tried many ticks mentioned here in stackexchange but they didn't work. Below is my default code, which can get rid of the whitespace on the left and right but not on the top and bottom. If I make the other two values in ax = plt.axes([0,0,1,1]) to 0 as well then I get an empty image.
wordcloud = WordCloud(font_path=None, width = 1500, height=500,
max_words=200, stopwords=None, background_color='whitesmoke', max_font_size=None, font_step=1, mode='RGB',
collocations=True, colormap=None, normalize_plurals=True).generate(filteredText)
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.axes([0,0,1,1])
plt.imshow(wordcloud, interpolation="nearest")
plt.axis('off')
plt.savefig('fig.png', figsize = (1500,500), dpi=300)
Could someone please help me out with this?
The wordcloud is an image, i.e. an array of pixels. plt.imshow makes pixels square by default. This means that unless the image has the same aspect ratio than the figure, there will be white space either the top and bottom, or the left and right side.
You can free the fixed aspect ratio setting aspect="auto",
plt.imshow(wc, interpolation="nearest", aspect="auto")
the result of which is probably undesired.
So what you would really want is to adapt the figure size to the image size.
Since the image is 1500 x 500 pixels, you may choose a dpi of 100 and a figure size of 15 x 5 inch.
wc = wordcloud.WordCloud(font_path=None, width = 1500, height=500,
max_words=200, stopwords=None, background_color='whitesmoke', max_font_size=None, font_step=1, mode='RGB',
collocations=True, colormap=None, normalize_plurals=True).generate(text)
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(15,5), dpi=100)
ax = plt.axes([0,0,1,1])
plt.imshow(wc, interpolation="nearest", aspect="equal")
plt.axis('off')
plt.savefig(__file__+'.png', figsize=(15,5), dpi=100)
plt.show()
At the end using matplotlib may not be the best choice anyways. Since you only want to save the image, you could just use
from scipy.misc import imsave
imsave(__file__+'.png', wc)
I would like to utilize customer markers in both scatter and line charts. How can I make custom marker out of a PNG file?
I don't believe matplotlib can customize markers like that. See here for the level of customization, which falls way short of what you need.
As an alternative, I've coded up this kludge which uses matplotlib.image to place images at the line point locations.
import matplotlib.pyplot as plt
from matplotlib import image
# constant
dpi = 72
path = 'smile.png'
# read in our png file
im = image.imread(path)
image_size = im.shape[1], im.shape[0]
fig = plt.figure(dpi=dpi)
ax = fig.add_subplot(111)
# plot our line with transparent markers, and markersize the size of our image
line, = ax.plot((1,2,3,4),(1,2,3,4),"bo",mfc="None",mec="None",markersize=image_size[0] * (dpi/ 96))
# we need to make the frame transparent so the image can be seen
# only in trunk can you put the image on top of the plot, see this link:
# http://www.mail-archive.com/matplotlib-users#lists.sourceforge.net/msg14534.html
ax.patch.set_alpha(0)
ax.set_xlim((0,5))
ax.set_ylim((0,5))
# translate point positions to pixel positions
# figimage needs pixels not points
line._transform_path()
path, affine = line._transformed_path.get_transformed_points_and_affine()
path = affine.transform_path(path)
for pixelPoint in path.vertices:
# place image at point, centering it
fig.figimage(im,pixelPoint[0]-image_size[0]/2,pixelPoint[1]-image_size[1]/2,origin="upper")
plt.show()
Produces:
Following on from Mark's answer. I just thought I would add to this a bit because I tried to run this and it does what I want with the exception of actually displaying the icons on the graph. Maybe something has changed with matplotlib. It has been 4 years.
The line of code that reads:
ax.get_frame().set_alpha(0)
does not seem to work, however
ax.patch.set_alpha(0)
does work.
The other answer may lead to problems when resizing the figure. Here is a different approach, positionning the images inside annotation boxes, which are anchored in data coordinates.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
path = "https://upload.wikimedia.org/wikipedia/commons/b/b5/Tango-example_icons.png"
image = plt.imread(path)[116:116+30, 236:236+30]
x = np.arange(10)
y = np.random.rand(10)
fig, ax = plt.subplots()
ax.plot(x,y)
def plot_images(x, y, image, ax=None):
ax = ax or plt.gca()
for xi, yi in zip(x,y):
im = OffsetImage(image, zoom=72/ax.figure.dpi)
im.image.axes = ax
ab = AnnotationBbox(im, (xi,yi), frameon=False, pad=0.0,)
ax.add_artist(ab)
plot_images(x, y, image, ax=ax)
plt.show()
I would like to plot a number of curves over an image
Using this code I am reasonably close:
G=plt.matplotlib.gridspec.GridSpec(64,1)
fig = plt.figure()
plt.imshow(img.data[:,:],cmap='gray')
plt.axis('off')
plt.axis([0,128,0,64])
for i in arange(64):
fig.add_subplot(G[i,0])
plt.axis('off')
# note that vtc.data.shape = (64, 128*400=51200)
# so every trace for each image pixel is 400 points long
plt.plot(vtc.data[i,:])
plt.axis([0, 51200, 0, 5])
The result that I am getting looks like this:
The problem is that while I seem to be able to get rid of all the padding in the horizontal (x) direction, there is different amount of padding in the image and the stacked plots in the vertical direction.
I tried using
ax = plt.gca()
ax.autoscale_view('tight')
but that didn't reduce the margin either.
How can I get a grid of m-by-n line plots to line up precisely with a blown up (by factor f) version of an image with dimensions (fm)-by-(fn)?
UPDATE and Solution:
The answer by #RutgerKassies works quite well. I achieved it using his code like so:
fig, axs = plt.subplots(1,1,figsize=(8,4))
axs.imshow(img.data[:,:],cmap='gray', interpolation='none')
nplots = 64
fig.canvas.draw()
box = axs._position.bounds
height = box[3] / nplots
for i in arange(nplots):
tmpax = fig.add_axes([box[0], box[1] + i * height, box[2], height])
tmpax.set_axis_off()
# make sure to get image orientation right and
tmpax.plot(vtc.data[nplots-i-1,:],alpha=.3)
tmpax.set_ylim(0,5)
tmpax.set_xlim(0, 51200)
I think the easiest way is to use the boundaries from your 'imshow axes' to manually calculate the boundaries of all your 'lineplot axes':
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(1,1,figsize=(15,10))
axs.imshow(np.random.rand(50,100) ,cmap='gray', interpolation='none', alpha=0.3)
nplots = 50
fig.canvas.draw()
box = axs._position.bounds
height = box[3] / nplots
for i in arange(nplots):
tmpax = fig.add_axes([box[0], box[1] + i * height, box[2], height])
tmpax.set_axis_off()
tmpax.plot(np.sin(np.linspace(0,np.random.randint(20,1000),1000))*0.4)
tmpax.set_ylim(-1,1)
The above code seems nice, but i do have some issues with the autoscale chopping off part of the plot. Try removing the last line to see the effect, im not sure why thats happening.