I am unable to save the image without the white borders and at the initial resolution (1037x627)
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import pyplot, lines
import matplotlib.image as mpimg
from matplotlib.patches import Ellipse
x=[0,0,0,0,0]
y=[0,0,0,0,0]
a=10**1.3*15
inc=25
b=np.cos(np.radians(inc))*a
x[0],y[0]=516.667,313.021
x[1],y[1]=x[0]-a,y[0]
x[2],y[2]=x[0]+a,y[0]
x[3],y[3]=x[0],y[0]+b
x[4],y[4]=x[0],y[0]-b
for pa in range(0,10,5):
fig, ax = plt.subplots()
img=mpimg.imread('IC342.png')
imgplot = plt.imshow(img)
x[1],y[1]=x[0]-a/2*np.cos(np.radians(pa)),y[0]-a/2*np.sin(np.radians(pa))
x[2],y[2]=x[0]+a/2*np.cos(np.radians(pa)),y[0]+a/2*np.sin(np.radians(pa))
x[3],y[3]=x[0]+b/2*np.cos(np.radians(pa+90)),y[0]+b/2*np.sin(np.radians(pa+90))
x[4],y[4]=x[0]-b/2*np.cos(np.radians(pa+90)),y[0]-b/2*np.sin(np.radians(pa+90))
ell = Ellipse(xy=[516.667,313.021], width=a, height=b, angle=pa, edgecolor='b',lw=4, alpha=0.5, facecolor='none')
name='plt'+str(pa)+'.png'
leg='PA='+str(pa)
#ax.text(10, 10, leg, fontsize=15,color='white')
ax.add_artist(ell)
xn=[x[1],x[2],x[0]]
yn=[y[1],y[2],y[0]]
xnw=[x[3],x[4],x[0]]
ynw=[y[3],y[4],y[0]]
line = lines.Line2D(xn, yn, linestyle='-.',lw=5., color='r', alpha=0.4)
line1 = lines.Line2D(xnw, ynw, linestyle='-.',lw=5., color='g', alpha=0.4)
ax.add_line(line)
ax.add_line(line1)
plt.axis('off')
fig.savefig(name, transparent=True, bbox_inches='tight', pad_inches=0,dpi=150 )
initial image
Result
Also I need the white text PA=something to be on the image without changing the resolution. From what I understand adding another figure like text might automatically change the resolution.
Thank you for your time!
There are two factors at play here:
An Axes doesn't take up the entire Figure by default
In matplotlib, the Figure's size is fixed, and the contents are stretched/squeezed/interpolated to fit the figure. You want the Figure's size to be defined by its contents.
To do what you want to do, there are three steps:
Create a figure based on the size of the image and a set DPI
Add a subplot/axes that takes up the entire figure
Save the figure with the DPI you used to calculate figure's size
Let's use a random Hubble image from Nasa http://www.nasa.gov/sites/default/files/thumbnails/image/hubble_friday_12102015.jpg. It's a 1280x1216 pixel image.
Here's a heavily commented example to walk you through it:
import matplotlib.pyplot as plt
# On-screen, things will be displayed at 80dpi regardless of what we set here
# This is effectively the dpi for the saved figure. We need to specify it,
# otherwise `savefig` will pick a default dpi based on your local configuration
dpi = 80
im_data = plt.imread('hubble_friday_12102015.jpg')
height, width, nbands = im_data.shape
# What size does the figure need to be in inches to fit the image?
figsize = width / float(dpi), height / float(dpi)
# Create a figure of the right size with one axes that takes up the full figure
fig = plt.figure(figsize=figsize)
ax = fig.add_axes([0, 0, 1, 1])
# Hide spines, ticks, etc.
ax.axis('off')
# Display the image.
ax.imshow(im_data, interpolation='nearest')
# Add something...
ax.annotate('Look at This!', xy=(590, 650), xytext=(500, 500),
color='cyan', size=24, ha='right',
arrowprops=dict(arrowstyle='fancy', fc='cyan', ec='none'))
# Ensure we're displaying with square pixels and the right extent.
# This is optional if you haven't called `plot` or anything else that might
# change the limits/aspect. We don't need this step in this case.
ax.set(xlim=[-0.5, width - 0.5], ylim=[height - 0.5, -0.5], aspect=1)
fig.savefig('test.jpg', dpi=dpi, transparent=True)
plt.show()
The saved test.jpg will be exactly 1280x1216 pixels. Of course, because we're using a lossy compressed format for both input and output, you won't get a perfect pixel match due to compression artifacts. If you used lossless input and output formats you should, though.
Related
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 have the following code:
#load in image
image = cv2.imread('lenna.png')
title = "foo"
ax = plt.axes([0,0,1,1])
ax.clear()
height, width = image.shape[:2]
ax.axis('off')
ax.set_title(title)
#some things plotted etc.
But then I need the figure as numpy array for further computation, so I am doing the following:
masked_image = image
ax.imshow(masked_image.astype(np.uint8),interpolation="nearest")
ax.figure.canvas.draw()
w,h = ax.figure.get_size_inches()*ax.figure.get_dpi()
I = np.fromstring(ax.figure.canvas.tostring_rgb(),dtype=np.uint8).reshape(int(h),int(w),3)
#Has the white borders around it
Image.fromarray(I)
However, I still has now white borders around it, is there an easy way to remove the white borders without saving the figure?
The image I used is the following:
which does not have any white borders around it
However after the code above, it looks like the following:
Which now has white bars around it.
Other already posted solution to this are all relying on saving the image, which I do not want
It doesn't actually matter if you save the figure or not. There are two conditions that need to be fulfilled to have no whitespace around an image.
1. Figure margins
You need to have no space between axes and figure edge. This can be achieved via setting the subplot params
fig, ax = plt.subplots()
fig.subplots_adjust(0,0,1,1)
or by manually setting the axes position
fig= plt.figure()
fig.add_axes([0,0,1,1])
The latter is the approach you took in the question, so this part is fine.
2. The figure aspect
Images are shown with an equal aspect, which means that each pixel is squared. This is usually desired for images, but it leads to axes not expanding to both directions equally.
The problem you face here is caused by the figure having a different aspect than the image.
Say you have an image of shape (n,m) (m pixels wide, n pixels high); then the necessary condition to get no whitespace around your image is that
n/m == h/w
where w, h are the figure width and height, respectively.
So you may directly set the figure size to the array shape times the dpi,
fig, ax = plt.subplots(figsize=(m*100,n*100), dpi=100)
or any other multiple in case you do not need to have one pixel per pixel in the output. In case you do not care about the figure size at all, but still need a figure with the same aspect as the image you may use figaspect
figsize=plt.figaspect(image)
To provide a full working examples here, the following creates a figure with an image which has no whitespace around it.
import matplotlib.pyplot as plt
import numpy as np
a = np.random.rand(5,3)
fig, ax = plt.subplots(figsize=plt.figaspect(a))
fig.subplots_adjust(0,0,1,1)
ax.imshow(a)
plt.show()
I have axes with each axis limited:
ul_lat, ul_long = (45.499426, 9.119963)
br_lat, br_long = (45.434210, 9.235803)
ax = fig.add_axes([0,0,1,1])
ax.set_xlim(ul_long,br_long)
ax.set_ylim(br_lat,ul_lat)
Then I try to put a satelile photo as background and set up grid:
ax.imshow(image,interpolation='none')
plt.grid()
As a result I can see no image, only grid.
Now, If I remove limits:
#ax.set_xlim(ul_long,br_long)
#ax.set_ylim(br_lat,ul_lat)
I can see the image, though the figure scale is wrong and grid is plotted within this wrong scale:
See this thin grey line in the upper part of the picture - it is a wrong-scaled grid. The size of figure equal to size of picture what I don't want to (903x708). I want to use correct latitude-longitude axes same I tried to set up with xlim/ylim.
What should I do to fix?
With imshow, you can specify the extent of your image to match your coordinates:
ul_lat, ul_long = (45.499426, 9.119963)
br_lat, br_long = (45.434210, 9.235803)
ax = fig.add_axes([0,0,1,1])
ax.set_xlim(ul_long, br_long)
ax.set_ylim(br_lat, ul_lat)
ax.imshow(image, interpolation='none', extent=[ul_long, br_long, br_lat, ul_lat])
plt.grid()
I have an image on which I want to plot (overlay) some points at some specific coordinates. I used zorder and plt.scatter.
I want to save the resulted image in its original size to a numpy array variable in Python and not offline to file (bitmap format) because I want to further process this image with the scatter plots on top without re-reading it.
My thinking is that it will be faster to save the overlaid image into a numpy array variable (in RGB image format - (height,width,channels) ) in Python, than to use plt.savefig and then re-read the image again with cv2.imread then do the necessary subsequent processing.
Speed is an important factor in my case.
I tried to set the axis limits, àxis.('off'), fig.tight_layout(pad=0), `bbox_inches=0/'tight' in order to get rid of any trailing space around my overlaid image, BUT :
there is still a trailing grey space around my image (shown when img is
displayed with cv2.imshow or cv2.imwrite )
if plt.savefig is used --> white space around the image appears
after conversion to numpy arrayof overlaid image it doesn't have
the original size.
Code below, suggestions welcome. All the things commented represent what I tried and didn't work. I have left them there so people don't suggest those again.
import numpy as np
import cv2
import matplotlib.pyplot as plt
img = cv2.imread('Lenna.png')
h, w ,ch = np.shape(img)
# some random coordinate points
x = [30,40, 45, 60]
y = [50,60, 65, 70]
fig = plt.figure()
# tried to set the axes, with these commands, didn't work
#ax = plt.axes([0,0,1,1])
#fig_axes = fig.add_subplot(111)
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB), zorder=0, extent=[0,w, h, 0], interpolation="nearest")
plt.scatter(x, y, zorder=1, facecolors='b', edgecolors='k', s=50)
# tried to limit the axes size based on the image --> didn't work
#plt.xlim(0,w)
#plt.ylim(0,h)
plt.axis('off')
# again didn't do anything
#fig.axes.get_xaxis().set_visible(False)
#fig.axes.get_yaxis().set_visible(False)
fig.tight_layout(pad=0)
fig.canvas.draw()
# save just for verification purpose that no space around the image is shown
plt.savefig("test_Lenna_scatter.png", bbox_inches=0)
# HERE I SAVE THE PLOTTED FIG IMAGE TO NUMPY ARRAY
data_img_array = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep='')
data_img_array = data_img_array.reshape(fig.canvas.get_width_height()[::-1] + (3,))
# HERE I DISPLAY THE NUMPY ARRAY IMAGE AFTER SCATTER
# ALSO PLOT ORIG IMG TO CHECK SIMILARITY
cv2.imwrite('Lenna_scatter.png', cv2.cvtColor(data_img, cv2.COLOR_BGR2RGB))
cv2.imshow('Lenna Scatter', cv2.cvtColor(data_img, cv2.COLOR_BGR2RGB))
cv2.imshow('orig', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
RESULTS
Overlaid Image with Scatter plots obtained from plt.savefig. There appears a white space (if you click on the picture) which I don't want .
Image reconstructed to numpy array after plt.scatter. saved with cv2.imwrite. Notice gray trailing space and different image size. Which i don't want.
To produce a borderless figure in matplotlib, use plt.axes([0,0,1,1]) or fig.add_axes([0,0,1,1]) which will make the axes as big as the figure.
Since you want to plot an image with square pixels, the figure itself must of course have the same aspect as the image. A reasonable way to do accomplish this is to set figsize=(w/100.,h/100.), dpi=100 where w and h are the dimensions of the image.
When saving the file, you need to choose the same dots per inch as you chose when creating the figure. The rest should be self explanatory.
import numpy as np
import matplotlib.pyplot as plt
img = plt.imread("https://i.stack.imgur.com/9qe6z.png")
h, w ,ch = np.shape(img)
x = [30,40, 45, 60]
y = [50,60, 65, 70]
fig = plt.figure(figsize=(w/100.,h/100.), dpi=100)
ax = fig.add_axes([0,0,1,1])
ax.axis('off')
ax.imshow(img, zorder=0, extent=[0,w, h, 0], interpolation="nearest")
ax.scatter(x, y, zorder=1, facecolors='b', edgecolors='k', s=50)
plt.savefig(__file__+".png", bbox_inches=0, dpi=100)
img_recovered = plt.imread(__file__+".png")
print img_recovered.shape
In the window shown on screen you may have a slight border, but the saved image has the exact same size as the input image.
Given an image of unknown size as input, the following python script shows it 8 times in a single pdf page:
pdf = PdfPages( './test.pdf' )
gs = gridspec.GridSpec(2, 4)
ax1 = plt.subplot(gs[0])
ax1.imshow( _img )
ax2 = plt.subplot(gs[1])
ax2.imshow( _img )
ax3 = plt.subplot(gs[2])
ax3.imshow( _img )
# so on so forth...
ax8 = plt.subplot(gs[7])
ax8.imshow( _img )
pdf.savefig()
pdf.close()
The input image can have different size (unknown a priori). I tried using the function gs.update(wspace=xxx, hspace=xxx) to change the spacing between the images, hoping that matplotlib would automagically resize and redistribute the images to have least white space possible. However, as you can see below, it didn't work as I expected.
Is there a better way to go to achieve the following?
Have images saved with max resolution possible
Have less white space possible
Ideally I would like the 8 images to completely will the page size of the pdf (with minimum amount of margin needed).
You were on the right path: hspace and wspace control the spaces between the images. You can also control the margins on the figure with top, bottom, left and right:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import matplotlib.image as mimage
from matplotlib.backends.backend_pdf import PdfPages
_img = mimage.imread('test.jpg')
pdf = PdfPages( 'test.pdf' )
gs = gridspec.GridSpec(2, 4, top=1., bottom=0., right=1., left=0., hspace=0.,
wspace=0.)
for g in gs:
ax = plt.subplot(g)
ax.imshow(_img)
ax.set_xticks([])
ax.set_yticks([])
# ax.set_aspect('auto')
pdf.savefig()
pdf.close()
Result:
If you want your images to really cover all the available space, then you can set the aspect ratio to auto:
ax.set_aspect('auto')
Result: