Find best coordinates in PIL image for insert rectangular image - python-3.x

I edited this post in order to ask a more clear question.
Starting from a figure plot generate by Matplotlib like this :
Bitmap data is returned by a function:
from PIL import Image
from matplotlib import pyplot as plt
def fig2rgb ( fig ):
fig.tight_layout()
fig.canvas.draw ( )
pil_fig=Image.frombytes('RGB',
fig.canvas.get_width_height(),
fig.canvas.tostring_rgb() )
return pil_fig
figure = plt.etcetera ...
...
fig_datas=fig2rgb (figure)
I have another rectangular PIL image with definite dimension (height=a and width=b) which can be inclued in PIL figure generated before.
How can we find an available rectangular zone with those finite dimensions in the whitespace of figure plot ?
The final result should be :
My initial hypothesis was to use a function that returns pixel coordinates of white space in figure and try to find x,y in them:
def getWhite_coords(rgb):
white_positions = [[x,y] for x in range(rgb.size[0]) for y in range(rgb.size[1]) \
if rgb.getdata()[x+y*rgb.size[0]] == (255,255,255)]
return np.array(white_positions)
white_space=getWhite_coords(fig_datas)
for y in range(a):
if all([x,y] in white_space for x in range(b)):
...
break
This method has not shown results for my aim.
All this presented above is trying to simulate what legend box does when its argument loc="best" is setted.
Do you have any proposals?
Thanks in advance for the reply

Related

How to save a manipulated numpy array (annotated image) as an image, using python?

I have loaded an image, converted it to an array and annotated it (56.01). Now I want to save it back as an image.
How can I do that?
With this code, the image is annotated. But I want to remove the axes and save it as an image.
from matplotlib import image
import matplotlib.pyplot as plt
ax=plt.gca()
# load image as pixel array
data = image.imread('yellow.jpg')
ax.annotate('56.05',xy=(1000, 500), xycoords='data')
# display the array of pixels as an image
plt.imshow(data)
plt.savefig('imagenet1.png', bbox_inches='tight', dpi = 1000)
plt.show()
ANNOTATED ARRAY
I WANT THIS, BUT THE ANNOTATION IS NOT HERE
You want to annotate after calling imshow, and hide the x and y axes. Alternatively you could plot things in whatever order you want as long as you provided them with a logical zorder parameter.
from matplotlib import image
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
# load image as pixel array
data = image.imread('yellow.jpg')
# display the array of pixels as an image
ax.imshow(data)
ax.annotate('56.05', xy=(1000, 500), xycoords='data')
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
fig.savefig('imagenet1.png', bbox_inches='tight', dpi=1000)
fig.show()

Detecting line-line intersections in an image?

I have an image with some intersecting lines where I need to find the point of intersection. I am using cv2.goodFeaturesToTrack to find strong corners, and working on the assumption that intersections are 'strong' corners so they will get detected. But it's not a sure fire way of getting the intersection points of the two lines. Another approach is that I can get the equations of the lines and calculate the line-line intersection... or any other suggestion.
import matplotlib.pyplot as plt
import cv2
import numpy as np
img = cv2.imread('test_lines.png')
new = img.copy()
#invert
imagem = cv2.bitwise_not(img)
gray = cv2.cvtColor(imagem, cv2.COLOR_BGR2GRAY)
corners = cv2.goodFeaturesToTrack(gray, 4, 0.01, 10, blockSize = 5)
corners = np.int0(corners)
for i in corners:
import pdb; pdb.set_trace()
x, y = i.ravel()
cv2.circle(imagem, (x,y),3,255,-1)
plt.imshow(imagem)
cv2.imwrite('hough_img.png',imagem)
How to detect lines in OpenCV?
This answer was helpful in giving me some good results to begin working with. I followed the steps there to get the following result.

Window size incorrect on matplotlib animation

