Euler Angles and Rotation Matrix from two 3D points - python

I am trying to find the Euler angles that allow the transformation from point A to point B in 3D space.
Consider the normalized vectors A = [1, 0, 0] and B = [0.32 0.88 -0.34].
I understand that by computing the cross product A × B I get the rotation axis. The angle between A and B is given by tan⁻¹(||cross||, A·B), where A·B is the dot product between A and B.
This gives me the rotation vector rotvec = [0 0.36 0.93 1.24359531111], which is rotvec = [A × B; angle] (the cross product is normalized).
Now my question is: How do I move from here to get the Euler angles that correspond to the transformation from A to B?
In MATLAB the function vrrotvec2mat receives as input a rotation vector and outputs a rotation matrix. Then the function rotm2eul should return the corresponding Euler angles. I get the following result (in radians): [0.2456 0.3490 1.2216], according to the XYZ convention. Yet, this is not the expected result.
The correct answer is [0 0.3490 1.2216] that corresponds to a rotation of 20° and 70° in Y and Z, respectively.
When I use eul2rot([0 0.3490 1.2216]) (with eul2rot taken from here) to verify the resulting rotation matrix, this one is different from the one I obtain when using vrrotvec2mat(rotvec).
I also have a Python spinet that yields the exactly same results as described above.
--- Python (2.7) using transform3d ---
import numpy as np
import transforms3d
cross = np.cross(A, B)
dot = np.dot(A, B.transpose())
angle = math.atan2(np.linalg.norm(cross), dot)
rotation_axes = sklearn.preprocessing.normalize(cross)
rotation_m = transforms3d.axangles.axangle2mat(rotation_axes[0], angle, True)
rotation_angles = transforms3d.euler.mat2euler(rotation_m, 'sxyz')
What I am missing here? What should I be doing instead?
Thank you

