Translate 3D points along arbitrary plane - python

I'm working with 3D PDB coordinates. I first use numpy.linalg.lstsq to solve the least squares equation, essentially giving me the coefficients of the plane (I think). I can view the plane using matplotlib, and it appears to be correct. I would like to be able to translate my 3D coordinates along the plane given by the least squares solution. For example, I would like to be able to translate points in the (X,Y) of the new plane. Would it be easier to rotate the points to be in the plane where (0,0,1) is the normal?

If I understand you question right you have a fixed point and want to move it on a plane where you have the normal vector.
Lets suppose your normal vector n is (0,0,1) and your point p is (1,1,1).
If we want to stay in the plane our translation vector t has to be normal to the normal vector.(n*t=0) where * is for the scalar product.
You said you want to keep z constant so we set t_z = 0. Then lets say you want to move your point by t_x = 1. Now you just have to solve the equation: 0*1+0*t_y+1*0=0 for t_y wich in this case is arbitraty because the equation is already 0. So your translation vector for t_x = 1 and t_z = 0 is t = (1,t_y,0).
In the general case you just fix as many coordinates of t as you need and calculate the remaining one with nt=0 equation.
That should not be too hard to implement.
So in one sentence: as long as the translation vector is perpendicular to your normal vector(nt=0) you remain in the same plane.

Related

Rotation and translation of 3D points based on a triangle in Python

I'm sure that this has been answer already but I'm still confused with the post I already found on stackoverflow, that's why I decided to post my question.
I'm not super familiar with geometry transformation (except translation but this one is easy), and I need to transform a set of 3D points based on a selection of 3 points as describe in the picture
Here's my plan so far :
Create a triangle based on 3 points (let's call it tri) -> This is OK
Calculate the centroid of the triangle formed by my 3 points -> This is this is OK
translate all the points to the origin (0,0,0) -> This is OK as well
Rotate every points so that tri's points Z coordinates are equal to 0 -> This is were I'm lost and unsure how to process (without any errors...)
I know it's not an hard issues, but if anyone knows how to process with numpy for example, I'm open :-)
Thank you for your help :-)
In step 4 what you want is to rotate the triangle so that its normal is vertical.
You need to calculate the triangle's normal first. You can do so by using the cross product between two vectors (a and b) along two sides of the triangle: N = a x b.
Then you can calculate an axis of rotation A using the cross product between the triangle's normal and the Z axis: A = N x Z.
Then you can rotate the points using axis A.
As pointed out elsewhere, the solution is not unique.

3D normal vector given 2D corner coordinates of an equilateral triangle

I'm interested in finding the normal vector of a plane given 3 2D projection/pixel coordinates of the corners of an equilateral triangle.
I have to modify my actual use case because I'm not allowed to share the details: In my use case I have a poster of an equilateral triangle pinned to the wall. I can detect the 3 corners in pixel coordinates and want to calculate the normal vector of the triangle coming straight out of the wall.
It's my intuition that there should be an analytical solution for this and I found similar questions but fail to apply them to my use case. Here are a few of my thoughts:
I thought about bringing the 2D pixel coordinates to 3D by adding a third dimension filled with zero putting it on the XY-plane. Doing the same with a reference triangle of side length 1 gives me the source and destination for finding their homography. But because all 6 z-values are 0, I don't think this would work.
I know the distance between all 3 points is the same in 3D which I want to use as a constraint to solve for the problem.
The normal can be calculated in 3D by taking the cross product of 2 sides of the triangle
Using openCV's findHomography() function requires at least 4 3D points to solve and I don't believe I can just take the mean coordinates of my 3 points in pixel space to find find the center (aka a 4th point), correct?
openCV also comes with a getAffineTransform() function that works with 3 pairs of 2D points. I tried to use it like this:
# equilateral reference triangle
tri_ref = np.array([
[0.0,0.0],
[0.5,0.866],
[1.0,0.0]
], dtype=np.float32)
# detected corners in pixel space
tri_pixel = np.array([
[0.397,0.317],
[0.441,0.848],
[0.698,0.324]
], dtype=np.float32)
A = cv2.getAffineTransform(tri_pixel, tri_ref)
img2 = cv2.warpAffine(img1, A, (400,300))
However, the transformation looks completely wrong and I still would need to know how I can calculate the normal from the transformation matrix.
You can't really get a 100% correct 3D normal vector from three 2D points.
For example , let us consider a pinhole camera system, draw a line from a point to camera, then randomly pick another point on the line we draw, you will still have the same image with the picked point.
If we add the condition that the distance between all three points are the same, you may still have at most 4 possible answers of the vector(imaging two fixed points,the possible position of the last point which have the same distance with the two fixed points will be a circle,draw a line start from the camera to attach the circle , if you cut through the circle than there is two possible position of the last point, the only condition there is only one position is true is the line is a tangent line of the circle, but most of the time this does not happen, change the fixed points so we can have at most 4 possible position of the three points)
You can't add the 4th point by just take the mean coordinates of the 3 points because you do not know where the real position of the centroid project to the image plane.

Quantify roughness of a 2D surface based on given scatter points geometrically

