vedo.transformations
Submodule to work with linear and non-linear transformations
1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3import numpy as np 4 5import vedo.vtkclasses as vtk 6 7__docformat__ = "google" 8 9__doc__ = """ 10Submodule to work with linear and non-linear transformations<br> 11 12 13""" 14 15__all__ = [ 16 "LinearTransform", 17 "NonLinearTransform", 18 "spher2cart", 19 "cart2spher", 20 "cart2cyl", 21 "cyl2cart", 22 "cyl2spher", 23 "spher2cyl", 24 "cart2pol", 25 "pol2cart", 26] 27 28################################################### 29def _is_sequence(arg): 30 if hasattr(arg, "strip"): 31 return False 32 if hasattr(arg, "__getslice__"): 33 return True 34 if hasattr(arg, "__iter__"): 35 return True 36 return False 37 38 39################################################### 40class LinearTransform: 41 """Work with linear transformations.""" 42 43 def __init__(self, T=None): 44 """ 45 Define a linear transformation. 46 Can be saved to file and reloaded. 47 48 Arguments: 49 T : (vtkTransform, numpy array) 50 input transformation. Defaults to unit. 51 52 Example: 53 ```python 54 from vedo import * 55 settings.use_parallel_projection = True 56 57 LT = LinearTransform() 58 LT.translate([3,0,1]).rotate_z(45) 59 LT.comment = "shifting by (3,0,1) and rotating by 45 deg" 60 print(LT) 61 62 sph = Sphere(r=0.2) 63 sph.apply_transform(LT) # same as: LT.move(s1) 64 print(sph.transform) 65 66 show(Point([0,0,0]), sph, str(LT.matrix), axes=1).close() 67 ``` 68 """ 69 self.name = "LinearTransform" 70 self.filename = "" 71 self.comment = "" 72 73 if T is None: 74 T = vtk.vtkTransform() 75 76 elif isinstance(T, vtk.vtkMatrix4x4): 77 S = vtk.vtkTransform() 78 S.SetMatrix(T) 79 T = S 80 81 elif isinstance(T, vtk.vtkLandmarkTransform): 82 S = vtk.vtkTransform() 83 S.SetMatrix(T.GetMatrix()) 84 T = S 85 86 elif _is_sequence(T): 87 S = vtk.vtkTransform() 88 M = vtk.vtkMatrix4x4() 89 n = len(T) 90 for i in range(n): 91 for j in range(n): 92 M.SetElement(i, j, T[i][j]) 93 S.SetMatrix(M) 94 T = S 95 96 elif isinstance(T, vtk.vtkLinearTransform): 97 S = vtk.vtkTransform() 98 S.DeepCopy(T) 99 T = S 100 101 elif isinstance(T, LinearTransform): 102 S = vtk.vtkTransform() 103 S.DeepCopy(T.T) 104 T = S 105 106 elif isinstance(T, str): 107 import json 108 self.filename = str(T) 109 try: 110 with open(self.filename, "r") as read_file: 111 D = json.load(read_file) 112 self.name = D["name"] 113 self.comment = D["comment"] 114 matrix = np.array(D["matrix"]) 115 except json.decoder.JSONDecodeError: 116 ### assuming legacy vedo format E.g.: 117 #aligned by manual_align.py 118 # 0.8026854838223 -0.0789823873914 -0.508476844097 38.17377632072 119 # 0.0679734082661 0.9501827489452 -0.040289803376 -69.53864247951 120 # 0.5100652300642 -0.0023313569781 0.805555043665 -81.20317788519 121 # 0.0 0.0 0.0 1.0 122 with open(self.filename, "r", encoding="UTF-8") as read_file: 123 lines = read_file.readlines() 124 i = 0 125 matrix = np.eye(4) 126 for l in lines: 127 if l.startswith("#"): 128 self.comment = l.replace("#", "").strip() 129 continue 130 vals = l.split(" ") 131 for j in range(len(vals)): 132 v = vals[j].replace("\n", "") 133 if v != "": 134 matrix[i, j] = float(v) 135 i += 1 136 T = vtk.vtkTransform() 137 m = vtk.vtkMatrix4x4() 138 for i in range(4): 139 for j in range(4): 140 m.SetElement(i, j, matrix[i][j]) 141 T.SetMatrix(m) 142 143 self.T = T 144 self.T.PostMultiply() 145 self.inverse_flag = False 146 147 def __str__(self): 148 module = self.__class__.__module__ 149 name = self.__class__.__name__ 150 s = f"\x1b[7m\x1b[1m{module}.{name} at ({hex(id(self))})".ljust(75) + "\x1b[0m" 151 s += "\nname".ljust(15) + ": " + self.name 152 if self.filename: 153 s += "\nfilename".ljust(15) + ": " + self.filename 154 if self.comment: 155 s += "\ncomment".ljust(15) + f': \x1b[3m"{self.comment}"\x1b[0m' 156 s += f"\nconcatenations".ljust(15) + f": {self.n_concatenated_transforms}" 157 s += "\ninverse flag".ljust(15) + f": {bool(self.inverse_flag)}" 158 arr = np.array2string(self.matrix, 159 separator=', ', precision=6, suppress_small=True) 160 s += "\nmatrix 4x4".ljust(15) + f":\n{arr}" 161 return s 162 163 def __repr__(self): 164 return self.__str__() 165 166 def print(self): 167 """Print transformation.""" 168 print(self.__str__()) 169 return self 170 171 def move(self, obj): 172 """ 173 Apply transformation to object or single point. 174 175 Note: 176 When applying a transformation to a mesh, the mesh is modified in place. 177 If you want to keep the original mesh unchanged, use `clone()` method. 178 179 Example: 180 ```python 181 from vedo import * 182 settings.use_parallel_projection = True 183 184 LT = LinearTransform() 185 LT.translate([3,0,1]).rotate_z(45) 186 print(LT) 187 188 s = Sphere(r=0.2) 189 LT.move(s) 190 # same as: 191 # s.apply_transform(LT) 192 193 zero = Point([0,0,0]) 194 show(s, zero, axes=1).close() 195 ``` 196 """ 197 if _is_sequence(obj): 198 n = len(obj) 199 if n == 2: 200 obj = [obj[0], obj[1], 0] 201 return np.array(self.T.TransformFloatPoint(obj)) 202 203 obj.apply_transform(self) 204 return obj 205 206 def reset(self): 207 """Reset transformation.""" 208 self.T.Identity() 209 return self 210 211 def pop(self): 212 """Delete the transformation on the top of the stack 213 and sets the top to the next transformation on the stack.""" 214 self.T.Pop() 215 return self 216 217 def is_identity(self): 218 """Check if identity.""" 219 m = self.T.GetMatrix() 220 M = [[m.GetElement(i, j) for j in range(4)] for i in range(4)] 221 if np.allclose(M - np.eye(4), 0): 222 return True 223 return False 224 225 def invert(self): 226 """Invert transformation.""" 227 self.T.Inverse() 228 self.inverse_flag = bool(self.T.GetInverseFlag()) 229 return self 230 231 def compute_inverse(self): 232 """Compute inverse.""" 233 t = self.clone() 234 t.invert() 235 return t 236 237 def copy(self): 238 """Return a copy of the transformation. Alias of `clone()`.""" 239 return self.clone() 240 241 def clone(self): 242 """Clone transformation to make an exact copy.""" 243 return LinearTransform(self.T) 244 245 def concatenate(self, T, pre_multiply=False): 246 """ 247 Post-multiply (by default) 2 transfomations. 248 T can also be a 4x4 matrix or 3x3 matrix. 249 250 Example: 251 ```python 252 from vedo import LinearTransform 253 254 A = LinearTransform() 255 A.rotate_x(45) 256 A.translate([7,8,9]) 257 A.translate([10,10,10]) 258 A.name = "My transformation A" 259 print(A) 260 261 B = A.compute_inverse() 262 B.shift([1,2,3]) 263 B.name = "My transformation B (shifted inverse of A)" 264 print(B) 265 266 # A is applied first, then B 267 # print("A.concatenate(B)", A.concatenate(B)) 268 269 # B is applied first, then A 270 print(B*A) 271 ``` 272 """ 273 if _is_sequence(T): 274 S = vtk.vtkTransform() 275 M = vtk.vtkMatrix4x4() 276 n = len(T) 277 for i in range(n): 278 for j in range(n): 279 M.SetElement(i, j, T[i][j]) 280 S.SetMatrix(M) 281 T = S 282 283 if pre_multiply: 284 self.T.PreMultiply() 285 try: 286 self.T.Concatenate(T) 287 except: 288 self.T.Concatenate(T.T) 289 self.T.PostMultiply() 290 return self 291 292 def __mul__(self, A): 293 """Pre-multiply 2 transfomations.""" 294 return self.concatenate(A, pre_multiply=True) 295 296 def get_concatenated_transform(self, i): 297 """Get intermediate matrix by concatenation index.""" 298 return LinearTransform(self.T.GetConcatenatedTransform(i)) 299 300 @property 301 def n_concatenated_transforms(self): 302 """Get number of concatenated transforms.""" 303 return self.T.GetNumberOfConcatenatedTransforms() 304 305 def translate(self, p): 306 """Translate, same as `shift`.""" 307 if len(p) == 2: 308 p = [p[0], p[1], 0] 309 self.T.Translate(p) 310 return self 311 312 def shift(self, p): 313 """Shift, same as `translate`.""" 314 return self.translate(p) 315 316 def scale(self, s, origin=True): 317 """Scale.""" 318 if not _is_sequence(s): 319 s = [s, s, s] 320 321 if origin is True: 322 p = np.array(self.T.GetPosition()) 323 if np.linalg.norm(p) > 0: 324 self.T.Translate(-p) 325 self.T.Scale(*s) 326 self.T.Translate(p) 327 else: 328 self.T.Scale(*s) 329 330 elif _is_sequence(origin): 331 origin = np.asarray(origin) 332 self.T.Translate(-origin) 333 self.T.Scale(*s) 334 self.T.Translate(origin) 335 336 else: 337 self.T.Scale(*s) 338 return self 339 340 def rotate(self, angle, axis=(1, 0, 0), point=(0, 0, 0), rad=False): 341 """ 342 Rotate around an arbitrary `axis` passing through `point`. 343 344 Example: 345 ```python 346 from vedo import * 347 c1 = Cube() 348 c2 = c1.clone().c('violet').alpha(0.5) # copy of c1 349 v = vector(0.2, 1, 0) 350 p = vector(1.0, 0, 0) # axis passes through this point 351 c2.rotate(90, axis=v, point=p) 352 l = Line(p-v, p+v).c('red5').lw(3) 353 show(c1, l, c2, axes=1).close() 354 ``` 355  356 """ 357 if not angle: 358 return self 359 if rad: 360 anglerad = angle 361 else: 362 anglerad = np.deg2rad(angle) 363 axis = np.asarray(axis) / np.linalg.norm(axis) 364 a = np.cos(anglerad / 2) 365 b, c, d = -axis * np.sin(anglerad / 2) 366 aa, bb, cc, dd = a * a, b * b, c * c, d * d 367 bc, ad, ac, ab, bd, cd = b * c, a * d, a * c, a * b, b * d, c * d 368 R = np.array( 369 [ 370 [aa + bb - cc - dd, 2 * (bc + ad), 2 * (bd - ac)], 371 [2 * (bc - ad), aa + cc - bb - dd, 2 * (cd + ab)], 372 [2 * (bd + ac), 2 * (cd - ab), aa + dd - bb - cc], 373 ] 374 ) 375 rv = np.dot(R, self.T.GetPosition() - np.asarray(point)) + point 376 377 if rad: 378 angle *= 180.0 / np.pi 379 # this vtk method only rotates in the origin of the object: 380 self.T.RotateWXYZ(angle, axis[0], axis[1], axis[2]) 381 self.T.Translate(rv - np.array(self.T.GetPosition())) 382 return self 383 384 def _rotatexyz(self, axe, angle, rad, around): 385 if not angle: 386 return self 387 if rad: 388 angle *= 180 / np.pi 389 390 rot = dict(x=self.T.RotateX, y=self.T.RotateY, z=self.T.RotateZ) 391 392 if around is None: 393 # rotate around its origin 394 rot[axe](angle) 395 else: 396 # displacement needed to bring it back to the origin 397 self.T.Translate(-np.asarray(around)) 398 rot[axe](angle) 399 self.T.Translate(around) 400 return self 401 402 def rotate_x(self, angle, rad=False, around=None): 403 """ 404 Rotate around x-axis. If angle is in radians set `rad=True`. 405 406 Use `around` to define a pivoting point. 407 """ 408 return self._rotatexyz("x", angle, rad, around) 409 410 def rotate_y(self, angle, rad=False, around=None): 411 """ 412 Rotate around y-axis. If angle is in radians set `rad=True`. 413 414 Use `around` to define a pivoting point. 415 """ 416 return self._rotatexyz("y", angle, rad, around) 417 418 def rotate_z(self, angle, rad=False, around=None): 419 """ 420 Rotate around z-axis. If angle is in radians set `rad=True`. 421 422 Use `around` to define a pivoting point. 423 """ 424 return self._rotatexyz("z", angle, rad, around) 425 426 def set_position(self, p): 427 """Set position.""" 428 if len(p) == 2: 429 p = np.array([p[0], p[1], 0]) 430 q = np.array(self.T.GetPosition()) 431 self.T.Translate(p - q) 432 return self 433 434 # def set_scale(self, s): 435 # """Set absolute scale.""" 436 # if not _is_sequence(s): 437 # s = [s, s, s] 438 # s0, s1, s2 = 1, 1, 1 439 # b = self.T.GetScale() 440 # print(b) 441 # if b[0]: 442 # s0 = s[0] / b[0] 443 # if b[1]: 444 # s1 = s[1] / b[1] 445 # if b[2]: 446 # s2 = s[2] / b[2] 447 # self.T.Scale(s0, s1, s2) 448 # print() 449 # return self 450 451 def get_scale(self): 452 """Get current scale.""" 453 return np.array(self.T.GetScale()) 454 455 @property 456 def orientation(self): 457 """Compute orientation.""" 458 return np.array(self.T.GetOrientation()) 459 460 @property 461 def position(self): 462 """Compute position.""" 463 return np.array(self.T.GetPosition()) 464 465 @property 466 def matrix(self): 467 """Get the 4x4 trasformation matrix.""" 468 m = self.T.GetMatrix() 469 M = [[m.GetElement(i, j) for j in range(4)] for i in range(4)] 470 return np.array(M) 471 472 @matrix.setter 473 def matrix(self, M): 474 """Set trasformation by assigning a 4x4 or 3x3 numpy matrix.""" 475 m = vtk.vtkMatrix4x4() 476 n = len(M) 477 for i in range(n): 478 for j in range(n): 479 m.SetElement(i, j, M[i][j]) 480 self.T.SetMatrix(m) 481 482 @property 483 def matrix3x3(self): 484 """Get the 3x3 trasformation matrix.""" 485 m = self.T.GetMatrix() 486 M = [[m.GetElement(i, j) for j in range(3)] for i in range(3)] 487 return np.array(M) 488 489 def write(self, filename="transform.mat"): 490 """Save transformation to ASCII file.""" 491 import json 492 m = self.T.GetMatrix() 493 M = [[m.GetElement(i, j) for j in range(4)] for i in range(4)] 494 arr = np.array(M) 495 dictionary = { 496 "name": self.name, 497 "comment": self.comment, 498 "matrix": arr.astype(float).tolist(), 499 "n_concatenated_transforms": self.n_concatenated_transforms, 500 } 501 with open(filename, "w") as outfile: 502 json.dump(dictionary, outfile, sort_keys=True, indent=2) 503 504 def reorient( 505 self, initaxis, newaxis, around=(0, 0, 0), rotation=0, rad=False, xyplane=True 506 ): 507 """ 508 Set/Get object orientation. 509 510 Arguments: 511 rotation : (float) 512 rotate object around newaxis. 513 concatenate : (bool) 514 concatenate the orientation operation with the previous existing transform (if any) 515 rad : (bool) 516 set to True if angle is expressed in radians. 517 xyplane : (bool) 518 make an extra rotation to keep the object aligned to the xy-plane 519 """ 520 newaxis = np.asarray(newaxis) / np.linalg.norm(newaxis) 521 initaxis = np.asarray(initaxis) / np.linalg.norm(initaxis) 522 523 if not np.any(initaxis - newaxis): 524 return self 525 526 if not np.any(initaxis + newaxis): 527 print("Warning: in reorient() initaxis and newaxis are parallel") 528 newaxis += np.array([0.0000001, 0.0000002, 0]) 529 angleth = np.pi 530 else: 531 angleth = np.arccos(np.dot(initaxis, newaxis)) 532 crossvec = np.cross(initaxis, newaxis) 533 534 p = np.asarray(around) 535 self.T.Translate(-p) 536 if rotation: 537 if rad: 538 rotation = np.rad2deg(rotation) 539 self.T.RotateWXYZ(rotation, initaxis) 540 541 self.T.RotateWXYZ(np.rad2deg(angleth), crossvec) 542 543 if xyplane: 544 self.T.RotateWXYZ(-self.orientation[0] * 1.4142, newaxis) 545 546 self.T.Translate(p) 547 return self 548 549 550################################################### 551class NonLinearTransform: 552 """Work with non-linear transformations.""" 553 554 def __init__(self, T=None, **kwargs): 555 """ 556 Define a non-linear transformation. 557 Can be saved to file and reloaded. 558 559 Arguments: 560 T : (vtkThinPlateSplineTransform, str, dict) 561 vtk transformation. 562 If T is a string, it is assumed to be a filename. 563 If T is a dictionary, it is assumed to be a set of keyword arguments. 564 Defaults to None. 565 **kwargs : (dict) 566 keyword arguments to define the transformation. 567 The following keywords are accepted: 568 - name : (str) name of the transformation 569 - comment : (str) comment 570 - source_points : (list) source points 571 - target_points : (list) target points 572 - mode : (str) either '2d' or '3d' 573 - sigma : (float) sigma parameter 574 575 Example: 576 ```python 577 from vedo import * 578 settings.use_parallel_projection = True 579 580 NLT = NonLinearTransform() 581 NLT.source_points = [[-2,0,0], [1,2,1], [2,-2,2]] 582 NLT.target_points = NLT.source_points + np.random.randn(3,3)*0.5 583 NLT.mode = '3d' 584 print(NLT) 585 586 s1 = Sphere() 587 NLT.move(s1) 588 # same as: 589 # s1.apply_transform(NLT) 590 591 arrs = Arrows(NLT.source_points, NLT.target_points) 592 show(s1, arrs, Sphere().alpha(0.1), axes=1).close() 593 ``` 594 """ 595 596 self.name = "NonLinearTransform" 597 self.filename = "" 598 self.comment = "" 599 600 if T is None and len(kwargs) == 0: 601 T = vtk.vtkThinPlateSplineTransform() 602 603 elif isinstance(T, vtk.vtkThinPlateSplineTransform): 604 S = vtk.vtkThinPlateSplineTransform() 605 S.DeepCopy(T) 606 T = S 607 608 elif isinstance(T, NonLinearTransform): 609 S = vtk.vtkThinPlateSplineTransform() 610 S.DeepCopy(T.T) 611 T = S 612 613 elif isinstance(T, str): 614 import json 615 filename = str(T) 616 self.filename = filename 617 with open(filename, "r") as read_file: 618 D = json.load(read_file) 619 self.name = D["name"] 620 self.comment = D["comment"] 621 source = D["source_points"] 622 target = D["target_points"] 623 mode = D["mode"] 624 sigma = D["sigma"] 625 626 T = vtk.vtkThinPlateSplineTransform() 627 vptss = vtk.vtkPoints() 628 for p in source: 629 if len(p) == 2: 630 p = [p[0], p[1], 0.0] 631 vptss.InsertNextPoint(p) 632 T.SetSourceLandmarks(vptss) 633 vptst = vtk.vtkPoints() 634 for p in target: 635 if len(p) == 2: 636 p = [p[0], p[1], 0.0] 637 vptst.InsertNextPoint(p) 638 T.SetTargetLandmarks(vptst) 639 T.SetSigma(sigma) 640 if mode == "2d": 641 T.SetBasisToR2LogR() 642 elif mode == "3d": 643 T.SetBasisToR() 644 else: 645 print(f'In {filename} mode can be either "2d" or "3d"') 646 647 elif len(kwargs) > 0: 648 T = kwargs.copy() 649 self.name = T.pop("name", "NonLinearTransform") 650 self.comment = T.pop("comment", "") 651 source = T.pop("source_points", []) 652 target = T.pop("target_points", []) 653 mode = T.pop("mode", "3d") 654 sigma = T.pop("sigma", 1.0) 655 if len(T) > 0: 656 print("Warning: NonLinearTransform got unexpected keyword arguments:") 657 print(T) 658 659 T = vtk.vtkThinPlateSplineTransform() 660 vptss = vtk.vtkPoints() 661 for p in source: 662 if len(p) == 2: 663 p = [p[0], p[1], 0.0] 664 vptss.InsertNextPoint(p) 665 T.SetSourceLandmarks(vptss) 666 vptst = vtk.vtkPoints() 667 for p in target: 668 if len(p) == 2: 669 p = [p[0], p[1], 0.0] 670 vptst.InsertNextPoint(p) 671 T.SetTargetLandmarks(vptst) 672 T.SetSigma(sigma) 673 if mode == "2d": 674 T.SetBasisToR2LogR() 675 elif mode == "3d": 676 T.SetBasisToR() 677 else: 678 print(f'In {filename} mode can be either "2d" or "3d"') 679 680 self.T = T 681 self.inverse_flag = False 682 683 def __str__(self): 684 module = self.__class__.__module__ 685 name = self.__class__.__name__ 686 s = f"\x1b[7m\x1b[1m{module}.{name} at ({hex(id(self))})".ljust(75) + "\x1b[0m\n" 687 s += "name".ljust(9) + ": " + self.name + "\n" 688 if self.filename: 689 s += "filename".ljust(9) + ": " + self.filename + "\n" 690 if self.comment: 691 s += "comment".ljust(9) + f': \x1b[3m"{self.comment}"\x1b[0m\n' 692 s += f"mode".ljust(9) + f": {self.mode}\n" 693 s += f"sigma".ljust(9) + f": {self.sigma}\n" 694 p = self.source_points 695 q = self.target_points 696 s += f"sources".ljust(9) + f": {p.size}, bounds {np.min(p, axis=0)}, {np.max(p, axis=0)}\n" 697 s += f"targets".ljust(9) + f": {q.size}, bounds {np.min(q, axis=0)}, {np.max(q, axis=0)}" 698 return s 699 700 def __repr__(self): 701 return self.__str__() 702 703 def update(self): 704 """Update transformation.""" 705 self.T.Update() 706 return self 707 708 @property 709 def position(self): 710 """ 711 Trying to get the position of a `NonLinearTransform` always returns [0,0,0]. 712 """ 713 return np.array([0.0, 0.0, 0.0], dtype=np.float32) 714 715 @position.setter 716 def position(self, p): 717 """ 718 Trying to set position of a `NonLinearTransform` 719 has no effect and prints a warning. 720 721 Use clone() method to create a copy of the object, 722 or reset it with 'object.transform = vedo.LinearTransform()' 723 """ 724 print("Warning: NonLinearTransform has no position.") 725 print(" Use clone() method to create a copy of the object,") 726 print(" or reset it with 'object.transform = vedo.LinearTransform()'") 727 728 @property 729 def source_points(self): 730 """Get the source points.""" 731 pts = self.T.GetSourceLandmarks() 732 vpts = [] 733 if pts: 734 for i in range(pts.GetNumberOfPoints()): 735 vpts.append(pts.GetPoint(i)) 736 return np.array(vpts, dtype=np.float32) 737 738 @property 739 def target_points(self): 740 """Get the target points.""" 741 pts = self.T.GetTargetLandmarks() 742 vpts = [] 743 for i in range(pts.GetNumberOfPoints()): 744 vpts.append(pts.GetPoint(i)) 745 return np.array(vpts, dtype=np.float32) 746 747 @source_points.setter 748 def source_points(self, pts): 749 """Set source points.""" 750 if _is_sequence(pts): 751 pass 752 else: 753 pts = pts.vertices 754 vpts = vtk.vtkPoints() 755 for p in pts: 756 if len(p) == 2: 757 p = [p[0], p[1], 0.0] 758 vpts.InsertNextPoint(p) 759 self.T.SetSourceLandmarks(vpts) 760 761 @target_points.setter 762 def target_points(self, pts): 763 """Set target points.""" 764 if _is_sequence(pts): 765 pass 766 else: 767 pts = pts.vertices 768 vpts = vtk.vtkPoints() 769 for p in pts: 770 if len(p) == 2: 771 p = [p[0], p[1], 0.0] 772 vpts.InsertNextPoint(p) 773 self.T.SetTargetLandmarks(vpts) 774 775 @property 776 def sigma(self) -> float: 777 """Set sigma.""" 778 return self.T.GetSigma() 779 780 @sigma.setter 781 def sigma(self, s): 782 """Get sigma.""" 783 self.T.SetSigma(s) 784 785 @property 786 def mode(self) -> str: 787 """Get mode.""" 788 m = self.T.GetBasis() 789 # print("T.GetBasis()", m, self.T.GetBasisAsString()) 790 if m == 2: 791 return "2d" 792 elif m == 1: 793 return "3d" 794 else: 795 print("Warning: NonLinearTransform has no valid mode.") 796 797 @mode.setter 798 def mode(self, m): 799 """Set mode.""" 800 if m=='3d': 801 self.T.SetBasisToR() 802 elif m=='2d': 803 self.T.SetBasisToR2LogR() 804 else: 805 print('In NonLinearTransform mode can be either "2d" or "3d"') 806 807 def clone(self): 808 """Clone transformation to make an exact copy.""" 809 return NonLinearTransform(self.T) 810 811 def write(self, filename): 812 """Save transformation to ASCII file.""" 813 import json 814 dictionary = { 815 "name": self.name, 816 "comment": self.comment, 817 "mode": self.mode, 818 "sigma": self.sigma, 819 "source_points": self.source_points.astype(float).tolist(), 820 "target_points": self.target_points.astype(float).tolist(), 821 } 822 with open(filename, "w") as outfile: 823 json.dump(dictionary, outfile, sort_keys=True, indent=2) 824 825 def invert(self): 826 """Invert transformation.""" 827 self.T.Inverse() 828 self.inverse_flag = bool(self.T.GetInverseFlag()) 829 return self 830 831 def compute_inverse(self): 832 """Compute inverse.""" 833 t = self.clone() 834 t.invert() 835 return t 836 837 def move(self, obj): 838 """ 839 Apply transformation to object or single point. 840 841 Note: 842 When applying a transformation to a mesh, the mesh is modified in place. 843 If you want to keep the original mesh unchanged, use `clone()` method. 844 845 Example: 846 ```python 847 from vedo import * 848 np.random.seed(0) 849 settings.use_parallel_projection = True 850 851 NLT = NonLinearTransform() 852 NLT.source_points = [[-2,0,0], [1,2,1], [2,-2,2]] 853 NLT.target_points = NLT.source_points + np.random.randn(3,3)*0.5 854 NLT.mode = '3d' 855 print(NLT) 856 857 s1 = Sphere() 858 NLT.move(s1) 859 # same as: 860 # s1.apply_transform(NLT) 861 862 arrs = Arrows(NLT.source_points, NLT.target_points) 863 show(s1, arrs, Sphere().alpha(0.1), axes=1).close() 864 ``` 865 """ 866 if _is_sequence(obj): 867 if len(obj) == 2: 868 obj = [obj[0], obj[1], 0] 869 return np.array(self.T.TransformFloatPoint(obj)) 870 871 obj.apply_transform(self) 872 return obj 873 874######################################################################## 875# 2d ###### 876def cart2pol(x, y): 877 """2D Cartesian to Polar coordinates conversion.""" 878 theta = np.arctan2(y, x) 879 rho = np.hypot(x, y) 880 return np.array([rho, theta]) 881 882 883def pol2cart(rho, theta): 884 """2D Polar to Cartesian coordinates conversion.""" 885 x = rho * np.cos(theta) 886 y = rho * np.sin(theta) 887 return np.array([x, y]) 888 889 890######################################################################## 891# 3d ###### 892def cart2spher(x, y, z): 893 """3D Cartesian to Spherical coordinate conversion.""" 894 hxy = np.hypot(x, y) 895 rho = np.hypot(hxy, z) 896 theta = np.arctan2(hxy, z) 897 phi = np.arctan2(y, x) 898 return np.array([rho, theta, phi]) 899 900 901def spher2cart(rho, theta, phi): 902 """3D Spherical to Cartesian coordinate conversion.""" 903 st = np.sin(theta) 904 sp = np.sin(phi) 905 ct = np.cos(theta) 906 cp = np.cos(phi) 907 rst = rho * st 908 x = rst * cp 909 y = rst * sp 910 z = rho * ct 911 return np.array([x, y, z]) 912 913 914def cart2cyl(x, y, z): 915 """3D Cartesian to Cylindrical coordinate conversion.""" 916 rho = np.sqrt(x * x + y * y) 917 theta = np.arctan2(y, x) 918 return np.array([rho, theta, z]) 919 920 921def cyl2cart(rho, theta, z): 922 """3D Cylindrical to Cartesian coordinate conversion.""" 923 x = rho * np.cos(theta) 924 y = rho * np.sin(theta) 925 return np.array([x, y, z]) 926 927 928def cyl2spher(rho, theta, z): 929 """3D Cylindrical to Spherical coordinate conversion.""" 930 rhos = np.sqrt(rho * rho + z * z) 931 phi = np.arctan2(rho, z) 932 return np.array([rhos, phi, theta]) 933 934 935def spher2cyl(rho, theta, phi): 936 """3D Spherical to Cylindrical coordinate conversion.""" 937 rhoc = rho * np.sin(theta) 938 z = rho * np.cos(theta) 939 return np.array([rhoc, phi, z])
41class LinearTransform: 42 """Work with linear transformations.""" 43 44 def __init__(self, T=None): 45 """ 46 Define a linear transformation. 47 Can be saved to file and reloaded. 48 49 Arguments: 50 T : (vtkTransform, numpy array) 51 input transformation. Defaults to unit. 52 53 Example: 54 ```python 55 from vedo import * 56 settings.use_parallel_projection = True 57 58 LT = LinearTransform() 59 LT.translate([3,0,1]).rotate_z(45) 60 LT.comment = "shifting by (3,0,1) and rotating by 45 deg" 61 print(LT) 62 63 sph = Sphere(r=0.2) 64 sph.apply_transform(LT) # same as: LT.move(s1) 65 print(sph.transform) 66 67 show(Point([0,0,0]), sph, str(LT.matrix), axes=1).close() 68 ``` 69 """ 70 self.name = "LinearTransform" 71 self.filename = "" 72 self.comment = "" 73 74 if T is None: 75 T = vtk.vtkTransform() 76 77 elif isinstance(T, vtk.vtkMatrix4x4): 78 S = vtk.vtkTransform() 79 S.SetMatrix(T) 80 T = S 81 82 elif isinstance(T, vtk.vtkLandmarkTransform): 83 S = vtk.vtkTransform() 84 S.SetMatrix(T.GetMatrix()) 85 T = S 86 87 elif _is_sequence(T): 88 S = vtk.vtkTransform() 89 M = vtk.vtkMatrix4x4() 90 n = len(T) 91 for i in range(n): 92 for j in range(n): 93 M.SetElement(i, j, T[i][j]) 94 S.SetMatrix(M) 95 T = S 96 97 elif isinstance(T, vtk.vtkLinearTransform): 98 S = vtk.vtkTransform() 99 S.DeepCopy(T) 100 T = S 101 102 elif isinstance(T, LinearTransform): 103 S = vtk.vtkTransform() 104 S.DeepCopy(T.T) 105 T = S 106 107 elif isinstance(T, str): 108 import json 109 self.filename = str(T) 110 try: 111 with open(self.filename, "r") as read_file: 112 D = json.load(read_file) 113 self.name = D["name"] 114 self.comment = D["comment"] 115 matrix = np.array(D["matrix"]) 116 except json.decoder.JSONDecodeError: 117 ### assuming legacy vedo format E.g.: 118 #aligned by manual_align.py 119 # 0.8026854838223 -0.0789823873914 -0.508476844097 38.17377632072 120 # 0.0679734082661 0.9501827489452 -0.040289803376 -69.53864247951 121 # 0.5100652300642 -0.0023313569781 0.805555043665 -81.20317788519 122 # 0.0 0.0 0.0 1.0 123 with open(self.filename, "r", encoding="UTF-8") as read_file: 124 lines = read_file.readlines() 125 i = 0 126 matrix = np.eye(4) 127 for l in lines: 128 if l.startswith("#"): 129 self.comment = l.replace("#", "").strip() 130 continue 131 vals = l.split(" ") 132 for j in range(len(vals)): 133 v = vals[j].replace("\n", "") 134 if v != "": 135 matrix[i, j] = float(v) 136 i += 1 137 T = vtk.vtkTransform() 138 m = vtk.vtkMatrix4x4() 139 for i in range(4): 140 for j in range(4): 141 m.SetElement(i, j, matrix[i][j]) 142 T.SetMatrix(m) 143 144 self.T = T 145 self.T.PostMultiply() 146 self.inverse_flag = False 147 148 def __str__(self): 149 module = self.__class__.__module__ 150 name = self.__class__.__name__ 151 s = f"\x1b[7m\x1b[1m{module}.{name} at ({hex(id(self))})".ljust(75) + "\x1b[0m" 152 s += "\nname".ljust(15) + ": " + self.name 153 if self.filename: 154 s += "\nfilename".ljust(15) + ": " + self.filename 155 if self.comment: 156 s += "\ncomment".ljust(15) + f': \x1b[3m"{self.comment}"\x1b[0m' 157 s += f"\nconcatenations".ljust(15) + f": {self.n_concatenated_transforms}" 158 s += "\ninverse flag".ljust(15) + f": {bool(self.inverse_flag)}" 159 arr = np.array2string(self.matrix, 160 separator=', ', precision=6, suppress_small=True) 161 s += "\nmatrix 4x4".ljust(15) + f":\n{arr}" 162 return s 163 164 def __repr__(self): 165 return self.__str__() 166 167 def print(self): 168 """Print transformation.""" 169 print(self.__str__()) 170 return self 171 172 def move(self, obj): 173 """ 174 Apply transformation to object or single point. 175 176 Note: 177 When applying a transformation to a mesh, the mesh is modified in place. 178 If you want to keep the original mesh unchanged, use `clone()` method. 179 180 Example: 181 ```python 182 from vedo import * 183 settings.use_parallel_projection = True 184 185 LT = LinearTransform() 186 LT.translate([3,0,1]).rotate_z(45) 187 print(LT) 188 189 s = Sphere(r=0.2) 190 LT.move(s) 191 # same as: 192 # s.apply_transform(LT) 193 194 zero = Point([0,0,0]) 195 show(s, zero, axes=1).close() 196 ``` 197 """ 198 if _is_sequence(obj): 199 n = len(obj) 200 if n == 2: 201 obj = [obj[0], obj[1], 0] 202 return np.array(self.T.TransformFloatPoint(obj)) 203 204 obj.apply_transform(self) 205 return obj 206 207 def reset(self): 208 """Reset transformation.""" 209 self.T.Identity() 210 return self 211 212 def pop(self): 213 """Delete the transformation on the top of the stack 214 and sets the top to the next transformation on the stack.""" 215 self.T.Pop() 216 return self 217 218 def is_identity(self): 219 """Check if identity.""" 220 m = self.T.GetMatrix() 221 M = [[m.GetElement(i, j) for j in range(4)] for i in range(4)] 222 if np.allclose(M - np.eye(4), 0): 223 return True 224 return False 225 226 def invert(self): 227 """Invert transformation.""" 228 self.T.Inverse() 229 self.inverse_flag = bool(self.T.GetInverseFlag()) 230 return self 231 232 def compute_inverse(self): 233 """Compute inverse.""" 234 t = self.clone() 235 t.invert() 236 return t 237 238 def copy(self): 239 """Return a copy of the transformation. Alias of `clone()`.""" 240 return self.clone() 241 242 def clone(self): 243 """Clone transformation to make an exact copy.""" 244 return LinearTransform(self.T) 245 246 def concatenate(self, T, pre_multiply=False): 247 """ 248 Post-multiply (by default) 2 transfomations. 249 T can also be a 4x4 matrix or 3x3 matrix. 250 251 Example: 252 ```python 253 from vedo import LinearTransform 254 255 A = LinearTransform() 256 A.rotate_x(45) 257 A.translate([7,8,9]) 258 A.translate([10,10,10]) 259 A.name = "My transformation A" 260 print(A) 261 262 B = A.compute_inverse() 263 B.shift([1,2,3]) 264 B.name = "My transformation B (shifted inverse of A)" 265 print(B) 266 267 # A is applied first, then B 268 # print("A.concatenate(B)", A.concatenate(B)) 269 270 # B is applied first, then A 271 print(B*A) 272 ``` 273 """ 274 if _is_sequence(T): 275 S = vtk.vtkTransform() 276 M = vtk.vtkMatrix4x4() 277 n = len(T) 278 for i in range(n): 279 for j in range(n): 280 M.SetElement(i, j, T[i][j]) 281 S.SetMatrix(M) 282 T = S 283 284 if pre_multiply: 285 self.T.PreMultiply() 286 try: 287 self.T.Concatenate(T) 288 except: 289 self.T.Concatenate(T.T) 290 self.T.PostMultiply() 291 return self 292 293 def __mul__(self, A): 294 """Pre-multiply 2 transfomations.""" 295 return self.concatenate(A, pre_multiply=True) 296 297 def get_concatenated_transform(self, i): 298 """Get intermediate matrix by concatenation index.""" 299 return LinearTransform(self.T.GetConcatenatedTransform(i)) 300 301 @property 302 def n_concatenated_transforms(self): 303 """Get number of concatenated transforms.""" 304 return self.T.GetNumberOfConcatenatedTransforms() 305 306 def translate(self, p): 307 """Translate, same as `shift`.""" 308 if len(p) == 2: 309 p = [p[0], p[1], 0] 310 self.T.Translate(p) 311 return self 312 313 def shift(self, p): 314 """Shift, same as `translate`.""" 315 return self.translate(p) 316 317 def scale(self, s, origin=True): 318 """Scale.""" 319 if not _is_sequence(s): 320 s = [s, s, s] 321 322 if origin is True: 323 p = np.array(self.T.GetPosition()) 324 if np.linalg.norm(p) > 0: 325 self.T.Translate(-p) 326 self.T.Scale(*s) 327 self.T.Translate(p) 328 else: 329 self.T.Scale(*s) 330 331 elif _is_sequence(origin): 332 origin = np.asarray(origin) 333 self.T.Translate(-origin) 334 self.T.Scale(*s) 335 self.T.Translate(origin) 336 337 else: 338 self.T.Scale(*s) 339 return self 340 341 def rotate(self, angle, axis=(1, 0, 0), point=(0, 0, 0), rad=False): 342 """ 343 Rotate around an arbitrary `axis` passing through `point`. 344 345 Example: 346 ```python 347 from vedo import * 348 c1 = Cube() 349 c2 = c1.clone().c('violet').alpha(0.5) # copy of c1 350 v = vector(0.2, 1, 0) 351 p = vector(1.0, 0, 0) # axis passes through this point 352 c2.rotate(90, axis=v, point=p) 353 l = Line(p-v, p+v).c('red5').lw(3) 354 show(c1, l, c2, axes=1).close() 355 ``` 356  357 """ 358 if not angle: 359 return self 360 if rad: 361 anglerad = angle 362 else: 363 anglerad = np.deg2rad(angle) 364 axis = np.asarray(axis) / np.linalg.norm(axis) 365 a = np.cos(anglerad / 2) 366 b, c, d = -axis * np.sin(anglerad / 2) 367 aa, bb, cc, dd = a * a, b * b, c * c, d * d 368 bc, ad, ac, ab, bd, cd = b * c, a * d, a * c, a * b, b * d, c * d 369 R = np.array( 370 [ 371 [aa + bb - cc - dd, 2 * (bc + ad), 2 * (bd - ac)], 372 [2 * (bc - ad), aa + cc - bb - dd, 2 * (cd + ab)], 373 [2 * (bd + ac), 2 * (cd - ab), aa + dd - bb - cc], 374 ] 375 ) 376 rv = np.dot(R, self.T.GetPosition() - np.asarray(point)) + point 377 378 if rad: 379 angle *= 180.0 / np.pi 380 # this vtk method only rotates in the origin of the object: 381 self.T.RotateWXYZ(angle, axis[0], axis[1], axis[2]) 382 self.T.Translate(rv - np.array(self.T.GetPosition())) 383 return self 384 385 def _rotatexyz(self, axe, angle, rad, around): 386 if not angle: 387 return self 388 if rad: 389 angle *= 180 / np.pi 390 391 rot = dict(x=self.T.RotateX, y=self.T.RotateY, z=self.T.RotateZ) 392 393 if around is None: 394 # rotate around its origin 395 rot[axe](angle) 396 else: 397 # displacement needed to bring it back to the origin 398 self.T.Translate(-np.asarray(around)) 399 rot[axe](angle) 400 self.T.Translate(around) 401 return self 402 403 def rotate_x(self, angle, rad=False, around=None): 404 """ 405 Rotate around x-axis. If angle is in radians set `rad=True`. 406 407 Use `around` to define a pivoting point. 408 """ 409 return self._rotatexyz("x", angle, rad, around) 410 411 def rotate_y(self, angle, rad=False, around=None): 412 """ 413 Rotate around y-axis. If angle is in radians set `rad=True`. 414 415 Use `around` to define a pivoting point. 416 """ 417 return self._rotatexyz("y", angle, rad, around) 418 419 def rotate_z(self, angle, rad=False, around=None): 420 """ 421 Rotate around z-axis. If angle is in radians set `rad=True`. 422 423 Use `around` to define a pivoting point. 424 """ 425 return self._rotatexyz("z", angle, rad, around) 426 427 def set_position(self, p): 428 """Set position.""" 429 if len(p) == 2: 430 p = np.array([p[0], p[1], 0]) 431 q = np.array(self.T.GetPosition()) 432 self.T.Translate(p - q) 433 return self 434 435 # def set_scale(self, s): 436 # """Set absolute scale.""" 437 # if not _is_sequence(s): 438 # s = [s, s, s] 439 # s0, s1, s2 = 1, 1, 1 440 # b = self.T.GetScale() 441 # print(b) 442 # if b[0]: 443 # s0 = s[0] / b[0] 444 # if b[1]: 445 # s1 = s[1] / b[1] 446 # if b[2]: 447 # s2 = s[2] / b[2] 448 # self.T.Scale(s0, s1, s2) 449 # print() 450 # return self 451 452 def get_scale(self): 453 """Get current scale.""" 454 return np.array(self.T.GetScale()) 455 456 @property 457 def orientation(self): 458 """Compute orientation.""" 459 return np.array(self.T.GetOrientation()) 460 461 @property 462 def position(self): 463 """Compute position.""" 464 return np.array(self.T.GetPosition()) 465 466 @property 467 def matrix(self): 468 """Get the 4x4 trasformation matrix.""" 469 m = self.T.GetMatrix() 470 M = [[m.GetElement(i, j) for j in range(4)] for i in range(4)] 471 return np.array(M) 472 473 @matrix.setter 474 def matrix(self, M): 475 """Set trasformation by assigning a 4x4 or 3x3 numpy matrix.""" 476 m = vtk.vtkMatrix4x4() 477 n = len(M) 478 for i in range(n): 479 for j in range(n): 480 m.SetElement(i, j, M[i][j]) 481 self.T.SetMatrix(m) 482 483 @property 484 def matrix3x3(self): 485 """Get the 3x3 trasformation matrix.""" 486 m = self.T.GetMatrix() 487 M = [[m.GetElement(i, j) for j in range(3)] for i in range(3)] 488 return np.array(M) 489 490 def write(self, filename="transform.mat"): 491 """Save transformation to ASCII file.""" 492 import json 493 m = self.T.GetMatrix() 494 M = [[m.GetElement(i, j) for j in range(4)] for i in range(4)] 495 arr = np.array(M) 496 dictionary = { 497 "name": self.name, 498 "comment": self.comment, 499 "matrix": arr.astype(float).tolist(), 500 "n_concatenated_transforms": self.n_concatenated_transforms, 501 } 502 with open(filename, "w") as outfile: 503 json.dump(dictionary, outfile, sort_keys=True, indent=2) 504 505 def reorient( 506 self, initaxis, newaxis, around=(0, 0, 0), rotation=0, rad=False, xyplane=True 507 ): 508 """ 509 Set/Get object orientation. 510 511 Arguments: 512 rotation : (float) 513 rotate object around newaxis. 514 concatenate : (bool) 515 concatenate the orientation operation with the previous existing transform (if any) 516 rad : (bool) 517 set to True if angle is expressed in radians. 518 xyplane : (bool) 519 make an extra rotation to keep the object aligned to the xy-plane 520 """ 521 newaxis = np.asarray(newaxis) / np.linalg.norm(newaxis) 522 initaxis = np.asarray(initaxis) / np.linalg.norm(initaxis) 523 524 if not np.any(initaxis - newaxis): 525 return self 526 527 if not np.any(initaxis + newaxis): 528 print("Warning: in reorient() initaxis and newaxis are parallel") 529 newaxis += np.array([0.0000001, 0.0000002, 0]) 530 angleth = np.pi 531 else: 532 angleth = np.arccos(np.dot(initaxis, newaxis)) 533 crossvec = np.cross(initaxis, newaxis) 534 535 p = np.asarray(around) 536 self.T.Translate(-p) 537 if rotation: 538 if rad: 539 rotation = np.rad2deg(rotation) 540 self.T.RotateWXYZ(rotation, initaxis) 541 542 self.T.RotateWXYZ(np.rad2deg(angleth), crossvec) 543 544 if xyplane: 545 self.T.RotateWXYZ(-self.orientation[0] * 1.4142, newaxis) 546 547 self.T.Translate(p) 548 return self
Work with linear transformations.
44 def __init__(self, T=None): 45 """ 46 Define a linear transformation. 47 Can be saved to file and reloaded. 48 49 Arguments: 50 T : (vtkTransform, numpy array) 51 input transformation. Defaults to unit. 52 53 Example: 54 ```python 55 from vedo import * 56 settings.use_parallel_projection = True 57 58 LT = LinearTransform() 59 LT.translate([3,0,1]).rotate_z(45) 60 LT.comment = "shifting by (3,0,1) and rotating by 45 deg" 61 print(LT) 62 63 sph = Sphere(r=0.2) 64 sph.apply_transform(LT) # same as: LT.move(s1) 65 print(sph.transform) 66 67 show(Point([0,0,0]), sph, str(LT.matrix), axes=1).close() 68 ``` 69 """ 70 self.name = "LinearTransform" 71 self.filename = "" 72 self.comment = "" 73 74 if T is None: 75 T = vtk.vtkTransform() 76 77 elif isinstance(T, vtk.vtkMatrix4x4): 78 S = vtk.vtkTransform() 79 S.SetMatrix(T) 80 T = S 81 82 elif isinstance(T, vtk.vtkLandmarkTransform): 83 S = vtk.vtkTransform() 84 S.SetMatrix(T.GetMatrix()) 85 T = S 86 87 elif _is_sequence(T): 88 S = vtk.vtkTransform() 89 M = vtk.vtkMatrix4x4() 90 n = len(T) 91 for i in range(n): 92 for j in range(n): 93 M.SetElement(i, j, T[i][j]) 94 S.SetMatrix(M) 95 T = S 96 97 elif isinstance(T, vtk.vtkLinearTransform): 98 S = vtk.vtkTransform() 99 S.DeepCopy(T) 100 T = S 101 102 elif isinstance(T, LinearTransform): 103 S = vtk.vtkTransform() 104 S.DeepCopy(T.T) 105 T = S 106 107 elif isinstance(T, str): 108 import json 109 self.filename = str(T) 110 try: 111 with open(self.filename, "r") as read_file: 112 D = json.load(read_file) 113 self.name = D["name"] 114 self.comment = D["comment"] 115 matrix = np.array(D["matrix"]) 116 except json.decoder.JSONDecodeError: 117 ### assuming legacy vedo format E.g.: 118 #aligned by manual_align.py 119 # 0.8026854838223 -0.0789823873914 -0.508476844097 38.17377632072 120 # 0.0679734082661 0.9501827489452 -0.040289803376 -69.53864247951 121 # 0.5100652300642 -0.0023313569781 0.805555043665 -81.20317788519 122 # 0.0 0.0 0.0 1.0 123 with open(self.filename, "r", encoding="UTF-8") as read_file: 124 lines = read_file.readlines() 125 i = 0 126 matrix = np.eye(4) 127 for l in lines: 128 if l.startswith("#"): 129 self.comment = l.replace("#", "").strip() 130 continue 131 vals = l.split(" ") 132 for j in range(len(vals)): 133 v = vals[j].replace("\n", "") 134 if v != "": 135 matrix[i, j] = float(v) 136 i += 1 137 T = vtk.vtkTransform() 138 m = vtk.vtkMatrix4x4() 139 for i in range(4): 140 for j in range(4): 141 m.SetElement(i, j, matrix[i][j]) 142 T.SetMatrix(m) 143 144 self.T = T 145 self.T.PostMultiply() 146 self.inverse_flag = False
Define a linear transformation. Can be saved to file and reloaded.
Arguments:
- T : (vtkTransform, numpy array) input transformation. Defaults to unit.
Example:
from vedo import * settings.use_parallel_projection = True LT = LinearTransform() LT.translate([3,0,1]).rotate_z(45) LT.comment = "shifting by (3,0,1) and rotating by 45 deg" print(LT) sph = Sphere(r=0.2) sph.apply_transform(LT) # same as: LT.move(s1) print(sph.transform) show(Point([0,0,0]), sph, str(LT.matrix), axes=1).close()
172 def move(self, obj): 173 """ 174 Apply transformation to object or single point. 175 176 Note: 177 When applying a transformation to a mesh, the mesh is modified in place. 178 If you want to keep the original mesh unchanged, use `clone()` method. 179 180 Example: 181 ```python 182 from vedo import * 183 settings.use_parallel_projection = True 184 185 LT = LinearTransform() 186 LT.translate([3,0,1]).rotate_z(45) 187 print(LT) 188 189 s = Sphere(r=0.2) 190 LT.move(s) 191 # same as: 192 # s.apply_transform(LT) 193 194 zero = Point([0,0,0]) 195 show(s, zero, axes=1).close() 196 ``` 197 """ 198 if _is_sequence(obj): 199 n = len(obj) 200 if n == 2: 201 obj = [obj[0], obj[1], 0] 202 return np.array(self.T.TransformFloatPoint(obj)) 203 204 obj.apply_transform(self) 205 return obj
Apply transformation to object or single point.
Note:
When applying a transformation to a mesh, the mesh is modified in place. If you want to keep the original mesh unchanged, use
clone()
method.
Example:
from vedo import * settings.use_parallel_projection = True LT = LinearTransform() LT.translate([3,0,1]).rotate_z(45) print(LT) s = Sphere(r=0.2) LT.move(s) # same as: # s.apply_transform(LT) zero = Point([0,0,0]) show(s, zero, axes=1).close()
212 def pop(self): 213 """Delete the transformation on the top of the stack 214 and sets the top to the next transformation on the stack.""" 215 self.T.Pop() 216 return self
Delete the transformation on the top of the stack and sets the top to the next transformation on the stack.
218 def is_identity(self): 219 """Check if identity.""" 220 m = self.T.GetMatrix() 221 M = [[m.GetElement(i, j) for j in range(4)] for i in range(4)] 222 if np.allclose(M - np.eye(4), 0): 223 return True 224 return False
Check if identity.
226 def invert(self): 227 """Invert transformation.""" 228 self.T.Inverse() 229 self.inverse_flag = bool(self.T.GetInverseFlag()) 230 return self
Invert transformation.
232 def compute_inverse(self): 233 """Compute inverse.""" 234 t = self.clone() 235 t.invert() 236 return t
Compute inverse.
238 def copy(self): 239 """Return a copy of the transformation. Alias of `clone()`.""" 240 return self.clone()
Return a copy of the transformation. Alias of clone()
.
242 def clone(self): 243 """Clone transformation to make an exact copy.""" 244 return LinearTransform(self.T)
Clone transformation to make an exact copy.
246 def concatenate(self, T, pre_multiply=False): 247 """ 248 Post-multiply (by default) 2 transfomations. 249 T can also be a 4x4 matrix or 3x3 matrix. 250 251 Example: 252 ```python 253 from vedo import LinearTransform 254 255 A = LinearTransform() 256 A.rotate_x(45) 257 A.translate([7,8,9]) 258 A.translate([10,10,10]) 259 A.name = "My transformation A" 260 print(A) 261 262 B = A.compute_inverse() 263 B.shift([1,2,3]) 264 B.name = "My transformation B (shifted inverse of A)" 265 print(B) 266 267 # A is applied first, then B 268 # print("A.concatenate(B)", A.concatenate(B)) 269 270 # B is applied first, then A 271 print(B*A) 272 ``` 273 """ 274 if _is_sequence(T): 275 S = vtk.vtkTransform() 276 M = vtk.vtkMatrix4x4() 277 n = len(T) 278 for i in range(n): 279 for j in range(n): 280 M.SetElement(i, j, T[i][j]) 281 S.SetMatrix(M) 282 T = S 283 284 if pre_multiply: 285 self.T.PreMultiply() 286 try: 287 self.T.Concatenate(T) 288 except: 289 self.T.Concatenate(T.T) 290 self.T.PostMultiply() 291 return self
Post-multiply (by default) 2 transfomations. T can also be a 4x4 matrix or 3x3 matrix.
Example:
from vedo import LinearTransform A = LinearTransform() A.rotate_x(45) A.translate([7,8,9]) A.translate([10,10,10]) A.name = "My transformation A" print(A) B = A.compute_inverse() B.shift([1,2,3]) B.name = "My transformation B (shifted inverse of A)" print(B) # A is applied first, then B # print("A.concatenate(B)", A.concatenate(B)) # B is applied first, then A print(B*A)
297 def get_concatenated_transform(self, i): 298 """Get intermediate matrix by concatenation index.""" 299 return LinearTransform(self.T.GetConcatenatedTransform(i))
Get intermediate matrix by concatenation index.
306 def translate(self, p): 307 """Translate, same as `shift`.""" 308 if len(p) == 2: 309 p = [p[0], p[1], 0] 310 self.T.Translate(p) 311 return self
Translate, same as shift
.
317 def scale(self, s, origin=True): 318 """Scale.""" 319 if not _is_sequence(s): 320 s = [s, s, s] 321 322 if origin is True: 323 p = np.array(self.T.GetPosition()) 324 if np.linalg.norm(p) > 0: 325 self.T.Translate(-p) 326 self.T.Scale(*s) 327 self.T.Translate(p) 328 else: 329 self.T.Scale(*s) 330 331 elif _is_sequence(origin): 332 origin = np.asarray(origin) 333 self.T.Translate(-origin) 334 self.T.Scale(*s) 335 self.T.Translate(origin) 336 337 else: 338 self.T.Scale(*s) 339 return self
Scale.
341 def rotate(self, angle, axis=(1, 0, 0), point=(0, 0, 0), rad=False): 342 """ 343 Rotate around an arbitrary `axis` passing through `point`. 344 345 Example: 346 ```python 347 from vedo import * 348 c1 = Cube() 349 c2 = c1.clone().c('violet').alpha(0.5) # copy of c1 350 v = vector(0.2, 1, 0) 351 p = vector(1.0, 0, 0) # axis passes through this point 352 c2.rotate(90, axis=v, point=p) 353 l = Line(p-v, p+v).c('red5').lw(3) 354 show(c1, l, c2, axes=1).close() 355 ``` 356  357 """ 358 if not angle: 359 return self 360 if rad: 361 anglerad = angle 362 else: 363 anglerad = np.deg2rad(angle) 364 axis = np.asarray(axis) / np.linalg.norm(axis) 365 a = np.cos(anglerad / 2) 366 b, c, d = -axis * np.sin(anglerad / 2) 367 aa, bb, cc, dd = a * a, b * b, c * c, d * d 368 bc, ad, ac, ab, bd, cd = b * c, a * d, a * c, a * b, b * d, c * d 369 R = np.array( 370 [ 371 [aa + bb - cc - dd, 2 * (bc + ad), 2 * (bd - ac)], 372 [2 * (bc - ad), aa + cc - bb - dd, 2 * (cd + ab)], 373 [2 * (bd + ac), 2 * (cd - ab), aa + dd - bb - cc], 374 ] 375 ) 376 rv = np.dot(R, self.T.GetPosition() - np.asarray(point)) + point 377 378 if rad: 379 angle *= 180.0 / np.pi 380 # this vtk method only rotates in the origin of the object: 381 self.T.RotateWXYZ(angle, axis[0], axis[1], axis[2]) 382 self.T.Translate(rv - np.array(self.T.GetPosition())) 383 return self
Rotate around an arbitrary axis
passing through point
.
Example:
from vedo import * c1 = Cube() c2 = c1.clone().c('violet').alpha(0.5) # copy of c1 v = vector(0.2, 1, 0) p = vector(1.0, 0, 0) # axis passes through this point c2.rotate(90, axis=v, point=p) l = Line(p-v, p+v).c('red5').lw(3) show(c1, l, c2, axes=1).close()
403 def rotate_x(self, angle, rad=False, around=None): 404 """ 405 Rotate around x-axis. If angle is in radians set `rad=True`. 406 407 Use `around` to define a pivoting point. 408 """ 409 return self._rotatexyz("x", angle, rad, around)
Rotate around x-axis. If angle is in radians set rad=True
.
Use around
to define a pivoting point.
411 def rotate_y(self, angle, rad=False, around=None): 412 """ 413 Rotate around y-axis. If angle is in radians set `rad=True`. 414 415 Use `around` to define a pivoting point. 416 """ 417 return self._rotatexyz("y", angle, rad, around)
Rotate around y-axis. If angle is in radians set rad=True
.
Use around
to define a pivoting point.
419 def rotate_z(self, angle, rad=False, around=None): 420 """ 421 Rotate around z-axis. If angle is in radians set `rad=True`. 422 423 Use `around` to define a pivoting point. 424 """ 425 return self._rotatexyz("z", angle, rad, around)
Rotate around z-axis. If angle is in radians set rad=True
.
Use around
to define a pivoting point.
427 def set_position(self, p): 428 """Set position.""" 429 if len(p) == 2: 430 p = np.array([p[0], p[1], 0]) 431 q = np.array(self.T.GetPosition()) 432 self.T.Translate(p - q) 433 return self
Set position.
490 def write(self, filename="transform.mat"): 491 """Save transformation to ASCII file.""" 492 import json 493 m = self.T.GetMatrix() 494 M = [[m.GetElement(i, j) for j in range(4)] for i in range(4)] 495 arr = np.array(M) 496 dictionary = { 497 "name": self.name, 498 "comment": self.comment, 499 "matrix": arr.astype(float).tolist(), 500 "n_concatenated_transforms": self.n_concatenated_transforms, 501 } 502 with open(filename, "w") as outfile: 503 json.dump(dictionary, outfile, sort_keys=True, indent=2)
Save transformation to ASCII file.
505 def reorient( 506 self, initaxis, newaxis, around=(0, 0, 0), rotation=0, rad=False, xyplane=True 507 ): 508 """ 509 Set/Get object orientation. 510 511 Arguments: 512 rotation : (float) 513 rotate object around newaxis. 514 concatenate : (bool) 515 concatenate the orientation operation with the previous existing transform (if any) 516 rad : (bool) 517 set to True if angle is expressed in radians. 518 xyplane : (bool) 519 make an extra rotation to keep the object aligned to the xy-plane 520 """ 521 newaxis = np.asarray(newaxis) / np.linalg.norm(newaxis) 522 initaxis = np.asarray(initaxis) / np.linalg.norm(initaxis) 523 524 if not np.any(initaxis - newaxis): 525 return self 526 527 if not np.any(initaxis + newaxis): 528 print("Warning: in reorient() initaxis and newaxis are parallel") 529 newaxis += np.array([0.0000001, 0.0000002, 0]) 530 angleth = np.pi 531 else: 532 angleth = np.arccos(np.dot(initaxis, newaxis)) 533 crossvec = np.cross(initaxis, newaxis) 534 535 p = np.asarray(around) 536 self.T.Translate(-p) 537 if rotation: 538 if rad: 539 rotation = np.rad2deg(rotation) 540 self.T.RotateWXYZ(rotation, initaxis) 541 542 self.T.RotateWXYZ(np.rad2deg(angleth), crossvec) 543 544 if xyplane: 545 self.T.RotateWXYZ(-self.orientation[0] * 1.4142, newaxis) 546 547 self.T.Translate(p) 548 return self
Set/Get object orientation.
Arguments:
- rotation : (float) rotate object around newaxis.
- concatenate : (bool) concatenate the orientation operation with the previous existing transform (if any)
- rad : (bool) set to True if angle is expressed in radians.
- xyplane : (bool) make an extra rotation to keep the object aligned to the xy-plane
552class NonLinearTransform: 553 """Work with non-linear transformations.""" 554 555 def __init__(self, T=None, **kwargs): 556 """ 557 Define a non-linear transformation. 558 Can be saved to file and reloaded. 559 560 Arguments: 561 T : (vtkThinPlateSplineTransform, str, dict) 562 vtk transformation. 563 If T is a string, it is assumed to be a filename. 564 If T is a dictionary, it is assumed to be a set of keyword arguments. 565 Defaults to None. 566 **kwargs : (dict) 567 keyword arguments to define the transformation. 568 The following keywords are accepted: 569 - name : (str) name of the transformation 570 - comment : (str) comment 571 - source_points : (list) source points 572 - target_points : (list) target points 573 - mode : (str) either '2d' or '3d' 574 - sigma : (float) sigma parameter 575 576 Example: 577 ```python 578 from vedo import * 579 settings.use_parallel_projection = True 580 581 NLT = NonLinearTransform() 582 NLT.source_points = [[-2,0,0], [1,2,1], [2,-2,2]] 583 NLT.target_points = NLT.source_points + np.random.randn(3,3)*0.5 584 NLT.mode = '3d' 585 print(NLT) 586 587 s1 = Sphere() 588 NLT.move(s1) 589 # same as: 590 # s1.apply_transform(NLT) 591 592 arrs = Arrows(NLT.source_points, NLT.target_points) 593 show(s1, arrs, Sphere().alpha(0.1), axes=1).close() 594 ``` 595 """ 596 597 self.name = "NonLinearTransform" 598 self.filename = "" 599 self.comment = "" 600 601 if T is None and len(kwargs) == 0: 602 T = vtk.vtkThinPlateSplineTransform() 603 604 elif isinstance(T, vtk.vtkThinPlateSplineTransform): 605 S = vtk.vtkThinPlateSplineTransform() 606 S.DeepCopy(T) 607 T = S 608 609 elif isinstance(T, NonLinearTransform): 610 S = vtk.vtkThinPlateSplineTransform() 611 S.DeepCopy(T.T) 612 T = S 613 614 elif isinstance(T, str): 615 import json 616 filename = str(T) 617 self.filename = filename 618 with open(filename, "r") as read_file: 619 D = json.load(read_file) 620 self.name = D["name"] 621 self.comment = D["comment"] 622 source = D["source_points"] 623 target = D["target_points"] 624 mode = D["mode"] 625 sigma = D["sigma"] 626 627 T = vtk.vtkThinPlateSplineTransform() 628 vptss = vtk.vtkPoints() 629 for p in source: 630 if len(p) == 2: 631 p = [p[0], p[1], 0.0] 632 vptss.InsertNextPoint(p) 633 T.SetSourceLandmarks(vptss) 634 vptst = vtk.vtkPoints() 635 for p in target: 636 if len(p) == 2: 637 p = [p[0], p[1], 0.0] 638 vptst.InsertNextPoint(p) 639 T.SetTargetLandmarks(vptst) 640 T.SetSigma(sigma) 641 if mode == "2d": 642 T.SetBasisToR2LogR() 643 elif mode == "3d": 644 T.SetBasisToR() 645 else: 646 print(f'In {filename} mode can be either "2d" or "3d"') 647 648 elif len(kwargs) > 0: 649 T = kwargs.copy() 650 self.name = T.pop("name", "NonLinearTransform") 651 self.comment = T.pop("comment", "") 652 source = T.pop("source_points", []) 653 target = T.pop("target_points", []) 654 mode = T.pop("mode", "3d") 655 sigma = T.pop("sigma", 1.0) 656 if len(T) > 0: 657 print("Warning: NonLinearTransform got unexpected keyword arguments:") 658 print(T) 659 660 T = vtk.vtkThinPlateSplineTransform() 661 vptss = vtk.vtkPoints() 662 for p in source: 663 if len(p) == 2: 664 p = [p[0], p[1], 0.0] 665 vptss.InsertNextPoint(p) 666 T.SetSourceLandmarks(vptss) 667 vptst = vtk.vtkPoints() 668 for p in target: 669 if len(p) == 2: 670 p = [p[0], p[1], 0.0] 671 vptst.InsertNextPoint(p) 672 T.SetTargetLandmarks(vptst) 673 T.SetSigma(sigma) 674 if mode == "2d": 675 T.SetBasisToR2LogR() 676 elif mode == "3d": 677 T.SetBasisToR() 678 else: 679 print(f'In {filename} mode can be either "2d" or "3d"') 680 681 self.T = T 682 self.inverse_flag = False 683 684 def __str__(self): 685 module = self.__class__.__module__ 686 name = self.__class__.__name__ 687 s = f"\x1b[7m\x1b[1m{module}.{name} at ({hex(id(self))})".ljust(75) + "\x1b[0m\n" 688 s += "name".ljust(9) + ": " + self.name + "\n" 689 if self.filename: 690 s += "filename".ljust(9) + ": " + self.filename + "\n" 691 if self.comment: 692 s += "comment".ljust(9) + f': \x1b[3m"{self.comment}"\x1b[0m\n' 693 s += f"mode".ljust(9) + f": {self.mode}\n" 694 s += f"sigma".ljust(9) + f": {self.sigma}\n" 695 p = self.source_points 696 q = self.target_points 697 s += f"sources".ljust(9) + f": {p.size}, bounds {np.min(p, axis=0)}, {np.max(p, axis=0)}\n" 698 s += f"targets".ljust(9) + f": {q.size}, bounds {np.min(q, axis=0)}, {np.max(q, axis=0)}" 699 return s 700 701 def __repr__(self): 702 return self.__str__() 703 704 def update(self): 705 """Update transformation.""" 706 self.T.Update() 707 return self 708 709 @property 710 def position(self): 711 """ 712 Trying to get the position of a `NonLinearTransform` always returns [0,0,0]. 713 """ 714 return np.array([0.0, 0.0, 0.0], dtype=np.float32) 715 716 @position.setter 717 def position(self, p): 718 """ 719 Trying to set position of a `NonLinearTransform` 720 has no effect and prints a warning. 721 722 Use clone() method to create a copy of the object, 723 or reset it with 'object.transform = vedo.LinearTransform()' 724 """ 725 print("Warning: NonLinearTransform has no position.") 726 print(" Use clone() method to create a copy of the object,") 727 print(" or reset it with 'object.transform = vedo.LinearTransform()'") 728 729 @property 730 def source_points(self): 731 """Get the source points.""" 732 pts = self.T.GetSourceLandmarks() 733 vpts = [] 734 if pts: 735 for i in range(pts.GetNumberOfPoints()): 736 vpts.append(pts.GetPoint(i)) 737 return np.array(vpts, dtype=np.float32) 738 739 @property 740 def target_points(self): 741 """Get the target points.""" 742 pts = self.T.GetTargetLandmarks() 743 vpts = [] 744 for i in range(pts.GetNumberOfPoints()): 745 vpts.append(pts.GetPoint(i)) 746 return np.array(vpts, dtype=np.float32) 747 748 @source_points.setter 749 def source_points(self, pts): 750 """Set source points.""" 751 if _is_sequence(pts): 752 pass 753 else: 754 pts = pts.vertices 755 vpts = vtk.vtkPoints() 756 for p in pts: 757 if len(p) == 2: 758 p = [p[0], p[1], 0.0] 759 vpts.InsertNextPoint(p) 760 self.T.SetSourceLandmarks(vpts) 761 762 @target_points.setter 763 def target_points(self, pts): 764 """Set target points.""" 765 if _is_sequence(pts): 766 pass 767 else: 768 pts = pts.vertices 769 vpts = vtk.vtkPoints() 770 for p in pts: 771 if len(p) == 2: 772 p = [p[0], p[1], 0.0] 773 vpts.InsertNextPoint(p) 774 self.T.SetTargetLandmarks(vpts) 775 776 @property 777 def sigma(self) -> float: 778 """Set sigma.""" 779 return self.T.GetSigma() 780 781 @sigma.setter 782 def sigma(self, s): 783 """Get sigma.""" 784 self.T.SetSigma(s) 785 786 @property 787 def mode(self) -> str: 788 """Get mode.""" 789 m = self.T.GetBasis() 790 # print("T.GetBasis()", m, self.T.GetBasisAsString()) 791 if m == 2: 792 return "2d" 793 elif m == 1: 794 return "3d" 795 else: 796 print("Warning: NonLinearTransform has no valid mode.") 797 798 @mode.setter 799 def mode(self, m): 800 """Set mode.""" 801 if m=='3d': 802 self.T.SetBasisToR() 803 elif m=='2d': 804 self.T.SetBasisToR2LogR() 805 else: 806 print('In NonLinearTransform mode can be either "2d" or "3d"') 807 808 def clone(self): 809 """Clone transformation to make an exact copy.""" 810 return NonLinearTransform(self.T) 811 812 def write(self, filename): 813 """Save transformation to ASCII file.""" 814 import json 815 dictionary = { 816 "name": self.name, 817 "comment": self.comment, 818 "mode": self.mode, 819 "sigma": self.sigma, 820 "source_points": self.source_points.astype(float).tolist(), 821 "target_points": self.target_points.astype(float).tolist(), 822 } 823 with open(filename, "w") as outfile: 824 json.dump(dictionary, outfile, sort_keys=True, indent=2) 825 826 def invert(self): 827 """Invert transformation.""" 828 self.T.Inverse() 829 self.inverse_flag = bool(self.T.GetInverseFlag()) 830 return self 831 832 def compute_inverse(self): 833 """Compute inverse.""" 834 t = self.clone() 835 t.invert() 836 return t 837 838 def move(self, obj): 839 """ 840 Apply transformation to object or single point. 841 842 Note: 843 When applying a transformation to a mesh, the mesh is modified in place. 844 If you want to keep the original mesh unchanged, use `clone()` method. 845 846 Example: 847 ```python 848 from vedo import * 849 np.random.seed(0) 850 settings.use_parallel_projection = True 851 852 NLT = NonLinearTransform() 853 NLT.source_points = [[-2,0,0], [1,2,1], [2,-2,2]] 854 NLT.target_points = NLT.source_points + np.random.randn(3,3)*0.5 855 NLT.mode = '3d' 856 print(NLT) 857 858 s1 = Sphere() 859 NLT.move(s1) 860 # same as: 861 # s1.apply_transform(NLT) 862 863 arrs = Arrows(NLT.source_points, NLT.target_points) 864 show(s1, arrs, Sphere().alpha(0.1), axes=1).close() 865 ``` 866 """ 867 if _is_sequence(obj): 868 if len(obj) == 2: 869 obj = [obj[0], obj[1], 0] 870 return np.array(self.T.TransformFloatPoint(obj)) 871 872 obj.apply_transform(self) 873 return obj
Work with non-linear transformations.
555 def __init__(self, T=None, **kwargs): 556 """ 557 Define a non-linear transformation. 558 Can be saved to file and reloaded. 559 560 Arguments: 561 T : (vtkThinPlateSplineTransform, str, dict) 562 vtk transformation. 563 If T is a string, it is assumed to be a filename. 564 If T is a dictionary, it is assumed to be a set of keyword arguments. 565 Defaults to None. 566 **kwargs : (dict) 567 keyword arguments to define the transformation. 568 The following keywords are accepted: 569 - name : (str) name of the transformation 570 - comment : (str) comment 571 - source_points : (list) source points 572 - target_points : (list) target points 573 - mode : (str) either '2d' or '3d' 574 - sigma : (float) sigma parameter 575 576 Example: 577 ```python 578 from vedo import * 579 settings.use_parallel_projection = True 580 581 NLT = NonLinearTransform() 582 NLT.source_points = [[-2,0,0], [1,2,1], [2,-2,2]] 583 NLT.target_points = NLT.source_points + np.random.randn(3,3)*0.5 584 NLT.mode = '3d' 585 print(NLT) 586 587 s1 = Sphere() 588 NLT.move(s1) 589 # same as: 590 # s1.apply_transform(NLT) 591 592 arrs = Arrows(NLT.source_points, NLT.target_points) 593 show(s1, arrs, Sphere().alpha(0.1), axes=1).close() 594 ``` 595 """ 596 597 self.name = "NonLinearTransform" 598 self.filename = "" 599 self.comment = "" 600 601 if T is None and len(kwargs) == 0: 602 T = vtk.vtkThinPlateSplineTransform() 603 604 elif isinstance(T, vtk.vtkThinPlateSplineTransform): 605 S = vtk.vtkThinPlateSplineTransform() 606 S.DeepCopy(T) 607 T = S 608 609 elif isinstance(T, NonLinearTransform): 610 S = vtk.vtkThinPlateSplineTransform() 611 S.DeepCopy(T.T) 612 T = S 613 614 elif isinstance(T, str): 615 import json 616 filename = str(T) 617 self.filename = filename 618 with open(filename, "r") as read_file: 619 D = json.load(read_file) 620 self.name = D["name"] 621 self.comment = D["comment"] 622 source = D["source_points"] 623 target = D["target_points"] 624 mode = D["mode"] 625 sigma = D["sigma"] 626 627 T = vtk.vtkThinPlateSplineTransform() 628 vptss = vtk.vtkPoints() 629 for p in source: 630 if len(p) == 2: 631 p = [p[0], p[1], 0.0] 632 vptss.InsertNextPoint(p) 633 T.SetSourceLandmarks(vptss) 634 vptst = vtk.vtkPoints() 635 for p in target: 636 if len(p) == 2: 637 p = [p[0], p[1], 0.0] 638 vptst.InsertNextPoint(p) 639 T.SetTargetLandmarks(vptst) 640 T.SetSigma(sigma) 641 if mode == "2d": 642 T.SetBasisToR2LogR() 643 elif mode == "3d": 644 T.SetBasisToR() 645 else: 646 print(f'In {filename} mode can be either "2d" or "3d"') 647 648 elif len(kwargs) > 0: 649 T = kwargs.copy() 650 self.name = T.pop("name", "NonLinearTransform") 651 self.comment = T.pop("comment", "") 652 source = T.pop("source_points", []) 653 target = T.pop("target_points", []) 654 mode = T.pop("mode", "3d") 655 sigma = T.pop("sigma", 1.0) 656 if len(T) > 0: 657 print("Warning: NonLinearTransform got unexpected keyword arguments:") 658 print(T) 659 660 T = vtk.vtkThinPlateSplineTransform() 661 vptss = vtk.vtkPoints() 662 for p in source: 663 if len(p) == 2: 664 p = [p[0], p[1], 0.0] 665 vptss.InsertNextPoint(p) 666 T.SetSourceLandmarks(vptss) 667 vptst = vtk.vtkPoints() 668 for p in target: 669 if len(p) == 2: 670 p = [p[0], p[1], 0.0] 671 vptst.InsertNextPoint(p) 672 T.SetTargetLandmarks(vptst) 673 T.SetSigma(sigma) 674 if mode == "2d": 675 T.SetBasisToR2LogR() 676 elif mode == "3d": 677 T.SetBasisToR() 678 else: 679 print(f'In {filename} mode can be either "2d" or "3d"') 680 681 self.T = T 682 self.inverse_flag = False
Define a non-linear transformation. Can be saved to file and reloaded.
Arguments:
- T : (vtkThinPlateSplineTransform, str, dict) vtk transformation. If T is a string, it is assumed to be a filename. If T is a dictionary, it is assumed to be a set of keyword arguments. Defaults to None.
- **kwargs : (dict)
keyword arguments to define the transformation.
The following keywords are accepted:
- name : (str) name of the transformation
- comment : (str) comment
- source_points : (list) source points
- target_points : (list) target points
- mode : (str) either '2d' or '3d'
- sigma : (float) sigma parameter
Example:
from vedo import * settings.use_parallel_projection = True NLT = NonLinearTransform() NLT.source_points = [[-2,0,0], [1,2,1], [2,-2,2]] NLT.target_points = NLT.source_points + np.random.randn(3,3)*0.5 NLT.mode = '3d' print(NLT) s1 = Sphere() NLT.move(s1) # same as: # s1.apply_transform(NLT) arrs = Arrows(NLT.source_points, NLT.target_points) show(s1, arrs, Sphere().alpha(0.1), axes=1).close()
808 def clone(self): 809 """Clone transformation to make an exact copy.""" 810 return NonLinearTransform(self.T)
Clone transformation to make an exact copy.
812 def write(self, filename): 813 """Save transformation to ASCII file.""" 814 import json 815 dictionary = { 816 "name": self.name, 817 "comment": self.comment, 818 "mode": self.mode, 819 "sigma": self.sigma, 820 "source_points": self.source_points.astype(float).tolist(), 821 "target_points": self.target_points.astype(float).tolist(), 822 } 823 with open(filename, "w") as outfile: 824 json.dump(dictionary, outfile, sort_keys=True, indent=2)
Save transformation to ASCII file.
826 def invert(self): 827 """Invert transformation.""" 828 self.T.Inverse() 829 self.inverse_flag = bool(self.T.GetInverseFlag()) 830 return self
Invert transformation.
832 def compute_inverse(self): 833 """Compute inverse.""" 834 t = self.clone() 835 t.invert() 836 return t
Compute inverse.
838 def move(self, obj): 839 """ 840 Apply transformation to object or single point. 841 842 Note: 843 When applying a transformation to a mesh, the mesh is modified in place. 844 If you want to keep the original mesh unchanged, use `clone()` method. 845 846 Example: 847 ```python 848 from vedo import * 849 np.random.seed(0) 850 settings.use_parallel_projection = True 851 852 NLT = NonLinearTransform() 853 NLT.source_points = [[-2,0,0], [1,2,1], [2,-2,2]] 854 NLT.target_points = NLT.source_points + np.random.randn(3,3)*0.5 855 NLT.mode = '3d' 856 print(NLT) 857 858 s1 = Sphere() 859 NLT.move(s1) 860 # same as: 861 # s1.apply_transform(NLT) 862 863 arrs = Arrows(NLT.source_points, NLT.target_points) 864 show(s1, arrs, Sphere().alpha(0.1), axes=1).close() 865 ``` 866 """ 867 if _is_sequence(obj): 868 if len(obj) == 2: 869 obj = [obj[0], obj[1], 0] 870 return np.array(self.T.TransformFloatPoint(obj)) 871 872 obj.apply_transform(self) 873 return obj
Apply transformation to object or single point.
Note:
When applying a transformation to a mesh, the mesh is modified in place. If you want to keep the original mesh unchanged, use
clone()
method.
Example:
from vedo import * np.random.seed(0) settings.use_parallel_projection = True NLT = NonLinearTransform() NLT.source_points = [[-2,0,0], [1,2,1], [2,-2,2]] NLT.target_points = NLT.source_points + np.random.randn(3,3)*0.5 NLT.mode = '3d' print(NLT) s1 = Sphere() NLT.move(s1) # same as: # s1.apply_transform(NLT) arrs = Arrows(NLT.source_points, NLT.target_points) show(s1, arrs, Sphere().alpha(0.1), axes=1).close()
902def spher2cart(rho, theta, phi): 903 """3D Spherical to Cartesian coordinate conversion.""" 904 st = np.sin(theta) 905 sp = np.sin(phi) 906 ct = np.cos(theta) 907 cp = np.cos(phi) 908 rst = rho * st 909 x = rst * cp 910 y = rst * sp 911 z = rho * ct 912 return np.array([x, y, z])
3D Spherical to Cartesian coordinate conversion.
893def cart2spher(x, y, z): 894 """3D Cartesian to Spherical coordinate conversion.""" 895 hxy = np.hypot(x, y) 896 rho = np.hypot(hxy, z) 897 theta = np.arctan2(hxy, z) 898 phi = np.arctan2(y, x) 899 return np.array([rho, theta, phi])
3D Cartesian to Spherical coordinate conversion.
915def cart2cyl(x, y, z): 916 """3D Cartesian to Cylindrical coordinate conversion.""" 917 rho = np.sqrt(x * x + y * y) 918 theta = np.arctan2(y, x) 919 return np.array([rho, theta, z])
3D Cartesian to Cylindrical coordinate conversion.
922def cyl2cart(rho, theta, z): 923 """3D Cylindrical to Cartesian coordinate conversion.""" 924 x = rho * np.cos(theta) 925 y = rho * np.sin(theta) 926 return np.array([x, y, z])
3D Cylindrical to Cartesian coordinate conversion.
929def cyl2spher(rho, theta, z): 930 """3D Cylindrical to Spherical coordinate conversion.""" 931 rhos = np.sqrt(rho * rho + z * z) 932 phi = np.arctan2(rho, z) 933 return np.array([rhos, phi, theta])
3D Cylindrical to Spherical coordinate conversion.
936def spher2cyl(rho, theta, phi): 937 """3D Spherical to Cylindrical coordinate conversion.""" 938 rhoc = rho * np.sin(theta) 939 z = rho * np.cos(theta) 940 return np.array([rhoc, phi, z])
3D Spherical to Cylindrical coordinate conversion.
877def cart2pol(x, y): 878 """2D Cartesian to Polar coordinates conversion.""" 879 theta = np.arctan2(y, x) 880 rho = np.hypot(x, y) 881 return np.array([rho, theta])
2D Cartesian to Polar coordinates conversion.
884def pol2cart(rho, theta): 885 """2D Polar to Cartesian coordinates conversion.""" 886 x = rho * np.cos(theta) 887 y = rho * np.sin(theta) 888 return np.array([x, y])
2D Polar to Cartesian coordinates conversion.