A rotation matrix has 3 degrees of freedom but the constraints of your problem only constrain 2 of those degrees.
This can be made more concrete by considering the case where we have a rotation matrix R which rotates from A to B so R*A == B. If we then construct another rotation matrix RB which rotates about vector B then applying this rotation to R*A won't have any effect, i.e. B == R*A == RB*R*A. It will, however, produce a different rotation matrix RB*R with different Euler angles.
Here's an example in MATLAB:
A = [1; 0; 0];
B = [0.32; 0.88; -0.34];
A = A / norm(A);
B = B / norm(B);
ax = cross(A, B);
ang = atan2(norm(ax), dot(A, B)); % ang = acos(dot(A, B)) works too
R = axang2rotm([ax; ang].');
ang_arbitrary = rand()*2*pi;
RB = axang2rotm([B; ang_arbitrary].');
R*A - B
RB*R*A - B
rotm2eul(R)
rotm2eul(RB*R)
Result
ans =
1.0e-15 *
-0.0555
0.1110
0
ans =
1.0e-15 *
0.2220
0.7772
-0.2776
ans =
1.2220 0.3483 0.2452
ans =
1.2220 0.3483 0.7549

I will give you a solution based on Euler's rotation theorem.
This solution gives you only the one angle, but the other angles can be derived.
import numpy as np
a_vec = np.array([1, 0, 0])/np.linalg.norm(np.array([1, 0, 0]))
b_vec = np.array([0.32, 0.88, -0.34])/np.linalg.norm(np.array([0.32, 0.88, -0.34]))
cross = np.cross(a_vec, b_vec)
ab_angle = np.arccos(np.dot(a_vec,b_vec))
vx = np.array([[0,-cross[2],cross[1]],[cross[2],0,-cross[0]],[-cross[1],cross[0],0]])
R = np.identity(3)*np.cos(ab_angle) + (1-np.cos(ab_angle))*np.outer(cross,cross) + np.sin(ab_angle)*vx
validation=np.matmul(R,a_vec)
This uses the common axis of rotation (eigenvector in this case), as the cross product.
The matrix R is then the rotation matrix.
This is a general way of doing it, and very simple.

Related

Best way to find the angle of a vector with positive y-axis, clockwise

So, I have been using the common mathematics formula till now: cos theta = a.b/|a|*|b|.
Implemented on python with the following bit of code
vector=[point2[0]-point1[0],point2[1]-point1[1]]
y_axis = [0, 1]
unit_vector = vector / np.linalg.norm(vector)
unit_y = y_axis / np.linalg.norm(y_axis)
dot_product = np.dot(unit_vector, unit_y)
angle = numpy.arccos(dot_product)
I found that this does not consistently provide the proper angle.
Is there any alternative/better way to do achieve this using just numpy?
I think the problem might be the fact that your angle is not always computed clockwise. That is, if the x-coordinate of "vector" is negative, then arccos() will give a result that effectively computes the angle in the counter-clockwise sense. In the code below, I also expressed the result in degrees.
import numpy as np
vector = [-0.25, -0.25]
y_axis = [0, 1]
unit_vector = vector / np.linalg.norm(vector)
unit_y = y_axis / np.linalg.norm(y_axis)
dot_product = np.dot(unit_vector, unit_y)
angle = np.arccos(dot_product)
AngleInDegrees = 360*angle/(2*np.pi);
if vector[0]<0:
AngleInDegrees = 360-AngleInDegrees
print(AngleInDegrees)

Generating solid spheres in python [duplicate]

This question already has an answer here:
How to generate uniform random points inside d-dimension ball / sphere?
(1 answer)
Closed 1 year ago.
I'd like to generate random uniform samples from n-dimensional solid spheres.
My current method is this
def sample_sphere(d, npoints):
points = np.zeros((npoints, d))
for i in range(npoints):
r = np.random.rand() #random radius
v = np.random.uniform(low= -1, high=1, size=d) #random direction
v = v / np.linalg.norm(v)
points[i] = r * v
return points
But unsurpringly, this method gives a higher concentration of points near 0, since the sampling density is not proportional to the volume:
How can I sample uniformly?
There are two basic interpretations of a spherical distribution:
1. Bounded solid sphere
The probability of getting a point at a given radius is given by the volume of a shell with thickness dr at that radius: p(r) ~ r^D up to a constant. The radial CDF is therefore (r / R)^(D+1), where R is the outer radius and D is the dimensionality. The sphere has to be bounded unless you want to select from all of space.
A standard way to generate samples given the CDF is to invert it:
random_radius = R * np.pow(np.random.uniform(0, 1), 1 / D)
By the way, a common way of picking properly uniform directions is to use the Gaussian distribution (see here):
random_direction = np.random.normal(size=D)
random_direction /= np.linalg.norm(random_direction)
You can, and should, vectorize your function. There is no need for looping:
def sample_sphere(r, d, n):
radii = r * np.pow(1 - np.random.uniform(0, 1, size=(n, 1)), 1 / d)
directions = np.random.normal(size=(n, d))
directions *= radii / np.linalg.norm(directions, axis=1, keepdims=True)
return directions
Notice the 1 - np.random.uniform(...). This is because the range of the function is [0, 1), while we want (0, 1]. The subtraction inverts the range without affecting the uniformity.
2. Unbounded decaying distribution
In this case you're looking for something like a Gaussian with spherical symmetry. The radial CDF of such a distribution scales with R^(D-1). For the Gaussian, this is called the Rayleigh distribution in 2D, Maxwell in 3D, and chi in the general case. These are available directly through the scipy.stats.rayleigh, scipy.stats.maxwell and scipy.stats.chi objects, respectively.
In this case, radius has less meaning, but it can be interpreted as the standards deviation of your distribution.
The unbounded version of your function (also vectorized), then becomes:
def sample_sphere(s, d, n):
radii = scipy.stats.chi.rvs(df=d, scale=s, size=(n, 1))
directions = np.random.normal(size=(n, d))
directions *= radii / np.linalg.norm(directions, axis=1, keepdims=True)
return directions

Apply a rotation matrix to xy coordinates

I have xy coordinates that represents a subject over a given space. It is referenced from another point and is therefore off centre. As in the longitudinal axes is not aligned along the x-axis.
The randomly generated ellipse below provides an indication of this:
import numpy as np
from matplotlib.pyplot import scatter
xx = np.array([-0.51, 51.2])
yy = np.array([0.33, 51.6])
means = [xx.mean(), yy.mean()]
stds = [xx.std() / 3, yy.std() / 3]
corr = 0.8 # correlation
covs = [[stds[0]**2 , stds[0]*stds[1]*corr],
[stds[0]*stds[1]*corr, stds[1]**2]]
m = np.random.multivariate_normal(means, covs, 1000).T
scatter(m[0], m[1])
To straighten the coordinates I was thinking of applying the vector to a rotation matrix.
Would something like this work?
angle = 65.
theta = (angle/180.) * np.pi
rotMatrix = np.array([[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]])
This may also seem like a silly question but is there a way to determine if the resulting vector of xy coordinates is perpendicular? Or will you just have to play around with the rotation angle?
You can use sklearn.decomposition.PCA (principal component analysis) with n_components=2 to extract the smallest angle required to rotate the point cloud such that its major axis is horizontal.
Runnable example
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
np.random.seed(1)
xx = np.array([-0.51, 51.2])
yy = np.array([0.33, 51.6])
means = [xx.mean(), yy.mean()]
stds = [xx.std() / 3, yy.std() / 3]
corr = 0.8 # correlation
covs = [[stds[0]**2, stds[0]*stds[1]*corr],
[stds[0]*stds[1]*corr, stds[1]**2]]
m = np.random.multivariate_normal(means, covs, 1000)
pca = PCA(2)
# This was in my first answer attempt: fit_transform works fine, but it randomly
# flips (mirrors) points across one of the principal axes.
# m2 = pca.fit_transform(m)
# Workaround: get the rotation angle from the PCA components and manually
# build the rotation matrix.
# Fit the PCA object, but do not transform the data
pca.fit(m)
# pca.components_ : array, shape (n_components, n_features)
# cos theta
ct = pca.components_[0, 0]
# sin theta
st = pca.components_[0, 1]
# One possible value of theta that lies in [0, pi]
t = np.arccos(ct)
# If t is in quadrant 1, rotate CLOCKwise by t
if ct > 0 and st > 0:
t *= -1
# If t is in Q2, rotate COUNTERclockwise by the complement of theta
elif ct < 0 and st > 0:
t = np.pi - t
# If t is in Q3, rotate CLOCKwise by the complement of theta
elif ct < 0 and st < 0:
t = -(np.pi - t)
# If t is in Q4, rotate COUNTERclockwise by theta, i.e., do nothing
elif ct > 0 and st < 0:
pass
# Manually build the ccw rotation matrix
rotmat = np.array([[np.cos(t), -np.sin(t)],
[np.sin(t), np.cos(t)]])
# Apply rotation to each row of m
m2 = (rotmat # m.T).T
# Center the rotated point cloud at (0, 0)
m2 -= m2.mean(axis=0)
fig, ax = plt.subplots()
plot_kws = {'alpha': '0.75',
'edgecolor': 'white',
'linewidths': 0.75}
ax.scatter(m[:, 0], m[:, 1], **plot_kws)
ax.scatter(m2[:, 0], m2[:, 1], **plot_kws)
Output
Warning: pca.fit_transform() sometimes flips (mirrors) the point cloud
The principal components can randomly come out as either positive or negative. In some cases, your point cloud may appear flipped upside down or even mirrored across one of its principal axes. (To test this, change the random seed and re-run the code until you observe flipping.) There's an in-depth discussion here (based in R, but the math is relevant). To correct this, you'd have to replace the fit_transform line with manual flipping of one or both components' signs, then multiply the sign-flipped component matrix by the point cloud array.
Indeed a very useful concept here is a linear transformation of a vector v performed by a matrix A. If you treat your scatter points as the tip of vectors originating from (0,0), then is very easy to rotate them any angle theta. A matrix that performs such rotation of theta would be
A = [[cos(theta) -sin(theta]
[sin(theta) cos(theta)]]
Evidently, when theta is 90 degrees this results into
A = [[ 0 1]
[-1 0]]
And to apply the rotation you would only need to perform the matrix multiplication w = A v
With this, the current goal is to perform a matrix multiplication of the vectors stored in m with x,y tips as m[0],m[1]. The rotated vector are gonna be stored in m2. Below is the relevant code to do so. Note that I have transposed m for an easier computation of the matrix multiplication (performed with #) and that the rotation angle is 90 degress counterclockwise.
import numpy as np
import matplotlib.pyplot as plt
xx = np.array([-0.51, 51.2])
yy = np.array([0.33, 51.6])
means = [xx.mean(), yy.mean()]
stds = [xx.std() / 3, yy.std() / 3]
corr = 0.8 # correlation
covs = [[stds[0]**2 , stds[0]*stds[1]*corr],
[stds[0]*stds[1]*corr, stds[1]**2]]
m = np.random.multivariate_normal(means, covs, 1000).T
plt.scatter(m[0], m[1])
theta_deg = 90
theta_rad = np.deg2rad(theta_deg)
A = np.matrix([[np.cos(theta_rad), -np.sin(theta_rad)],
[np.sin(theta_rad), np.cos(theta_rad)]])
m2 = np.zeros(m.T.shape)
for i,v in enumerate(m.T):
w = A # v.T
m2[i] = w
m2 = m2.T
plt.scatter(m2[0], m2[1])
This leads to the rotated scatter plot:
You can be sure that the rotated version is exactly 90 degrees counterclockwise with the linear transformation.
Edit
To find the rotation angle you need to apply in order for the scatter plot to be aligned with the x axis a good approach is to find the linear approximation of the scattered data with numpy.polyfit. This yields to a linear function by providing the slope and the intercept of the y axis b. Then get the rotation angle with the arctan function of the slope and compute the transformation matrix as before. You can do this by adding the following part to the code
slope, b = np.polyfit(m[1], m[0], 1)
x = np.arange(min(m[0]), max(m[0]), 1)
y_line = slope*x + b
plt.plot(x, y_line, color='r')
theta_rad = -np.arctan(slope)
And result to the plot you were seeking
Edit 2
Because #Peter Leimbigler pointed out that numpy.polyfit does not find the correct global direction of the scattered data, I have thought that you can get the average slope by averaging the x and y parts of the data. This is to find another slope, called slope2 (depicted in green now) to apply the rotation. So simply,
slope, b = np.polyfit(m[1], m[0], 1)
x = np.arange(min(m[0]), max(m[0]), 1)
y_line = slope*x + b
slope2 = np.mean(m[1])/np.mean(m[0])
y_line2 = slope2*x + b
plt.plot(x, y_line, color='r')
plt.plot(x, y_line2, color='g')
theta_rad = -np.arctan(slope2)
And by applying the linear transformation with the rotation matrix you get
If the slope of the two lines multiplied together is equal to -1 than they are perpendicular.
The other case this is true, is when one slope is 0 and the other is undefined (a perfectly horizontal line and a perfectly vertical line).

Recover angles after transformation

This seems like an easy enough task but I've failed to find a solution and I've run out of ideas.
I have two angles which I employ to define some transformation coefficients. Now, I don't actually have the values for those angles in my real data, I have the coefficients and I need to recover the angles.
I thought the arctan2 function would take care of this, but there are cases where it fails to recover the proper a1 angle and instead returns its 180 complement, which later affects the recovery of the a2 angle.
What am I doing wrong and how can I recover the a1, a2 angles properly?
import numpy as np
# Repeat 100 times
for _ in range(100):
# Define two random angles in the range [-pi, pi]. I do not have these
# angles in my actual data, I have the A,B,C coefficients shown below.
a1, a2 = np.random.uniform(-180., 180., (2,))
# Transformation coefficients using the above angles.
# This is the data I actually have.
a1_rad, a2_rad = np.deg2rad(a1), np.deg2rad(a2) # to radians
A = - np.sin(a1_rad) * np.sin(a2_rad)
B = np.cos(a1_rad) * np.sin(a2_rad)
C = np.cos(a2_rad)
# Recover a1 using 'arctan2' (returns angle in the range [-pi, pi])
a1_recover = np.arctan2(-A / B, 1.)
# Now obtain sin(a2), used below to obtain 'a2'
sin_a2 = -A / np.sin(a1_recover)
# Recover a2 using 'arctan2', where: C = cos(a2)
a2_recover = np.arctan2(sin_a2, C)
# Print differences.
a1_recover = np.rad2deg(a1_recover)
print("a1: {:.2f} = {} - {}".format(a1 - a1_recover, a1, a1_recover))
a2_recover = np.rad2deg(a2_recover)
print("a2: {:.2f} = {} - {}\n".format(a2 - a2_recover, a2, a2_recover))
When a2_rad equals 0, (A, B, C) equals (0, 0, 1) no matter what a1_rad equals. So the transformation is not 1-to-1. Therefore there is no well-defined inverse.
def ABC(a1, a2):
a1_rad, a2_rad = np.deg2rad(a1), np.deg2rad(a2) # to radians
A = - np.sin(a1_rad) * np.sin(a2_rad)
B = np.cos(a1_rad) * np.sin(a2_rad)
C = np.cos(a2_rad)
return A, B, C
print(ABC(0, 0))
# (-0.0, 0.0, 1.0)
print(90, 0)
# (-0.0, 0.0, 1.0)
print(-90, 0)
# (-0.0, 0.0, 1.0)
A similar problem happens at the opposite (South) pole. Within the limits of floating point accuracy, all these values (of the form ABC(a1, 180)) are essentially equal too:
ABC(1, 180)
# (-2.1373033680837913e-18, 1.2244602795081332e-16, -1.0)
ABC(0, 180)
# (-0.0, 1.2246467991473532e-16, -1.0)
ABC(90, 180)
# (-1.2246467991473532e-16, 7.498798913309288e-33, -1.0)
You can think of a1, a2 as coordinates on a unit sphere where a1
represents the angle away from the x-axis (more often called theta) and a2
represents the angle away from the z-axis (often called phi).
A,B,C represents the same point on the unit sphere in Cartesian coordinates.
Usually spherical coordinates restrict a1 to the range [0, 2*pi) and a2 to the range [0, pi].
Even with this restriction, the North and South poles have more than one (actually infinite number of) valid representation.
You cannot restore angle sign information because it was loosed in A,B calculation (formation).
8 possible combinations of sin/cos signs give only 4 results of A/B signs (and sign of cos(a2) cannot help here).
Note that for spherical coordinates inclination range is only 0..Pi
You should use np.arctan2(-A , B) instead of np.arctan2(-A / B, 1.). With the latter you are losing information: A = -1 and B = 1 will give the same result as A - 1 and B = -1, hence the 180 mismatch sometimes.
If you restrict a2 to be in (0,180) then you can recover the angles. Note that with this restriction a2 can be recovered as acos(C). (I've tried this but since my program is in C it might not be helpful)

calculating angle between three points but only anticlockwise in python

So I'm trying to calculate the angle between three points. for example
a = [14, 140]
b = [13, 120]
c = [12, 130]
d = [11, 110]
|
| c a
|
| d b
|____________
say i want to calculate the angle between ABC i use the follow code
#create vectors
ba = a - b
bc = c- b
# calculate angle
cosine_angle = numpy.dot(ba,bc) / (numpy.linalg.norm(ba) * numpy.linalg.norm(bc))
angle = numpy.arccos(cosine_angle)
pAngle = numpy.degrees(angle)
My script runs and the output angle works, my issue though is when i want to calculate BCD I want the angle on the outside not the inside so say instead of the angle being 120 degrees i want the angle 240. So i only want the anti-clockwise angles.
Not sure how to get this value, can anyone point me in the right direction?
*edit: in other terms i want to identify angles that are over 180 degrees anticlockwise
*edit2: the duplicate answer does not answer the question for me as i have not used alot of java so not sure how to code that in python
Now using angular cosine distance to calculate the angle between two vectors is quite good, but in your case it might be better to use arc tangent as mentioned in the comments.
Now assuming you want to calculate the counterclockwise angle between BCD, you can do this by using the numpy's atan2 function. atan2(x, y) will give the angle between the origin point y to x.
import numpy as np
def calculate_angle(point_a, point_b):
""" Calculate angle between two points """
ang_a = np.arctan2(*point_a[::-1])
ang_b = np.arctan2(*point_b[::-1])
return np.rad2deg((ang_a - ang_b) % (2 * np.pi))
a = np.array([14, 140])
b = np.array([13, 120])
c = np.array([12, 130])
d = np.array([11, 110])
# create vectors
ba = a - b
bc = c - b
cd = d - c
# calculate angle
cosine_angle = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc))
angle = np.arccos(cosine_angle)
inner_angle = np.degrees(angle)
print inner_angle # 8.57299836361
# see how changing the direction changes the angle
print calculate_angle(bc, cd) # 188.572998364
print calculate_angle(cd, bc) # 171.427001636
You are able to calculate the inner angle, but you want the reversed angle? why not just calculating 360 - angle

Categories

Resources