How to design a simple code to automatically quantify a 2D rough surface based on given scatter points geometrically? For example, to use a number, r=0 for a smooth surface, r=1 for a very rough surface and the surface is in between smooth and rough when 0 < r < 1.
To more explicitly illustrate this question, the attached figure below is used to show several sketches of 2D rough surfaces. The dots are the scattered points with given coordinates. Accordingly, every two adjacent dots can be connected and a normal vector of each segment can be computed (marked with arrow). I would like to design a function like
def roughness(x, y):
...
return r
where x and y are sequences of coordinates of each scatter point. For example, in case (a), x=[0,1,2,3,4,5,6], y=[0,1,0,1,0,1,0]; in case (b), x=[0,1,2,3,4,5], y=[0,0,0,0,0,0]. When we call the function roughness(x, y), we will get r=1 (very rough) for case (a) and r=0 (smooth) for case (b). Maybe r=0.5 (medium) for case (d). The question is refined to what appropriate components do we need to put inside the function roughness?
Some initial thoughts:
Roughness of a surface is a local concept, which we only consider within a specific range of area, i.e. only with several local points around the location of interest. To use mean of local normal vectors? This may fail: (a) and (b) are with the same mean, (0,1), but (a) is rough surface and (b) is smooth surface. To use variance of local normal vectors? This may also fail: (c) and (d) are with the same variance, but (c) is rougher than (d).
maybe something like this:
import numpy as np
def roughness(x, y):
# angles between successive points
t = np.arctan2(np.diff(y), np.diff(x))
# differences between angles
ts = np.sin(t)
tc = np.cos(t)
dt = ts[1:] * tc[:-1] - tc[1:] * ts[:-1]
# sum of squares
return np.sum(dt**2) / len(dt)
would give you something like you're asking?
Maybe you should consider a protocol definition:
1) geometric definition of the surface first
2) grant unto that geometric surface intrinsic properties.
2.a) step function can be based on quadratic curve between two peaks or two troughs with their concatenated point as the focus of the 'roughness quadratic' using the slope to define roughness in analogy to the science behind road speed-bumps.
2.b) elliptical objects can be defined by a combination of deformation analysis with centered circles on the incongruity within the body. This can be solved in many ways analogous to step functions.
2.c) flat lines: select points that deviate from the mean and do a Newtonian around with a window of 5-20 concatenated points or what ever is clever.
3) define a proper threshold that fits what ever intuition you are defining as "roughness" or apply conventions of any professional field to your liking.
This branched approach might be quicker to program, but I am certain this solution can be refactored into a Euclidean construct of 3-point ellipticals, if someone is up for a geometry problem.
The mathematical definitions of many surface parameters can be found here, which can be easily put into numpy:
https://www.keyence.com/ss/products/microscope/roughness/surface/parameters.jsp
Image (d) shows a challenge: basically you want to flatten the shape before doing the calculation. This requires prior knowledge of the type of geometry you want to fit. I found an app Gwyddion that can do this in 3D, but it can only interface with Python 2.7, not 3.
If you know which base shape lies underneath:
fit the known shape
calculate the arc distance between each two points
remap the numbers by subtracting 1) from the original data and assigning new coordinates according to 2)
perform normal 2D/3D roughness calculations

Compute integral of a "map" along a given direction

EDIT: this is for Python!
Im trying to compute an integral of a "map" to produce a line density but I want my integration direction to vary based on the map itself. Here is the idea:
Now, the test case that I am using is the following picture:
So I want to integrate along the cross direction of the "plume", and as such my axes are rotated by an angle of around 90 + theta compared to the regular x and y axes, where theta is rotation angle of the plume with respect to the (original) x axis.
I have tried rotating the entire matrix of values using ndimage.rotate and integrate the rotated picture in the normal x and y directions. This does, however, not produce the desired results for the final line density as I lose information regarding the "direction" of the plume (all integrations are done for a northern plume).
I'm sure there must be a way to perform an integration along a specified axis but I can't quite find it.
Thanks in advance!

3D rotations to connect balls and cylinders

