Draw rounded fancyarrowpatch with midpoint arrow in matplotlib - python

I've been trying to push the boundaries of matplotlib's patches and instruct it to draw a rounded FancyArrowPatch with a directional arrow on its midpoint. This would prove incredibly useful in a network representation I am trying to create.
My coding hours with python are not yet in the double digit, so I can't say I have a clear understanding of matplotlib's patches.py, but I have narrowed down the solution to two possible strategies:
the smart, possibly pythonic way: create a custom arrowstyle class which further requires a modification of the _get_arrow_wedge() function to include a midpoint coordinates. This may be beyond my possibilities for now, or
the lazy way: extract the midpoint coordinates from an elicited FancyArrowPatch and draw the desired arrowstyle on such coordinates.
Of course, so far I've chosen the lazy way. I did some early experimenting with extracting the midpoint coordinates of a curved FancyArrowPatch using get_path() and get_path_in_displaycoord(), but I can't seem to predict the precise midpoint coordinates. Some help would be very appreciated.
My fiddling so far:
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch
n1 = (2,3)
n2 = (4,6)
# Try with multiple arc radius sizes, draw a separate plot each time
for rad in range(20):
#setup figure
figure = plt.figure()
ax = plt.subplot(111)
plt.annotate('rad:' + str(rad/25.),xy=(2,5))
# create rounded fancyarrowpatch
t = FancyArrowPatch(posA=n1,posB=n2,
connectionstyle='arc3,rad=%s'%float(rad/25.),
arrowstyle='->',
shrinkA=0,
shrinkB=0,
mutation_scale=0.5)
# extract vertices from get_path: points P#
path = t.get_path().vertices.tolist()
lab, px, py = ['P{0}'.format(i) for i in range(len(path))], [u[0] for u in path],[u[1] for u in path]
for i in range(len(path)):
plt.annotate(lab[i],xy=(px[i],py[i]))
# extract vertices from get_path_in_displaycoord (but they are useless) : points G#
newpath = t.get_path_in_displaycoord()
a,b = newpath[0][0].vertices.tolist(), newpath[0][1].vertices.tolist()
a.extend(b)
glab, gx, gy = ['G{0}'.format(i) for i in range(len(a))], [u[0] for u in a],[u[1] for u in a]
for i in range(len(a)):
plt.annotate(glab[i],xy=(gx[i],gy[i]))
#point A: start
x1, y1 = n1
plt.annotate('A',xy=(x1,y1))
#point B:end
x2, y2 = n2
plt.annotate('B',xy=(x2,y2))
#point M: the 'midpoint' as defined by class Arc3, specifically its connect() function
x12, y12 = (x1 + x2) / 2., (y1 + y2) / 2.
dx, dy = x2 - x1, y2 - y1
cx, cy = x12 + (rad/100.) * dy, y12 - (rad/100.) * dx
plt.annotate('M',xy=(cx,cy))
#point O : midpoint between M and P1, the second vertex from get_path
mx,my = (cx + px[1])/2., (cy + py[1])/2.
plt.annotate('O',xy=(mx,my))
ax.add_patch(t)
plt.scatter([x1,cx,x2,mx,gx].extend(px),[y1,cy,y2,my,gy].extend(py))
plt.show()
EDIT: taking onboard #cphlewis suggestions: I tried to reconstruct the Bezier curve:
def bezcurv(start,control,end,tau):
ans = []
for t in tau:
B = [(1-t)**2 * start[i] + 2*(1-t)*t*end[i] + (t**2)*control[i] for i in range(len(start))]
ans.append(tuple(B))
return ans
I thus add the generated line to the original plot:
tau = [time/100. for time in range(101)]
bezsim = bezcurv(n1,n2,(cx,cy),tau)
simx,simy = [b[0] for b in bezsim], [b[1] for b in bezsim]
The green line below is (should be?) the reconstructed bezier curve, though it's clearly not.

After much struggling, I convinced myself that to solve this I had to part away from the FancyArrowPatch suite and create something from scratch. Here is a working solution that, far from fulfilling any perfectionist spirit, satisfied me:
import matplotlib.pyplot as plt
import numpy as np
from numpy.random import seed, randint
# Build function that connects two points with a curved line,
# and an arrow on the middle of it
seed(1679)
narrow = 3
rad_one = 50
numpoints = 3
random_points = list(randint(1,20,[numpoints,4]))
rpoints = [[(a,b),(c,d)] for a,b,c,d in random_points]
def curvline(start,end,rad,t=100,arrows=1,push=0.8):
#Compute midpoint
rad = rad/100.
x1, y1 = start
x2, y2 = end
y12 = (y1 + y2) / 2
dy = (y2 - y1)
cy = y12 + (rad) * dy
#Prepare line
tau = np.linspace(0,1,t)
xsupport = np.linspace(x1,x2,t)
ysupport = [(1-i)**2 * y1 + 2*(1-i)*i*cy + (i**2)*y2 for i in tau]
#Create arrow data
arset = list(np.linspace(0,1,arrows+2))
c = zip([xsupport[int(t*a*push)] for a in arset[1:-1]],
[ysupport[int(t*a*push)] for a in arset[1:-1]])
dt = zip([xsupport[int(t*a*push)+1]-xsupport[int(t*a*push)] for a in arset[1:-1]],
[ysupport[int(t*a*push)+1]-ysupport[int(t*a*push)] for a in arset[1:-1]])
arrowpath = zip(c,dt)
return xsupport, ysupport, arrowpath
def plotcurv(start,end,rad,t=100,arrows=1,arwidth=.25):
x, y, c = curvline(start,end,rad,t,arrows)
plt.plot(x,y,'k-')
for d,dt in c:
plt.arrow(d[0],d[1],dt[0],dt[1], shape='full', lw=0,
length_includes_head=False, head_width=arwidth)
return c
#Create figure
figure = plt.figure()
ax = plt.subplot(111)
for n1,n2 in rpoints:
#First line
plotcurv(n1,n2,rad_one,200,narrow,0.5)
#Second line
plotcurv(n2,n1,rad_one,200,narrow,0.5)
ax.set_xlim(0,20)
ax.set_ylim(0,20)
plt.show
I have tested it with three random couple of points, plotting back and forth lines. Which gives the figure below:
The function allows for the user to set a number of desired arrow-heads, and it places them evenly on the plotted Bezier, making sure the appropriate direction is represented. However, because the Bezier curve is not exactly an 'arc', I heuristically push the start of the arrow-heads to make them look more centered. Any improvement to this solution will be greatly appreciated.

Related

Plotting arrows perpendicular to coordinates