Trying to get an animation of a rotating arrow in a Jupyter notebook.
Can't get the window size and circle display correct.
I'm trying to get an animation of a rotating arrow in matplotlib. This is part of a jupyter engineering mechanics book I'm building for my students.
The idea of the question is that the animation shows what the two dimensional force balance is of multiple vectors on a node (the black dot in the code).
The animation is based on the following three sources:
1) Drawing a shape
2) Matplotlib animation
3) Arrow animation
get_ipython().run_line_magic('matplotlib', 'inline')
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.patches as patches
from matplotlib import animation, rc
from IPython.display import HTML
from math import degrees,radians,cos,sin,atan,acos,sqrt
# Create figure
fig, ax = plt.subplots()
# Axes labels and title are established
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_ylim(-100,100) #<---- This window size is not displayed
ax.set_xlim(-100,100) #<---- This window size is not displayed
ax.set_aspect('equal', adjustable='box')
#the circle
circle = plt.Circle((0, 0), radius=10, fc='black')
plt.gca().add_patch(circle) #<---- The circle is not displayed
#arrow1 (more arrows will me added)
arrow1x=[]
arrow1y=[]
arrow1dx=[]
arrow1dy=[]
for t in range(1000):
if t <= 250:
arrow1x.append(0)
arrow1y.append(0)
arrow1dx.append(t/250*100)
arrow1dy.append(0)
elif t <= 500:
arrow1x.append(0)
arrow1y.append(0)
arrow1dx.append(100)
arrow1dy.append(0)
elif t <= 750:
arrow1x.append(0)
arrow1y.append(0)
arrow1dx.append(100*cos(radians((t-500)/250*180.)))
arrow1dy.append(100*sin(radians((t-500)/250*180.)))
else:
arrow1x.append(0)
arrow1y.append(0)
arrow1dx.append((100-100*(t-750)/250)*-sin(radians((t-750)/250*180.)))
arrow1dy.append((100-100*(t-750)/250)*-sin(radians((t-750)/250*180.)))
patch = patches.Arrow(arrow1x[0], arrow1y[0], arrow1dx[0], arrow1dy[0])
#the animation (I have no idea how this works:)
def init():
ax.add_patch(patch)
return patch,
def animate(t):
ax.clear()
patch = plt.Arrow(arrow1x[t], arrow1y[t], arrow1dx[t], arrow1dy[t])
ax.add_patch(patch)
return patch,
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=1000, interval=20,
blit=True)
HTML(anim.to_jshtml())
As a result of this code I would like to see a square screen with range (-100 x 100,-100 y 100), the black node and the arrow.
What I'm seeing is a square screen (0 x 1,0 y 1), the rotating arrow, and no black dot.
There is no error output in jupyter which makes this really difficult to follow. Additionally the code takes really long to compile, which is also something that is not desired for a webpage, if this keeps taking so long I think i should look in a pre-compiled image (any tips for that perhaps ?).
Thus for some reason the window size and the dot are not adopted, but as far as I'm seeing the code from the sources is adopted as depicted on the webpages.
You took inappropriate part of "Arrow animation". Since you have static elements on your plot, you don't want to fully clear your ax: you should remove one patch during execution of animate function. Just replace ax.clear() with the next lines:
global patch
ax.patches.remove(patch)

Issue with drawparallels argument in Basemap

