ModuleGeometry
Contains a bunch of useful function for geometric transformations and measurements. Usually these don't need to be called by users of ART, but they may be useful.
Some general conventions:
- As much as possible, avoid having lists of points or vectors transiting between functions. Instead, use the Vector and Point classes defined at the end of this file.
- Functions operating on Rays should preferentially operate on lists of Rays, not individual Rays. The reason for that is that it's fairly rare to manipluate a single Ray, and it's easier to just put it in a single-element list and call the function that way.
Created in 2019
@author: Anthony Guillaume + Stefan Haessler + André Kalouguine
1""" 2Contains a bunch of useful function for geometric transformations and measurements. 3Usually these don't need to be called by users of ART, but they may be useful. 4 5Some general conventions: 6- As much as possible, avoid having lists of points or vectors transiting between functions. 7 Instead, use the Vector and Point classes defined at the end of this file. 8- Functions operating on Rays should preferentially operate on lists of Rays, not individual Rays. 9 The reason for that is that it's fairly rare to manipluate a single Ray, and it's easier to 10 just put it in a single-element list and call the function that way. 11 12Created in 2019 13 14@author: Anthony Guillaume + Stefan Haessler + André Kalouguine 15""" 16# %% Modules 17import numpy as np 18import quaternion 19from functools import singledispatch 20import logging 21import math 22 23logger = logging.getLogger(__name__) 24 25# %% Points and Vectors classes 26class Vector(np.ndarray): 27 def __new__(cls, input_array): 28 input_array = np.asarray(input_array) 29 if input_array.ndim > 1: 30 return VectorArray(input_array) 31 obj = input_array.view(cls) # Ensure we are viewing as `Vector` 32 return obj 33 @property 34 def norm(self): 35 return np.linalg.norm(self) 36 def normalized(self): 37 return self / self.norm 38 def __add__(self, other): 39 if isinstance(other, Point): 40 return Point(super().__add__(other)) 41 return Vector(super().__add__(other)) 42 def __sub__(self, other): 43 if isinstance(other, Point): 44 return Point(super().__sub__(other)) 45 return Vector(super().__sub__(other)) 46 def translate(self, vector): 47 return self 48 def rotate(self,q): 49 return Vector((q*np.quaternion(0,*self)*q.conj()).imag) 50 def from_basis(self, r0, r, q): 51 return self.rotate(q) 52 def to_basis(self, r0, r, q): 53 return self.rotate(q.conj()) 54 def _add_dimension(self, value=0): 55 return Vector(np.concatenate((self, [value]))) 56 def __hash__(self) -> int: 57 vector_tuple = tuple(self.reshape(1, -1)[0]) 58 return hash(vector_tuple) 59class VectorArray(np.ndarray): 60 def __new__(cls, input_array): 61 input_array = np.asarray(input_array) 62 if input_array.ndim == 1: 63 return Vector(input_array) 64 obj = input_array.view(cls) # Ensure we are viewing as `Vector` 65 return obj 66 @property 67 def norm(self): 68 return np.linalg.norm(self, axis=1) 69 def __getitem__(self, index): 70 return Vector(super().__getitem__(index)) 71 def normalized(self): 72 return self / self.norm[:, np.newaxis] 73 def __add__(self, other): 74 if isinstance(other, Point): 75 return PointArray(super().__add__(other)) 76 return VectorArray(super().__add__(other)) 77 def __sub__(self, other): 78 if isinstance(other, Point): 79 return PointArray(super().__sub__(other)) 80 return VectorArray(super().__sub__(other)) 81 def translate(self, vector): 82 return self 83 def rotate(self,q): 84 return VectorArray(quaternion.rotate_vectors(q, self)) 85 def from_basis(self, r0, r, q): 86 return self.rotate(q) 87 def to_basis(self, r0, r, q): 88 return self.rotate(q.conj()) 89 def _add_dimension(self, values=0): 90 if values == 0: 91 values = np.zeros((self.shape[0], 1)) 92 if not isinstance(values, np.ndarray): 93 values = np.array(values) 94 return VectorArray(np.concatenate((self, values), axis=1)) 95 96class Point(np.ndarray): 97 def __new__(cls, input_array): 98 input_array = np.asarray(input_array) 99 if input_array.ndim > 1: 100 return PointArray(input_array) 101 obj = input_array.view(cls) # Ensure we are viewing as `Point` 102 return obj 103 def __add__(self, other): 104 return Point(super().__add__(other)) 105 def __sub__(self, other): 106 return Vector(super().__sub__(other)) 107 def translate(self, vector): 108 return Point(super()._add__(vector)) 109 def rotate(self,q): 110 return (self-Origin).rotate(q) 111 def from_basis(self, r0, r, q): 112 return Point([0,0,0]) + r0 + Vector(self-r0).rotate(q) + r 113 def to_basis(self, r0, r, q): 114 return Point([0,0,0]) + r0 + Vector(self-r0-r).rotate(q.conj()) 115 def _add_dimension(self, value=0): 116 return Point(np.concatenate((self, [value]))) 117 def __hash__(self) -> int: 118 point_tuple = tuple(self.reshape(1, -1)[0]) 119 return hash(point_tuple) 120 121class PointArray(np.ndarray): 122 def __new__(cls, input_array): 123 input_array = np.asarray(input_array) 124 if input_array.ndim == 1: 125 return Point(input_array) 126 obj = input_array.view(cls) # Ensure we are viewing as `Point` 127 return obj 128 def __getitem__(self, index): 129 return Point(super().__getitem__(index)) 130 def __add__(self, other): 131 return PointArray(super().__add__(other)) 132 def __sub__(self, other): 133 return VectorArray(super().__sub__(other)) 134 def translate(self, vector): 135 return PointArray(super().__add__(vector)) 136 def rotate(self,q): 137 return PointArray(quaternion.rotate_vectors(q, self)) 138 def from_basis(self, r0, r, q): 139 return Point([0,0,0]) + r0 + VectorArray(self-r0).rotate(q) + r 140 #return PointArray([Point([0,0,0]) + r0 + (p-r0).rotate(q) + r for p in self]) 141 def to_basis(self, r0, r, q): 142 return Point([0,0,0]) + r0 + VectorArray(self-r0-r).rotate(q.conj()) 143 #return PointArray([Point([0,0,0]) + r0 + (p-r0-r).rotate(q.conj()) for p in self]) 144 def _add_dimension(self, values=0): 145 if values == 0: 146 values = np.zeros((self.shape[0], 1)) 147 if not isinstance(values, np.ndarray): 148 values = np.array(values) 149 return PointArray(np.concatenate((self, values), axis=1)) 150 151Origin = Point([0,0,0]) 152# %% More traditional vector operations that don't belong in the classes 153def Normalize(vector): 154 """ 155 Normalize Vector. 156 Obsolete, use use the mgeo.Vector class instead as it has a `normalize` method. 157 """ 158 return vector / np.linalg.norm(vector) 159 160def VectorPerpendicular(vector): 161 """ 162 Find a perpendicular 3D vector in some arbitrary direction 163 Undefined behavior, use with caution. There is no unique perpendicular vector to a 3D vector. 164 """ 165 logger.warning("VectorPerpendicular is undefined behavior. There is no unique perpendicular vector to a 3D vector.") 166 if abs(vector[0]) < 1e-15: 167 return Vector([1, 0, 0]) 168 if abs(vector[1]) < 1e-15: 169 return Vector([0, 1, 0]) 170 if abs(vector[2]) < 1e-15: 171 return Vector([0, 0, 1]) 172 173 # set arbitrarily a = b =1 174 return Vector([1, 1, -1.0 * (vector[0] + vector[1]) / vector[2]]).normalized() 175 176def AngleBetweenTwoVectors(U, V): 177 """ 178 Return the angle in radians between the vectors U and V ; formula from W.Kahan 179 Value in radians between 0 and pi. 180 """ 181 u = np.linalg.norm(U) 182 v = np.linalg.norm(V) 183 return 2 * np.arctan2(np.linalg.norm(U * v - V * u), np.linalg.norm(U * v + V * u)) 184 185def SymmetricalVector(V, SymmetryAxis): 186 """ 187 Return the symmetrical vector to V 188 """ 189 q = QRotationAroundAxis(SymmetryAxis, np.pi) 190 return V.rotate(q) 191 192def normal_add(N1, N2): 193 """ 194 Simple function that takes in two normal vectors of a deformation and calculates 195 the total normal vector if the two deformations were individually applied. 196 Be very careful, this *only* works when the surface is z = f(x,y) and when 197 the deformation is small. 198 Might be made obsolete when shifting to a modernised deformation system. 199 """ 200 normal1 = N1.normalized() 201 normal2 = N2.normalized() 202 grad1 = -normal1[:2] / normal1[2] 203 grad2 = -normal2[:2] / normal2[2] 204 grad = grad1 + grad2 205 total_normal = np.append(-grad, 1) 206 return Vector(total_normal).normalized() 207 208# %% Intersection finding 209def IntersectionLinePlane(A, u, P, n): 210 """ 211 Return the intersection point between a line and a plane. 212 A is a point of the line, u a vector of the line ; P is a point of the plane, n a normal vector 213 Line's equation : OM = u*t + OA , t a real 214 Plane's equation : n.OP - n.OM = 0 215 """ 216 t = np.dot(n, -A + P) / np.dot(u, n) 217 I = u * t + A 218 return I 219 220def IntersectionRayListZPlane(RayList, Z=np.array([0])): 221 """ 222 Return the intersection of a list of rays with a different planes with equiations z = Z[i] 223 Basically, by default it returns the intersection of the rays with the Z=0 plane but you can 224 give it a few values of Z and it should be faster than calling it multiple times. 225 This should let us quickly find the optimal position of the detector as well as trace the caustics. 226 If a ray does not intersect the plane... it should replace that point with a NaN. 227 """ 228 Positions = np.vstack([i.point for i in RayList]) 229 Vectors = np.vstack([i.vector for i in RayList]) 230 non_zero = Vectors[:,2] != 0 231 Positions = Positions[non_zero] 232 Vectors = Vectors[non_zero] 233 Z = Z[:, np.newaxis] 234 A = Positions[:,2]-Z 235 B = -Vectors[:,2] 236 #times = (Positions[:,2]-Z)/Vectors[:,2] 237 #return A,B 238 with np.errstate(divide='ignore', invalid='ignore'): 239 times = np.divide(A, B, where=(B != 0), out=np.full_like(A, np.nan)) 240 #times[times < 0] = np.nan # Set negative results to NaN 241 #return times 242 #positive_times = times >= 0 243 intersect_positions = Positions[:, :2] + times[:, :, np.newaxis] * Vectors[:, :2] 244 result = [] 245 for i in range(Z.shape[0]): 246 # For each plane, we find the intersection points 247 #valid_intersections = intersect_positions[i][positive_times[i]] 248 valid_intersections = intersect_positions[i] 249 result.append(PointArray(valid_intersections)) 250 return result 251 252 253# %% Geometrical utilities for plotting 254def SpiralVogel(NbPoint, Radius): 255 """ 256 Return a NbPoint x 2 matrix of 2D points representative of Vogel's spiral with radius Radius 257 Careful, contrary to most of the code, this is *not* in the 258 ARTcore.Vector or ARTcore.Point format. It is a simple numpy array. 259 The reason is that this is a utility function that can be used both to define directions 260 and to generate grids of points. 261 """ 262 GoldenAngle = np.pi * (3 - np.sqrt(5)) 263 r = np.sqrt(np.arange(NbPoint) / NbPoint) * Radius 264 265 theta = GoldenAngle * np.arange(NbPoint) 266 267 Matrix = np.zeros((NbPoint, 2)) 268 Matrix[:, 0] = np.cos(theta) 269 Matrix[:, 1] = np.sin(theta) 270 Matrix = Matrix * r.reshape((NbPoint, 1)) 271 272 return Matrix 273 274def find_hull(points): 275 """ 276 Find the convex hull of a set of points using a greedy algorithm. 277 This is used to create a polygon that encloses the points. 278 """ 279 # start from leftmost point 280 current_point = min(range(len(points)), key=lambda i: points[i][0]) 281 # initialize hull with current point 282 hull = [current_point] 283 # initialize list of linked points 284 linked = [] 285 # continue until all points have been linked 286 while len(linked) < len(points) - 1: 287 # initialize minimum distance and closest point 288 min_distance = math.inf 289 closest_point = None 290 # find closest unlinked point to current point 291 for i, point in enumerate(points): 292 if i not in linked: 293 distance = math.dist(points[current_point], point) 294 if distance < min_distance: 295 min_distance = distance 296 closest_point = i 297 # add closest point to hull and linked list 298 hull.append(closest_point) 299 linked.append(closest_point) 300 # update current point 301 current_point = closest_point 302 # add link between last point and first point 303 hull.append(hull[0]) 304 # convert hull to a list of pairs of indices 305 indices = [[hull[i], hull[i + 1]] for i in range(len(hull) - 1)] 306 return indices 307 308 309# %% Solvers and utilities for solving equations 310def SolverQuadratic(a, b, c): 311 """ 312 Solve the quadratic equation a*x^2 + b*x +c = 0 ; keep only real solutions 313 """ 314 Solution = np.roots([a, b, c]) 315 RealSolution = [] 316 317 for k in range(len(Solution)): 318 if abs(Solution[k].imag) < 1e-15: 319 RealSolution.append(Solution[k].real) 320 321 return RealSolution 322 323 324def SolverQuartic(a, b, c, d, e): 325 """ 326 Solve the quartic equation a*x^4 + b*x^3 +c*x^2 + d*x + e = 0 ; keep only real solutions 327 """ 328 Solution = np.roots([a, b, c, d, e]) 329 RealSolution = [] 330 331 for k in range(len(Solution)): 332 if abs(Solution[k].imag) < 1e-15: 333 RealSolution.append(Solution[k].real) 334 335 return RealSolution 336 337 338def KeepPositiveSolution(SolutionList): 339 """ 340 Keep only positive solution (numbers) in the list 341 """ 342 PositiveSolutionList = [] 343 epsilon = 1e-12 344 for k in SolutionList: 345 if k > epsilon: 346 PositiveSolutionList.append(k) 347 348 return PositiveSolutionList 349 350 351def KeepNegativeSolution(SolutionList): 352 """ 353 Keep only positive solution (numbers) in the list 354 """ 355 NegativeSolutionList = [] 356 epsilon = -1e-12 357 for k in SolutionList: 358 if k < epsilon: 359 NegativeSolutionList.append(k) 360 361 return NegativeSolutionList 362 363 364# %% Point geometry tools 365def ClosestPoint(A: Point, Points: PointArray): 366 """ 367 Given a reference point A and an array of points, return the index of the point closest to A 368 """ 369 distances = (Points-A).norm 370 return np.argmin(distances) 371 372def DiameterPointArray(Points: PointArray): 373 """ 374 Return the diameter of the smallest circle (for 2D points) 375 or sphere (3D points) including all the points. 376 """ 377 if len(Points) == 0: 378 return None 379 return float(np.ptp(Points, axis=0).max()) 380 381def CentrePointList(Points): 382 """ 383 Shift all 2D-points in PointList so as to center the point-cloud on the origin [0,0]. 384 """ 385 return Points - np.mean(Points, axis=0) 386 387# %% Solid object orientation 388 389def RotateSolid(Object, q): 390 """ 391 Rotate object around basepoint by quaternion q 392 """ 393 Object.q = q*Object.q 394 395def TranslateSolid(Object, T): 396 """ 397 Translate object by vector T 398 """ 399 Object.r = Object.r + T 400 401def RotateSolidAroundInternalPointByQ(Object, q, P): 402 """ 403 Rotate object around P by quaternion q where P is in the object's frame 404 """ 405 pass #TODO 406 407def RotateSolidAroundExternalPointByQ(Object, q, P): 408 """Rotate object around P by quaternion q, where P is in the global frame""" 409 pass #TODO 410 411 412# %% Signed distance functions 413 414def SDF_Rectangle(Point, SizeX, SizeY): 415 """Signed distance function for a rectangle centered at the origin""" 416 d = np.abs(Point[:2]) - np.array([SizeX, SizeY]) / 2 417 return (np.linalg.norm(np.maximum(d, 0)) + np.min(np.max(d, 0)))/2 418 419def SDF_Circle(Point, Radius): 420 """Signed distance function for a circle centered at the origin""" 421 return np.linalg.norm(Point[:2]) - Radius 422 423def Union_SDF(SDF1, SDF2): 424 """Union of two signed distance functions""" 425 return np.minimum(SDF1, SDF2) 426 427def Difference_SDF(SDF1, SDF2): 428 """Difference of two signed distance functions""" 429 return np.maximum(SDF1, -SDF2) 430 431def Intersection_SDF(SDF1, SDF2): 432 """Intersection of two signed distance functions""" 433 return np.maximum(SDF1, SDF2) 434 435 436 437# %% Quaternion calculations 438def QRotationAroundAxis(Axis, Angle): 439 """ 440 Return quaternion for rotation by Angle (in rad) around Axis 441 """ 442 rot_axis = Normalize(np.array([0.0] + Axis)) 443 axis_angle = (Angle * 0.5) * rot_axis 444 qlog = np.quaternion(*axis_angle) 445 q = np.exp(qlog) 446 return q 447 448def QRotationVector2Vector(Vector1, Vector2): 449 """ 450 Return a possible quaternion (among many) that would rotate Vector1 into Vector2. 451 Undefined behavior, use with caution. There is no unique quaternion that rotates one vector into another. 452 """ 453 Vector1 = Normalize(Vector1) 454 Vector2 = Normalize(Vector2) 455 a = np.cross(Vector1, Vector2) 456 return np.quaternion(1 + np.dot(Vector1, Vector2), *a).normalized() 457 458def QRotationVectorPair2VectorPair(InitialVector1, Vector1, InitialVector2, Vector2): 459 """ 460 Return the only quaternion that rotates InitialVector1 to Vector1 and InitialVector2 to Vector2. 461 Please ensure orthogonality two input and two output vectors. 462 """ 463 Vector1 = Normalize(Vector1) 464 Vector2 = Normalize(Vector2) 465 Vector3 = Normalize(np.cross(Vector1,Vector2)) 466 InitialVector1 = Normalize(InitialVector1) 467 InitialVector2 = Normalize(InitialVector2) 468 InitialVector3 = Normalize(np.cross(InitialVector1,InitialVector2)) 469 rot_2Initial = np.zeros((3,3)) 470 rot_2Initial[:,0] = InitialVector1 471 rot_2Initial[:,1] = InitialVector2 472 rot_2Initial[:,2] = InitialVector3 473 rot_2Final = np.zeros((3,3)) 474 rot_2Final[:,0] = Vector1 475 rot_2Final[:,1] = Vector2 476 rot_2Final[:,2] = Vector3 477 q2Init = quaternion.from_rotation_matrix(rot_2Initial) 478 q2Fin = quaternion.from_rotation_matrix(rot_2Final) 479 return (q2Fin/q2Init).normalized() 480 481 482# %% RayList stuff 483def RotationRayList(RayList, q): 484 """Like RotationPointList but with a list of Ray objects""" 485 return [i.rotate(q) for i in RayList] 486 487def TranslationRayList(RayList, T): 488 """Translate a RayList by vector T""" 489 return [i.translate(T) for i in RayList]
27class Vector(np.ndarray): 28 def __new__(cls, input_array): 29 input_array = np.asarray(input_array) 30 if input_array.ndim > 1: 31 return VectorArray(input_array) 32 obj = input_array.view(cls) # Ensure we are viewing as `Vector` 33 return obj 34 @property 35 def norm(self): 36 return np.linalg.norm(self) 37 def normalized(self): 38 return self / self.norm 39 def __add__(self, other): 40 if isinstance(other, Point): 41 return Point(super().__add__(other)) 42 return Vector(super().__add__(other)) 43 def __sub__(self, other): 44 if isinstance(other, Point): 45 return Point(super().__sub__(other)) 46 return Vector(super().__sub__(other)) 47 def translate(self, vector): 48 return self 49 def rotate(self,q): 50 return Vector((q*np.quaternion(0,*self)*q.conj()).imag) 51 def from_basis(self, r0, r, q): 52 return self.rotate(q) 53 def to_basis(self, r0, r, q): 54 return self.rotate(q.conj()) 55 def _add_dimension(self, value=0): 56 return Vector(np.concatenate((self, [value]))) 57 def __hash__(self) -> int: 58 vector_tuple = tuple(self.reshape(1, -1)[0]) 59 return hash(vector_tuple)
ndarray(shape, dtype=float, buffer=None, offset=0, strides=None, order=None)
An array object represents a multidimensional, homogeneous array of fixed-size items. An associated data-type object describes the format of each element in the array (its byte-order, how many bytes it occupies in memory, whether it is an integer, a floating point number, or something else, etc.)
Arrays should be constructed using array, zeros or empty (refer
to the See Also section below). The parameters given here refer to
a low-level method (ndarray(...)) for instantiating an array.
For more information, refer to the numpy module and examine the
methods and attributes of an array.
Parameters
(for the __new__ method; see Notes below)
shape : tuple of ints Shape of created array. dtype : data-type, optional Any object that can be interpreted as a numpy data type. buffer : object exposing buffer interface, optional Used to fill the array with data. offset : int, optional Offset of array data in buffer. strides : tuple of ints, optional Strides of data in memory. order : {'C', 'F'}, optional Row-major (C-style) or column-major (Fortran-style) order.
Attributes
T : ndarray
Transpose of the array.
data : buffer
The array's elements, in memory.
dtype : dtype object
Describes the format of the elements in the array.
flags : dict
Dictionary containing information related to memory use, e.g.,
'C_CONTIGUOUS', 'OWNDATA', 'WRITEABLE', etc.
flat : numpy.flatiter object
Flattened version of the array as an iterator. The iterator
allows assignments, e.g., x.flat = 3 (See ndarray.flat for
assignment examples; TODO).
imag : ndarray
Imaginary part of the array.
real : ndarray
Real part of the array.
size : int
Number of elements in the array.
itemsize : int
The memory use of each array element in bytes.
nbytes : int
The total number of bytes required to store the array data,
i.e., itemsize * size.
ndim : int
The array's number of dimensions.
shape : tuple of ints
Shape of the array.
strides : tuple of ints
The step-size required to move from one element to the next in
memory. For example, a contiguous (3, 4) array of type
int16 in C-order has strides (8, 2). This implies that
to move from element to element in memory requires jumps of 2 bytes.
To move from row-to-row, one needs to jump 8 bytes at a time
(2 * 4).
ctypes : ctypes object
Class containing properties of the array needed for interaction
with ctypes.
base : ndarray
If the array is a view into another array, that array is its base
(unless that array is also a view). The base array is where the
array data is actually stored.
See Also
array : Construct an array.
zeros : Create an array, each element of which is zero.
empty : Create an array, but leave its allocated memory unchanged (i.e.,
it contains "garbage").
dtype : Create a data-type.
numpy.typing.NDArray : An ndarray alias :term:generic <generic type>
w.r.t. its dtype.type <numpy.dtype.type>.
Notes
There are two modes of creating an array using __new__:
- If
bufferis None, then onlyshape,dtype, andorderare used. - If
bufferis an object exposing the buffer interface, then all keywords are interpreted.
No __init__ method is needed because the array is fully initialized
after the __new__ method.
Examples
These examples illustrate the low-level ndarray constructor. Refer
to the See Also section above for easier ways of constructing an
ndarray.
First mode, buffer is None:
>>> np.ndarray(shape=(2,2), dtype=float, order='F')
array([[0.0e+000, 0.0e+000], # random
[ nan, 2.5e-323]])
Second mode:
>>> np.ndarray((2,), buffer=np.array([1,2,3]),
... offset=np.int_().itemsize,
... dtype=int) # offset = 1*itemsize, i.e. skip first element
array([2, 3])
60class VectorArray(np.ndarray): 61 def __new__(cls, input_array): 62 input_array = np.asarray(input_array) 63 if input_array.ndim == 1: 64 return Vector(input_array) 65 obj = input_array.view(cls) # Ensure we are viewing as `Vector` 66 return obj 67 @property 68 def norm(self): 69 return np.linalg.norm(self, axis=1) 70 def __getitem__(self, index): 71 return Vector(super().__getitem__(index)) 72 def normalized(self): 73 return self / self.norm[:, np.newaxis] 74 def __add__(self, other): 75 if isinstance(other, Point): 76 return PointArray(super().__add__(other)) 77 return VectorArray(super().__add__(other)) 78 def __sub__(self, other): 79 if isinstance(other, Point): 80 return PointArray(super().__sub__(other)) 81 return VectorArray(super().__sub__(other)) 82 def translate(self, vector): 83 return self 84 def rotate(self,q): 85 return VectorArray(quaternion.rotate_vectors(q, self)) 86 def from_basis(self, r0, r, q): 87 return self.rotate(q) 88 def to_basis(self, r0, r, q): 89 return self.rotate(q.conj()) 90 def _add_dimension(self, values=0): 91 if values == 0: 92 values = np.zeros((self.shape[0], 1)) 93 if not isinstance(values, np.ndarray): 94 values = np.array(values) 95 return VectorArray(np.concatenate((self, values), axis=1))
ndarray(shape, dtype=float, buffer=None, offset=0, strides=None, order=None)
An array object represents a multidimensional, homogeneous array of fixed-size items. An associated data-type object describes the format of each element in the array (its byte-order, how many bytes it occupies in memory, whether it is an integer, a floating point number, or something else, etc.)
Arrays should be constructed using array, zeros or empty (refer
to the See Also section below). The parameters given here refer to
a low-level method (ndarray(...)) for instantiating an array.
For more information, refer to the numpy module and examine the
methods and attributes of an array.
Parameters
(for the __new__ method; see Notes below)
shape : tuple of ints Shape of created array. dtype : data-type, optional Any object that can be interpreted as a numpy data type. buffer : object exposing buffer interface, optional Used to fill the array with data. offset : int, optional Offset of array data in buffer. strides : tuple of ints, optional Strides of data in memory. order : {'C', 'F'}, optional Row-major (C-style) or column-major (Fortran-style) order.
Attributes
T : ndarray
Transpose of the array.
data : buffer
The array's elements, in memory.
dtype : dtype object
Describes the format of the elements in the array.
flags : dict
Dictionary containing information related to memory use, e.g.,
'C_CONTIGUOUS', 'OWNDATA', 'WRITEABLE', etc.
flat : numpy.flatiter object
Flattened version of the array as an iterator. The iterator
allows assignments, e.g., x.flat = 3 (See ndarray.flat for
assignment examples; TODO).
imag : ndarray
Imaginary part of the array.
real : ndarray
Real part of the array.
size : int
Number of elements in the array.
itemsize : int
The memory use of each array element in bytes.
nbytes : int
The total number of bytes required to store the array data,
i.e., itemsize * size.
ndim : int
The array's number of dimensions.
shape : tuple of ints
Shape of the array.
strides : tuple of ints
The step-size required to move from one element to the next in
memory. For example, a contiguous (3, 4) array of type
int16 in C-order has strides (8, 2). This implies that
to move from element to element in memory requires jumps of 2 bytes.
To move from row-to-row, one needs to jump 8 bytes at a time
(2 * 4).
ctypes : ctypes object
Class containing properties of the array needed for interaction
with ctypes.
base : ndarray
If the array is a view into another array, that array is its base
(unless that array is also a view). The base array is where the
array data is actually stored.
See Also
array : Construct an array.
zeros : Create an array, each element of which is zero.
empty : Create an array, but leave its allocated memory unchanged (i.e.,
it contains "garbage").
dtype : Create a data-type.
numpy.typing.NDArray : An ndarray alias :term:generic <generic type>
w.r.t. its dtype.type <numpy.dtype.type>.
Notes
There are two modes of creating an array using __new__:
- If
bufferis None, then onlyshape,dtype, andorderare used. - If
bufferis an object exposing the buffer interface, then all keywords are interpreted.
No __init__ method is needed because the array is fully initialized
after the __new__ method.
Examples
These examples illustrate the low-level ndarray constructor. Refer
to the See Also section above for easier ways of constructing an
ndarray.
First mode, buffer is None:
>>> np.ndarray(shape=(2,2), dtype=float, order='F')
array([[0.0e+000, 0.0e+000], # random
[ nan, 2.5e-323]])
Second mode:
>>> np.ndarray((2,), buffer=np.array([1,2,3]),
... offset=np.int_().itemsize,
... dtype=int) # offset = 1*itemsize, i.e. skip first element
array([2, 3])
97class Point(np.ndarray): 98 def __new__(cls, input_array): 99 input_array = np.asarray(input_array) 100 if input_array.ndim > 1: 101 return PointArray(input_array) 102 obj = input_array.view(cls) # Ensure we are viewing as `Point` 103 return obj 104 def __add__(self, other): 105 return Point(super().__add__(other)) 106 def __sub__(self, other): 107 return Vector(super().__sub__(other)) 108 def translate(self, vector): 109 return Point(super()._add__(vector)) 110 def rotate(self,q): 111 return (self-Origin).rotate(q) 112 def from_basis(self, r0, r, q): 113 return Point([0,0,0]) + r0 + Vector(self-r0).rotate(q) + r 114 def to_basis(self, r0, r, q): 115 return Point([0,0,0]) + r0 + Vector(self-r0-r).rotate(q.conj()) 116 def _add_dimension(self, value=0): 117 return Point(np.concatenate((self, [value]))) 118 def __hash__(self) -> int: 119 point_tuple = tuple(self.reshape(1, -1)[0]) 120 return hash(point_tuple)
ndarray(shape, dtype=float, buffer=None, offset=0, strides=None, order=None)
An array object represents a multidimensional, homogeneous array of fixed-size items. An associated data-type object describes the format of each element in the array (its byte-order, how many bytes it occupies in memory, whether it is an integer, a floating point number, or something else, etc.)
Arrays should be constructed using array, zeros or empty (refer
to the See Also section below). The parameters given here refer to
a low-level method (ndarray(...)) for instantiating an array.
For more information, refer to the numpy module and examine the
methods and attributes of an array.
Parameters
(for the __new__ method; see Notes below)
shape : tuple of ints Shape of created array. dtype : data-type, optional Any object that can be interpreted as a numpy data type. buffer : object exposing buffer interface, optional Used to fill the array with data. offset : int, optional Offset of array data in buffer. strides : tuple of ints, optional Strides of data in memory. order : {'C', 'F'}, optional Row-major (C-style) or column-major (Fortran-style) order.
Attributes
T : ndarray
Transpose of the array.
data : buffer
The array's elements, in memory.
dtype : dtype object
Describes the format of the elements in the array.
flags : dict
Dictionary containing information related to memory use, e.g.,
'C_CONTIGUOUS', 'OWNDATA', 'WRITEABLE', etc.
flat : numpy.flatiter object
Flattened version of the array as an iterator. The iterator
allows assignments, e.g., x.flat = 3 (See ndarray.flat for
assignment examples; TODO).
imag : ndarray
Imaginary part of the array.
real : ndarray
Real part of the array.
size : int
Number of elements in the array.
itemsize : int
The memory use of each array element in bytes.
nbytes : int
The total number of bytes required to store the array data,
i.e., itemsize * size.
ndim : int
The array's number of dimensions.
shape : tuple of ints
Shape of the array.
strides : tuple of ints
The step-size required to move from one element to the next in
memory. For example, a contiguous (3, 4) array of type
int16 in C-order has strides (8, 2). This implies that
to move from element to element in memory requires jumps of 2 bytes.
To move from row-to-row, one needs to jump 8 bytes at a time
(2 * 4).
ctypes : ctypes object
Class containing properties of the array needed for interaction
with ctypes.
base : ndarray
If the array is a view into another array, that array is its base
(unless that array is also a view). The base array is where the
array data is actually stored.
See Also
array : Construct an array.
zeros : Create an array, each element of which is zero.
empty : Create an array, but leave its allocated memory unchanged (i.e.,
it contains "garbage").
dtype : Create a data-type.
numpy.typing.NDArray : An ndarray alias :term:generic <generic type>
w.r.t. its dtype.type <numpy.dtype.type>.
Notes
There are two modes of creating an array using __new__:
- If
bufferis None, then onlyshape,dtype, andorderare used. - If
bufferis an object exposing the buffer interface, then all keywords are interpreted.
No __init__ method is needed because the array is fully initialized
after the __new__ method.
Examples
These examples illustrate the low-level ndarray constructor. Refer
to the See Also section above for easier ways of constructing an
ndarray.
First mode, buffer is None:
>>> np.ndarray(shape=(2,2), dtype=float, order='F')
array([[0.0e+000, 0.0e+000], # random
[ nan, 2.5e-323]])
Second mode:
>>> np.ndarray((2,), buffer=np.array([1,2,3]),
... offset=np.int_().itemsize,
... dtype=int) # offset = 1*itemsize, i.e. skip first element
array([2, 3])
122class PointArray(np.ndarray): 123 def __new__(cls, input_array): 124 input_array = np.asarray(input_array) 125 if input_array.ndim == 1: 126 return Point(input_array) 127 obj = input_array.view(cls) # Ensure we are viewing as `Point` 128 return obj 129 def __getitem__(self, index): 130 return Point(super().__getitem__(index)) 131 def __add__(self, other): 132 return PointArray(super().__add__(other)) 133 def __sub__(self, other): 134 return VectorArray(super().__sub__(other)) 135 def translate(self, vector): 136 return PointArray(super().__add__(vector)) 137 def rotate(self,q): 138 return PointArray(quaternion.rotate_vectors(q, self)) 139 def from_basis(self, r0, r, q): 140 return Point([0,0,0]) + r0 + VectorArray(self-r0).rotate(q) + r 141 #return PointArray([Point([0,0,0]) + r0 + (p-r0).rotate(q) + r for p in self]) 142 def to_basis(self, r0, r, q): 143 return Point([0,0,0]) + r0 + VectorArray(self-r0-r).rotate(q.conj()) 144 #return PointArray([Point([0,0,0]) + r0 + (p-r0-r).rotate(q.conj()) for p in self]) 145 def _add_dimension(self, values=0): 146 if values == 0: 147 values = np.zeros((self.shape[0], 1)) 148 if not isinstance(values, np.ndarray): 149 values = np.array(values) 150 return PointArray(np.concatenate((self, values), axis=1))
ndarray(shape, dtype=float, buffer=None, offset=0, strides=None, order=None)
An array object represents a multidimensional, homogeneous array of fixed-size items. An associated data-type object describes the format of each element in the array (its byte-order, how many bytes it occupies in memory, whether it is an integer, a floating point number, or something else, etc.)
Arrays should be constructed using array, zeros or empty (refer
to the See Also section below). The parameters given here refer to
a low-level method (ndarray(...)) for instantiating an array.
For more information, refer to the numpy module and examine the
methods and attributes of an array.
Parameters
(for the __new__ method; see Notes below)
shape : tuple of ints Shape of created array. dtype : data-type, optional Any object that can be interpreted as a numpy data type. buffer : object exposing buffer interface, optional Used to fill the array with data. offset : int, optional Offset of array data in buffer. strides : tuple of ints, optional Strides of data in memory. order : {'C', 'F'}, optional Row-major (C-style) or column-major (Fortran-style) order.
Attributes
T : ndarray
Transpose of the array.
data : buffer
The array's elements, in memory.
dtype : dtype object
Describes the format of the elements in the array.
flags : dict
Dictionary containing information related to memory use, e.g.,
'C_CONTIGUOUS', 'OWNDATA', 'WRITEABLE', etc.
flat : numpy.flatiter object
Flattened version of the array as an iterator. The iterator
allows assignments, e.g., x.flat = 3 (See ndarray.flat for
assignment examples; TODO).
imag : ndarray
Imaginary part of the array.
real : ndarray
Real part of the array.
size : int
Number of elements in the array.
itemsize : int
The memory use of each array element in bytes.
nbytes : int
The total number of bytes required to store the array data,
i.e., itemsize * size.
ndim : int
The array's number of dimensions.
shape : tuple of ints
Shape of the array.
strides : tuple of ints
The step-size required to move from one element to the next in
memory. For example, a contiguous (3, 4) array of type
int16 in C-order has strides (8, 2). This implies that
to move from element to element in memory requires jumps of 2 bytes.
To move from row-to-row, one needs to jump 8 bytes at a time
(2 * 4).
ctypes : ctypes object
Class containing properties of the array needed for interaction
with ctypes.
base : ndarray
If the array is a view into another array, that array is its base
(unless that array is also a view). The base array is where the
array data is actually stored.
See Also
array : Construct an array.
zeros : Create an array, each element of which is zero.
empty : Create an array, but leave its allocated memory unchanged (i.e.,
it contains "garbage").
dtype : Create a data-type.
numpy.typing.NDArray : An ndarray alias :term:generic <generic type>
w.r.t. its dtype.type <numpy.dtype.type>.
Notes
There are two modes of creating an array using __new__:
- If
bufferis None, then onlyshape,dtype, andorderare used. - If
bufferis an object exposing the buffer interface, then all keywords are interpreted.
No __init__ method is needed because the array is fully initialized
after the __new__ method.
Examples
These examples illustrate the low-level ndarray constructor. Refer
to the See Also section above for easier ways of constructing an
ndarray.
First mode, buffer is None:
>>> np.ndarray(shape=(2,2), dtype=float, order='F')
array([[0.0e+000, 0.0e+000], # random
[ nan, 2.5e-323]])
Second mode:
>>> np.ndarray((2,), buffer=np.array([1,2,3]),
... offset=np.int_().itemsize,
... dtype=int) # offset = 1*itemsize, i.e. skip first element
array([2, 3])
154def Normalize(vector): 155 """ 156 Normalize Vector. 157 Obsolete, use use the mgeo.Vector class instead as it has a `normalize` method. 158 """ 159 return vector / np.linalg.norm(vector)
Normalize Vector.
Obsolete, use use the mgeo.Vector class instead as it has a normalize method.
161def VectorPerpendicular(vector): 162 """ 163 Find a perpendicular 3D vector in some arbitrary direction 164 Undefined behavior, use with caution. There is no unique perpendicular vector to a 3D vector. 165 """ 166 logger.warning("VectorPerpendicular is undefined behavior. There is no unique perpendicular vector to a 3D vector.") 167 if abs(vector[0]) < 1e-15: 168 return Vector([1, 0, 0]) 169 if abs(vector[1]) < 1e-15: 170 return Vector([0, 1, 0]) 171 if abs(vector[2]) < 1e-15: 172 return Vector([0, 0, 1]) 173 174 # set arbitrarily a = b =1 175 return Vector([1, 1, -1.0 * (vector[0] + vector[1]) / vector[2]]).normalized()
Find a perpendicular 3D vector in some arbitrary direction Undefined behavior, use with caution. There is no unique perpendicular vector to a 3D vector.
177def AngleBetweenTwoVectors(U, V): 178 """ 179 Return the angle in radians between the vectors U and V ; formula from W.Kahan 180 Value in radians between 0 and pi. 181 """ 182 u = np.linalg.norm(U) 183 v = np.linalg.norm(V) 184 return 2 * np.arctan2(np.linalg.norm(U * v - V * u), np.linalg.norm(U * v + V * u))
Return the angle in radians between the vectors U and V ; formula from W.Kahan Value in radians between 0 and pi.
186def SymmetricalVector(V, SymmetryAxis): 187 """ 188 Return the symmetrical vector to V 189 """ 190 q = QRotationAroundAxis(SymmetryAxis, np.pi) 191 return V.rotate(q)
Return the symmetrical vector to V
193def normal_add(N1, N2): 194 """ 195 Simple function that takes in two normal vectors of a deformation and calculates 196 the total normal vector if the two deformations were individually applied. 197 Be very careful, this *only* works when the surface is z = f(x,y) and when 198 the deformation is small. 199 Might be made obsolete when shifting to a modernised deformation system. 200 """ 201 normal1 = N1.normalized() 202 normal2 = N2.normalized() 203 grad1 = -normal1[:2] / normal1[2] 204 grad2 = -normal2[:2] / normal2[2] 205 grad = grad1 + grad2 206 total_normal = np.append(-grad, 1) 207 return Vector(total_normal).normalized()
Simple function that takes in two normal vectors of a deformation and calculates the total normal vector if the two deformations were individually applied. Be very careful, this only works when the surface is z = f(x,y) and when the deformation is small. Might be made obsolete when shifting to a modernised deformation system.
210def IntersectionLinePlane(A, u, P, n): 211 """ 212 Return the intersection point between a line and a plane. 213 A is a point of the line, u a vector of the line ; P is a point of the plane, n a normal vector 214 Line's equation : OM = u*t + OA , t a real 215 Plane's equation : n.OP - n.OM = 0 216 """ 217 t = np.dot(n, -A + P) / np.dot(u, n) 218 I = u * t + A 219 return I
Return the intersection point between a line and a plane. A is a point of the line, u a vector of the line ; P is a point of the plane, n a normal vector Line's equation : OM = u*t + OA , t a real Plane's equation : n.OP - n.OM = 0
221def IntersectionRayListZPlane(RayList, Z=np.array([0])): 222 """ 223 Return the intersection of a list of rays with a different planes with equiations z = Z[i] 224 Basically, by default it returns the intersection of the rays with the Z=0 plane but you can 225 give it a few values of Z and it should be faster than calling it multiple times. 226 This should let us quickly find the optimal position of the detector as well as trace the caustics. 227 If a ray does not intersect the plane... it should replace that point with a NaN. 228 """ 229 Positions = np.vstack([i.point for i in RayList]) 230 Vectors = np.vstack([i.vector for i in RayList]) 231 non_zero = Vectors[:,2] != 0 232 Positions = Positions[non_zero] 233 Vectors = Vectors[non_zero] 234 Z = Z[:, np.newaxis] 235 A = Positions[:,2]-Z 236 B = -Vectors[:,2] 237 #times = (Positions[:,2]-Z)/Vectors[:,2] 238 #return A,B 239 with np.errstate(divide='ignore', invalid='ignore'): 240 times = np.divide(A, B, where=(B != 0), out=np.full_like(A, np.nan)) 241 #times[times < 0] = np.nan # Set negative results to NaN 242 #return times 243 #positive_times = times >= 0 244 intersect_positions = Positions[:, :2] + times[:, :, np.newaxis] * Vectors[:, :2] 245 result = [] 246 for i in range(Z.shape[0]): 247 # For each plane, we find the intersection points 248 #valid_intersections = intersect_positions[i][positive_times[i]] 249 valid_intersections = intersect_positions[i] 250 result.append(PointArray(valid_intersections)) 251 return result
Return the intersection of a list of rays with a different planes with equiations z = Z[i] Basically, by default it returns the intersection of the rays with the Z=0 plane but you can give it a few values of Z and it should be faster than calling it multiple times. This should let us quickly find the optimal position of the detector as well as trace the caustics. If a ray does not intersect the plane... it should replace that point with a NaN.
255def SpiralVogel(NbPoint, Radius): 256 """ 257 Return a NbPoint x 2 matrix of 2D points representative of Vogel's spiral with radius Radius 258 Careful, contrary to most of the code, this is *not* in the 259 ARTcore.Vector or ARTcore.Point format. It is a simple numpy array. 260 The reason is that this is a utility function that can be used both to define directions 261 and to generate grids of points. 262 """ 263 GoldenAngle = np.pi * (3 - np.sqrt(5)) 264 r = np.sqrt(np.arange(NbPoint) / NbPoint) * Radius 265 266 theta = GoldenAngle * np.arange(NbPoint) 267 268 Matrix = np.zeros((NbPoint, 2)) 269 Matrix[:, 0] = np.cos(theta) 270 Matrix[:, 1] = np.sin(theta) 271 Matrix = Matrix * r.reshape((NbPoint, 1)) 272 273 return Matrix
Return a NbPoint x 2 matrix of 2D points representative of Vogel's spiral with radius Radius Careful, contrary to most of the code, this is not in the ARTcore.Vector or ARTcore.Point format. It is a simple numpy array. The reason is that this is a utility function that can be used both to define directions and to generate grids of points.
275def find_hull(points): 276 """ 277 Find the convex hull of a set of points using a greedy algorithm. 278 This is used to create a polygon that encloses the points. 279 """ 280 # start from leftmost point 281 current_point = min(range(len(points)), key=lambda i: points[i][0]) 282 # initialize hull with current point 283 hull = [current_point] 284 # initialize list of linked points 285 linked = [] 286 # continue until all points have been linked 287 while len(linked) < len(points) - 1: 288 # initialize minimum distance and closest point 289 min_distance = math.inf 290 closest_point = None 291 # find closest unlinked point to current point 292 for i, point in enumerate(points): 293 if i not in linked: 294 distance = math.dist(points[current_point], point) 295 if distance < min_distance: 296 min_distance = distance 297 closest_point = i 298 # add closest point to hull and linked list 299 hull.append(closest_point) 300 linked.append(closest_point) 301 # update current point 302 current_point = closest_point 303 # add link between last point and first point 304 hull.append(hull[0]) 305 # convert hull to a list of pairs of indices 306 indices = [[hull[i], hull[i + 1]] for i in range(len(hull) - 1)] 307 return indices
Find the convex hull of a set of points using a greedy algorithm. This is used to create a polygon that encloses the points.
311def SolverQuadratic(a, b, c): 312 """ 313 Solve the quadratic equation a*x^2 + b*x +c = 0 ; keep only real solutions 314 """ 315 Solution = np.roots([a, b, c]) 316 RealSolution = [] 317 318 for k in range(len(Solution)): 319 if abs(Solution[k].imag) < 1e-15: 320 RealSolution.append(Solution[k].real) 321 322 return RealSolution
Solve the quadratic equation ax^2 + bx +c = 0 ; keep only real solutions
325def SolverQuartic(a, b, c, d, e): 326 """ 327 Solve the quartic equation a*x^4 + b*x^3 +c*x^2 + d*x + e = 0 ; keep only real solutions 328 """ 329 Solution = np.roots([a, b, c, d, e]) 330 RealSolution = [] 331 332 for k in range(len(Solution)): 333 if abs(Solution[k].imag) < 1e-15: 334 RealSolution.append(Solution[k].real) 335 336 return RealSolution
Solve the quartic equation ax^4 + bx^3 +cx^2 + dx + e = 0 ; keep only real solutions
339def KeepPositiveSolution(SolutionList): 340 """ 341 Keep only positive solution (numbers) in the list 342 """ 343 PositiveSolutionList = [] 344 epsilon = 1e-12 345 for k in SolutionList: 346 if k > epsilon: 347 PositiveSolutionList.append(k) 348 349 return PositiveSolutionList
Keep only positive solution (numbers) in the list
352def KeepNegativeSolution(SolutionList): 353 """ 354 Keep only positive solution (numbers) in the list 355 """ 356 NegativeSolutionList = [] 357 epsilon = -1e-12 358 for k in SolutionList: 359 if k < epsilon: 360 NegativeSolutionList.append(k) 361 362 return NegativeSolutionList
Keep only positive solution (numbers) in the list
366def ClosestPoint(A: Point, Points: PointArray): 367 """ 368 Given a reference point A and an array of points, return the index of the point closest to A 369 """ 370 distances = (Points-A).norm 371 return np.argmin(distances)
Given a reference point A and an array of points, return the index of the point closest to A
373def DiameterPointArray(Points: PointArray): 374 """ 375 Return the diameter of the smallest circle (for 2D points) 376 or sphere (3D points) including all the points. 377 """ 378 if len(Points) == 0: 379 return None 380 return float(np.ptp(Points, axis=0).max())
Return the diameter of the smallest circle (for 2D points) or sphere (3D points) including all the points.
382def CentrePointList(Points): 383 """ 384 Shift all 2D-points in PointList so as to center the point-cloud on the origin [0,0]. 385 """ 386 return Points - np.mean(Points, axis=0)
Shift all 2D-points in PointList so as to center the point-cloud on the origin [0,0].
390def RotateSolid(Object, q): 391 """ 392 Rotate object around basepoint by quaternion q 393 """ 394 Object.q = q*Object.q
Rotate object around basepoint by quaternion q
396def TranslateSolid(Object, T): 397 """ 398 Translate object by vector T 399 """ 400 Object.r = Object.r + T
Translate object by vector T
402def RotateSolidAroundInternalPointByQ(Object, q, P): 403 """ 404 Rotate object around P by quaternion q where P is in the object's frame 405 """ 406 pass #TODO
Rotate object around P by quaternion q where P is in the object's frame
408def RotateSolidAroundExternalPointByQ(Object, q, P): 409 """Rotate object around P by quaternion q, where P is in the global frame""" 410 pass #TODO
Rotate object around P by quaternion q, where P is in the global frame
415def SDF_Rectangle(Point, SizeX, SizeY): 416 """Signed distance function for a rectangle centered at the origin""" 417 d = np.abs(Point[:2]) - np.array([SizeX, SizeY]) / 2 418 return (np.linalg.norm(np.maximum(d, 0)) + np.min(np.max(d, 0)))/2
Signed distance function for a rectangle centered at the origin
420def SDF_Circle(Point, Radius): 421 """Signed distance function for a circle centered at the origin""" 422 return np.linalg.norm(Point[:2]) - Radius
Signed distance function for a circle centered at the origin
424def Union_SDF(SDF1, SDF2): 425 """Union of two signed distance functions""" 426 return np.minimum(SDF1, SDF2)
Union of two signed distance functions
428def Difference_SDF(SDF1, SDF2): 429 """Difference of two signed distance functions""" 430 return np.maximum(SDF1, -SDF2)
Difference of two signed distance functions
432def Intersection_SDF(SDF1, SDF2): 433 """Intersection of two signed distance functions""" 434 return np.maximum(SDF1, SDF2)
Intersection of two signed distance functions
439def QRotationAroundAxis(Axis, Angle): 440 """ 441 Return quaternion for rotation by Angle (in rad) around Axis 442 """ 443 rot_axis = Normalize(np.array([0.0] + Axis)) 444 axis_angle = (Angle * 0.5) * rot_axis 445 qlog = np.quaternion(*axis_angle) 446 q = np.exp(qlog) 447 return q
Return quaternion for rotation by Angle (in rad) around Axis
449def QRotationVector2Vector(Vector1, Vector2): 450 """ 451 Return a possible quaternion (among many) that would rotate Vector1 into Vector2. 452 Undefined behavior, use with caution. There is no unique quaternion that rotates one vector into another. 453 """ 454 Vector1 = Normalize(Vector1) 455 Vector2 = Normalize(Vector2) 456 a = np.cross(Vector1, Vector2) 457 return np.quaternion(1 + np.dot(Vector1, Vector2), *a).normalized()
Return a possible quaternion (among many) that would rotate Vector1 into Vector2. Undefined behavior, use with caution. There is no unique quaternion that rotates one vector into another.
459def QRotationVectorPair2VectorPair(InitialVector1, Vector1, InitialVector2, Vector2): 460 """ 461 Return the only quaternion that rotates InitialVector1 to Vector1 and InitialVector2 to Vector2. 462 Please ensure orthogonality two input and two output vectors. 463 """ 464 Vector1 = Normalize(Vector1) 465 Vector2 = Normalize(Vector2) 466 Vector3 = Normalize(np.cross(Vector1,Vector2)) 467 InitialVector1 = Normalize(InitialVector1) 468 InitialVector2 = Normalize(InitialVector2) 469 InitialVector3 = Normalize(np.cross(InitialVector1,InitialVector2)) 470 rot_2Initial = np.zeros((3,3)) 471 rot_2Initial[:,0] = InitialVector1 472 rot_2Initial[:,1] = InitialVector2 473 rot_2Initial[:,2] = InitialVector3 474 rot_2Final = np.zeros((3,3)) 475 rot_2Final[:,0] = Vector1 476 rot_2Final[:,1] = Vector2 477 rot_2Final[:,2] = Vector3 478 q2Init = quaternion.from_rotation_matrix(rot_2Initial) 479 q2Fin = quaternion.from_rotation_matrix(rot_2Final) 480 return (q2Fin/q2Init).normalized()
Return the only quaternion that rotates InitialVector1 to Vector1 and InitialVector2 to Vector2. Please ensure orthogonality two input and two output vectors.
484def RotationRayList(RayList, q): 485 """Like RotationPointList but with a list of Ray objects""" 486 return [i.rotate(q) for i in RayList]
Like RotationPointList but with a list of Ray objects
488def TranslationRayList(RayList, T): 489 """Translate a RayList by vector T""" 490 return [i.translate(T) for i in RayList]
Translate a RayList by vector T