I have a plot like this, plotting a semicircle with x and y
I want to add arrows at each point like so (ignore the horrible paint job):
Is there an easy way to add arrows perpendicular to the plot?
Current code:
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
x = np.linspace(x0,x1,9)
y = k + np.sqrt(r**2 - (x-h)**2)
plt.scatter(x,y)
plt.xlim(-4,4)
plt.ylim(-4,4)
PERPENDICULAR TO THE TANGENT OF THE CURVE I'M SORRY I FORGOT TO ADD THIS
A point in space has no idea what "perpendicular" means, but assuming your y is some function of x that has a derivate, you can think of the derivate of the function at some point to be the tangent of the curve at that point, and to get a perpendicular vector you just need to rotate the vector counter-clockwise 90 degrees:
x1, y1 = -y0, x0
We know that these points come from a circle. So given three points we can easily find the center using basic geometry notions. If you need a refresher, take a look here.
For this particular case, the center is at the origin. Knowing the center coordinates, the normal at each point is just the vector from the center to the point itself. Since the center is the origin, the normals' components are just given by the coordinates of the points themselves.
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
x = np.linspace(x0, x1, 9)
y = k + np.sqrt(r**2 - (x-h)**2)
center = np.array([0.0, 0.0])
plt.scatter(x, y)
plt.quiver(x, y, x, y, width=0.005)
plt.xlim(-4, 4)
plt.ylim(-4, 4)
plt.show()
If you are in a hurry and you do not have time to implement equations, you could use the scikit-spatial library in the following way:
from skspatial.objects import Circle, Vector, Points
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
x = np.linspace(x0, x1, 9)
y = k + np.sqrt(r**2 - (x-h)**2)
points = Points(np.vstack((x, y)).T)
circle = Circle.best_fit(np.vstack((x, y)).T)
center = circle.point
normals = np.array([Vector.from_points(center, point) for point in points])
plt.scatter(x, y)
plt.quiver(x, y, normals[:, 0], normals[:, 1], width=0.005)
plt.xlim(-4, 4)
plt.ylim(-4, 4)
plt.show()
Postulate of blunova's and simon's answers is correct, generally speaking: points have no normal, but curve have; so you need to rely on what you know your curve is. Either, as blunova described it, by the knowledge that it is a circle, and computing those normal with ad-hoc computation from that knowledge.
Or, as I am about to describe, using the function f such as y=f(x). and using knowledge on what is the normal to such a (x,f(x)) chart.
Here is your code, written with such a function f
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
x = np.linspace(x0,x1,9)
def f(x):
return k + np.sqrt(r**2 - (x-h)**2)
y=f(x)
plt.scatter(x,y)
plt.xlim(-4,4)
plt.ylim(-4,4)
So, all I did here is rewriting your line y=... in the form of a function.
From there, it is possible to compute the normal to each point of the chart (x,f(x)).
The tangent to a point (x,f(x)) is well known: it is vector (1,f'(x)), where f'(x) is the derivative of f. So, normal to that is (-f'(x), 1).
Divided by √(f'(x)²+1) to normalize this vector.
So, just use that as entry to quiver.
First compute a derivative of your function
dx=0.001
def fprime(x):
return (f(x+dx)-f(x-dx))/(2*dx)
Then, just
plt.quiver(x, f(x), -fprime(x), 1)
Or, to have all vector normalized
plt.quiver(x, f(x), -fprime(x)/np.sqrt(fprime(x)**2+1), 1/np.sqrt(fprime(x)**2+1))
(note that fprime and the normalization part are all vectorizable operation, so it works with x being a arange)
All together
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
def f(x):
return k+ np.sqrt(r**2 - (x-h)**2)
dx=0.001
x = np.linspace(x0+dx,x1-dx,9)
y = f(x)
def fprime(x):
return (f(x+dx)-f(x-dx))/(2*dx)
plt.scatter(x,y)
plt.quiver(x,f(x), -fprime(x)/np.sqrt(fprime(x)**2+1), 1/np.sqrt(fprime(x)**2+1))
plt.xlim(-4,4)
plt.ylim(-4,4)
plt.show()
That is almost an exact copy of your code, but for the quiver line, and with the addition of fprime.
One other slight change, specific to your curve, is that I changed x range to ensure the computability of fprime (if first x is x0, then fprime need f(x0-dx) which does not exist because of sqrt. Likewise for x1. So, first x is x0+dx, and last is x1-dx, which is visually the same)
That is the main advantage of this solution over blunova's: it is your code, essentially. And would work if you change f, without assuming that f is a circle. All that is assume is that f is derivable (and if it were not, you could not define what those normal are anyway).
For example, if you want to do the same with a parabola instead, just change f
import numpy as np
import matplotlib.pyplot as plt
r = 2
h = 0
k = 0
x0 = h-r
x1 = h+r
def f(x):
return x**2
dx=0.001
x = np.linspace(x0+dx,x1-dx,9)
y = f(x)
def fprime(x):
return (f(x+dx)-f(x-dx))/(2*dx)
plt.scatter(x,y)
plt.quiver(x,f(x), -fprime(x)/np.sqrt(fprime(x)**2+1), 1/np.sqrt(fprime(x)**2+1))
plt.xlim(-4,4)
plt.ylim(-2,5)
plt.show()
All I changed here is the f formula. Not need for a new reasoning to compute the normal.
Last remark: an even more accurate version (not forcing the approximate computation of fprime with a dx) would be to use sympy to define f, and then compute the real, symbolic, derivative of f. But that doesn't seem necessary for your case.

How can I create a Circle with a small notch at bottom in Matplotlib?

I am trying to create a circle on co-ordinate plane and fill the pixels in it with conditional colors. However, the circle also need to show a small triangular notch at the bottom from the center. Something like the attached picture.
I have used the matplotlib's patches class to create acircle and tried different values in attributes but of no help. I googled enough but I couldn't find it.
circle = matplotlib.patches.Circle((0,0),150,facecolor='lightgrey')
ax.add_patch(circle)
Sample
Can someone please help me or provide me a hint or direct me to right library which can do this.
Your problem looks like 2D vector graphics which in this case you should look for SVG
if it is a CAD problem then you can check some CAD libraries
- Python module for parametric CAD
I think CAD approach is much better for engineering operations which you can find https://www.freecadweb.org/wiki/Part_Slice and https://www.freecadweb.org/wiki/Part_SliceApart#Scripting
or for SVG approach found something: https://inkscape.org/~Moini/%E2%98%85multi-bool-extension-cut-difference-division
If you have to do that in mathplotlib then you can export/import vector graphics into plots
I tried to build the proposed shape using parametric equations.
There are some approximations, but if you get the exact geometry equations you could refine this to a better version.
In fact, the key is to define the correct equations ... the ideal case would be to ensure continuity.
This example has some caveats: approximations when joining the 2 circles due to geometrical simplification when drawing the small circle from pi to 0. Small circle start/end angles should be chosen as the interception of the 2 full circles for a more accurate shape continuity. But again, in the end it depends entirely on your shape specification
Heavly inspired from Plot equation showing a circle
import math
import numpy as np
import matplotlib.pyplot as plt
def compute_x_values(r, theta):
return r * np.cos(theta)
def compute_y_values(r, theta):
return r * np.sin(theta)
def compute_circle(r, theta):
return compute_x_values(r, theta), compute_y_values(r, theta)
def build_big_circle(crop_angle, offset, radius):
start_angle = offset + crop_angle
end_angle = offset + (2 * np.pi) - crop_angle
theta = np.linspace(start_angle, end_angle, 250)
# compute main circle vals
x, y = compute_circle(radius, theta)
return x, y
r = 1
offset = - np.pi / 2
crop_angle = np.pi / 20
x, y = build_big_circle(crop_angle, offset, r)
# now the other form:
# its a half circle from pi to 0
theta2 = np.linspace(np.pi, 0, 100)
# according our code above, angular space left on the circle for the notch is
missing_angle = crop_angle * 2
# the length between to points on a circle is given by the formula
# length = 2 * r * sin(angle/2)
l = math.sin(missing_angle / 2) * r * 2
# we want half the length for a future radius
r2 = l / 2
# the above lines could be optimized to this
# r2 = math.sin(crop_angle) * r
# but I kept intermediate steps for sake of geometric clarity
# equation is same of a circle
x1, y1 = compute_circle(r2, theta2)
# change center on y axis to - big circle radius
y1 = y1 - r
# merge the 2
x_total = np.append(x, x1)
y_total = np.append(y, y1)
# create the global figure
fig, ax = plt.subplots(1)
ax.plot(x_total, y_total)
ax.fill(x_total, y_total, facecolor='lightgrey', linewidth=1)
ax.set_aspect(1)
plt.show()
fig, ax = plt.subplots(1)
ax.plot(x, y)
ax.plot(x1, y1)
ax.set_aspect(1)
plt.show()

Implementing initial conditions for a numerically solved differential equation

Imagine someone jumping off a balcony under a certain angle theta and velocity v0 (the height of the balcony is denoted as ystar). Looking at this problem in 2D and considering drag you get a system of differential equations which can be solved with a Runge-Kutta method (I choose explicit-midpoint, not sure what the butcher tableu for this one is). I implemented this and it works perfectly fine, for some given initial conditions I get the trajectory of the moving particle.
My problem is that I want to fix two of the initial conditions (starting point on the x-axis is zero and on the y-axis is ystar) and make sure that the trajectory goes trough a certain point on the x-axis (let's call it xstar). For this of course exist multiple combinations of the other two initial conditions, which in this case are the velocities in the x- and y-direction. The problem is that I don't know how to implement that.
The code that I used to solve the problem up to this point:
1) Implementation of the Runge-Kutta method
import numpy as np
import matplotlib.pyplot as plt
def integrate(methode_step, rhs, y0, T, N):
star = (int(N+1),y0.size)
y= np.empty(star)
t0, dt = 0, 1.* T/N
y[0,...] = y0
for i in range(0,int(N)):
y[i+1,...]=methode_step(rhs,y[i,...], t0+i*dt, dt)
t = np.arange(N+1) * dt
return t,y
def explicit_midpoint_step(rhs, y0, t0, dt):
return y0 + dt * rhs(t0+0.5*dt,y0+0.5*dt*rhs(t0,y0))
def explicit_midpoint(rhs,y0,T,N):
return integrate(explicit_midpoint_step,rhs,y0,T,N)
2) Implementation of the right-hand-side of the differential equation and the nessecery parameters
A = 1.9/2.
cw = 0.78
rho = 1.293
g = 9.81
# Mass and referece length
l = 1.95
m = 118
# Position
xstar = 8*l
ystar = 4*l
def rhs(t,y):
lam = cw * A * rho /(2 * m)
return np.array([y[1],-lam*y[1]*np.sqrt(y[1]**2+y[3]**2),y[3],-lam*y[3]*np.sqrt(y[1]**2+y[3]**2)-g])
3) solving the problem with it
# Parametrize the two dimensional velocity with an angle theta and speed v0
v0 = 30
theta = np.pi/6
v0x = v0 * np.cos(theta)
v0y = v0 * np.sin(theta)
# Initial condintions
z0 = np.array([0, v0x, ystar, v0y])
# Calculate solution
t, z = explicit_midpoint(rhs, z0, 5, 1000)
4) Visualization
plt.figure()
plt.plot(0,ystar,"ro")
plt.plot(x,0,"ro")
plt.plot(z[:,0],z[:,1])
plt.grid(True)
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
plt.show()
To make the question concrete: With this set up in mind, how do I find all possible combinations of v0 and theta such that z[some_element,0]==xstar
I tried of course some things, mainly the brute force method of fixing theta and then trying out all the possible velocities (in an intervall that makes sense) but finally didn't know how to compare the resulting arrays with the desired result...
Since this is mainly a coding issue I hope stack overflow is the right place to ask for help...
EDIT:
As requested here is my try to solve the problem (replacing 3) and 4) from above)..
theta = np.pi/4.
xy = np.zeros((50,1001,2))
z1 = np.zeros((1001,2))
count=0
for v0 in range(0,50):
v0x = v0 * np.cos(theta)
v0y = v0 * np.sin(theta)
z0 = np.array([0, v0x, ystar, v0y])
# Calculate solution
t, z = explicit_midpoint(rhs, z0, 5, 1000)
if np.around(z[:,0],3).any() == round(xstar,3):
z1[:,0] = z[:,0]
z1[:,1] = z[:,2]
break
else:
xy[count,:,0] = z[:,0]
xy[count,:,1] = z[:,2]
count+=1
plt.figure()
plt.plot(0,ystar,"ro")
plt.plot(xstar,0,"ro")
for k in range(0,50):
plt.plot(xy[k,:,0],xy[k,:,1])
plt.plot(z[:,0],z[:,1])
plt.grid(True)
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
plt.show()
I'm sure that I'm using the .any() function wrong, the idea there is to round the values of z[:,0] to three digits and than compare them to xstar, if it matches the loop should terminate and retrun the current z, if not it should save it in another array and then increase v0.
Edit 2018-07-16
Here I post a corrected answer taking into account the drag by air.
Below is a python script to compute the set of (v0,theta) values so that the air-dragged trajectory passes through (x,y) = (xstar,0) at some time t=tstar. I used the trajectory without air-drag as the initial guess and also to guess the dependence of x(tstar) on v0 for the first refinement. The number of iterations needed to arrive at the correct v0 was typically 3 to 4. The script finished in 0.99 seconds on my laptop, including the time for generating figures.
The script generates two figures and one text file.
fig_xdrop_v0_theta.png
The black dots indicates the solution set (v0,theta)
The yellow line indicates the reference (v0,theta) which would be a solution if there were no air drag.
fig_traj_sample.png
Checking that the trajectory (blue solid line) passes through (x,y)=(xstar,0) when (v0,theta) is sampled from the solution set.
The black dashed line shows a trajectory without drag by air as a reference.
output.dat
contains the numerical data of (v0,theta) as well as the landing time tstar and number of iteration needed to find v0.
Here begins script.
#!/usr/bin/env python3
import numpy as np
import scipy.integrate
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.image as img
mpl.rcParams['lines.linewidth'] = 2
mpl.rcParams['lines.markeredgewidth'] = 1.0
mpl.rcParams['axes.formatter.limits'] = (-4,4)
#mpl.rcParams['axes.formatter.limits'] = (-2,2)
mpl.rcParams['axes.labelsize'] = 'large'
mpl.rcParams['xtick.labelsize'] = 'large'
mpl.rcParams['ytick.labelsize'] = 'large'
mpl.rcParams['xtick.direction'] = 'out'
mpl.rcParams['ytick.direction'] = 'out'
############################################
len_ref = 1.95
xstar = 8.0*len_ref
ystar = 4.0*len_ref
g_earth = 9.81
#
mass = 118
area = 1.9/2.
cw = 0.78
rho = 1.293
lam = cw * area * rho /(2.0 * mass)
############################################
ngtheta=51
theta_min = -0.1*np.pi
theta_max = 0.4*np.pi
theta_grid = np.linspace(theta_min, theta_max, ngtheta)
#
ngv0=100
v0min =6.0
v0max =18.0
v0_grid=np.linspace(v0min, v0max, ngv0)
# .. this grid is used for the initial coarse scan by reference trajecotry
############################################
outf=open('output.dat','w')
print('data file generated: output.dat')
###########################################
def calc_tstar_ref_and_x_ref_at_tstar_ref(v0, theta, ystar, g_earth):
'''return the drop time t* and drop point x(t*) of a reference trajectory
without air drag.
'''
vx = v0*np.cos(theta)
vy = v0*np.sin(theta)
ts_ref = (vy+np.sqrt(vy**2+2.0*g_earth*ystar))/g_earth
x_ref = vx*ts_ref
return (ts_ref, x_ref)
def rhs_drag(yvec, time, g_eath, lamb):
'''
dx/dt = v_x
dy/dt = v_y
du_x/dt = -lambda v_x sqrt(u_x^2 + u_y^2)
du_y/dt = -lambda v_y sqrt(u_x^2 + u_y^2) -g
yvec[0] .. x
yvec[1] .. y
yvec[2] .. v_x
yvec[3] .. v_y
'''
vnorm = (yvec[2]**2+yvec[3]**2)**0.5
return [ yvec[2], yvec[3], -lamb*yvec[2]*vnorm, -lamb*yvec[3]*vnorm -g_earth]
def try_tstar_drag(v0, theta, ystar, g_earth, lamb, tstar_search_grid):
'''one trial run to find the drop point x(t*), y(t*) of a trajectory
under the air drag.
'''
tinit=0.0
tgrid = [tinit]+list(tstar_search_grid)
yvec_list = scipy.integrate.odeint(rhs_drag,
[0.0, ystar, v0*np.cos(theta), v0*np.sin(theta)],
tgrid, args=(g_earth, lam))
y_drag = [yvec[1] for yvec in yvec_list]
x_drag = [yvec[0] for yvec in yvec_list]
if y_drag[0]<0.0:
ierr=-1
jtstar=0
tstar_braket=None
elif y_drag[-1]>0.0:
ierr=1
jtstar=len(y_drag)-1
tstar_braket=None
else:
ierr=0
for jt in range(len(y_drag)-1):
if y_drag[jt+1]*y_drag[jt]<=0.0:
tstar_braket=[tgrid[jt],tgrid[jt+1]]
if abs(y_drag[jt+1])<abs(y_drag[jt]):
jtstar = jt+1
else:
jtstar = jt
break
tstar_est = tgrid[jtstar]
x_drag_at_tstar_est = x_drag[jtstar]
y_drag_at_tstar_est = y_drag[jtstar]
return (tstar_est, x_drag_at_tstar_est, y_drag_at_tstar_est, ierr, tstar_braket)
def calc_x_drag_at_tstar(v0, theta, ystar, g_earth, lamb, tstar_est,
eps_y=1.0e-3, ngt_search=20,
rel_range_lower=0.8, rel_range_upper=1.2,
num_try=5):
'''compute the dop point x(t*) of a trajectory under the air drag.
'''
flg_success=False
tstar_est_lower=tstar_est*rel_range_lower
tstar_est_upper=tstar_est*rel_range_upper
for jtry in range(num_try):
tstar_search_grid = np.linspace(tstar_est_lower, tstar_est_upper, ngt_search)
tstar_est, x_drag_at_tstar_est, y_drag_at_tstar_est, ierr, tstar_braket \
= try_tstar_drag(v0, theta, ystar, g_earth, lamb, tstar_search_grid)
if ierr==-1:
tstar_est_upper = tstar_est_lower
tstar_est_lower = tstar_est_lower*rel_range_lower
elif ierr==1:
tstar_est_lower = tstar_est_upper
tstar_est_upper = tstar_est_upper*rel_range_upper
else:
if abs(y_drag_at_tstar_est)<eps_y:
flg_success=True
break
else:
tstar_est_lower=tstar_braket[0]
tstar_est_upper=tstar_braket[1]
return (tstar_est, x_drag_at_tstar_est, y_drag_at_tstar_est, flg_success)
def find_v0(xstar, v0_est, theta, ystar, g_earth, lamb, tstar_est,
eps_x=1.0e-3, num_try=6):
'''solve for v0 so that x(t*)==x*.
'''
flg_success=False
v0_hist=[]
x_drag_at_tstar_hist=[]
jtry_end=None
for jtry in range(num_try):
tstar_est, x_drag_at_tstar_est, y_drag_at_tstar_est, flg_success_x_drag \
= calc_x_drag_at_tstar(v0_est, theta, ystar, g_earth, lamb, tstar_est)
v0_hist.append(v0_est)
x_drag_at_tstar_hist.append(x_drag_at_tstar_est)
if not flg_success_x_drag:
break
elif abs(x_drag_at_tstar_est-xstar)<eps_x:
flg_success=True
jtry_end=jtry
break
else:
# adjust v0
# better if tstar_est is also adjusted, but maybe that is too much.
if len(v0_hist)<2:
# This is the first run. Use the analytical expression of
# dx(tstar)/dv0 of the refernece trajectory
dx = xstar - x_drag_at_tstar_est
dv0 = dx/(tstar_est*np.cos(theta))
v0_est += dv0
else:
# use linear interpolation
v0_est = v0_hist[-2] \
+ (v0_hist[-1]-v0_hist[-2]) \
*(xstar -x_drag_at_tstar_hist[-2])\
/(x_drag_at_tstar_hist[-1]-x_drag_at_tstar_hist[-2])
return (v0_est, tstar_est, flg_success, jtry_end)
# make a reference table of t* and x(t*) of a trajectory without air drag
# as a function of v0 and theta.
tstar_ref=np.empty((ngtheta,ngv0))
xdrop_ref=np.empty((ngtheta,ngv0))
for j1 in range(ngtheta):
for j2 in range(ngv0):
tt, xx = calc_tstar_ref_and_x_ref_at_tstar_ref(v0_grid[j2], theta_grid[j1], ystar, g_earth)
tstar_ref[j1,j2] = tt
xdrop_ref[j1,j2] = xx
# make an estimate of v0 and t* of a dragged trajectory for each theta
# based on the reference trajectroy's landing position xdrop_ref.
tstar_est=np.empty((ngtheta,))
v0_est=np.empty((ngtheta,))
v0_est[:]=-1.0
# .. null value
for j1 in range(ngtheta):
for j2 in range(ngv0-1):
if (xdrop_ref[j1,j2+1]-xstar)*(xdrop_ref[j1,j2]-xstar)<=0.0:
tstar_est[j1] = tstar_ref[j1,j2]
# .. lazy
v0_est[j1] \
= v0_grid[j2] \
+ (v0_grid[j2+1]-v0_grid[j2])\
*(xstar-xdrop_ref[j1,j2])/(xdrop_ref[j1,j2+1]-xdrop_ref[j1,j2])
# .. linear interpolation
break
print('compute v0 for each theta under air drag..')
# compute v0 for each theta under air drag
theta_sol_list=[]
tstar_sol_list=[]
v0_sol_list=[]
outf.write('# theta v0 tstar numiter_v0\n')
for j1 in range(ngtheta):
if v0_est[j1]>0.0:
v0, tstar, flg_success, jtry_end \
= find_v0(xstar, v0_est[j1], theta_grid[j1], ystar, g_earth, lam, tstar_est[j1])
if flg_success:
theta_sol_list.append(theta_grid[j1])
v0_sol_list.append(v0)
tstar_sol_list.append(tstar)
outf.write('%26.16e %26.16e %26.16e %10i\n'
%(theta_grid[j1], v0, tstar, jtry_end+1))
theta_sol = np.array(theta_sol_list)
v0_sol = np.array(v0_sol_list)
tstar_sol = np.array(tstar_sol_list)
### Check a sample
jsample=np.size(v0_sol)//3
theta_sol_sample= theta_sol[jsample]
v0_sol_sample = v0_sol[jsample]
tstar_sol_sample= tstar_sol[jsample]
ngt_chk = 50
tgrid = np.linspace(0.0, tstar_sol_sample, ngt_chk)
yvec_list = scipy.integrate.odeint(rhs_drag,
[0.0, ystar,
v0_sol_sample*np.cos(theta_sol_sample),
v0_sol_sample*np.sin(theta_sol_sample)],
tgrid, args=(g_earth, lam))
y_drag_sol_sample = [yvec[1] for yvec in yvec_list]
x_drag_sol_sample = [yvec[0] for yvec in yvec_list]
# compute also the trajectory without drag starting form the same initial
# condiiton by setting lambda=0.
yvec_list = scipy.integrate.odeint(rhs_drag,
[0.0, ystar,
v0_sol_sample*np.cos(theta_sol_sample),
v0_sol_sample*np.sin(theta_sol_sample)],
tgrid, args=(g_earth, 0.0))
y_ref_sample = [yvec[1] for yvec in yvec_list]
x_ref_sample = [yvec[0] for yvec in yvec_list]
#######################################################################
# canvas setting
#######################################################################
f_size = (8,5)
#
a1_left = 0.15
a1_bottom = 0.15
a1_width = 0.65
a1_height = 0.80
#
hspace=0.02
#
ac_left = a1_left+a1_width+hspace
ac_bottom = a1_bottom
ac_width = 0.03
ac_height = a1_height
###########################################
############################################
# plot
############################################
#------------------------------------------------
print('plotting the solution..')
fig1=plt.figure(figsize=f_size)
ax1 =plt.axes([a1_left, a1_bottom, a1_width, a1_height], axisbg='w')
im1=img.NonUniformImage(ax1,
interpolation='bilinear', \
cmap=mpl.cm.Blues, \
norm=mpl.colors.Normalize(vmin=0.0,
vmax=np.max(xdrop_ref), clip=True))
im1.set_data(v0_grid, theta_grid/np.pi, xdrop_ref )
ax1.images.append(im1)
plt.contour(v0_grid, theta_grid/np.pi, xdrop_ref, [xstar], colors='y')
plt.plot(v0_sol, theta_sol/np.pi, 'ok', lw=4, label='Init Cond with Drag')
plt.legend(loc='lower left')
plt.xlabel(r'Initial Velocity $v_0$', fontsize=18)
plt.ylabel(r'Angle of Projection $\theta/\pi$', fontsize=18)
plt.yticks([-0.50, -0.25, 0.0, 0.25, 0.50])
ax1.set_xlim([v0min, v0max])
ax1.set_ylim([theta_min/np.pi, theta_max/np.pi])
axc =plt.axes([ac_left, ac_bottom, ac_width, ac_height], axisbg='w')
mpl.colorbar.Colorbar(axc,im1)
axc.set_ylabel('Distance from Blacony without Drag')
# 'Distance from Blacony $x(t^*)$'
plt.savefig('fig_xdrop_v0_theta.png')
print('figure file genereated: fig_xdrop_v0_theta.png')
plt.close()
#------------------------------------------------
print('plotting a sample trajectory..')
fig1=plt.figure(figsize=f_size)
ax1 =plt.axes([a1_left, a1_bottom, a1_width, a1_height], axisbg='w')
plt.plot(x_drag_sol_sample, y_drag_sol_sample, '-b', lw=2, label='with drag')
plt.plot(x_ref_sample, y_ref_sample, '--k', lw=2, label='without drag')
plt.axvline(x=xstar, color=[0.3, 0.3, 0.3], lw=1.0)
plt.axhline(y=0.0, color=[0.3, 0.3, 0.3], lw=1.0)
plt.legend()
plt.text(0.1*xstar, 0.6*ystar,
r'$v_0=%5.2f$'%(v0_sol_sample)+'\n'+r'$\theta=%5.2f \pi$'%(theta_sol_sample/np.pi),
fontsize=18)
plt.text(xstar, 0.5*ystar, 'xstar', fontsize=18)
plt.xlabel(r'Horizontal Distance $x$', fontsize=18)
plt.ylabel(r'Height $y$', fontsize=18)
ax1.set_xlim([0.0, 1.5*xstar])
ax1.set_ylim([-0.1*ystar, 1.5*ystar])
plt.savefig('fig_traj_sample.png')
print('figure file genereated: fig_traj_sample.png')
plt.close()
outf.close()
Here is the figure fig_xdrop_v0_theta.png.
Here is the figure fig_traj_sample.png.
Edit 2018-07-15
I realized that I overlooked that the question considers the drag by air. What a shame on me. So, my answer below is not correct. I'm afraid that deleting my answer by myself looks like hiding a mistake, and I leave it below for now. If people think it's annoying that an incorrect answer hanging around, I'm O.K. someone delete it.
The differential equation can actually be solved by hand,
and it does not require much computational resource
to map out how far the person reach from the balcony
on the ground as a function of the initial velocity v0 and the
angle theta. Then, you can select the condition (v0,theta)
such that distance_from_balcony_on_the_ground(v0,theta) = xstar
from this data table.
Let's write the horizontal and vertical coordinates of the
person at time t is x(t) and y(t), respectively.
I think you took x=0 at the wall of the building and y=0
as the ground level, and I do so here, too. Let's say the
horizontal and vertical velocity of the person at time t
are v_x(t) and v_y(t), respectively.
The initial conditions at t=0 are given as
x(0) = 0
y(0) = ystar
v_x(0) = v0 cos theta
v_y(0) = v0 sin theta
The Newton eqution you are solving is,
dx/dt = v_x .. (1)
dy/dt = v_y .. (2)
m d v_x /dt = 0 .. (3)
m d v_y /dt = -m g .. (4)
where m is the mass of the person,
and g is the constant which I don't know the English name of,
but we all know what it is.
From eq. (3),
v_x(t) = v_x(0) = v0 cos theta.
Using this with eq. (1),
x(t) = x(0) + \int_0^t dt' v_x(t') = t v0 cos theta,
where we also used the initial condition. \int_0^t means
integral from 0 to t.
From eq. (4),
v_y(t)
= v_y (0) + \int_0^t dt' (-g)
= v0 sin theta -g t,
where we used the initial condition.
Using this with eq. (3) and also using the initial condition,
y(t)
= y(0) + \int_0^t dt' v_y(t')
= ystar + t v0 sin theta -t^2 (g/2).
where t^2 means t squared.
From the expression for y(t), we can get the time tstar
at which the person hits the ground. That is, y(tstar) =0.
This equation can be solved by quadratic formula
(or something similar name) as
tstar = (v0 sin theta + sqrt((v0 sin theta)^2 + 2g ystar)/g,
where I used a condition tstar>0. Now we know
the distance from the balcony the person reached when he hit
the ground as x(tstar). Using the expression for x(t) above,
x(tstar) = (v0 cos theta) (v0 sin theta + sqrt((v0 sin theta)^2 + 2g ystar))/g.
.. (5)
Actually x(tstar) depends on v0 and theta as well as g and ystar.
You hold g and ystar as constants, and you want to find
all (v0,theta) such that x(tstar) = xstar for a given xstar value.
Since the right hand side of eq. (5) can be computed cheaply,
you can set up grids for v0 and theta and compute xstar
on this 2D grid. Then, you can see where roughly is the solution set
of (v0,theta) lies. If you need precise solution, you can pick up
a segment which encloses the solution from this data table.
Below is a python script that demonstrates this idea.
I also attach here a figure generated by this script.
The yellow curve is the solution set (v0,theta) such that the
person hit the ground at xstar from the wall
when xstar = 8.0*1.95 and ystar=4.0*1.95 as you set.
The blue color coordinate indicates x(tstar), i.e., how far the
person jumped from the balcony horizontally.
Note that at a given v0 (higher than a threshold value aruond v0=9.9),
the there are two theta values (two directions for the person
to project himself) to reach the aimed point (x,y) = (xstar,0).
The smaller branch of the theta value can be negative, meaning that the person can jump downward to reach the aimed point, as long as the initial velocity is sufficiently high.
The script also generates a data file output.dat, which has
the solution-enclosing segments.
#!/usr/bin/python3
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.image as img
mpl.rcParams['lines.linewidth'] = 2
mpl.rcParams['lines.markeredgewidth'] = 1.0
mpl.rcParams['axes.formatter.limits'] = (-4,4)
#mpl.rcParams['axes.formatter.limits'] = (-2,2)
mpl.rcParams['axes.labelsize'] = 'large'
mpl.rcParams['xtick.labelsize'] = 'large'
mpl.rcParams['ytick.labelsize'] = 'large'
mpl.rcParams['xtick.direction'] = 'out'
mpl.rcParams['ytick.direction'] = 'out'
############################################
len_ref = 1.95
xstar = 8.0*len_ref
ystar = 4.0*len_ref
g_earth = 9.81
############################################
ngv0=100
v0min =0.0
v0max =20.0
v0_grid=np.linspace(v0min, v0max, ngv0)
############################################
outf=open('output.dat','w')
print('data file generated: output.dat')
###########################################
def x_at_tstar(v0, theta, ystar, g_earth):
vx = v0*np.cos(theta)
vy = v0*np.sin(theta)
return (vy+np.sqrt(vy**2+2.0*g_earth*ystar))*vx/g_earth
ngtheta=100
theta_min = -0.5*np.pi
theta_max = 0.5*np.pi
theta_grid = np.linspace(theta_min, theta_max, ngtheta)
xdrop=np.empty((ngv0,ngtheta))
# x(t*) as a function of v0 and theta.
for j1 in range(ngv0):
for j2 in range(ngtheta):
xdrop[j1,j2] = x_at_tstar(v0_grid[j1], theta_grid[j2], ystar, g_earth)
outf.write('# domain [theta_lower, theta_upper] that encloses the solution\n')
outf.write('# theta such that x_at_tstart(v0,theta, ystart, g_earth)=xstar\n')
outf.write('# v0 theta_lower theta_upper x_lower x_upper\n')
for j1 in range(ngv0):
for j2 in range(ngtheta-1):
if (xdrop[j1,j2+1]-xstar)*(xdrop[j1,j2]-xstar)<=0.0:
outf.write('%26.16e %26.16e %26.16e %26.16e %26.16e\n'
%(v0_grid[j1], theta_grid[j2], theta_grid[j2+1],
xdrop[j1,j2], xdrop[j1,j2+1]))
print('See output.dat for the segments enclosing solutions.')
print('You can hunt further for precise solutions using this data.')
#######################################################################
# canvas setting
#######################################################################
f_size = (8,5)
#
a1_left = 0.15
a1_bottom = 0.15
a1_width = 0.65
a1_height = 0.80
#
hspace=0.02
#
ac_left = a1_left+a1_width+hspace
ac_bottom = a1_bottom
ac_width = 0.03
ac_height = a1_height
###########################################
############################################
# plot
############################################
print('plotting..')
fig1=plt.figure(figsize=f_size)
ax1 =plt.axes([a1_left, a1_bottom, a1_width, a1_height], axisbg='w')
im1=img.NonUniformImage(ax1,
interpolation='bilinear', \
cmap=mpl.cm.Blues, \
norm=mpl.colors.Normalize(vmin=0.0,
vmax=np.max(xdrop), clip=True))
im1.set_data(v0_grid, theta_grid/np.pi, np.transpose(xdrop))
ax1.images.append(im1)
plt.contour(v0_grid, theta_grid/np.pi, np.transpose(xdrop), [xstar], colors='y')
plt.xlabel(r'Initial Velocity $v_0$', fontsize=18)
plt.ylabel(r'Angle of Projection $\theta/\pi$', fontsize=18)
plt.yticks([-0.50, -0.25, 0.0, 0.25, 0.50])
ax1.set_xlim([v0min, v0max])
ax1.set_ylim([theta_min/np.pi, theta_max/np.pi])
axc =plt.axes([ac_left, ac_bottom, ac_width, ac_height], axisbg='w')
mpl.colorbar.Colorbar(axc,im1)
# 'Distance from Blacony $x(t^*)$'
plt.savefig('fig_xdrop_v0_theta.png')
print('figure file genereated: fig_xdrop_v0_theta.png')
plt.close()
outf.close()
So after some trying out I found a way to achieve what I wanted... It is the brute force method that I mentioned in my starting post, but at least now it works...
The idea is quite simple: define a function find_v0 which finds for a given theta a v0. In this function you take a starting value for v0 (I choose 8 but this was just a guess from me), then take the starting value and check with the difference function how far away the interesting point is from (xstar,0). The interesting point in this case can be determined by setting all points on the x-axis that are bigger than xstar to zero (and their corresponding y-values) and then trimming of all the zeros with trim_zeros, now the last element of correspond to the desired output. If the output of the difference function is smaller than a critical value (in my case 0.1) pass the current v0 on, if not, increase it by 0.01 and do the same thing again.
The code for this looks like this (again replacing 3) and 4) ):
th = np.linspace(0,np.pi/3,100)
def find_v0(theta):
v0=8
while(True):
v0x = v0 * np.cos(theta)
v0y = v0 * np.sin(theta)
z0 = np.array([0, v0x, ystar, v0y])
# Calculate solution
t, z = explicit_midpoint(rhs, z0, 5, 1000)
for k in range(1001):
if z[k,0] > xstar:
z[k,0] = 0
z[k,2] = 0
x = np.trim_zeros(z[:,0])
y = np.trim_zeros(z[:,2])
diff = difference(x[-1],y[-1])
if diff < 0.1:
break
else: v0+=0.01
return v0#,x,y[0:]
v0 = np.zeros_like(th)
from tqdm import tqdm
count=0
for k in tqdm(th):
v0[count] = find_v0(k)
count+=1
v0_interp = interpolate.interp1d(th,v0)
plt.figure()
plt.plot(th,v0_interp(th),"g")
plt.grid(True)
plt.xlabel(r"$\theta$")
plt.ylabel(r"$v_0$")
plt.show()
The problem with this thing is that it takes forever to compute (with the current settings around 5-6 mins). If anyone has some hints how to improve the code to get a little bit faster or has a different approach it would be still appreciated.
Assuming that the velocity in x direction never goes down to zero, you can take x as independent parameter instead of the time. The state vector is then time, position, velocity and the vector field in this state space is scaled so that the vx component is always 1. Then integrate from zero to xstar to compute the state (approximation) where the trajectory meets xstar as x-value.
def derivs(u,x):
t,x,y,vx,vy = u
v = hypot(vx,vy)
ax = -lam*v*vx
ay = -lam*v*vy - g
return [ 1/vx, 1, vy/vx, ax/vx, ay/vx ]
odeint(derivs, [0, x0, y0, vx0, vy0], [0, xstar])
or with your own integration method. I used odeint as documented interface to show how this derivatives function is used in the integration.
The resulting time and y-value can be extreme

