Insert a matplotlib text on an existing image and preserve dpi - python

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)

Related

Adjust figure height automatically, when the width is fixed and aspect ratio is defined by figure content

When using matplotlib to prepare publication-ready figures that include text, one wants to avoid re-scaling or stretching the image when including it into the (e.g. LaTeX) document. To achieve this, the standard procedure is to choose a figure width which matches the document in preparation (in LaTeX this is often the \textwidth or \columnwidth). On the other hand, it is usually less important to fix the figure height. If the figure content has a well-defined aspect ratio (for example, when plotting a grid of square images), there is an optimal figure height that maximally fills the figure, avoiding undue white space.
Ideally then, it should be possible to fix the figure width, tell matplotlib to use a constrained-layout, and let it figure out a good figure height.
Consider the following scenario:
import matplotlib.pyplot as plt
import numpy as np
ims = [np.random.random((20, 20)) for i in range(3)]
# Based on the \textwidth in our LaTeX document
width_inch = 4
# We plot the images in a 1x3 grid
fig, axes = plt.subplots(
nrows=1,
ncols=3,
layout="constrained",
linewidth=5,
edgecolor="black") # Uses the default figsize
for ax, im in zip(axes.flatten(), ims):
ax.imshow(im)
# Fix the figure width
fig.set_figwidth(width_inch)
This produces:
Clearly the figure height is much too large. On the other hand, making it too small leaves too much whitespace:
fig, axes = plt.subplots(
nrows=1,
ncols=3,
layout="constrained",
linewidth=5,
edgecolor="black",
figsize=(width_inch, 1))
Is it possible to get matplotlib to calculate and apply an optimal figure height, or does this need to be manually adjusted?

Paste an image on another image of different size & display on matlibplot in python

I am new to python and I have wrote the following python code to paste spot.jpg onto background.jpg at coordinate (2,3) given that background.jpg lies on extents [0,20, 0, 16]. Both images are of different sizes.
from PIL import Image
import matplotlib.pyplot as plt
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
# imbg is layout/ background
imbg = Image.open(r"background.jpg")
# imfg is furniture/ foreground
imfg = Image.open(r"spot.jpg")
# manually set aspect dimension for background
ext= [0,20,0,16]
ax = plt.subplot() # add sub-plot
oi= OffsetImage(imfg, zoom= 0.1)
ab1= AnnotationBbox(oi, (2, 3), frameon=False)
ax.add_artist(ab1)
plt.imshow(imbg, zorder= 0, extent=ext )
plt.xlabel("Dimensions", fontsize= 12)
plt.title('Proposed', fontsize= 20)
mng = plt.get_current_fig_manager()
mng.window.state("zoomed")
plt.show()
Results is matlibplot results
I see 3 problems here.
If I don't put zoom= 0.1 (a number I anyhow guess), spot.jpg will be very much bigger than background.jpg. In fact, spot is less than 1m by 1m and background is 20m by 16m. If I need to put a number to zoom in order to put both images to the same scale, what should be the number or how can I calculate the number?
When I zoom into the matplotlib results, spot.jpg doesn't seem to turn bigger. zooming into spot on plot. I wonder why.
The image quality of spot is affected on the plot. Is there anyway to improve how spot.jpg look on plot?
Many thanks in advance to help a noob like me.
Using an offset box maynot be the good approach here. It seems you want to have both images in the same data coordinates. Hence plot both images as imshow with different extents according to the desired positions.
import matplotlib.pyplot as plt
# imbg is layout/ background
imbg = plt.imread(r"data/room.jpg")
# imfg is furniture/ foreground
imfg = plt.imread(r"data/spot.jpg")
# manually set aspect dimension for background
ext= [0,20,0,16]
fig, ax = plt.subplots()
ax.imshow(imbg, zorder= 0, extent=ext )
ax.imshow(imfg, zorder= 1, extent=[2,5,3,6] )
ax.axis(ext)
plt.xlabel("Dimensions", fontsize= 12)
plt.title('Proposed', fontsize= 20)
plt.show()
The extent for the chair is also rather arbitrary here, but you would know better where to put it and in what size.

Matplotlib notebook cropped figure

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

Python wordcloud without any whitespaces around the plot

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)

How to insert a small image on the corner of a plot with matplotlib?

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!

Categories

Resources