Rotating curves around an origin in python - python

I have two curves which meet around origin z and y, listed below. When I plot these according to certain functions I get the attached plot.
origin_z = 260
origin_y = 244
plt.plot(phi_z+origin_z,phi_y+origin_y,'b')
plt.plot(phi_z+origin_z,phi_y+origin_y,'r')
Where phi_z and _y are some functions (which I have avoided posting for the sake of clarity). I want to rotate both lines about 45 degrees clockwise around the specified origin, but when I try the following code, it merely shifts the curves further along each axis rather than rotating them:
phi_z_rot = origin_z + np.cos(45) * (phi_z - origin_z) - np.sin(45) * (phi_z - origin_z)
phi_y_rot = origin_y + np.cos(45) * (phi_y - origin_y) - np.sin(45) * (phi_y - origin_y)
Can anyone tell me what I'm doing wrong? Sorry for not posting more of the functions, but hopefully it isn't necessary.

Without much information is very little what I can explicitly provide. Anyhow, you have your rotation wrong. First the angles are in degrees instead of radians, and then you use the incorrect rotation matrix.
Avoiding the translation of the coordinates, the proper rotation is as follows:
rot = np.pi/4
phi_z_rot = phi_z*np.cos(rot)+phi_y*np.sin(rot)
phi_y_rot = -phi_z*np.sin(rot)+phi_y*np.cos(rot)

Related

Rotate to face vector in Houdini with python