How to extract a 1d profile (with integrated width) from a 2D array in an arbitrary direction

i have the following problem: I would like to extract a 1D profile from a 2D array, which is relatively simple. And it is also easy to do this in an arbitrary direction (see here).
But i would like to give the profile a certain width, so that the values perpendicular to the profile are averaged. I managed to do this, but it is extremely slow.
Does anyone have a good solution for that?
Thanks!
import numpy as np
import os
import math
import itertools
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
def closest_point(points, coords):
min_distances = []
coords = coords
for point in points:
distances = []
for coord in coords:
distances.append(np.sqrt((point[0]-coord[0])**2 + (point[1]-coord[1])**2))
val, idx = min((val, idx) for (idx, val) in enumerate(distances))
min_distances.append(coords[idx])
return min_distances
def rect_profile(x0, y0, x1, y1, width):
xd=x1-x0
yd=y1-y0
alpha = (np.angle(xd+1j*yd))
y00 = y0 - np.cos(math.pi - alpha)*width
x00 = x0 - np.sin(math.pi - alpha)*width
y01 = y0 + np.cos(math.pi - alpha)*width
x01 = x0 + np.sin(math.pi - alpha)*width
y10 = y1 + np.cos(math.pi - alpha)*width
x10 = x1 + np.sin(math.pi - alpha)*width
y11 = y1 - np.cos(math.pi - alpha)*width
x11 = x1 - np.sin(math.pi - alpha)*width
vertices = ((y00, x00), (y01, x01), (y10, x10), (y11, x11))
poly_points = [x00, x01, x10, x11], [y00, y01, y10, y11]
poly = Polygon(((y00, x00), (y01, x01), (y10, x10), (y11, x11)))
return poly, poly_points
def averaged_profile(image, x0, y0, x1, y1, width):
num = np.sqrt((x1-x0)**2 + (y1-y0)**2)
x, y = np.linspace(x0, x1, num), np.linspace(y0, y1, num)
coords = list(zip(x, y))
# Get all points that are in Rectangle
poly, poly_points = rect_profile(x0, y0, x1, y1, width)
points_in_poly = []
for point in itertools.product(range(image.shape[0]), range(image.shape[1])):
if poly.get_path().contains_point(point, radius=1) == True:
points_in_poly.append((point[1], point[0]))
# Finds closest point on line for each point in poly
neighbour = closest_point(points_in_poly, coords)
# Add all phase values corresponding to closest point on line
data = []
for i in range(len(coords)):
data.append([])
for idx in enumerate(points_in_poly):
index = coords.index(neighbour[idx[0]])
data[index].append(image[idx[1][1], idx[1][0]])
# Average data perpendicular to profile
for i in enumerate(data):
data[i[0]] = np.nanmean(data[i[0]])
# Plot
fig, axes = plt.subplots(figsize=(10, 5), nrows=1, ncols=2)
axes[0].imshow(image)
axes[0].plot([poly_points[0][0], poly_points[0][1]], [poly_points[1][0], poly_points[1][1]], 'yellow')
axes[0].plot([poly_points[0][1], poly_points[0][2]], [poly_points[1][1], poly_points[1][2]], 'yellow')
axes[0].plot([poly_points[0][2], poly_points[0][3]], [poly_points[1][2], poly_points[1][3]], 'yellow')
axes[0].plot([poly_points[0][3], poly_points[0][0]], [poly_points[1][3], poly_points[1][0]], 'yellow')
axes[0].axis('image')
axes[1].plot(data)
return data
from scipy.misc import face
img = face(gray=True)
profile = averaged_profile(img, 10, 10, 500, 500, 10)
As another option, There's now a scipy measure function that does exactly this (get profile between arbitrary points in a 2d array, with optional width specified):skimage.measure.profile_line.
As a big plus, it also lets you specify the interpolation value to use for off-grid locations.
I'm not sure how it compares to the above code though - I know for orthogonal cases it is much much faster (ie order magnitude or more) to use simple array slicing/summing.
Like heltonbiker says, if you're really needing speed (large array and/or many times) it is faster to first rotate the matrix, then just use slicing. A technique I've used in the past is basically his approach, but also first essentially masking the original unrotated array, and then only rotating the portion of the array that is the size of the area of your profile (plus a bit).
The downside with that approach (for speed) is that for the rotation you need to use some form of interpolation, which is generally slow, and to get accurate results you need at least linear (order 1) interpolation. However most of the python library modules (there are at least 3) for array rotation seem fairly optimised.
...However for pure convenience, then profile_line is the way to go
The main performance hog is the function closest_point. Computing the distances between all points on the line with all points in the rectangle is really slow.
You can speed the function up considerably by projecting all rectangle points onto the line. The projected point is the closest point on the line, so there is no need for computing all distances. Further, by correctly normalizing and rounding the projection (distance from line start) it can be directly used as an index.
def closest_point(points, x0, y0, x1, y1):
line_direction = np.array([x1 - x0, y1 - y0], dtype=float)
line_length = np.sqrt(line_direction[0]**2 + line_direction[1]**2)
line_direction /= line_length
n_bins = int(np.ceil(line_length))
# project points on line
projections = np.array([(p[0] * line_direction[0] + p[1] * line_direction[1]) for p in points])
# normalize projections so that they can be directly used as indices
projections -= np.min(projections)
projections *= (n_bins - 1) / np.max(projections)
return np.floor(projections).astype(int), n_bins
If you wonder about the strange for inside brackets - these are list comprehensions.
Use the function like this inside averaged_profile:
#...
# Finds closest point on line for each point in poly
neighbours, n_bins = closest_point(points_in_poly, x0, y0, x1, y1)
# Add all phase values corresponding to closest point on line
data = [[] for _ in range(n_bins)]
for idx in enumerate(points_in_poly):
index = neighbours[idx[0]]
data[index].append(image[idx[1][1], idx[1][0]])
#...
This optimization will make the computation noticably faster. If it is still too slow for you, you can also optimize how you find the points inside the polygon. Instead of testing if each point in the image is inside the rectangle you can use a polygon rasterization algorithm to directly generate the coordinates. See here for details.
Finally, although it is not a performance issue, the use of complex numbers to compute an angle is very creative :)
Instead of trigonometric functions you can use the fact that the normal vector of the line is [yd, -xd] devided by line length:
def rect_profile(x0, y0, x1, y1, width):
xd = x1 - x0
yd = y1 - y0
length = np.sqrt(xd**2 + yd**2)
y00 = y0 + xd * width / length
x00 = x0 - xd * width / length
y01 = y0 - xd * width / length
x01 = x0 + xd * width / length
y10 = y1 - xd * width / length
x10 = x1 + xd * width / length
y11 = y1 + xd * width / length
x11 = x1 - xd * width / length
poly_points = [x00, x01, x10, x11], [y00, y01, y10, y11]
poly = Polygon(((y00, x00), (y01, x01), (y10, x10), (y11, x11)))
return poly, poly_points
I would do the following:
Find out the direction of the desired line, and thus its angle;
Rotate the 2D array via matrix multiplication, using a rotation matrix with the angle you found;
Do a simple bounding box filtering with a rectangle representing your selected area. This will by definition be aligned to one of the axes;
Discard the y coordinate of the points inside the bounding box;
Smooth the results, possibly via 1D spline interpolation (available in scipy).

