I'm trying to write a function to automate the placement of watermark in the lower right of my figures. Here is my function so far.
from PIL import Image
import matplotlib.pyplot as plt
def watermark(fig, ax):
""" Place watermark in bottom right of figure. """
# Get the pixel dimensions of the figure
width, height = fig.get_size_inches()*fig.dpi
# Import logo and scale accordingly
img = Image.open('logo.png')
wm_width = int(width/4) # make the watermark 1/4 of the figure size
scaling = (wm_width / float(img.size[0]))
wm_height = int(float(img.size[1])*float(scaling))
img = img.resize((wm_width, wm_height), Image.ANTIALIAS)
# Place the watermark in the lower right of the figure
xpos = ax.transAxes.transform((0.7,0))[0]
ypos = ax.transAxes.transform((0.7,0))[1]
plt.figimage(img, xpos, ypos, alpha=.25, zorder=1)
The problem is that when I add a label to either axis the position of the water mark changes. E.g. adding ax.set_xlabel('x-label', rotation=45) changes the watermark position significantly. This seems to be the case because the placement of the watermark is relative to the whole figure (e.g. the plotting and axis area), however the function get_size_inches() only calculates the plotting area (e.g. not including the axis area).
Is there anyway to get the pixel dimensions of the entire figure (e.g. including axis area) or another easy workaround.
Thanks in advance.
You may want to use an AnchoredOffsetbox in which you place an OffsetImage. The advantage would be that you can use the loc=4 argument to place the Offsetbox in the lower right corner of the axes (just like in the case of a legend).
from PIL import Image
import matplotlib.pyplot as plt
from matplotlib.offsetbox import ( OffsetImage,AnchoredOffsetbox)
def watermark2(ax):
img = Image.open('house.png')
width, height = ax.figure.get_size_inches()*fig.dpi
wm_width = int(width/4) # make the watermark 1/4 of the figure size
scaling = (wm_width / float(img.size[0]))
wm_height = int(float(img.size[1])*float(scaling))
img = img.resize((wm_width, wm_height), Image.ANTIALIAS)
imagebox = OffsetImage(img, zoom=1, alpha=0.2)
imagebox.image.axes = ax
ao = AnchoredOffsetbox(4, pad=0.01, borderpad=0, child=imagebox)
ao.patch.set_alpha(0)
ax.add_artist(ao)
fig, ax = plt.subplots()
ax.plot([1,2,3,4], [1,3,4.5,5])
watermark2(ax)
ax.set_xlabel("some xlabel")
plt.show()
Related
I have specs for a mockup, for example, Photoshop/Illustrator/Sketch file with each elements specifications like size, position, and coordinates in pixels.
An image looks like:
I can draw similar image just using standard matplotlib technic without any problems.
The question is, how to render an image exactly with its specs? All sizes, font sizes, alignments, should be the same as in the specs (as it's drawn in Illustrator).
I've researched over matplotlib docs, but even transformers tutorial doesn't help.
Update with an exact example.
I have a mock at Zeplin which shows coordinated of each plot (Image is also a plot here). So I know, that the image has margins 25x25px from a border and its size is 80x80 pixels.
this is mock image (again not allowed to embed the image).
How would you do that?
The code I use for drawing
fig, ax = plt.subplots(1, 2, figsize=(20, 10), sharey=True)
recs = ax[0].barh(y_pos, widths, align='edge');
img = mpimg.imread('/Users/iwitaly/Downloads/2017-11-12 17.40.46.jpg')
ax[0].spines['left'].set_visible(False);
ax[0].spines['right'].set_visible(False);
ax[0].spines['bottom'].set_visible(False);
ax[0].spines['top'].set_visible(False);
ax[0].get_xaxis().set_visible(True);
ax[0].get_yaxis().set_visible(True);
obj = ax[0].text(x=0, y=3.9, s=r'Name: Vitaly Davydov',
fontsize=30, fontname="Courier New", weight='bold');
ax[0].axhline(y=3.85, xmin=0, xmax=0.75, color='black');
# I'd like to place axicon exactly with 25x25 marging from top left corner
axicon = fig.add_axes([0.08, 1, 0.2, 0.2], transform=None)
axicon.axis('off');
axicon.imshow(img, interpolation='none');
for i, r in enumerate(recs):
r.set_color(index_to_color[i]);
r.set_height(col_wight);
ax[0].text(x=0, y=text_positions[i], s=index_to_text[i], fontsize=30, fontname="Courier New");
ax[1].spines['left'].set_visible(False);
ax[1].spines['right'].set_visible(False);
ax[1].spines['bottom'].set_visible(False);
ax[1].spines['top'].set_visible(False);
ax[1].get_xaxis().set_visible(False);
ax[1].get_yaxis().set_visible(False);
ax[1].text(x=0, y=3.9, s='Increment:', fontsize=30,
fontname="Courier New", weight='bold');
ax[1].axhline(y=3.85, xmin=0, xmax=0.4, color='black');
for i, r in enumerate(recs):
text_x, text_y = r.xy
increment_pos_y = text_y + col_wight / 2
if increment_values[i] > 0:
increment_text = '+{}'.format(increment_values[i])
elif increment_values[i] < 0:
increment_text = '-{}'.format(increment_values[i])
else:
increment_text = '{}'.format(increment_values[i])
ax[1].text(x=0, y=increment_pos_y, s=increment_text,
fontsize=30, color=index_to_color[i],
fontname="Courier New", weight='bold');
In this example I'd like to place axicon axes that is an image with 25x25 margin and 80x80 size (all in pixels).
To place an axes to the figure, you can use fig.add_axes([left, bottom, width, height]), where left, bottom, width, height are fractions of the figure size.
To convert from pixels to fraction of figure size, you need to divide the pixels by the figure dpi and the figure size. E.g. for the left edge
left = 25/fig.dpi/fig.get_size_inches()[0]
Complete example:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(1, 2, figsize=(10, 7))
y_pos, widths = np.linspace(0,3,4), np.random.rand(4)
recs = ax[0].barh(y_pos, widths, align='edge');
#img = plt.imread('/Users/iwitaly/Downloads/2017-11-12 17.40.46.jpg')
img = np.random.rand(80,80)
# I'd like to place axicon exactly with 25x25 marging from top left corner
x,y= 25,25 #pixels
dx,dy = 80,80
w,h = fig.get_size_inches()
axicon = fig.add_axes([x/float(fig.dpi)/w, 1.-(y+dy)/float(fig.dpi)/h,
dx/float(fig.dpi)/w, dy/float(fig.dpi)/h])
axicon.axis('off');
axicon.imshow(img, interpolation='none');
plt.show()
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()
This question lend code from Joe Kington(how to insert a small image on the corner of a plot with matplotlib?):
import matplotlib.pyplot as plt
import Image
import numpy as np
im = Image.open('/home/jofer/logo.png')
height = im.size[1]
# We need a float array between 0-1, rather than
# a uint8 array between 0-255
im = np.array(im).astype(np.float) / 255
fig = plt.figure()
plt.plot(np.arange(10), 4 * np.arange(10))
# With newer (1.0) versions of matplotlib, you can
# use the "zorder" kwarg to make the image overlay
# the plot, rather than hide behind it... (e.g. zorder=10)
fig.figimage(im, 0, fig.bbox.ymax - height)
# (Saving with the same dpi as the screen default to
# avoid displacing the logo image)
fig.savefig('/home/jofer/temp.png', dpi=80)
plt.show()
I tried the following:
import matplotlib.pyplot as plt
import Image
import numpy as np
im = Image.open('/home/po/pic.jpg')
height = im.size[1]
im = np.array(im).astype(np.float) / 255
fig = plt.figure()
fig.subplots_adjust(top=0.80)
fig.patch.set_facecolor('black')
ax1 = fig.add_subplot(1, 1, 1, axisbg='white')
fig.figimage(im, 0, fig.bbox.ymax - height)
But my image is at the center rather than at the middle, is there a way to shift it up, i have tried read up on http://effbot.org/imagingbook/image.htm but to no avail
Thanks in advance:)
I don't see the need for an extra module import when matplotlib has the capabilities to do the same.
If you want to make an inset, simply add (and position) an extra axes object to the figure window. It has some advantages over the figimage method here, because figimage
Adds a non-resampled image to the figure
(matplotlib docs).
Here's an example:
from scipy import misc
face = misc.face()
import matplotlib.pyplot as plt
plt.plot(range(10), range(10))
ax = plt.axes([0.5,0.8, 0.1, 0.1], frameon=True) # Change the numbers in this array to position your image [left, bottom, width, height])
ax.imshow(face)
ax.axis('off') # get rid of the ticks and ticklabels
plt.show()
I used face as an image, to make sure anyone can run the code, but you can have matplotlib load your image by typing:
image = plt.imread('/home/po/pic.jpg')
This replaces your call to the Image module, making it obsolete. The variable image serves the same purpose as the variable face in the small script above.
Depending on your use-case, it may be far faster and cleaner to just resize the image in your photo editor and then use two lines of code:
img = image.imread("my_image.png")
plt.figimage(img, 100, 200, zorder=1, alpha=0.3)
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.
What I want is really simple: I have a small image file called "logo.png" that I want to display on the upper left corner of my plots. But you can't find any example of that in the matplotlib examples gallery.
I'm using django, and my code is something like this:
def get_bars(request)
...
fig = Figure(facecolor='#F0F0F0',figsize=(4.6,4))
...
ax1 = fig.add_subplot(111,ylabel="Valeur",xlabel="Code",autoscale_on=True)
ax1.bar(ind,values,width=width, color='#FFCC00',edgecolor='#B33600',linewidth=1)
...
canvas = FigureCanvas(fig)
response = HttpResponse(content_type='image/png')
canvas.print_png(response)
return response
If you want the image at the corner of your actual figure (rather than the corner of your axis), look into figimage.
Perhaps something like this? (using PIL to read the image):
import matplotlib.pyplot as plt
import Image
import numpy as np
im = Image.open('/home/jofer/logo.png')
height = im.size[1]
# We need a float array between 0-1, rather than
# a uint8 array between 0-255
im = np.array(im).astype(np.float) / 255
fig = plt.figure()
plt.plot(np.arange(10), 4 * np.arange(10))
# With newer (1.0) versions of matplotlib, you can
# use the "zorder" kwarg to make the image overlay
# the plot, rather than hide behind it... (e.g. zorder=10)
fig.figimage(im, 0, fig.bbox.ymax - height)
# (Saving with the same dpi as the screen default to
# avoid displacing the logo image)
fig.savefig('/home/jofer/temp.png', dpi=80)
plt.show()
Another option, if you'd like to have the image be a fixed fraction of the figure's width/height is to create a "dummy" axes and place the image in it with imshow. This way the image's size and position is independent of DPI and the figure's absolute size:
import matplotlib.pyplot as plt
from matplotlib.cbook import get_sample_data
im = plt.imread(get_sample_data('grace_hopper.jpg'))
fig, ax = plt.subplots()
ax.plot(range(10))
# Place the image in the upper-right corner of the figure
#--------------------------------------------------------
# We're specifying the position and size in _figure_ coordinates, so the image
# will shrink/grow as the figure is resized. Remove "zorder=-1" to place the
# image in front of the axes.
newax = fig.add_axes([0.8, 0.8, 0.2, 0.2], anchor='NE', zorder=-1)
newax.imshow(im)
newax.axis('off')
plt.show()
There is now a much easier way, using the new inset_axes command (matplotlib >3.0 required).
This command allows one to define a new set of axes as a child of an existing axes object. The advantage of this is that you can define your inset axes in whatever units you please, like axes fraction or data coordinates, using the appropriate transform expression.
So here's a code example:
# Imports
import matplotlib.pyplot as plt
import matplotlib as mpl
# read image file
with mpl.cbook.get_sample_data(r"C:\path\to\file\image.png") as file:
arr_image = plt.imread(file, format='png')
# Draw image
axin = ax.inset_axes([105,-145,40,40],transform=ax.transData) # create new inset axes in data coordinates
axin.imshow(arr_image)
axin.axis('off')
The advantage of this method is that your image will scale automatically as your axes get rescaled!