I am trying to make a geometry in Houdini face the same direction as a normal on a line. I am getting the normal as a vector with magnitude 1 and then using the formula angle = arccos((x dot y)/(magnitude x * magnitude y). to get the angle to rotate the object and then multiplying by 180 to convert it to degrees. for some reason this does not give a consistent result.
Am I doing something obviously wrong with my math? I am new to Houdini so I'm not sure if I might be missing something about the environment that would complicate this.
Here is the python code I am using.
#normalize and store as vector3
currentNormal = hou.Vector3(currentNormal).normalized()
previousNormal = hou.Vector3(previousNormal).normalized()
#get dot product and magnitudes
dotProd = numpy.dot(previousNormal, currentNormal)
previousMagnitude = numpy.sqrt((previousNormal[0]**2) + (previousNormal[1]**2) + (previousNormal[2]**2))
currentMagnitude = numpy.sqrt((currentNormal[0]**2) + (currentNormal[1]**2) + (currentNormal[2]**2))
nextLocationRotate = numpy.arccos(dotProd/(previousMagnitude * currentMagnitude))
nextLocationRotate = [0.0, nextLocationRotate*180, 0.0]
I am trying to get a rotation around the y axis.

How to account for radian to degrees inaccuracy

I am trying to perform a simple task using simple math in python and I suspect that the inherit error in converting from radians to degrees as a result of an error with floating point math (as garnered from another question on the topic please don't mark this as a duplicate question, it's not).
I am trying to extend a line by 500m. To do this I am taking the the endpoint coordinates from a supplied line and using the existing heading of said line to generate the coordinates of the point which is 500m in the same heading.
Heading is important in this case as it is the source of my error. Or so I suspect.
I use the following function to calculate the interior angle of my right angle triangle, built using the existing line, or in this case my hypotenuse:
def intangle(xypoints):
angle = []
for i in xypoints:
x1 = i[0][0]
x2 = i[1][0]
y1 = i[0][1]
y2 = i[1][1]
gradient = (x1 - x2)/(y1-y2)
radangle = math.atan(gradient)
angle.append((math.degrees(radangle)))
return angle
My input points are, for example:
(22732.23679147904, 6284399.7935522054)
(20848.591367954294, 6281677.926560438)
I know going into this that my angle is 35° as these coordinates are programmatically generated by a separate function and when plotted are out by around 3.75" for each KM. Another error as a result of converting radians to degrees but acceptable in its scope.
The error generated by the above function however, results in an angle that plots my new endpoint in such a place that the line is no longer perfectly straight when I connect the dots and I absolutely have to have a straight line.
How can I go about doing this differently to account for the floating point error? Is it even possible? If not, then what would be an acceptable method of extending my line by howevermany meters using euclidean geometry?
To add to this, I have already done all relevant geographic conversions and I am 100% sure that I am working on a 2D plane so the ellipsoid and such do not play a role in this at all.
Using angles is unnecessary, and there are problems in the way you do it. Using the atan will only give you angles between -pi/2 and pi/2, and you will get the same angle value for opposite directions.
You should rather use Thales:
import math
a = (22732.23679147904, 6284399.7935522054)
b = (20848.591367954294, 6281677.926560438)
def extend_line(a, b, length):
"""
Returns the coordinates of point C at length beyond B in the direction of A->B"""
ab = math.sqrt((a[0]-b[0])**2 + (a[1]-b[1])**2)
coeff = (ab + length)/ab
return (a[0] + coeff*(b[0]-a[0]), a[1] + coeff*(b[1]-a[1]) )
print(extend_line(a, b, 500))
# (20564.06031560228, 6281266.7792872535)

Polar plot Magnus effect not showing correct data

I wanted to plot the velocity equations of the flow around a rotating cylinder on a polar plot. (The equations are from "Fundamentals of Aerodynamics" by Andersen.) You can see the two equations inside the for loop statements.
I cannot for crying out loud manage to represent the calculated data onto the polar plot. I have tried every idea of mine, but arrived nowhere. I did check the data, and this one seems all correct, as it behaves how it should.
Here the code of my last attempt:
import numpy as np
import matplotlib.pyplot as plt
RadiusColumn = 1.0
VelocityInfinity = 10.0
RPM_Columns = 0.0#
ColumnOmega = (2*np.pi*RPM_Columns)/(60)#rad/s
VortexStrength = 2*np.pi*RadiusColumn**2 * ColumnOmega#rad m^2/s
NumberRadii = 6
NumberThetas = 19
theta = np.linspace(0,2*np.pi,NumberThetas)
radius = np.linspace(RadiusColumn, 10 * RadiusColumn, NumberRadii)
f = plt.figure()
ax = f.add_subplot(111, polar=True)
for r in xrange(len(radius)):
for t in xrange(len(theta)):
VelocityRadius = (1.0 - (RadiusColumn**2/radius[r]**2)) * VelocityInfinity * np.cos(theta[t])
VelocityTheta = - (1.0 + (RadiusColumn**2/radius[r]**2))* VelocityInfinity * np.sin(theta[t]) - (VortexStrength/(2*np.pi*radius[r]))
TotalVelocity = np.linalg.norm((VelocityRadius, VelocityTheta))
ax.quiver(theta[t], radius[r], theta[t] + VelocityTheta/TotalVelocity, radius[r] + VelocityRadius/TotalVelocity)
plt.show()
As you can see, I have set for now the RPMs to 0. That means that the flow should go from left to right, and be symmetric across the horizontal axis. (The flow should go around the cylinder the same way on both sides.) The result however looks more like this:
This is complete nonsense. There seems to be a vorticity, even when there is none set! Even weirder, when I only display data from 0 to pi/2, the flow changes!
As you can see from the code, I have tried to make use of unit vectors, but clearly this is not the way to go. I would appreciate any useful input.
Thanks!
The basic problem is that the .quiver method of a polar Axes object still expects its vector components in Cartesian coordinates, so you need to convert your theta and radial components to x and y yourself:
for r in range(len(radius)):
for t in range(len(theta)):
VelocityRadius = (1.0 - (RadiusColumn**2/radius[r]**2)) * VelocityInfinity * np.cos(theta[t])
VelocityTheta = - (1.0 + (RadiusColumn**2/radius[r]**2))* VelocityInfinity * np.sin(theta[t]) - (VortexStrength/(2*np.pi*radius[r]))
TotalVelocity = np.linalg.norm((VelocityRadius, VelocityTheta))
ax.quiver(theta[t], radius[r],
VelocityRadius/TotalVelocity*np.cos(theta[t])
- VelocityTheta/TotalVelocity*np.sin(theta[t]),
VelocityRadius/TotalVelocity*np.sin(theta[t])
+ VelocityTheta/TotalVelocity*np.cos(theta[t]))
plt.show()
However, you can improve your code a lot by making use of vectorization: you don't need to loop over each point to obtain what you need. So the equivalent of your code, but even clearer:
def pol2cart(th,v_th,v_r):
"""convert polar velocity components to Cartesian, return v_x,v_y"""
return v_r*np.cos(th) - v_th*np.sin(th), v_r*np.sin(th) + v_th*np.cos(th)
theta = np.linspace(0, 2*np.pi, NumberThetas, endpoint=False)
radius = np.linspace(RadiusColumn, 10 * RadiusColumn, NumberRadii)[:,None]
f = plt.figure()
ax = f.add_subplot(111, polar=True)
VelocityRadius = (1.0 - (RadiusColumn**2/radius**2)) * VelocityInfinity * np.cos(theta)
VelocityTheta = - (1.0 + (RadiusColumn**2/radius**2))* VelocityInfinity * np.sin(theta) - (VortexStrength/(2*np.pi*radius))
TotalVelocity = np.linalg.norm([VelocityRadius, VelocityTheta],axis=0)
VelocityX,VelocityY = pol2cart(theta, VelocityTheta, VelocityRadius)
ax.quiver(theta, radius, VelocityX/TotalVelocity, VelocityY/TotalVelocity)
plt.show()
Few notable changes:
I added endpoint=False to theta: since your function is periodic in 2*pi, you don't need to plot the endpoints twice. Note that this means that currently you have more visible arrows; if you want the original behaviour I suggest that you decrease NumberThetas by one.
I added [:,None] to radius: this will make it a 2d array, so later operations in the definition of the velocities will give you 2d arrays: different columns correspond to different angles, different rows correspond to different radii. quiver is compatible with array-valued input, so a single call to quiver will do your work.
Since the velocities are now 2d arrays, we need to call np.linalg.norm on essentially a 3d array, but this works as expected if we specify an axis to work over.
I defined the pol2cart auxiliary function to do the conversion from polar to Cartesian components; this is not necessary but it seems clearer to me this way.
Final remark: I suggest choosing shorter variable names, and ones that don't have CamelCase. That would probably make your coding faster too.

How to get start and end coordinates (x, y) of major axis of a rotating ellipse in opencv?

I am performing motion tracking of an object, and I am trying to identify the front and back of the object. The object is asymmetrical, which means that the centroid of the contour is closer to the front than the back. Using this information, I am approaching this as follows:
Draw contours of object
Find centroid
centroidx, centroidy = int(moments['m10']/moments['m00']), int(moments['m10']/moments['m00'])
Draw bounding ellipse
cv2.fitEllipse(contour)
Calculate major axis length as follows (and as shown in the figure)
MAx, MAy = int(0.5 * ellipseMajorAxisx*math.sin(ellipseAngle)), int(0.5 * ellipseMajorAxisy*math.cos(ellipseAngle))
Calculate beginning and ending x, y coordinates of the major axis
MAxtop, MAytop = int(ellipseCentrex + MAx), int(ellipseCentrey + MAy)
MAxbot, MAybot = int(ellipseCentrex - MAx), int(ellipseCentrey - MAy)
Identify which of the points is closer to the centroid of the contour
distancetop = math.sqrt((centroidx - MAxtop)**2 + (centroidy - MAytop)**2)
distancebot = math.sqrt((centroidx - MAxbot)**2 + (centroidy - MAybot)**2)
min(distancetop, distancebot)
The problem I am encountering is, while I get the "front" end of the ellipse correct most of the time, occasionally the point is a little bit away. As far as I have observed, this seems to be happening such that the x value is correct, but y value is different (in effect, I think this represents the major axis of an ellipse that is perpendicular to mine). I am not sure if this is an issue with opencv's calculation of angles or (more than likely) my calculations are incorrect. I do realize this is a complicated example, hope my figures help!
EDIT: When I get the wrong point, it is not from a perpendicular ellipse, but of a mirror image of my ellipse. And it happens with the x values too, not just y.
After following ssm's suggestion below, I am getting the desired point most of the time. The point still goes wrong occasionally, but "snaps back" into place soon after. For example, this is a few frames when this happens:
By the way, the above images are after "correcting" for angle by using this code:
if angle > 90:
angle = 180 - angle
If I do not do the correction, I get the wrong point at other times, as shown below for the same frames.
So it looks like I get it right for some angles with angle correction and the other angles without correction. How do I get all the right points in both conditions?
(White dot inside the ellipse is the centroid of the contour, whereas the dot on or outside the ellipse is the point I am getting)
I think your only problem is MAytop. You can consider doing the following:
if ycen<yc:
# switch MAytop and MAybot
temp = MAytop
MAytop = MAybot
MAybot = temp
You may have to do a similar check on the x - scale

Pixels and geometrical shapes - Python/PIL

I'm trying to build a basic heatmap based on points. Each point has a heat radius, and therefore is represented by a circle.
Problem is that the circle needs to be converted in a list of pixels colored based on the distance from the circle's center.
Finding it hard to find an optimal solution for many points, what I have for now is something similar to this:
for pixels in pixels:
if (pixel.x - circle.x)**2 + (pixel.y - circle.y)**2 <= circle.radius:
pixel.set_color(circle.color)
Edit:
data I have:
pixel at the center of the circle
circle radius (integer)
Any tips?
Instead of doing it pixel-by-pixel, use a higher level interface with anti-aliasing, like the aggdraw module and its ellipse(xy, pen, brush) function.
Loop over the number of color steps you want (lets say, radius/2) and use 255/number_of_steps*current_step as the alpha value for the fill color.
For plotting it is usually recommended to use the matplotlib library (e.g. using imshow for heatmaps). Of course matplotlib also supports color gradients.
However, I don't really understand what you are trying to accomplish. If you just want to draw a bunch of colored circles then pretty much any graphics library will do (e.g. using the ellipse function in PIL).
It sounds like you want to color the pixel according to their distance from the center, but your own example code suggests that the color is constant?
If you are handling your pixels by yourself and your point is to increase performances, you can just focus on the square [x - radius; x + radius] * [y - radius; y + radius] since the points of your circle live here. That will save you a lot of useless iterations, if of course you CAN focus on this region (i.e. your pixels are not just an array without index per line and column).
You can even be sure that the pixels in the square [x - radius*sqrt(2)/2; x + radius*sqrt(2)/2] * [y - radius*sqrt(2)/2; y + radius*sqrt(2)/2] must be colored, with basic trigonometry (maximum square inside the circle).
So you could do:
import math
half_sqrt = math.sqrt(2) / 2
x_max = x + half_sqrt
y_max = y + half_sqrt
for (i in range(x, x + radius + 1):
for (j in range(y, y + radius + 1):
if (x <= x_max and y <= y_max):
colorize_4_parts(i, j)
else:
pixel = get_pixel(i, j)
if (pixel.x - circle.x)**2 + (pixel.y - circle.y)**2 <= circle.radius:
# Apply same colors as above, could be a function
colorize_4_parts(i, j)
def colorize_4_parts(i, j):
# Hoping you have access to such a function get_pixel !
pixel_top_right = get_pixel(i, j)
pixel_top_right.set_color(circle.color)
pixel_top_left = get_pixel(2 * x - i, j)
pixel_top_leftt.set_color(circle.color)
pixel_bot_right = get_pixel(i, 2 * y - j)
pixel_bot_right.set_color(circle.color)
pixel_bot_left = get_pixel(2 * x - i, 2 * y - j)
pixel_bot_leftt.set_color(circle.color)
This is optimized to reduce costly computations to the minimum.
EDIT: function updated to be more efficient again: I had forgotten that we had a double symetry horizontal and vertical, so we can compute only for the top right corner !
This is a very common operation, and here's how people do it...
summary: Represent the point density on a grid, smooth this using a 2D convolution if needed (this gives your points to circles), and plot this as a heatmap using matplotlib.
In more detail: First, make a 2D grid for your heatmap, and add your data points to the grid, incrementing by the cells by 1 when a data point lands in the cell. Second, make another grid to represents the shape you want to give each point (usually people use a cylinder or gaussian or something like this). Third, convolve these two together, using, say scipy.signal.convolve2d. Finally, use matplotlib's imshow function to plot the convolution, and this will be your heatmap.
If you can't use the tools suggested in the standard approach then you might find work-arounds, but it has advantages. For example, the convolution will deal well with cases when the circles overlap.

Categories

Resources