This seems like it should be an easy fix but I can't get it to work. I would like 40°N to display in the attached plot, but setting the labels argument in drawparallels to [1,0,1,1] isn't doing the trick. That should plot the parallels lables where they intersect the left, top and bottom of the plot according to the documentation. I would also like for 0° to once again show up in the bottom right corner. Any idea of how I can fix those 2 issues?
from netCDF4 import Dataset as NetCDFFile
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.basemap import Basemap
from mpl_toolkits.basemap import addcyclic
nc = NetCDFFile('C:/myfile.nc')
lat = nc.variables['lat'][:]
lon = nc.variables['lon'][:]
time = nc.variables['time'][:]
olr = nc.variables['olr'][:]
olr,lon = addcyclic(olr,lon)
map = Basemap(llcrnrlon=0.,llcrnrlat=-40.,urcrnrlon=360.,urcrnrlat=40.,resolution='l')
lons,lats = np.meshgrid(lon,lat)
x,y = map(lons,lats)
levels = np.arange(-19.5,20.0,0.5)
levels = levels[levels!=0]
ticks = np.arange(-20.0,20.0,4.0)
cs = map.contourf(x,y,olr[0],levels, cmap='bwr')
cbar = plt.colorbar(cs, orientation='horizontal', cmap='bwr', spacing='proportional', ticks=ticks)
cbar.set_label('Outgoing Longwave Radiation Anomalies $\mathregular{(W/m^2)}$')
map.drawcoastlines()
map.drawparallels(np.arange(-40,40,20),labels=[1,0,1,1], linewidth=0.5, fontsize=7)
map.drawmeridians(np.arange(0,360,40),labels=[1,1,0,1], linewidth=0.5, fontsize=7)
The first part of the question is easy. In order for the label to show up, you have to actually draw the parallel, but np.arange(-40,40,20) does not include 40. So, if you change that statement to np.arange(-40,41,20) your 40N label will show up.
The second part should in principle be solvable in the same way, but Basemap apparently uses the modulo of the longitudes to compute the position of the labels, so just using np.arange(0,361,40) when drawing the meridians will result in two 0 labels on top of each other. However, we can capture the labels that drawmeridians generates and manually change the position of the second 0 label. The labels are stored in a dictionary, so they are easy to deal with. To compute the x position of the last label, I compute the difference in x-position between the first and the second label, multiply that with the amount of meridians to be drawn (360/40) and add the x-position of the first label.
Here the complete example:
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.basemap import Basemap
map = Basemap(llcrnrlon=0.,llcrnrlat=-40.,urcrnrlon=360.,urcrnrlat=40.,resolution='l')
map.drawcoastlines()
yticks = map.drawparallels(
np.arange(-40,41,20),labels=[1,0,1,1], linewidth=0.5, fontsize=7
)
xticks = map.drawmeridians(
np.arange(0,361,40),labels=[1,1,0,1], linewidth=0.5, fontsize=7
)
first_pos = xticks[0][1][0].get_position()
second_pos = xticks[40][1][0].get_position()
last_x = first_pos[0]+(second_pos[0]-first_pos[0])*360/40
xticks[360][1][0].set_position((last_x,first_pos[1]))
plt.show()
Here the resulting plot:
Hope this helps.

Matplotlib: personalize imshow axis

I have the results of a (H,ranges) = numpy.histogram2d() computation and I'm trying to plot it.
Given H I can easily put it into plt.imshow(H) to get the corresponding image. (see http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.imshow )
My problem is that the axis of the produced image are the "cell counting" of H and are completely unrelated to the values of ranges.
I know I can use the keyword extent (as pointed in: Change values on matplotlib imshow() graph axis ). But this solution does not work for me: my values on range are not growing linearly (actually they are going exponentially)
My question is: How can I put the value of range in plt.imshow()? Or at least, or can I manually set the label values of the plt.imshow resulting object?
Editing the extent is not a good solution.
You can just change the tick labels to something more appropriate for your data.
For example, here we'll set every 5th pixel to an exponential function:
import numpy as np
import matplotlib.pyplot as plt
im = np.random.rand(21,21)
fig,(ax1,ax2) = plt.subplots(1,2)
ax1.imshow(im)
ax2.imshow(im)
# Where we want the ticks, in pixel locations
ticks = np.linspace(0,20,5)
# What those pixel locations correspond to in data coordinates.
# Also set the float format here
ticklabels = ["{:6.2f}".format(i) for i in np.exp(ticks/5)]
ax2.set_xticks(ticks)
ax2.set_xticklabels(ticklabels)
ax2.set_yticks(ticks)
ax2.set_yticklabels(ticklabels)
plt.show()
Expanding a bit on #thomas answer
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mi
im = np.random.rand(20, 20)
ticks = np.exp(np.linspace(0, 10, 20))
fig, ax = plt.subplots()
ax.pcolor(ticks, ticks, im, cmap='viridis')
ax.set_yscale('log')
ax.set_xscale('log')
ax.set_xlim([1, np.exp(10)])
ax.set_ylim([1, np.exp(10)])
By letting mpl take care of the non-linear mapping you can now accurately over-plot other artists. There is a performance hit for this (as pcolor is more expensive to draw than AxesImage), but getting accurate ticks is worth it.
imshow is for displaying images, so it does not support x and y bins.
You could either use pcolor instead,
H,xedges,yedges = np.histogram2d()
plt.pcolor(xedges,yedges,H)
or use plt.hist2d which directly plots your histogram.

Resources