Plotting a line from a coordinate with and angle

I basically want to plot a line from a coordinate (x, y) with a given angle (calculating the tangent value).
With a simple line of code like this pl.plot([x1, x2], [y1, y2], 'k-', lw=1) I can plot a line between two points but for this I need to calculate (x2, y2) coordinate. My (x1, y1) coordinate is fixed and the angle is known. Calculating (x2, y2) causes a problem at some point so I just want to plot the line from (x1, y1) with an angle (and preferably with a length).
The simplest solution I came up with that was to use point-slope function which is y - y1 = m(x - X1). Interpreting thiss and searching a little I used this piece of code:
x1 = 10
y1 = -50
angle = 30
sl = tan(radians(angle))
x = np.array(range(-10,10))
y = sl*(x-x1) + y1
pl.plot(x,y)
pl.show
sl is here slope and x1 and y1 are the coordinates. I needed to explain myself since this found to be a poor question.
So now, any ideas on how I can do/solve that?
I'm not really sure what exactly you want from the explanation, but I think this will do something close to what you asked for.
You should use trigonometry to get the new point if you know the angle and length of a line you want to use.
import numpy as np
import math
import matplotlib.pyplot as plt
def plot_point(point, angle, length):
'''
point - Tuple (x, y)
angle - Angle you want your end point at in degrees.
length - Length of the line you want to plot.
Will plot the line on a 10 x 10 plot.
'''
# unpack the first point
x, y = point
# find the end point
endy = y + length * math.sin(math.radians(angle))
endx = length * math.cos(math.radians(angle))
# plot the points
fig = plt.figure()
ax = plt.subplot(111)
ax.set_ylim([0, 10]) # set the bounds to be 10, 10
ax.set_xlim([0, 10])
ax.plot([x, endx], [y, endy])
fig.show()
Inspired by this website, given a WGS84 coordinate, a bearing (sometimes referred to as forward azimuth) and a distance, you can compute a resulting destination point with the following logic:
import math
distance = 100 # kilometres
radius = 6371 # earth's radius in kilometres
lon, lat = -7.83197, 37.040893
bearing = 40
δ = distance / radius
θ = math.radians(bearing)
φ1 = math.radians(lat)
λ1 = math.radians(lon)
sinφ2 = math.sin(φ1) * math.cos(δ) + math.cos(φ1) * math.sin(δ) * math.cos(θ)
φ2 = math.asin(sinφ2)
y = math.sin(θ) * math.sin(δ) * math.cos(φ1)
x = math.cos(δ) - math.sin(φ1) * sinφ2
λ2 = λ1 + math.atan2(y, x)
lat2 = math.degrees(φ2)
lon2 = math.degrees(λ2)
Which will yield
>>> lon2, lat2
(-7.831861171142511, 37.04091627610624)
Standard module for complex numbers, cmath, makes it easy.
import cmath
pt = cmath.rect(r, angle)
x = pt.real
y = pt.imag
Given the length of the line (or radius), r, and the angle in radians, we can get the terminal point x and y coordinates (x, y) for the line starting from origin (0, 0).
Not starting from origin: if line starts from any other point (x1, y1), simply add to get (x2, y2) as x2 = x1 + x and y2 = y1 + y
Degrees to Radians: if angle is available in degrees, use math.radians(deg) to get the same in radians. Of course, remember to import math before use.
cmath.rect(r, phi) is the function you will call. It returns a complex number! Simply take its real and imaginary parts as the x and y values you need.
What you want is kind new and is called axline.
import numpy as np
import matplotlib.pyplot as plt
x1 = 10
y1 = -50
angle = 30
sl = np.tan(np.radians(angle))
x = np.arange(-10,10)
y = sl*(x-x1) + y1
plt.plot(x,y, 'o', label='manual')
plt.axline((x1,y1), slope=sl, color='red', label='axline')
plt.legend()
plt.grid()
plt.show()

Categories

Resources