I've been tasked with writing a python based plugin for a graph drawing program that generates an STL model of a graph. A graph being an object made up of vertices and edges, where a vertex is represented by a 3D ball (a tessellated icosahedron), and an edge is represented with a cylinder that connects with two balls at either end. The end result of the 3D model is that it will get dumped out to an STL file for 3D printing. I'm able to generate the 3D models for the balls and cylinders without any issues, but I'm having some issues generating the overall model, and getting the balls and cylinders to connect properly.
My original idea was to create tessellated icosahedrons at the origin, then translate them out to the positions of the vertices. This works fine. I then, for each edge, I would create a cylinder at the origin, rotate it to the correct angle so that it points in the correct direction, then translate it to the midpoint between the two vertices so that the ends of the cylinders are embedded in the icosahedrons. This is where things are going wrong. I'm having some difficulties getting the rotations correct. To calculate the rotations, I'm doing the following:
First, I find the angle between the two points as follows (where source and target are both vertices in the graph, belonging to the edge that I'm currently processing):
deltaX = source.x - target.x
deltaY = source.y - target.y
deltaZ = source.z - target.z
xyAngle = math.atan2(deltaX, deltaY)
xzAngle = math.atan2(deltaX, deltaZ)
yzAngle = math.atan2(deltaY, deltaZ)
The angles being calculated seem reasonable, and as far as I can tell, do actually represent the angle between the vertices. For example, if I have a vertex at (1, 1, 0) and another vertex at (3, 3, 0), the angle edge connecting them does show up as a 45 degree angle between the two vertices. (That, or -135 degrees, depending which vertex is the source and which is the target).
Once I have the angles calculated, I create a cylinder and rotate it by the angles that have been calculated, like so, using some other classes that I've created:
c = cylinder()
c.createCylinder(edgeThickness, edgeLength)
c.rotateX(-yzAngle)
c.rotateY(xzAngle)
c.rotateZ(-xyAngle)
c.translate(edgePosition.x, edgePosition.y, edgePosition.z)
(Where edgePosition is the midpoint between the two vertices in the graph, edgeThickness is the radius of the cylinder being created, and edgeLength is the distance between the two vertices).
As mentioned, its the rotating of the cylinders that doesn't work as expected. It seems to do the correct rotation on the x/y plane, but as soon as an edge has vertices that differ in all three components (x, y, and z), the rotation fails. Here's an example of a graph that differs in the x, and y components, but not in the z component:
And here's the resulting STL file, as seen in Makerware (which is used to send the 3D models to the 3D printer):
(The extra cylinder looking bit in the bottom left is something I've currently left in for testing purposes - a cylinder that points in the direction of the z axis, located at the origin).
If I take that same graph and move the middle vertex out in the z axis, so now all the edges involve angles in all three axis, I get a result something like the following:
As show in the app:
The resulting STL file, as show in Makerware:
...and that same model as viewed from the side:
As you can see, the cylinders definitely aren't meeting up with the balls like I thought they would. My question is this: Is my approach to doing this flawed, or is it some small but critical mistake that I'm making somewhere in my rotations? I'm pretty sure it isn't a problem with the rotation functions themselves, as I've been able to independently verify that they work as expected. I also tried creating a rotate function that takes in a yaw, pitch, and roll and does all three at once, and it seemed to generate the same result, like so:
c.rotateYawPitchRoll(xzAngle, -yzAngle, -xyAngle)
So... anyone have any ideas on what I might be doing wrong?
UPDATE: As joojaa pointed out, it was a combination of calculating the correct angles as well as the order that they were applied. In order to get things working, I first calculate the rotation on the x axis, as follows:
zyAngle = math.atan2(deltaVector.z, deltaVector.y)
where deltaVector is the difference between the target and source vectors. This rotation is not yet applied though! The next step is to calculate the rotation on the y axis, as follows:
angle = vector.angleBetweenVectors(vector(target.x - source.x, target.y - source.y, target.z - source.z), vector(target.x - source.x, target.y - source.y, 0.0))
Once both rotations are calculated, they are then applied... in the reverse order! First, the x, then the y:
c.rotateY(angle)
c.rotateX(-zyAngle) #... where c is a cylinder object
There still seems to be a few bugs, but this seems to at least work for a simple test case.
Rotation happens in successive order, so the angles affect each other. It is not possible to use a Euler model to rotate them at once. This is why you can not just calculate the rotations based on the first static situation. Just imagine turning a cube so that it is standing on its corner upright. Yes the first rotation is 45 but the second is not since the cube is already turned by that time (draw a each step of the sequence and see what happens). Space rotations aren't trivial.
So you need to rotate one angle then re calculate the second angle and so forth. This is also why your first rotation works right. You only need 2 rotations unless your interested in making sure the rotation around the shaft has a certain direction.
I would suggest you use axis angles or matrices instead to do this. Mainly because in axis angles this is trivial the angle is the dot between the along tube start and end vectors and the axis is the cross between those 2. You can then convert those to Euler angles if you need. But probably you can just use the matrix directly. For ideas on how conversions and how the rotation could directly be calculated see: transformations.py by Christoph Gohlke. Also see the accompanying c source.
I think i need to expand this answer a bit
There is a really easy way out for this question that sidesteps all your and many other persons problems. The answer is do not use Euler angle rotation. Ive used a lot of brainpower to try to explain Euler rotations to problems that are ultimately solved more easily without Euler rotations. To justify i will leave just one reason for this if you want more think up of some more answers.
The reason most to use Euler rotation sequences is that you probably don't understand Euler angles. There are in fact only a handful of situations where they are good. No self respecting programmer uses Euler rotations to solve this issue. What you do is you use vector math instead.
So you have the direction vector from the source to target which is usually calculated:
along = normalize(target-source)
this is simply one of your matrix rows (or column notation is up to model maker), the one that corresponds to your cylinders original direction (the rows are just x y z w), then you need another vector perpendicular to this one. Choose a arbitrary vector like up (or left if your along is pointing close to up). cross product this up vector by your along for the second row direction. and finally put your source as the last row with 1 in the last column. Done fully formed affine matrix describing the cylinders prition. Much easier to understand since you can draw the vectors.
There are shorter ways but this one is easy to understand.

Categories