I'm trying to draw an arc of n number of steps between two points so that I can bevel a 2D shape. This image illustrates what I'm looking to create (the blue arc) and how I'm trying to go about it:
move by the radius away from the target point (red)
get the normals of those lines
get the intersections of the normals to find the center of the circle
Draw an arc between those points from the circle's center
This is what I have so far:
As you can see, the circle is not tangent to the line segments. I think my approach may be flawed thinking that the two points used for the normal lines should be moved by the circle's radius. Can anyone please tell me where I am going wrong and how I might be able to find this arc of points? Here is my code:
import matplotlib.pyplot as plt
import numpy as np
#https://stackoverflow.com/questions/51223685/create-circle-tangent-to-two-lines-with-radius-r-geometry
def travel(dx, x1, y1, x2, y2):
a = {"x": x2 - x1, "y": y2 - y1}
mag = np.sqrt(a["x"]*a["x"] + a["y"]*a["y"])
if (mag == 0):
a["x"] = a["y"] = 0;
else:
a["x"] = a["x"]/mag*dx
a["y"] = a["y"]/mag*dx
return [x1 + a["x"], y1 + a["y"]]
def plot_line(line,color="go-",label=""):
plt.plot([p[0] for p in line],
[p[1] for p in line],color,label=label)
def line_intersection(line1, line2):
xdiff = (line1[0][0] - line1[1][0], line2[0][0] - line2[1][0])
ydiff = (line1[0][1] - line1[1][1], line2[0][1] - line2[1][1])
def det(a, b):
return a[0] * b[1] - a[1] * b[0]
div = det(xdiff, ydiff)
if div == 0:
raise Exception('lines do not intersect')
d = (det(*line1), det(*line2))
x = det(d, xdiff) / div
y = det(d, ydiff) / div
return x, y
line_segment1 = [[1,1],[4,8]]
line_segment2 = [[4,8],[8,8]]
line = line_segment1 + line_segment2
plot_line(line,'k-')
radius = 2
l1_x1 = line_segment1[0][0]
l1_y1 = line_segment1[0][1]
l1_x2 = line_segment1[1][0]
l1_y2 = line_segment1[1][1]
new_point1 = travel(radius, l1_x2, l1_y2, l1_x1, l1_y1)
l2_x1 = line_segment2[0][0]
l2_y1 = line_segment2[0][1]
l2_x2 = line_segment2[1][0]
l2_y2 = line_segment2[1][1]
new_point2 = travel(radius, l2_x1, l2_y1, l2_x2, l2_y2)
plt.plot(line_segment1[1][0], line_segment1[1][1],'ro',label="Point 1")
plt.plot(new_point2[0], new_point2[1],'go',label="radius from Point 1")
plt.plot(new_point1[0], new_point1[1],'mo',label="radius from Point 1")
# normal 1
dx = l1_x2 - l1_x1
dy = l1_y2 - l1_y1
normal_line1 = [[new_point1[0]+-dy, new_point1[1]+dx],[new_point1[0]+dy, new_point1[1]+-dx]]
plot_line(normal_line1,'m',label="normal 1")
# normal 2
dx2 = l2_x2 - l2_x1
dy2 = l2_y2 - l2_y1
normal_line2 = [[new_point2[0]+-dy2, new_point2[1]+dx2],[new_point2[0]+dy2, new_point2[1]+-dx2]]
plot_line(normal_line2,'g',label="normal 2")
x, y = line_intersection(normal_line1,normal_line2)
plt.plot(x, y,'bo',label="intersection") #'blue'
theta = np.linspace( 0 , 2 * np.pi , 150 )
a = x + radius * np.cos( theta )
b = y + radius * np.sin( theta )
plt.plot(a, b)
plt.legend()
plt.axis('square')
plt.show()
Thanks a lot!
You could try making a Bezier curve, like in this example. A basic implementation might be:
import matplotlib.path as mpath
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
Path = mpath.Path
fig, ax = plt.subplots()
# roughly equivalent of your purple, red and green points
points = [(3, 6.146), (4, 8), (6, 8.25)]
pp1 = mpatches.PathPatch(
Path(points, [Path.MOVETO, Path.CURVE3, Path.CURVE3]),
fc="none",
transform=ax.transData
)
ax.add_patch(pp1)
# lines between points
ax.plot([points[0][0], points[1][0]], [points[0][1], points[1][1]], 'b')
ax.plot([points[1][0], points[2][0]], [points[1][1], points[2][1]], 'b')
# plot points
for point in points:
ax.plot(point[0], point[1], 'o')
ax.set_aspect("equal")
plt.show()
which gives:
To do this without using a Matplotlib PathPatch object, you can calculate the Bezier points as, for example, in this answer, which I'll use below to do the same as above (note to avoid using scipy's comb function, as in that answer, I've used the comb function from here):
import numpy as np
from math import factorial
from matplotlib import pyplot as plt
def comb(n, k):
"""
N choose k
"""
return factorial(n) / factorial(k) / factorial(n - k)
def bernstein_poly(i, n, t):
"""
The Bernstein polynomial of n, i as a function of t
"""
return comb(n, i) * ( t**(n-i) ) * (1 - t)**i
def bezier_curve(points, n=1000):
"""
Given a set of control points, return the
bezier curve defined by the control points.
points should be a list of lists, or list of tuples
such as [ [1,1],
[2,3],
[4,5], ..[Xn, Yn] ]
n is the number of points at which to return the curve, defaults to 1000
See http://processingjs.nihongoresources.com/bezierinfo/
"""
nPoints = len(points)
xPoints = np.array([p[0] for p in points])
yPoints = np.array([p[1] for p in points])
t = np.linspace(0.0, 1.0, n)
polynomial_array = np.array(
[bernstein_poly(i, nPoints-1, t) for i in range(0, nPoints)]
)
xvals = np.dot(xPoints, polynomial_array)
yvals = np.dot(yPoints, polynomial_array)
return xvals, yvals
# set control points (as in the first example)
points = [(3, 6.146), (4, 8), (6, 8.25)]
# get the Bezier curve points at 100 points
xvals, yvals = bezier_curve(points, n=100)
# make the plot
fig, ax = plt.subplots()
# lines between control points
ax.plot([points[0][0], points[1][0]], [points[0][1], points[1][1]], 'b')
ax.plot([points[1][0], points[2][0]], [points[1][1], points[2][1]], 'b')
# plot control points
for point in points:
ax.plot(point[0], point[1], 'o')
# plot the Bezier curve
ax.plot(xvals, yvals, "k--")
ax.set_aspect("equal")
fig.show()
This gives:
If you are not just interested in the solution but in better understanding of this problem, you should read the article on Curved Paths that Amit Patel wrote in his 'Red Blob Games' blog.
https://www.redblobgames.com/articles/curved-paths/
Related
I need help to create a torus out of a circle by revolving it about x=2r, r is the radius of the circle.
I am open to either JULIA code or Python code. Whichever that can solve my problem the most efficient.
I have Julia code to plot circle and the x=2r as the axis of revolution.
using Plots, LaTeXStrings, Plots.PlotMeasures
gr()
θ = 0:0.1:2.1π
x = 0 .+ 2cos.(θ)
y = 0 .+ 2sin.(θ)
plot(x, y, label=L"x^{2} + y^{2} = a^{2}",
framestyle=:zerolines, legend=:outertop)
plot!([4], seriestype="vline", color=:green, label="x=2a")
I want to create a torus out of it, but unable, meanwhile I have solid of revolution Python code like this:
# Calculate the surface area of y = sqrt(r^2 - x^2)
# revolved about the x-axis
import matplotlib.pyplot as plt
import numpy as np
import sympy as sy
x = sy.Symbol("x", nonnegative=True)
r = sy.Symbol("r", nonnegative=True)
def f(x):
return sy.sqrt(r**2 - x**2)
def fd(x):
return sy.simplify(sy.diff(f(x), x))
def f2(x):
return sy.sqrt((1 + (fd(x)**2)))
def vx(x):
return 2*sy.pi*(f(x)*sy.sqrt(1 + (fd(x) ** 2)))
vxi = sy.Integral(vx(x), (x, -r, r))
vxf = vxi.simplify().doit()
vxn = vxf.evalf()
n = 100
fig = plt.figure(figsize=(14, 7))
ax1 = fig.add_subplot(221)
ax2 = fig.add_subplot(222, projection='3d')
ax3 = fig.add_subplot(223)
ax4 = fig.add_subplot(224, projection='3d')
# 1 is the starting point. The first 3 is the end point.
# The last 200 is the number of discretization points.
# help(np.linspace) to read its documentation.
x = np.linspace(1, 3, 200)
# Plot the circle
y = np.sqrt(2 ** 2 - x ** 2)
t = np.linspace(0, np.pi * 2, n)
xn = np.outer(x, np.cos(t))
yn = np.outer(x, np.sin(t))
zn = np.zeros_like(xn)
for i in range(len(x)):
zn[i:i + 1, :] = np.full_like(zn[0, :], y[i])
ax1.plot(x, y)
ax1.set_title("$f(x)$")
ax2.plot_surface(xn, yn, zn)
ax2.set_title("$f(x)$: Revolution around $y$")
# find the inverse of the function
y_inverse = x
x_inverse = np.power(2 ** 2 - y_inverse ** 2, 1 / 2)
xn_inverse = np.outer(x_inverse, np.cos(t))
yn_inverse = np.outer(x_inverse, np.sin(t))
zn_inverse = np.zeros_like(xn_inverse)
for i in range(len(x_inverse)):
zn_inverse[i:i + 1, :] = np.full_like(zn_inverse[0, :], y_inverse[i])
ax3.plot(x_inverse, y_inverse)
ax3.set_title("Inverse of $f(x)$")
ax4.plot_surface(xn_inverse, yn_inverse, zn_inverse)
ax4.set_title("$f(x)$: Revolution around $x$ \n Surface Area = {}".format(vxn))
plt.tight_layout()
plt.show()
Here is a way that actually allows rotating any figure in the XY plane around the Y axis.
"""
Rotation of a figure in the XY plane about the Y axis:
ϕ = angle of rotation
z' = z * cos(ϕ) - x * sin(ϕ)
x' = z * sin(ϕ) + x * cos(ϕ)
y' = y
"""
using Plots
# OP definition of the circle, but we put center at x, y of 4, 0
# for the torus, otherwise we get a bit of a sphere
θ = 0:0.1:2.1π
x = 4 .+ 2cos.(θ) # center at (s, 0, 0)
y = 0 .+ 2sin.(θ)
# add the original z values as 0
z = zeros(length(x))
plot(x, y, z, color=:red)
# add the rotation axis
ϕ = 0:0.1:π/2 # for full torus use 2π at stop of range
xprime, yprime, zprime = Float64[], Float64[], Float64[]
for a in ϕ, i in eachindex(θ)
push!(zprime, z[i] + z[i] * cos(a) - x[i] * sin(a))
push!(xprime, z[i] * sin(a) + x[i] * cos(a))
push!(yprime, y[i])
end
plot!(xprime, yprime, zprime, alpha=0.3, color=:green)
Here is a way using the Meshes package for the construction of the mesh and the MeshViz package for the visualization. You'll just have to translate to fulfill your desiderata.
using Meshes
using MeshViz
using LinearAlgebra
using GLMakie
# revolution of the polygon defined by (x,y) around the z-axis
# x and y have the same length
function revolution(x, y, n)
u_ = LinRange(0, 2*pi, n+1)[1:n]
j_ = 1:(length(x) - 1) # subtract 1 because of periodicity
function f(u, j)
return [x[j] * sin(u), x[j] * cos(u), y[j]]
end
points = [f(u, j) for u in u_ for j in j_]
topo = GridTopology((length(j_), n), (true, true))
return SimpleMesh(Meshes.Point.(points), topo)
end
# define the section to be rotated: a circle
R = 3 # major radius
r = 1 # minor radius
ntheta = 100
theta_ = LinRange(0, 2*pi, ntheta)
x = [R + r*cos(theta) for theta in theta_]
y = [r*sin(theta) for theta in theta_]
# make mesh
mesh = revolution(x, y, 100)
# visualize mesh
viz(mesh)
EDIT: animation
using Meshes
using MeshViz
using LinearAlgebra
using GLMakie
using Makie
using Printf
function revolutionTorus(R, r, alpha; n1=30, n2=90)
theta_ = LinRange(0, 2, n1+1)[1:n1]
x = [R + r*cospi(theta) for theta in theta_]
y = [r*sinpi(theta) for theta in theta_]
full = alpha == 2
u_ = LinRange(0, alpha, n2 + full)[1:n2]
function f(u, j)
return [x[j] * sinpi(u), x[j] * cospi(u), y[j]]
end
points = [f(u, j) for u in u_ for j in 1:n1]
topo = GridTopology((n1, n2 - !full), (true, full))
return SimpleMesh(Meshes.Point.(points), topo)
end
# generates `nframes` meshes for alpha = 0 -> 2 (alpha is a multiple of pi)
R = 3
r = 1
nframes = 10
alpha_ = LinRange(0, 2, nframes+1)[2:(nframes+1)]
meshes = [revolutionTorus(R, r, alpha) for alpha in alpha_]
# draw and save the frames in a loop
for i in 1:nframes
# make a bounding box in order that all frames have the same aspect
fig, ax, plt =
viz(Meshes.Box(Meshes.Point(-4.5, -4.5, -2.5), Meshes.Point(4.5, 4.5, 2.5)); alpha = 0)
ax.show_axis = false
viz!(meshes[i])
scale!(ax.scene, 1.8, 1.8, 1.8)
png = #sprintf "revolutionTorus%02d.png" i
Makie.save(png, fig)
end
# make GIF with ImageMagick
comm = #cmd "convert -delay 1x2 'revolutionTorus*.png' revolutionTorus.gif"
run(comm)
Assuming that an object is moving vertically downwards as well along Y-axis at a velocity of lets say 0.5 m/s while following the elliptical equation,
major axis radius = 100
minor axis radius = 25
Body's initial position can be assumed to be at (x,y) = (175, 150). Ellipse Image
I know how to fetch co-ordinates o ellipse when it is not moving downwards.
a = 25
b = 100
h = 150
k = 150
$ x = \sqrt{a^2 - {(y-k)^2 * a^2 \over b^2} } + h $
$ x = \sqrt{25^2 - {(y-150)^2 * 25^2 \over 100^2} } + 150 $
# Python code:
t = np.linspace(0,360,360)
x = 150 + 25*np.cos(np.radians(t)) # 150 is major axis of ellipse
y = 150 + 100*np.sin(np.radians(t)) # 150 is minor axis of ellipse
# plt.plot(x,y)
# plt.show()
df = pd.DataFrame(list(zip(x, y)), columns = ['x', 'y'])
# remove duplicate rows
df = df.drop_duplicates(keep = 'first')
ax = sns.scatterplot(data = df, x = 'x', y = 'y')
ax.set_xlim(0, 400)
ax.set_ylim(0, 300)
plt.grid()
def solve_for_x(y):
a = 25
b = 100
h = 150
k = 150
x1 = math.sqrt(a**2 - ( ( (y-k)**2 * a**2 )/b**2 )) + h
x2 = - math.sqrt(a**2 - ( ( (y-k)**2 * a**2 )/b**2 )) + h
# print(f'x = {x}')
return x1, x2
This code will return both sides of ellipse.
But my question is how to calculate x and y co-ordinates when the body is moving downwards. I imagine the path traced will be a weird 2d spring like structure.
You have to define the angular speed of a point on your ellipse, and from that you can determine a new np.linspace array that will represent time. Then you can simply substract 1/2*g*time**2 from your y array to get the y-coordinates of points on the falling ellipse.
I added a colormap to represent the time too.
# Imports.
import matplotlib.pyplot as plt
import numpy as np
# Constants.
G = 9.81
ANGULAR_SPEED = 60 # °/s
SPINS = 2.5
POINTS = 10000
# "Physics!"
angle = np.linspace(0, 360*SPINS, POINTS)
x = 150 + 25*np.cos(np.radians(angle))
ys = 150 + 100*np.sin(np.radians(angle)) # Static.
time = angle/ANGULAR_SPEED
yf = ys - 1/2*G*time**2
# Show the result.
fig, ax = plt.subplots()
# ax.set_aspect(1) # Optional: so that autoscaling doesn't squish our ellipse into a circle.
ax.scatter(x, ys, label="static ellipse")
ax.scatter(x, yf, label="falling ellipse", c=time, cmap="autumn")
ax.legend()
fig.show()
If you want to stick to a constant downward velocity, it's possible too:
# Constants.
...
VY = -200
...
# "Physics!"
...
yf = ys + VY*time
...
I need help to get the real "start" and "end" points where a line is drawn in the image matrix. In the example below, a detected line is shown using y0 and y1.
import numpy as np
from skimage.transform import hough_line, hough_line_peaks
from skimage.feature import canny
from skimage import data
import matplotlib.pyplot as plt
from matplotlib import cm
# Constructing test image
image = np.zeros((200, 200))
idx = np.arange(0, 200)
image[idx, idx//2] = 255
imgOriginal = image.copy()
# Classic straight-line Hough transform
# Set a precision of 0.5 degree.
tested_angles = np.linspace(-np.pi / 2, np.pi / 2, 360)
h, theta, d = hough_line(image, theta=tested_angles)
# Generating figure 1
fig, axes = plt.subplots(1, 3, figsize=(15, 6))
ax = axes.ravel()
ax[0].imshow(image, cmap=cm.gray)
ax[0].set_title('Input image')
ax[0].set_axis_off()
ax[1].imshow(np.log(1 + h),
extent=[np.rad2deg(theta[-1]), np.rad2deg(theta[0]), d[-1], d[0]],
cmap=cm.gray, aspect=1/1.5)
ax[1].set_title('Hough transform')
ax[1].set_xlabel('Angles (degrees)')
ax[1].set_ylabel('Distance (pixels)')
ax[1].axis('image')
ax[2].imshow(image, cmap=cm.gray)
origin = np.array((0, image.shape[1]))
for _, angle, dist in zip(*hough_line_peaks(h, theta, d)):
y0, y1 = (dist - origin * np.cos(angle)) / np.sin(angle)
print('y0 = {} y1 = {}'.format(y0, y1))
ax[2].plot(origin, (y0, y1), '-r')
ax[2].set_xlim(origin)
ax[2].set_ylim((image.shape[0], 0))
ax[2].set_axis_off()
ax[2].set_title('Detected lines')
plt.tight_layout()
plt.show()
This code results in:
What I want is to get the following points in the real image matrix:
Which is likely to be (0,0) and (199, 100)
In summary, I want to transform y0 and y1 into real points in my numpy matrix.
Your problem is to find the point of intersection between two lines essentially. One is your given line and the other is the line defined by the edge of the image.
That can be done as explained here. I am borrowing the code in the first answer. Define these functions -
def line_intersection(line1, line2):
xdiff = (line1[0][0] - line1[1][0], line2[0][0] - line2[1][0])
ydiff = (line1[0][1] - line1[1][1], line2[0][1] - line2[1][1])
def det(a, b):
return a[0] * b[1] - a[1] * b[0]
div = det(xdiff, ydiff)
if div == 0:
raise Exception('lines do not intersect')
d = (det(*line1), det(*line2))
x = det(d, xdiff) / div
y = det(d, ydiff) / div
return x, y
Change your loop as follows -
for _, angle, dist in zip(*hough_line_peaks(h, theta, d)):
y0, y1 = (dist - origin * np.cos(angle)) / np.sin(angle)
print('y0 = {} y1 = {}'.format(y0, y1))
ax[2].plot(origin, (y0, y1), '-r')
l1 = ((origin[0], y0), (origin[1], y1))
l2 = ((0, 200), (200, 200))
print(line_intersection(l1, l2))
This is obviously assuming that the line of interest always intersects with the lower edge of the image. If the line intersects with the right edge, l2 will have to be modified accordingly. In practice, I would suggest finding the intersect with the two edges and picking the "closest" intersect.
This also assumes that the the line of interest always starts from the top left corner of the image (as you have defined your problem) . If that's not the case, you would need to do this for all four edges of the image and pick the first two intersects.
I would like to find the best-fit axis of points that are on a cylindrical surface, using python.
Seems that scipy.linalg.svd is the function to look for.
So to test out, I decide to generate some points, function makeCylinder, from this thread How to generate regular points on cylindrical surface, and estimate the axis.
This is the code:
def rotMatrixAxisAngle(axis, theta, theta2deg=False):
# Load
from math import radians, cos, sin
from numpy import array
# Convert to radians
if theta2deg:
theta = radians(theta)
#
a = cos(theta/2.0)
b, c, d = -array(axis)*sin(theta/2.0)
# Rotation matrix
R = array([ [a*a+b*b-c*c-d*d, 2.0*(b*c-a*d), 2.0*(b*d+a*c)],
[2.0*(b*c+a*d), a*a+c*c-b*b-d*d, 2.0*(c*d-a*b)],
[2.0*(b*d-a*c), 2.0*(c*d+a*b), a*a+d*d-b*b-c*c] ])
return R
def makeCylinder(radius, length, nlength, alpha, nalpha, center, orientation):
# Load
from numpy import array, allclose, linspace, tile, vstack
from numpy import pi, cos, sin, arccos, cross
from numpy.linalg import norm
# Create the length array
I = linspace(0, length, nlength)
# Create alpha array avoid duplication of endpoints
if int(alpha) == 360:
A = linspace(0, alpha, num=nalpha, endpoint=False)/180.0*pi
else:
A = linspace(0, alpha, num=nalpha)/180.0*pi
# Calculate X and Y
X = radius * cos(A)
Y = radius * sin(A)
# Tile/repeat indices so all unique pairs are present
pz = tile(I, nalpha)
px = X.repeat(nlength)
py = Y.repeat(nlength)
# Points
points = vstack(( pz, px, py )).T
## Shift to center
points += array(center) - points.mean(axis=0)
# Orient tube to new vector
ovec = orientation / norm(orientation)
cylvec = array([1,0,0])
if allclose(cylvec, ovec):
return points
# Get orthogonal axis and rotation
oaxis = cross(ovec, cylvec)
rot = arccos(ovec.dot(cylvec))
R = rotMatrixAxisAngle(oaxis, rot)
return points.dot(R)
from numpy.linalg import norm
from numpy.random import rand
from scipy.linalg import svd
for i in xrange(100):
orientation = rand(3)
orientation[0] = 0
orientation /= norm(orientation)
# Generate sample points
points = makeCylinder(radius = 3.0,
length = 20.0, nlength = 20,
alpha = 360, nalpha = 30,
center = [0,0,0],
orientation = orientation)
# Least Square
uu, dd, vv = svd(points - points.mean(axis=0))
asse = vv[0]
assert abs( abs(orientation.dot(asse)) - 1) <= 1e-4, orientation.dot(asse)
As you can see, I generate multiple cylinder whose axis is random (rand(3)).
The funny thing is that svd returns an axis that is absolutely perfect if the first component of orientation is zero (orientation[0] = 0).
If I comment this line the estimated axis is way off.
Update 1:
Even using leastsq on a cylinder equation returns the same behavior:
def bestLSQ1(points):
from numpy import array, sqrt
from scipy.optimize import leastsq
# Expand
points = array(points)
x = points[:,0]
y = points[:,1]
z = points[:,2]
# Calculate the distance of each points from the center (xc, yc, zc)
# http://geometry.puzzles.narkive.com/2HaVJ3XF/geometry-equation-of-an-arbitrary-orientated-cylinder
def calc_R(xc, yc, zc, u1, u2, u3):
return sqrt( (x-xc)**2 + (y-yc)**2 + (z-zc)**2 - ( (x-xc)*u1 + (y-yc)*u2 + (z-zc)*u3 )**2 )
# Calculate the algebraic distance between the data points and the mean circle centered at c=(xc, yc, zc)
def dist(c):
Ri = calc_R(*c)
return Ri - Ri.mean()
# Axes - Minimize residu
xM, yM, zM = points.mean(axis=0)
# Calculate the center
center, ier = leastsq(dist, (xM, yM, zM, 0, 0, 1))
xc, yc, zc, u1, u2, u3 = center
asse = u1, u2, u3
return asse
Despite your interesting approach using svd, you could also do a more intuitive approach with scipy.optimize.leastsq.
This would need a function to calculate distance between the axis and your cloud of points in order to find the best-fitting axis.
The code could be something like shown below (distance_axis_points adapted from alg3dpy):
from numpy.linalg import norm
from numpy.random import rand
from scipy.optimize import leastsq
for i in range(100):
orientation = rand(3)
orientation[0] = 0
orientation /= norm(orientation)
# Generate sample points
points = makeCylinder(radius = 3.0,
length = 20.0, nlength = 20,
alpha = 360, nalpha = 30,
center = [0,0,0],
orientation = orientation)
def dist_axis_points(axis, points):
axis_pt0 = points.mean(axis=0)
axis = np.asarray(axis)
x1 = axis_pt0[0]
y1 = axis_pt0[1]
z1 = axis_pt0[2]
x2 = axis[0]
y2 = axis[1]
z2 = axis[2]
x3 = points[:, 0]
y3 = points[:, 1]
z3 = points[:, 2]
den = ((x1 - x2)**2 + (y1 - y2)**2 + (z1 - z2)**2)
t = ((x1**2 + x2 * x3 - x1 * x3 - x1 * x2 +
y1**2 + y2 * y3 - y1 * y3 - y1 * y2 +
z1**2 + z2 * z3 - z1 * z3 - z1 * z2)/den)
projected_pt = t[:, None]*(axis[None, :] - axis_pt0[None, :]) + axis_pt0[None, :]
return np.sqrt(((points - projected_pt)**2).sum(axis=-1))
popt, pconv = leastsq(dist_axis_points, x0=[1, 1, 1], args=(points,))
popt /= norm(popt)
assert abs(abs(orientation.dot(popt)) - 1) <= 1e-4, orientation.dot(popt)
I'm trying to recreate the diagram (or just a contour plot would be okay) for the Dirichlet distribution that's on Wikipedia using matplotlib and numpy. I am having trouble easily generating a triangular contourf. The first problem is that meshgrid doesn't return a triangle of points. Even if I get a triangle of points, will contourf handle the non-rectangular input?
Here's what I have so far:
#!/usr/bin/env python
from __future__ import division
import matplotlib
matplotlib.use("TkAgg")
matplotlib.rc('text', usetex=True)
matplotlib.rcParams['text.latex.preamble']=r"""\usepackage{amsmath}
"""
import math
import scipy.special
root_three_over_two = np.sqrt(3) / 2
def new_figure():
# 1.45
plt.figure(figsize = [2.6, 2.6 * root_three_over_two], dpi = 1200)
plt.axes([0.05, 0.10, 0.90, 0.90], frameon = False)
xsize = 1.0
ysize = root_three_over_two * xsize
plt.axis([0, xsize, 0, ysize])
resolution = 0.05
R = inclusive_arange(0.0, 1.0, resolution)
x, y = np.meshgrid(inclusive_arange(0.0, 1.0, resolution),
inclusive_arange(0.0, 1.0, resolution))
# UNFORTUNATELY x, and y include a lot of points where x+y>1
x = []
y = []
for yy in R:
x.append(list(inclusive_arange(0.0, 1.0 - yy, resolution)))
y.append([yy for xx in R])
print x
print y
z = 1 - x - y
# We can use these to convert to and from the equilateral triangle.
M = [[1, 0.5], [0, root_three_over_two]]
Mi = np.linalg.inv(M)
def dirichlet(x, y, z, a, b, c):
if z < 0:
return 0
return x ** (a - 1) * y ** (b - 1) * z ** (c - 1) \
* math.gamma(a + b + c) \
/ (math.gamma(a) * math.gamma(b) * math.gamma(c))
dirichlet = np.frompyfunc(dirichlet, 6, 1)
for (dirichlet_parm, filename) in [((5.0, 1.5, 2.5), "dir_small.pdf")]:
new_figure()
height = dirichlet(x, y, z, *dirichlet_parm)
M = np.max(height)
cs = plt.contourf(x, y, height, 50)
S = sum(dirichlet_parm)
plt.savefig(filename)
You do not need a rectilinear meshgrid to create a contour plot. You can define the perimeter and perform a Delaunay triangulation. The coordinates, of course will still be rectilinear and (it appears) you'll need to do a transformation. This example should be enough to create a 2D contour. I have produced some 3D surface plots with a non-rectangle perimeter and you get some edge artifacts that can be unsightly. A very fine grid might ameliorate some of these.