vedo.utils
Utilities submodule.
1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3import math 4import os 5import time 6 7import numpy as np 8 9try: 10 import vedo.vtkclasses as vtk 11except ImportError: 12 import vtkmodules.all as vtk 13 14from vtkmodules.util.numpy_support import numpy_to_vtk 15from vtkmodules.util.numpy_support import numpy_to_vtkIdTypeArray 16from vtkmodules.util.numpy_support import vtk_to_numpy 17 18import vedo 19 20 21__docformat__ = "google" 22 23__doc__ = "Utilities submodule." 24 25__all__ = [ 26 "OperationNode", 27 "ProgressBar", 28 "progressbar", 29 "geometry", 30 "extract_cells_by_type", 31 "is_sequence", 32 "lin_interpolate", 33 "vector", 34 "mag", 35 "mag2", 36 "versor", 37 "precision", 38 "round_to_digit", 39 "point_in_triangle", 40 "point_line_distance", 41 "grep", 42 "print_info", 43 "make_bands", 44 "pack_spheres", 45 "spher2cart", 46 "cart2spher", 47 "cart2cyl", 48 "cyl2cart", 49 "cyl2spher", 50 "spher2cyl", 51 "cart2pol", 52 "pol2cart", 53 "humansort", 54 "print_histogram", 55 "camera_from_quaternion", 56 "camera_from_neuroglancer", 57 "oriented_camera", 58 "vedo2trimesh", 59 "trimesh2vedo", 60 "vedo2meshlab", 61 "meshlab2vedo", 62 "vedo2open3d", 63 "open3d2vedo", 64 "vtk2numpy", 65 "numpy2vtk", 66 "get_uv", 67] 68 69 70########################################################################### 71class OperationNode: 72 """ 73 Keep track of the operations which led to a final object. 74 """ 75 # https://www.graphviz.org/doc/info/shapes.html#html 76 # Mesh #e9c46a 77 # Follower #d9ed92 78 # Volume, UGrid #4cc9f0 79 # TetMesh #9e2a2b 80 # File #8a817c 81 # Picture #f28482 82 # Assembly #f08080 83 84 def __init__( 85 self, 86 operation, 87 parents=(), 88 comment="", 89 shape="none", 90 c="#e9c46a", 91 style="filled", 92 ): 93 """ 94 Keep track of the operations which led to a final object. 95 This allows to show the `pipeline` tree for any `vedo` object with e.g.: 96 97 ```python 98 from vedo import * 99 sp = Sphere() 100 sp.clean().subdivide() 101 sp.pipeline.show() 102 ``` 103 104 Arguments: 105 operation : (str, class) 106 descriptor label, if a class is passed then grab its name 107 parents : (list) 108 list of the parent classes the object comes from 109 comment : (str) 110 a second-line text description 111 shape : (str) 112 shape of the frame, check out [this link.](https://graphviz.org/doc/info/shapes.html) 113 c : (hex) 114 hex color 115 style : (str) 116 comma-separated list of styles 117 118 Example: 119 ```python 120 from vedo.utils import OperationNode 121 122 op_node1 = OperationNode("Operation1", c="lightblue") 123 op_node2 = OperationNode("Operation2") 124 op_node3 = OperationNode("Operation3", shape='diamond') 125 op_node4 = OperationNode("Operation4") 126 op_node5 = OperationNode("Operation5") 127 op_node6 = OperationNode("Result", c="lightgreen") 128 129 op_node3.add_parent(op_node1) 130 op_node4.add_parent(op_node1) 131 op_node3.add_parent(op_node2) 132 op_node5.add_parent(op_node2) 133 op_node6.add_parent(op_node3) 134 op_node6.add_parent(op_node5) 135 op_node6.add_parent(op_node1) 136 137 op_node6.show(orientation="TB") 138 ``` 139  140 """ 141 if not vedo.settings.enable_pipeline: 142 return 143 144 if isinstance(operation, str): 145 self.operation = operation 146 else: 147 self.operation = operation.__class__.__name__ 148 self.operation_plain = str(self.operation) 149 150 pp = [] # filter out invalid stuff 151 for p in parents: 152 if hasattr(p, "pipeline"): 153 pp.append(p.pipeline) 154 self.parents = pp 155 156 if comment: 157 self.operation = f"<{self.operation}<BR/><SUB><I>{comment}</I></SUB>>" 158 159 self.dot = None 160 self.time = time.time() 161 self.shape = shape 162 self.style = style 163 self.color = c 164 165 def add_parent(self, parent): 166 self.parents.append(parent) 167 168 def _build_tree(self, dot): 169 dot.node( 170 str(id(self)), 171 label=self.operation, 172 shape=self.shape, 173 color=self.color, 174 style=self.style, 175 ) 176 for parent in self.parents: 177 if parent: 178 t = f"{self.time - parent.time: .1f}s" 179 dot.edge(str(id(parent)), str(id(self)), label=t) 180 parent._build_tree(dot) 181 182 def __repr__(self): 183 try: 184 from treelib import Tree 185 except ImportError: 186 vedo.logger.error("To use this functionality please install treelib:" 187 "\n pip install treelib") 188 return "" 189 190 def _build_tree(parent): 191 for par in parent.parents: 192 if par: 193 op = par.operation_plain 194 tree.create_node( 195 op, 196 op + str(par.time), 197 parent=parent.operation_plain + str(parent.time)) 198 _build_tree(par) 199 200 tree = Tree() 201 tree.create_node( 202 self.operation_plain, self.operation_plain + str(self.time) 203 ) 204 _build_tree(self) 205 return tree.show(reverse=True, stdout=False) 206 207 def show(self, orientation="LR", popup=True): 208 """Show the graphviz output for the pipeline of this object""" 209 if not vedo.settings.enable_pipeline: 210 return 211 212 try: 213 from graphviz import Digraph 214 except ImportError: 215 vedo.logger.error("please install graphviz with command\n pip install graphviz") 216 return 217 218 # visualize the entire tree 219 dot = Digraph( 220 node_attr={ 221 'fontcolor':'#201010', 222 'fontname': "Helvetica", 223 'fontsize': '12', 224 }, 225 edge_attr={ 226 'fontname': "Helvetica", 227 'fontsize': '6', 228 'arrowsize': '0.4', 229 } 230 ) 231 dot.attr(rankdir=orientation) 232 233 self.counts = 0 234 self._build_tree(dot) 235 self.dot = dot 236 dot.render('.vedo_pipeline_graphviz', view=popup) 237 238 239########################################################################### 240class ProgressBar: 241 """ 242 Class to print a progress bar. 243 """ 244 def __init__( 245 self, 246 start, 247 stop, 248 step=1, 249 c=None, 250 bold=True, 251 italic=False, 252 title="", 253 eta=True, 254 delay=0, 255 width=25, 256 char="\U00002501", 257 char_back="\U00002500", 258 ): 259 """ 260 Class to print a progress bar with optional text message. 261 262 Check out also function `progressbar()`. 263 264 Example: 265 ```python 266 import time 267 pb = ProgressBar(0,400, c='red') 268 for i in pb.range(): 269 time.sleep(0.1) 270 pb.print('some message') 271 ``` 272  273 """ 274 self.char = char 275 self.char_back = char_back 276 277 self.title = title + " " 278 if title: 279 self.title = " " + self.title 280 281 self.start = start 282 self.stop = stop 283 self.step = step 284 285 self.color = c 286 self.bold = bold 287 self.italic = italic 288 self.width = width 289 self.pbar = "" 290 self.percent = 0.0 291 self.percent_int = 0 292 self.eta = eta 293 self.delay = delay 294 295 self.t0 = time.time() 296 self._remaining = 1e10 297 298 self._update(0) 299 300 self._counts = 0 301 self._oldbar = "" 302 self._lentxt = 0 303 self._range = np.arange(start, stop, step) 304 305 def print(self, txt="", c=None): 306 """Print the progress bar with an optional message.""" 307 if not c: 308 c = self.color 309 310 self._update(self._counts + self.step) 311 312 if self.delay: 313 if time.time() - self.t0 < self.delay: 314 return 315 316 if self.pbar != self._oldbar: 317 self._oldbar = self.pbar 318 319 if self.eta and self._counts > 1: 320 321 tdenom = time.time() - self.t0 322 if tdenom: 323 vel = self._counts / tdenom 324 self._remaining = (self.stop - self._counts) / vel 325 else: 326 vel = 1 327 self._remaining = 0.0 328 329 if self._remaining > 60: 330 mins = int(self._remaining / 60) 331 secs = self._remaining - 60 * mins 332 mins = f"{mins}m" 333 secs = f"{int(secs + 0.5)}s " 334 else: 335 mins = "" 336 secs = f"{int(self._remaining + 0.5)}s " 337 338 vel = round(vel, 1) 339 eta = f"eta: {mins}{secs}({vel} it/s) " 340 if self._remaining < 0.5: 341 dt = time.time() - self.t0 342 if dt > 60: 343 mins = int(dt / 60) 344 secs = dt - 60 * mins 345 mins = f"{mins}m" 346 secs = f"{int(secs + 0.5)}s " 347 else: 348 mins = "" 349 secs = f"{int(dt + 0.5)}s " 350 eta = f"elapsed: {mins}{secs}({vel} it/s) " 351 txt = "" 352 else: 353 eta = "" 354 355 eraser = " " * self._lentxt + "\b" * self._lentxt 356 357 s = f"{self.pbar} {eraser}{eta}{txt}\r" 358 vedo.printc(s, c=c, bold=self.bold, italic=self.italic, end="") 359 if self.percent > 99.999: 360 print("") 361 362 self._lentxt = len(txt) 363 364 def range(self): 365 """Return the range iterator.""" 366 return self._range 367 368 def _update(self, counts): 369 if counts < self.start: 370 counts = self.start 371 elif counts > self.stop: 372 counts = self.stop 373 self._counts = counts 374 375 self.percent = (self._counts - self.start) * 100.0 376 377 delta = self.stop - self.start 378 if delta: 379 self.percent /= delta 380 else: 381 self.percent = 0.0 382 383 self.percent_int = int(round(self.percent)) 384 af = self.width - 2 385 nh = int(round(self.percent_int / 100 * af)) 386 pbar_background = "\x1b[2m" + self.char_back * (af - nh) 387 self.pbar = f"{self.title}{self.char * (nh-1)}{pbar_background}" 388 if self.percent < 100.0: 389 ps = f" {self.percent_int}%" 390 else: 391 ps = "" 392 self.pbar += ps 393 394##################################### 395def progressbar( 396 iterable, 397 c=None, 398 bold=True, 399 italic=False, 400 title="", 401 eta=True, 402 width=25, 403 delay=0, 404 ): 405 """ 406 Function to print a progress bar with optional text message. 407 408 Example: 409 ```python 410 import time 411 for i in progressbar(range(100), c='red'): 412 time.sleep(0.1) 413 ``` 414  415 """ 416 try: 417 if is_number(iterable): 418 total = int(iterable) 419 iterable = range(total) 420 else: 421 total = len(iterable) 422 except TypeError: 423 iterable = list(iterable) 424 total = len(iterable) 425 426 pb = ProgressBar( 427 0, total, 428 c=c, bold=bold, italic=italic, title=title, eta=eta, delay=delay, width=width, 429 ) 430 for item in iterable: 431 pb.print() 432 yield item 433 434########################################################### 435def numpy2vtk(arr, dtype=None, deep=True, name=""): 436 """ 437 Convert a numpy array into a `vtkDataArray`. 438 Use `dtype='id'` for `vtkIdTypeArray` objects. 439 """ 440 # https://github.com/Kitware/VTK/blob/master/Wrapping/Python/vtkmodules/util/numpy_support.py 441 if arr is None: 442 return None 443 444 arr = np.ascontiguousarray(arr) 445 446 if dtype == "id": 447 varr = numpy_to_vtkIdTypeArray(arr.astype(np.int64), deep=deep) 448 elif dtype: 449 varr = numpy_to_vtk(arr.astype(dtype), deep=deep) 450 else: 451 # let numpy_to_vtk() decide what is best type based on arr type 452 varr = numpy_to_vtk(arr, deep=deep) 453 454 if name: 455 varr.SetName(name) 456 return varr 457 458 459def vtk2numpy(varr): 460 """Convert a `vtkDataArray`, `vtkIdList` or `vtTransform` into a numpy array.""" 461 if isinstance(varr, vtk.vtkIdList): 462 return np.array([varr.GetId(i) for i in range(varr.GetNumberOfIds())]) 463 elif isinstance(varr, vtk.vtkBitArray): 464 carr = vtk.vtkCharArray() 465 carr.DeepCopy(varr) 466 varr = carr 467 elif isinstance(varr, vtk.vtkHomogeneousTransform): 468 try: 469 varr = varr.GetMatrix() 470 except AttributeError: 471 pass 472 n = 4 473 M = [[varr.GetElement(i, j) for j in range(n)] for i in range(n)] 474 return np.array(M) 475 476 return vtk_to_numpy(varr) 477 478 479def make3d(pts, transpose=False): 480 """ 481 Make an array which might be 2D to 3D. 482 483 Array can also be in the form `[allx, ally, allz]`. 484 Use `transpose` to resolve ambigous cases (eg, shapes like `[3,3]`). 485 """ 486 pts = np.asarray(pts) 487 488 if pts.dtype == "object": 489 raise ValueError("Cannot form a valid numpy array, input may be non-homogenous") 490 491 if pts.shape[0] == 0: # empty list 492 return pts 493 494 if pts.ndim == 1: 495 if pts.shape[0] == 2: 496 return np.hstack([pts, [0]]).astype(pts.dtype) 497 elif pts.shape[0] == 3: 498 return pts 499 else: 500 raise ValueError 501 502 if pts.shape[1] == 3: 503 return pts 504 505 if transpose or (2 <= pts.shape[0] <= 3 and pts.shape[1] > 3): 506 pts = pts.T 507 508 if pts.shape[1] == 2: 509 return np.c_[pts, np.zeros(pts.shape[0], dtype=pts.dtype)] 510 511 if pts.shape[1] != 3: 512 raise ValueError("input shape is not supported.") 513 return pts 514 515 516def geometry(obj, extent=None): 517 """ 518 Apply the `vtkGeometryFilter` to the input object. 519 This is a general-purpose filter to extract geometry (and associated data) 520 from any type of dataset. 521 This filter also may be used to convert any type of data to polygonal type. 522 The conversion process may be less than satisfactory for some 3D datasets. 523 For example, this filter will extract the outer surface of a volume 524 or structured grid dataset. 525 526 Returns a `vedo.Mesh` object. 527 528 Set `extent` as the `[xmin,xmax, ymin,ymax, zmin,zmax]` bounding box to clip data. 529 """ 530 gf = vtk.vtkGeometryFilter() 531 gf.SetInputData(obj) 532 if extent is not None: 533 gf.SetExtent(extent) 534 gf.Update() 535 return vedo.Mesh(gf.GetOutput()) 536 537 538def extract_cells_by_type(obj, types=()): 539 """ 540 Extract cells of a specified type from a vtk dataset. 541 542 Given an input `vtkDataSet` and a list of cell types, produce an output 543 containing only cells of the specified type(s). 544 545 Find [here](https://vtk.org/doc/nightly/html/vtkCellType_8h_source.html) 546 the list of possible cell types. 547 548 Return: 549 a `vtkDataSet` object which can be wrapped. 550 """ 551 ef = vtk.vtkExtractCellsByType() 552 try: 553 ef.SetInputData(obj._data) 554 except: 555 ef.SetInputData(obj) 556 557 for ct in types: 558 ef.AddCellType(ct) 559 ef.Update() 560 return ef.GetOutput() 561 562 563def buildPolyData(vertices, faces=None, lines=None, index_offset=0, tetras=False): 564 """ 565 Build a `vtkPolyData` object from a list of vertices 566 where faces represents the connectivity of the polygonal mesh. 567 568 E.g. : 569 - `vertices=[[x1,y1,z1],[x2,y2,z2], ...]` 570 - `faces=[[0,1,2], [1,2,3], ...]` 571 - `lines=[[0,1], [1,2,3,4], ...]` 572 573 Use `index_offset=1` if face numbering starts from 1 instead of 0. 574 575 If `tetras=True`, interpret 4-point faces as tetrahedrons instead of surface quads. 576 """ 577 poly = vtk.vtkPolyData() 578 579 if len(vertices) == 0: 580 return poly 581 582 if not is_sequence(vertices[0]): 583 return poly 584 585 vertices = make3d(vertices) 586 587 source_points = vtk.vtkPoints() 588 source_points.SetData(numpy2vtk(vertices, dtype=np.float32)) 589 poly.SetPoints(source_points) 590 591 if lines is not None: 592 # Create a cell array to store the lines in and add the lines to it 593 linesarr = vtk.vtkCellArray() 594 if is_sequence(lines[0]): # assume format [(id0,id1),..] 595 for iline in lines: 596 for i in range(0, len(iline) - 1): 597 i1, i2 = iline[i], iline[i + 1] 598 if i1 != i2: 599 vline = vtk.vtkLine() 600 vline.GetPointIds().SetId(0, i1) 601 vline.GetPointIds().SetId(1, i2) 602 linesarr.InsertNextCell(vline) 603 else: # assume format [id0,id1,...] 604 for i in range(0, len(lines) - 1): 605 vline = vtk.vtkLine() 606 vline.GetPointIds().SetId(0, lines[i]) 607 vline.GetPointIds().SetId(1, lines[i + 1]) 608 linesarr.InsertNextCell(vline) 609 # print('Wrong format for lines in utils.buildPolydata(), skip.') 610 poly.SetLines(linesarr) 611 612 if faces is None: 613 source_vertices = vtk.vtkCellArray() 614 for i in range(len(vertices)): 615 source_vertices.InsertNextCell(1) 616 source_vertices.InsertCellPoint(i) 617 poly.SetVerts(source_vertices) 618 return poly ################### 619 620 # faces exist 621 source_polygons = vtk.vtkCellArray() 622 623 if isinstance(faces, np.ndarray) or not is_ragged(faces): 624 ##### all faces are composed of equal nr of vtxs, FAST 625 faces = np.asarray(faces) 626 ast = np.int32 627 if vtk.vtkIdTypeArray().GetDataTypeSize() != 4: 628 ast = np.int64 629 630 if faces.ndim > 1: 631 nf, nc = faces.shape 632 hs = np.hstack((np.zeros(nf)[:, None] + nc, faces)).astype(ast).ravel() 633 arr = numpy_to_vtkIdTypeArray(hs, deep=True) 634 source_polygons.SetCells(nf, arr) 635 636 else: 637 ############################# manually add faces, SLOW 638 639 showbar = False 640 if len(faces) > 25000: 641 showbar = True 642 pb = ProgressBar(0, len(faces), eta=False) 643 644 for f in faces: 645 n = len(f) 646 647 if n == 3: 648 ele = vtk.vtkTriangle() 649 pids = ele.GetPointIds() 650 for i in range(3): 651 pids.SetId(i, f[i] - index_offset) 652 source_polygons.InsertNextCell(ele) 653 654 elif n == 4 and tetras: 655 # do not use vtkTetra() because it fails 656 # with dolfin faces orientation 657 ele0 = vtk.vtkTriangle() 658 ele1 = vtk.vtkTriangle() 659 ele2 = vtk.vtkTriangle() 660 ele3 = vtk.vtkTriangle() 661 if index_offset: 662 for i in [0, 1, 2, 3]: 663 f[i] -= index_offset 664 f0, f1, f2, f3 = f 665 pid0 = ele0.GetPointIds() 666 pid1 = ele1.GetPointIds() 667 pid2 = ele2.GetPointIds() 668 pid3 = ele3.GetPointIds() 669 670 pid0.SetId(0, f0) 671 pid0.SetId(1, f1) 672 pid0.SetId(2, f2) 673 674 pid1.SetId(0, f0) 675 pid1.SetId(1, f1) 676 pid1.SetId(2, f3) 677 678 pid2.SetId(0, f1) 679 pid2.SetId(1, f2) 680 pid2.SetId(2, f3) 681 682 pid3.SetId(0, f2) 683 pid3.SetId(1, f3) 684 pid3.SetId(2, f0) 685 686 source_polygons.InsertNextCell(ele0) 687 source_polygons.InsertNextCell(ele1) 688 source_polygons.InsertNextCell(ele2) 689 source_polygons.InsertNextCell(ele3) 690 691 else: 692 ele = vtk.vtkPolygon() 693 pids = ele.GetPointIds() 694 pids.SetNumberOfIds(n) 695 for i in range(n): 696 pids.SetId(i, f[i] - index_offset) 697 source_polygons.InsertNextCell(ele) 698 if showbar: 699 pb.print("converting mesh... ") 700 701 poly.SetPolys(source_polygons) 702 return poly 703 704 705############################################################################## 706def get_font_path(font): 707 """Internal use.""" 708 if font in vedo.settings.font_parameters.keys(): 709 if vedo.settings.font_parameters[font]["islocal"]: 710 fl = os.path.join(vedo.fonts_path, f"{font}.ttf") 711 else: 712 try: 713 fl = vedo.io.download(f"https://vedo.embl.es/fonts/{font}.ttf", verbose=False) 714 except: 715 vedo.logger.warning(f"Could not download https://vedo.embl.es/fonts/{font}.ttf") 716 fl = os.path.join(vedo.fonts_path, "Normografo.ttf") 717 else: 718 if font.startswith("https://"): 719 fl = vedo.io.download(font, verbose=False) 720 elif os.path.isfile(font): 721 fl = font # assume user is passing a valid file 722 else: 723 if font.endswith(".ttf"): 724 vedo.logger.error( 725 f"Could not set font file {font}" 726 f"-> using default: {vedo.settings.default_font}" 727 ) 728 else: 729 vedo.settings.default_font = "Normografo" 730 vedo.logger.error( 731 f"Could not set font name {font}" 732 f" -> using default: Normografo\n" 733 f"Check out https://vedo.embl.es/fonts for additional fonts\n" 734 f"Type 'vedo -r fonts' to see available fonts" 735 ) 736 fl = get_font_path(vedo.settings.default_font) 737 return fl 738 739 740def isSequence(arg): 741 "Deprecated. Please use `is_sequence()`" 742 m = "Warning! isSequence() is deprecated. Please use is_sequence()." 743 print("\x1b[1m\x1b[33;1m " + m + "\x1b[0m") 744 return is_sequence(arg) 745 746 747def is_sequence(arg): 748 """Check if the input is iterable.""" 749 if hasattr(arg, "strip"): 750 return False 751 if hasattr(arg, "__getslice__"): 752 return True 753 if hasattr(arg, "__iter__"): 754 return True 755 return False 756 757 758def is_ragged(arr, deep=False): 759 """ 760 A ragged array in Python is an array with arrays of different 761 lengths as its elements. To check if an array is ragged, 762 we iterate through the elements and check if their lengths are the same. 763 764 Example: 765 ```python 766 arr = [[1, 2, 3], [[4, 5], [6], 1], [7, 8, 9]] 767 print(is_ragged(arr, deep=True)) # output: True 768 ``` 769 """ 770 if is_sequence(arr[0]): 771 length = len(arr[0]) 772 for i in range(1, len(arr)): 773 if len(arr[i]) != length or (deep and is_ragged(arr[i])): 774 return True 775 return False 776 return False 777 778 779def flatten(list_to_flatten): 780 """Flatten out a list.""" 781 782 def _genflatten(lst): 783 for elem in lst: 784 if isinstance(elem, (list, tuple)): 785 for x in flatten(elem): 786 yield x 787 else: 788 yield elem 789 790 return list(_genflatten(list_to_flatten)) 791 792 793def humansort(alist): 794 """ 795 Sort in place a given list the way humans expect. 796 797 E.g. `['file11', 'file1'] -> ['file1', 'file11']` 798 799 .. warning:: input list is modified in-place by this function. 800 """ 801 import re 802 803 def alphanum_key(s): 804 # Turn a string into a list of string and number chunks. 805 # e.g. "z23a" -> ["z", 23, "a"] 806 def tryint(s): 807 if s.isdigit(): 808 return int(s) 809 return s 810 811 return [tryint(c) for c in re.split("([0-9]+)", s)] 812 813 alist.sort(key=alphanum_key) 814 return alist # NB: input list is modified 815 816 817def sort_by_column(arr, nth, invert=False): 818 """Sort a numpy array by its `n-th` column.""" 819 arr = np.asarray(arr) 820 arr = arr[arr[:, nth].argsort()] 821 if invert: 822 return np.flip(arr, axis=0) 823 return arr 824 825 826def point_in_triangle(p, p1, p2, p3): 827 """ 828 Return True if a point is inside (or above/below) a triangle defined by 3 points in space. 829 """ 830 p1 = np.array(p1) 831 u = p2 - p1 832 v = p3 - p1 833 n = np.cross(u, v) 834 w = p - p1 835 ln = np.dot(n, n) 836 if not ln: 837 return None # degenerate triangle 838 gamma = (np.dot(np.cross(u, w), n)) / ln 839 if 0 < gamma < 1: 840 beta = (np.dot(np.cross(w, v), n)) / ln 841 if 0 < beta < 1: 842 alpha = 1 - gamma - beta 843 if 0 < alpha < 1: 844 return True 845 return False 846 847 848def intersection_ray_triangle(P0, P1, V0, V1, V2): 849 """ 850 Fast intersection between a directional ray defined by `P0,P1` 851 and triangle `V0, V1, V2`. 852 853 Returns the intersection point or 854 - `None` if triangle is degenerate, or ray is parallel to triangle plane. 855 - `False` if no intersection, or ray direction points away from triangle. 856 """ 857 # Credits: http://geomalgorithms.com/a06-_intersect-2.html 858 # Get triangle edge vectors and plane normal 859 # todo : this is slow should check 860 # https://vtk.org/doc/nightly/html/classvtkCell.html#aa850382213d7b8693f0eeec0209c347b 861 V0 = np.asarray(V0, dtype=float) 862 P0 = np.asarray(P0, dtype=float) 863 u = V1 - V0 864 v = V2 - V0 865 n = np.cross(u, v) 866 if not np.abs(v).sum(): # triangle is degenerate 867 return None # do not deal with this case 868 869 rd = P1 - P0 # ray direction vector 870 w0 = P0 - V0 871 a = -np.dot(n, w0) 872 b = np.dot(n, rd) 873 if not b: # ray is parallel to triangle plane 874 return None 875 876 # Get intersect point of ray with triangle plane 877 r = a / b 878 if r < 0.0: # ray goes away from triangle 879 return False # => no intersect 880 881 # Gor a segment, also test if (r > 1.0) => no intersect 882 I = P0 + r * rd # intersect point of ray and plane 883 884 # is I inside T? 885 uu = np.dot(u, u) 886 uv = np.dot(u, v) 887 vv = np.dot(v, v) 888 w = I - V0 889 wu = np.dot(w, u) 890 wv = np.dot(w, v) 891 D = uv * uv - uu * vv 892 893 # Get and test parametric coords 894 s = (uv * wv - vv * wu) / D 895 if s < 0.0 or s > 1.0: # I is outside T 896 return False 897 t = (uv * wu - uu * wv) / D 898 if t < 0.0 or (s + t) > 1.0: # I is outside T 899 return False 900 return I # I is in T 901 902def triangle_solver(**input_dict): 903 """ 904 Solve a triangle from any 3 known elements. 905 (Note that there might be more than one solution or none). 906 Angles are in radians. 907 908 Example: 909 ```python 910 print(triangle_solver(a=3, b=4, c=5)) 911 print(triangle_solver(a=3, ac=0.9273, ab=1.5716)) 912 print(triangle_solver(a=3, b=4, ab=1.5716)) 913 print(triangle_solver(b=4, bc=.64, ab=1.5716)) 914 print(triangle_solver(c=5, ac=.9273, bc=0.6435)) 915 print(triangle_solver(a=3, c=5, bc=0.6435)) 916 print(triangle_solver(b=4, c=5, ac=0.927)) 917 ``` 918 """ 919 a = input_dict.get("a") 920 b = input_dict.get("b") 921 c = input_dict.get("c") 922 ab = input_dict.get("ab") 923 bc = input_dict.get("bc") 924 ac = input_dict.get("ac") 925 926 if ab and bc: 927 ac = np.pi - bc - ab 928 elif bc and ac: 929 ab = np.pi - bc - ac 930 elif ab and ac: 931 bc = np.pi - ab - ac 932 933 if a is not None and b is not None and c is not None: 934 ab = np.arccos((a ** 2 + b ** 2 - c ** 2) / (2 * a * b)) 935 sinab = np.sin(ab) 936 ac = np.arcsin(a / c * sinab) 937 bc = np.arcsin(b / c * sinab) 938 939 elif a is not None and b is not None and ab is not None: 940 c = np.sqrt(a ** 2 + b ** 2 - 2 * a * b * np.cos(ab)) 941 sinab = np.sin(ab) 942 ac = np.arcsin(a / c * sinab) 943 bc = np.arcsin(b / c * sinab) 944 945 elif a is not None and ac is not None and ab is not None: 946 h = a * np.sin(ac) 947 b = h / np.sin(bc) 948 c = b * np.cos(bc) + a * np.cos(ac) 949 950 elif b is not None and bc is not None and ab is not None: 951 h = b * np.sin(bc) 952 a = h / np.sin(ac) 953 c = np.sqrt(a * a + b * b) 954 955 elif c is not None and ac is not None and bc is not None: 956 h = c * np.sin(bc) 957 b1 = c * np.cos(bc) 958 b2 = h / np.tan(ab) 959 b = b1 + b2 960 a = np.sqrt(b2 * b2 + h * h) 961 962 elif a is not None and c is not None and bc is not None: 963 # double solution 964 h = c * np.sin(bc) 965 k = np.sqrt(a * a - h * h) 966 omega = np.arcsin(k / a) 967 cosbc = np.cos(bc) 968 b = c * cosbc - k 969 phi = np.pi / 2 - bc - omega 970 ac = phi 971 ab = np.pi - ac - bc 972 if k: 973 b2 = c * cosbc + k 974 ac2 = phi + 2 * omega 975 ab2 = np.pi - ac2 - bc 976 return [ 977 {"a": a, "b": b, "c": c, "ab": ab, "bc": bc, "ac": ac}, 978 {"a": a, "b": b2, "c": c, "ab": ab2, "bc": bc, "ac": ac2}, 979 ] 980 981 elif b is not None and c is not None and ac is not None: 982 # double solution 983 h = c * np.sin(ac) 984 k = np.sqrt(b * b - h * h) 985 omega = np.arcsin(k / b) 986 cosac = np.cos(ac) 987 a = c * cosac - k 988 phi = np.pi / 2 - ac - omega 989 bc = phi 990 ab = np.pi - bc - ac 991 if k: 992 a2 = c * cosac + k 993 bc2 = phi + 2 * omega 994 ab2 = np.pi - ac - bc2 995 return [ 996 {"a": a, "b": b, "c": c, "ab": ab, "bc": bc, "ac": ac}, 997 {"a": a2, "b": b, "c": c, "ab": ab2, "bc": bc2, "ac": ac}, 998 ] 999 1000 else: 1001 vedo.logger.error(f"Case {input_dict} is not supported.") 1002 return [] 1003 1004 return [{"a": a, "b": b, "c": c, "ab": ab, "bc": bc, "ac": ac}] 1005 1006 1007def point_line_distance(p, p1, p2): 1008 """ 1009 Compute the distance of a point to a line (not the segment) 1010 defined by `p1` and `p2`. 1011 """ 1012 d = np.sqrt(vtk.vtkLine.DistanceToLine(p, p1, p2)) 1013 return d 1014 1015 1016def linInterpolate(x, rangeX, rangeY): 1017 "Deprecated. Please `lin_interpolate()`" 1018 m = "Warning! linInterpolate() is deprecated. Please use lin_interpolate()" 1019 print("\x1b[1m\x1b[33;1m " + m + "\x1b[0m") 1020 return lin_interpolate(x, rangeX, rangeY) 1021 1022 1023def lin_interpolate(x, rangeX, rangeY): 1024 """ 1025 Interpolate linearly the variable `x` in `rangeX` onto the new `rangeY`. 1026 If `x` is a 3D vector the linear weight is the distance to the two 3D `rangeX` vectors. 1027 1028 E.g. if `x` runs in `rangeX=[x0,x1]` and I want it to run in `rangeY=[y0,y1]` then 1029 1030 `y = lin_interpolate(x, rangeX, rangeY)` will interpolate `x` onto `rangeY`. 1031 1032 Examples: 1033 - [lin_interpolate.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/lin_interpolate.py) 1034 1035  1036 """ 1037 if is_sequence(x): 1038 x = np.asarray(x) 1039 x0, x1 = np.asarray(rangeX) 1040 y0, y1 = np.asarray(rangeY) 1041 dx = x1 - x0 1042 dxn = np.linalg.norm(dx) 1043 if not dxn: 1044 return y0 1045 s = np.linalg.norm(x - x0) / dxn 1046 t = np.linalg.norm(x - x1) / dxn 1047 st = s + t 1048 out = y0 * (t / st) + y1 * (s / st) 1049 1050 else: # faster 1051 1052 x0 = rangeX[0] 1053 dx = rangeX[1] - x0 1054 if not dx: 1055 return rangeY[0] 1056 s = (x - x0) / dx 1057 out = rangeY[0] * (1 - s) + rangeY[1] * s 1058 return out 1059 1060 1061def get_uv(p, x, v): 1062 """ 1063 Obtain the texture uv-coords of a point p belonging to a face that has point 1064 coordinates (x0, x1, x2) with the corresponding uv-coordinates v=(v0, v1, v2). 1065 All p and x0,x1,x2 are 3D-vectors, while v are their 2D uv-coordinates. 1066 1067 Example: 1068 ```python 1069 from vedo import * 1070 1071 pic = Picture(dataurl+"coloured_cube_faces.jpg") 1072 cb = Mesh(dataurl+"coloured_cube.obj").lighting("off").texture(pic) 1073 1074 cbpts = cb.points() 1075 faces = cb.faces() 1076 uv = cb.pointdata["Material"] 1077 1078 pt = [-0.2, 0.75, 2] 1079 pr = cb.closest_point(pt) 1080 1081 idface = cb.closest_point(pt, return_cell_id=True) 1082 idpts = faces[idface] 1083 uv_face = uv[idpts] 1084 1085 uv_pr = utils.get_uv(pr, cbpts[idpts], uv_face) 1086 print("interpolated uv =", uv_pr) 1087 1088 sx, sy = pic.dimensions() 1089 i_interp_uv = uv_pr * [sy, sx] 1090 ix, iy = i_interp_uv.astype(int) 1091 mpic = pic.tomesh() 1092 rgba = mpic.pointdata["RGBA"].reshape(sy, sx, 3) 1093 print("color =", rgba[ix, iy]) 1094 1095 show( 1096 [[cb, Point(pr), cb.labels("Material")], 1097 [pic, Point(i_interp_uv)]], 1098 N=2, axes=1, sharecam=False, 1099 ).close() 1100 ``` 1101  1102 """ 1103 # Vector vp=p-x0 is representable as alpha*s + beta*t, 1104 # where s = x1-x0 and t = x2-x0, in matrix form 1105 # vp = [alpha, beta] . matrix(s,t) 1106 # M = matrix(s,t) is 2x3 matrix, so (alpha, beta) can be found by 1107 # inverting any of its minor A with non-zero determinant. 1108 # Once found, uv-coords of p are vt0 + alpha (vt1-v0) + beta (vt2-v0) 1109 1110 p = np.asarray(p) 1111 x0, x1, x2 = np.asarray(x)[:3] 1112 vt0, vt1, vt2 = np.asarray(v)[:3] 1113 1114 s = x1 - x0 1115 t = x2 - x0 1116 vs = vt1 - vt0 1117 vt = vt2 - vt0 1118 vp = p - x0 1119 1120 # finding a minor with independent rows 1121 M = np.matrix([s, t]) 1122 mnr = [0, 1] 1123 A = M[:, mnr] 1124 if np.abs(np.linalg.det(A)) < 0.000001: 1125 mnr = [0, 2] 1126 A = M[:, mnr] 1127 if np.abs(np.linalg.det(A)) < 0.000001: 1128 mnr = [1, 2] 1129 A = M[:, mnr] 1130 Ainv = np.linalg.inv(A) 1131 alpha_beta = vp[mnr].dot(Ainv) # [alpha, beta] 1132 return np.asarray(vt0 + alpha_beta.dot(np.matrix([vs, vt])))[0] 1133 1134 1135def vector(x, y=None, z=0.0, dtype=np.float64): 1136 """ 1137 Return a 3D numpy array representing a vector. 1138 1139 If `y` is `None`, assume input is already in the form `[x,y,z]`. 1140 """ 1141 if y is None: # assume x is already [x,y,z] 1142 return np.asarray(x, dtype=dtype) 1143 return np.array([x, y, z], dtype=dtype) 1144 1145 1146def versor(x, y=None, z=0.0, dtype=np.float64): 1147 """Return the unit vector. Input can be a list of vectors.""" 1148 v = vector(x, y, z, dtype) 1149 if isinstance(v[0], np.ndarray): 1150 return np.divide(v, mag(v)[:, None]) 1151 return v / mag(v) 1152 1153 1154def mag(v): 1155 """Get the magnitude of a vector or array of vectors.""" 1156 v = np.asarray(v) 1157 if v.ndim == 1: 1158 return np.linalg.norm(v) 1159 return np.linalg.norm(v, axis=1) 1160 1161 1162def mag2(v): 1163 """Get the squared magnitude of a vector or array of vectors.""" 1164 v = np.asarray(v) 1165 if v.ndim == 1: 1166 return np.square(v).sum() 1167 return np.square(v).sum(axis=1) 1168 1169 1170def is_integer(n): 1171 """Check if input is an integer.""" 1172 try: 1173 float(n) 1174 except ValueError: 1175 return False 1176 else: 1177 return float(n).is_integer() 1178 1179 1180def is_number(n): 1181 """Check if input is a number""" 1182 try: 1183 float(n) 1184 return True 1185 except ValueError: 1186 return False 1187 1188 1189def round_to_digit(x, p): 1190 """Round a real number to the specified number of significant digits.""" 1191 if not x: 1192 return 0 1193 r = np.round(x, p - int(np.floor(np.log10(abs(x)))) - 1) 1194 if int(r) == r: 1195 return int(r) 1196 return r 1197 1198 1199def packSpheres(bounds, radius): 1200 "Deprecated. Please use `pack_spheres()`" 1201 m = "Warning! packSpheres() is deprecated. Please use pack_spheres()" 1202 print("\x1b[1m\x1b[33;1m " + m + "\x1b[0m") 1203 return pack_spheres(bounds, radius) 1204 1205 1206def pack_spheres(bounds, radius): 1207 """ 1208 Packing spheres into a bounding box. 1209 Returns a numpy array of sphere centers. 1210 """ 1211 h = 0.8164965 / 2 1212 d = 0.8660254 1213 a = 0.288675135 1214 1215 if is_sequence(bounds): 1216 x0, x1, y0, y1, z0, z1 = bounds 1217 else: 1218 x0, x1, y0, y1, z0, z1 = bounds.bounds() 1219 1220 x = np.arange(x0, x1, radius) 1221 nul = np.zeros_like(x) 1222 nz = int((z1 - z0) / radius / h / 2 + 1.5) 1223 ny = int((y1 - y0) / radius / d + 1.5) 1224 1225 pts = [] 1226 for iz in range(nz): 1227 z = z0 + nul + iz * h * radius 1228 dx, dy, dz = [radius * 0.5, radius * a, iz * h * radius] 1229 for iy in range(ny): 1230 y = y0 + nul + iy * d * radius 1231 if iy % 2: 1232 xs = x 1233 else: 1234 xs = x + radius * 0.5 1235 if iz % 2: 1236 p = np.c_[xs, y, z] + [dx, dy, dz] 1237 else: 1238 p = np.c_[xs, y, z] + [0, 0, dz] 1239 pts += p.tolist() 1240 return np.array(pts) 1241 1242 1243def precision(x, p, vrange=None, delimiter="e"): 1244 """ 1245 Returns a string representation of `x` formatted to precision `p`. 1246 1247 Set `vrange` to the range in which x exists (to snap x to '0' if below precision). 1248 """ 1249 # Based on the webkit javascript implementation 1250 # `from here <https://code.google.com/p/webkit-mirror/source/browse/JavaScriptCore/kjs/number_object.cpp>`_, 1251 # and implemented by `randlet <https://github.com/randlet/to-precision>`_. 1252 # Modified for vedo by M.Musy 2020 1253 1254 if isinstance(x, str): # do nothing 1255 return x 1256 1257 if is_sequence(x): 1258 out = "(" 1259 nn = len(x) - 1 1260 for i, ix in enumerate(x): 1261 1262 try: 1263 if np.isnan(ix): 1264 return "NaN" 1265 except: 1266 # cannot handle list of list 1267 continue 1268 1269 out += precision(ix, p) 1270 if i < nn: 1271 out += ", " 1272 return out + ")" ############ <-- 1273 1274 if np.isnan(x): 1275 return "NaN" 1276 1277 x = float(x) 1278 1279 if x == 0.0 or (vrange is not None and abs(x) < vrange / pow(10, p)): 1280 return "0" 1281 1282 out = [] 1283 if x < 0: 1284 out.append("-") 1285 x = -x 1286 1287 e = int(math.log10(x)) 1288 tens = math.pow(10, e - p + 1) 1289 n = math.floor(x / tens) 1290 1291 if n < math.pow(10, p - 1): 1292 e = e - 1 1293 tens = math.pow(10, e - p + 1) 1294 n = math.floor(x / tens) 1295 1296 if abs((n + 1.0) * tens - x) <= abs(n * tens - x): 1297 n = n + 1 1298 1299 if n >= math.pow(10, p): 1300 n = n / 10.0 1301 e = e + 1 1302 1303 m = "%.*g" % (p, n) 1304 if e < -2 or e >= p: 1305 out.append(m[0]) 1306 if p > 1: 1307 out.append(".") 1308 out.extend(m[1:p]) 1309 out.append(delimiter) 1310 if e > 0: 1311 out.append("+") 1312 out.append(str(e)) 1313 elif e == (p - 1): 1314 out.append(m) 1315 elif e >= 0: 1316 out.append(m[: e + 1]) 1317 if e + 1 < len(m): 1318 out.append(".") 1319 out.extend(m[e + 1 :]) 1320 else: 1321 out.append("0.") 1322 out.extend(["0"] * -(e + 1)) 1323 out.append(m) 1324 return "".join(out) 1325 1326 1327################################################################################## 1328# 2d ###### 1329def cart2pol(x, y): 1330 """2D Cartesian to Polar coordinates conversion.""" 1331 theta = np.arctan2(y, x) 1332 rho = np.hypot(x, y) 1333 return np.array([rho, theta]) 1334 1335 1336def pol2cart(rho, theta): 1337 """2D Polar to Cartesian coordinates conversion.""" 1338 x = rho * np.cos(theta) 1339 y = rho * np.sin(theta) 1340 return np.array([x, y]) 1341 1342 1343# 3d ###### 1344def cart2spher(x, y, z): 1345 """3D Cartesian to Spherical coordinate conversion.""" 1346 hxy = np.hypot(x, y) 1347 rho = np.hypot(hxy, z) 1348 theta = np.arctan2(hxy, z) 1349 phi = np.arctan2(y, x) 1350 return np.array([rho, theta, phi]) 1351 1352 1353def spher2cart(rho, theta, phi): 1354 """3D Spherical to Cartesian coordinate conversion.""" 1355 st = np.sin(theta) 1356 sp = np.sin(phi) 1357 ct = np.cos(theta) 1358 cp = np.cos(phi) 1359 rst = rho * st 1360 x = rst * cp 1361 y = rst * sp 1362 z = rho * ct 1363 return np.array([x, y, z]) 1364 1365 1366def cart2cyl(x, y, z): 1367 """3D Cartesian to Cylindrical coordinate conversion.""" 1368 rho = np.sqrt(x * x + y * y) 1369 theta = np.arctan2(y, x) 1370 return np.array([rho, theta, z]) 1371 1372 1373def cyl2cart(rho, theta, z): 1374 """3D Cylindrical to Cartesian coordinate conversion.""" 1375 x = rho * np.cos(theta) 1376 y = rho * np.sin(theta) 1377 return np.array([x, y, z]) 1378 1379 1380def cyl2spher(rho, theta, z): 1381 """3D Cylindrical to Spherical coordinate conversion.""" 1382 rhos = np.sqrt(rho * rho + z * z) 1383 phi = np.arctan2(rho, z) 1384 return np.array([rhos, phi, theta]) 1385 1386 1387def spher2cyl(rho, theta, phi): 1388 """3D Spherical to Cylindrical coordinate conversion.""" 1389 rhoc = rho * np.sin(theta) 1390 z = rho * np.cos(theta) 1391 return np.array([rhoc, phi, z]) 1392 1393 1394################################################################################## 1395def grep(filename, tag, first_occurrence_only=False): 1396 """Greps the line in a file that starts with a specific `tag` string inside the file.""" 1397 import re 1398 1399 with open(filename, "r", encoding="UTF-8") as afile: 1400 content = [] 1401 for line in afile: 1402 if re.search(tag, line): 1403 c = line.split() 1404 c[-1] = c[-1].replace("\n", "") 1405 content.append(c) 1406 if first_occurrence_only: 1407 break 1408 return content 1409 1410 1411def print_info(obj): 1412 """Print information about a `vedo` object.""" 1413 1414 def _print_data(poly, mapper, c): 1415 ptdata = poly.GetPointData() 1416 cldata = poly.GetCellData() 1417 fldata = poly.GetFieldData() 1418 if ptdata.GetNumberOfArrays() + cldata.GetNumberOfArrays(): 1419 arrtypes = {} 1420 arrtypes[vtk.VTK_UNSIGNED_CHAR] = ("UNSIGNED_CHAR", "np.uint8") 1421 arrtypes[vtk.VTK_UNSIGNED_SHORT]= ("UNSIGNED_SHORT", "np.uint16") 1422 arrtypes[vtk.VTK_UNSIGNED_INT] = ("UNSIGNED_INT", "np.uint32") 1423 arrtypes[vtk.VTK_UNSIGNED_LONG_LONG] = ("UNSIGNED_LONG_LONG", "np.uint64") 1424 arrtypes[vtk.VTK_CHAR] = ("CHAR", "np.int8") 1425 arrtypes[vtk.VTK_SHORT] = ("SHORT", "np.int16") 1426 arrtypes[vtk.VTK_INT] = ("INT", "np.int32") 1427 arrtypes[vtk.VTK_LONG] = ("LONG", "") # ?? 1428 arrtypes[vtk.VTK_LONG_LONG] = ("LONG_LONG", "np.int64") 1429 arrtypes[vtk.VTK_FLOAT] = ("FLOAT", "np.float32") 1430 arrtypes[vtk.VTK_DOUBLE] = ("DOUBLE", "np.float64") 1431 arrtypes[vtk.VTK_SIGNED_CHAR] = ("SIGNED_CHAR", "np.int8") 1432 arrtypes[vtk.VTK_ID_TYPE] = ("ID", "np.int64") 1433 1434 if ptdata.GetScalars(): 1435 vedo.printc("active array".ljust(14)+": ", c=c, bold=True, end="") 1436 vedo.printc(ptdata.GetScalars().GetName(), "(pointdata) ", c=c, bold=False) 1437 1438 if cldata.GetScalars(): 1439 vedo.printc("active array".ljust(14)+": ", c=c, bold=True, end="") 1440 vedo.printc(cldata.GetScalars().GetName(), "(celldata)", c=c, bold=False) 1441 1442 for i in range(ptdata.GetNumberOfArrays()): 1443 name = ptdata.GetArrayName(i) 1444 if name and ptdata.GetArray(i): 1445 vedo.printc("pointdata".ljust(14) + ": ", c=c, bold=True, end="") 1446 try: 1447 tt, nptt = arrtypes[ptdata.GetArray(i).GetDataType()] 1448 except: 1449 tt = "VTKTYPE" + str(ptdata.GetArray(i).GetDataType()) 1450 nptt = "" 1451 ncomp = ptdata.GetArray(i).GetNumberOfComponents() 1452 rng = ptdata.GetArray(i).GetRange() 1453 vedo.printc(f'"{name}" ({ncomp} {tt}),', c=c, bold=False, end="") 1454 vedo.printc( 1455 " range=(" + precision(rng[0], 3) + "," + precision(rng[1], 3) + ")", 1456 c=c, 1457 bold=False, 1458 ) 1459 1460 for i in range(cldata.GetNumberOfArrays()): 1461 name = cldata.GetArrayName(i) 1462 if name and cldata.GetArray(i): 1463 vedo.printc("celldata".ljust(14) + ": ", c=c, bold=True, end="") 1464 try: 1465 tt, nptt = arrtypes[cldata.GetArray(i).GetDataType()] 1466 except: 1467 tt = cldata.GetArray(i).GetDataType() 1468 ncomp = cldata.GetArray(i).GetNumberOfComponents() 1469 rng = cldata.GetArray(i).GetRange() 1470 vedo.printc(f'"{name}" ({ncomp} {tt}),', c=c, bold=False, end="") 1471 vedo.printc( 1472 " range=(" + precision(rng[0], 4) + "," + precision(rng[1], 4) + ")", 1473 c=c, 1474 bold=False, 1475 ) 1476 1477 for i in range(fldata.GetNumberOfArrays()): 1478 name = fldata.GetArrayName(i) 1479 if name and fldata.GetAbstractArray(i): 1480 arr = fldata.GetAbstractArray(i) 1481 vedo.printc("metadata".ljust(14) + ": ", c=c, bold=True, end="") 1482 ncomp = arr.GetNumberOfComponents() 1483 nvals = arr.GetNumberOfValues() 1484 vedo.printc(f'"{name}" ({ncomp} components, {nvals} values)', c=c, bold=False) 1485 1486 else: 1487 vedo.printc("mesh data".ljust(14) + ":", c=c, bold=True, end=" ") 1488 vedo.printc("no point or cell data is present.", c=c, bold=False) 1489 1490 ################################ 1491 def _printvtkactor(actor): 1492 1493 if not actor.GetPickable(): 1494 return 1495 1496 mapper = actor.GetMapper() 1497 if hasattr(actor, "polydata"): 1498 poly = actor.polydata() 1499 else: 1500 poly = mapper.GetInput() 1501 1502 pro = actor.GetProperty() 1503 pos = actor.GetPosition() 1504 bnds = actor.bounds() 1505 col = precision(pro.GetColor(), 3) 1506 alpha = pro.GetOpacity() 1507 npt = poly.GetNumberOfPoints() 1508 ncl = poly.GetNumberOfCells() 1509 npl = poly.GetNumberOfPolys() 1510 nln = poly.GetNumberOfLines() 1511 1512 vedo.printc("Mesh/Points".ljust(70), c="g", bold=True, invert=True, dim=1, end="") 1513 1514 if hasattr(actor, "info") and "legend" in actor.info.keys() and actor.info["legend"]: 1515 vedo.printc("legend".ljust(14)+": ", c="g", bold=True, end="") 1516 vedo.printc(actor.info["legend"], c="g", bold=False) 1517 else: 1518 print() 1519 1520 if hasattr(actor, "name") and actor.name: 1521 vedo.printc("name".ljust(14) + ": ", c="g", bold=True, end="") 1522 vedo.printc(actor.name, c="g", bold=False) 1523 1524 if hasattr(actor, "filename") and actor.filename: 1525 vedo.printc("file name".ljust(14) + ": ", c="g", bold=True, end="") 1526 vedo.printc(actor.filename, c="g", bold=False) 1527 1528 if not actor.GetMapper().GetScalarVisibility(): 1529 vedo.printc("color".ljust(14) + ": ", c="g", bold=True, end="") 1530 cname = vedo.colors.get_color_name(pro.GetColor()) 1531 vedo.printc(f"{cname}, rgb={col}, alpha={alpha}", c="g", bold=False) 1532 1533 if actor.GetBackfaceProperty(): 1534 bcol = actor.GetBackfaceProperty().GetDiffuseColor() 1535 cname = vedo.colors.get_color_name(bcol) 1536 vedo.printc("back color".ljust(14) + ": ", c="g", bold=True, end="") 1537 vedo.printc(f"{cname}, rgb={precision(bcol,3)}", c="g", bold=False) 1538 1539 vedo.printc("points".ljust(14) + ":", npt, c="g", bold=True) 1540 # vedo.printc("cells".ljust(14)+":", ncl, c="g", bold=True) 1541 vedo.printc("polygons".ljust(14) + ":", npl, c="g", bold=True) 1542 if nln: 1543 vedo.printc("lines".ljust(14) + ":", nln, c="g", bold=True) 1544 vedo.printc("position".ljust(14) + ":", pos, c="g", bold=True) 1545 1546 if hasattr(actor, "GetScale"): 1547 vedo.printc("scale".ljust(14) + ":", c="g", bold=True, end=" ") 1548 vedo.printc(precision(actor.GetScale(), 3), c="g", bold=False) 1549 1550 if hasattr(actor, "polydata") and actor.npoints: 1551 vedo.printc("center of mass".ljust(14) + ":", c="g", bold=True, end=" ") 1552 cm = tuple(actor.center_of_mass()) 1553 vedo.printc(precision(cm, 3), c="g", bold=False) 1554 1555 vedo.printc("average size".ljust(14) + ":", c="g", bold=True, end=" ") 1556 vedo.printc(precision(actor.average_size(), 6), c="g", bold=False) 1557 1558 vedo.printc("diagonal size".ljust(14) + ":", c="g", bold=True, end=" ") 1559 vedo.printc(precision(actor.diagonal_size(), 6), c="g", bold=False) 1560 1561 vedo.printc("bounds".ljust(14) + ":", c="g", bold=True, end=" ") 1562 bx1, bx2 = precision(bnds[0], 3), precision(bnds[1], 3) 1563 vedo.printc("x=(" + bx1 + ", " + bx2 + ")", c="g", bold=False, end="") 1564 by1, by2 = precision(bnds[2], 3), precision(bnds[3], 3) 1565 vedo.printc(" y=(" + by1 + ", " + by2 + ")", c="g", bold=False, end="") 1566 bz1, bz2 = precision(bnds[4], 3), precision(bnds[5], 3) 1567 vedo.printc(" z=(" + bz1 + ", " + bz2 + ")", c="g", bold=False) 1568 1569 _print_data(poly, mapper, "g") 1570 1571 if hasattr(actor, "picked3d") and actor.picked3d is not None: 1572 idpt = actor.closest_point(actor.picked3d, return_point_id=True) 1573 idcell = actor.closest_point(actor.picked3d, return_cell_id=True) 1574 vedo.printc( 1575 "clicked point".ljust(14) + ":", 1576 precision(actor.picked3d, 6), 1577 f"pointID={idpt}, cellID={idcell}", 1578 c="g", 1579 bold=True, 1580 ) 1581 1582 if obj is None: 1583 return 1584 1585 if isinstance(obj, np.ndarray): 1586 obj = obj 1587 cf = "y" 1588 vedo.printc("Numpy Array".ljust(70), c=cf, invert=True) 1589 vedo.printc(obj, c=cf) 1590 vedo.printc("shape".ljust(8) + ":", obj.shape, c=cf) 1591 vedo.printc("range".ljust(8) + f": ({np.min(obj)}, {np.max(obj)})", c=cf) 1592 vedo.printc("mean".ljust(8) + ":", np.mean(obj), c=cf) 1593 vedo.printc("std_dev".ljust(8) + ":", np.std(obj), c=cf) 1594 if len(obj.shape) >= 2: 1595 vedo.printc("Axis 0".ljust(8) + ":", c=cf, italic=1) 1596 vedo.printc("\tmin :", np.min(obj, axis=0), c=cf) 1597 vedo.printc("\tmax :", np.max(obj, axis=0), c=cf) 1598 vedo.printc("\tmean:", np.mean(obj, axis=0), c=cf) 1599 if obj.shape[1] > 3: 1600 vedo.printc("Axis 1".ljust(8) + ":", c=cf, italic=1) 1601 tmin = str(np.min(obj, axis=1).tolist()[:2]).replace("]", ", ...") 1602 tmax = str(np.max(obj, axis=1).tolist()[:2]).replace("]", ", ...") 1603 tmea = str(np.mean(obj, axis=1).tolist()[:2]).replace("]", ", ...") 1604 vedo.printc(f"\tmin : {tmin}", c=cf) 1605 vedo.printc(f"\tmax : {tmax}", c=cf) 1606 vedo.printc(f"\tmean: {tmea}", c=cf) 1607 1608 elif isinstance(obj, vedo.Points): 1609 _printvtkactor(obj) 1610 1611 elif isinstance(obj, vedo.Assembly): 1612 vedo.printc("Assembly".ljust(75), c="g", bold=True, invert=True) 1613 1614 pos = obj.GetPosition() 1615 bnds = obj.GetBounds() 1616 vedo.printc("position".ljust(14) + ": ", c="g", bold=True, end="") 1617 vedo.printc(pos, c="g", bold=False) 1618 1619 vedo.printc("bounds".ljust(14) + ": ", c="g", bold=True, end="") 1620 bx1, bx2 = precision(bnds[0], 3), precision(bnds[1], 3) 1621 vedo.printc("x=(" + bx1 + ", " + bx2 + ")", c="g", bold=False, end="") 1622 by1, by2 = precision(bnds[2], 3), precision(bnds[3], 3) 1623 vedo.printc(" y=(" + by1 + ", " + by2 + ")", c="g", bold=False, end="") 1624 bz1, bz2 = precision(bnds[4], 3), precision(bnds[5], 3) 1625 vedo.printc(" z=(" + bz1 + ", " + bz2 + ")", c="g", bold=False) 1626 1627 cl = vtk.vtkPropCollection() 1628 obj.GetActors(cl) 1629 cl.InitTraversal() 1630 for _ in range(obj.GetNumberOfPaths()): 1631 act = vtk.vtkActor.SafeDownCast(cl.GetNextProp()) 1632 if isinstance(act, vtk.vtkActor): 1633 _printvtkactor(act) 1634 1635 elif isinstance(obj, vedo.TetMesh): 1636 cf = "m" 1637 vedo.printc("TetMesh".ljust(70), c=cf, bold=True, invert=True) 1638 pos = obj.GetPosition() 1639 bnds = obj.GetBounds() 1640 ug = obj.inputdata() 1641 vedo.printc("nr. of tetras".ljust(14) + ": ", c=cf, bold=True, end="") 1642 vedo.printc(ug.GetNumberOfCells(), c=cf, bold=False) 1643 vedo.printc("position".ljust(14) + ": ", c=cf, bold=True, end="") 1644 vedo.printc(pos, c=cf, bold=False) 1645 vedo.printc("bounds".ljust(14) + ": ", c=cf, bold=True, end="") 1646 bx1, bx2 = precision(bnds[0], 3), precision(bnds[1], 3) 1647 vedo.printc("x=(" + bx1 + ", " + bx2 + ")", c=cf, bold=False, end="") 1648 by1, by2 = precision(bnds[2], 3), precision(bnds[3], 3) 1649 vedo.printc(" y=(" + by1 + ", " + by2 + ")", c=cf, bold=False, end="") 1650 bz1, bz2 = precision(bnds[4], 3), precision(bnds[5], 3) 1651 vedo.printc(" z=(" + bz1 + ", " + bz2 + ")", c=cf, bold=False) 1652 _print_data(ug, obj._mapper, cf) 1653 1654 elif isinstance(obj, (vedo.volume.Volume, vedo.volume.VolumeSlice)): 1655 vedo.printc("Volume".ljust(70), c="b", bold=True, invert=True) 1656 1657 img = obj.GetMapper().GetInput() 1658 vedo.printc("origin".ljust(14) + ": ", c="b", bold=True, end="") 1659 vedo.printc(precision(obj.origin(),6), c="b", bold=False) 1660 1661 vedo.printc("center".ljust(14) + ": ", c="b", bold=True, end="") 1662 vedo.printc(precision(obj.center(),6), c="b", bold=False) 1663 1664 vedo.printc("dimensions".ljust(14) + ": ", c="b", bold=True, end="") 1665 vedo.printc(img.GetDimensions(), c="b", bold=False) 1666 vedo.printc("spacing".ljust(14) + ": ", c="b", bold=True, end="") 1667 vedo.printc(precision(img.GetSpacing(),6), c="b", bold=False) 1668 # vedo.printc("data dimension".ljust(14) + ": ", c="b", bold=True, end="") 1669 # vedo.printc(img.GetDataDimension(), c="b", bold=False) 1670 1671 vedo.printc("memory size".ljust(14) + ": ", c="b", bold=True, end="") 1672 vedo.printc(int(img.GetActualMemorySize() / 1024), "MB", c="b", bold=False) 1673 1674 vedo.printc("scalar #bytes".ljust(14) + ": ", c="b", bold=True, end="") 1675 vedo.printc(img.GetScalarSize(), c="b", bold=False) 1676 1677 bnds = obj.GetBounds() 1678 vedo.printc("bounds".ljust(14) + ": ", c="b", bold=True, end="") 1679 bx1, bx2 = precision(bnds[0], 4), precision(bnds[1], 4) 1680 vedo.printc("x=(" + bx1 + ", " + bx2 + ")", c="b", bold=False, end="") 1681 by1, by2 = precision(bnds[2], 4), precision(bnds[3], 4) 1682 vedo.printc(" y=(" + by1 + ", " + by2 + ")", c="b", bold=False, end="") 1683 bz1, bz2 = precision(bnds[4], 4), precision(bnds[5], 4) 1684 vedo.printc(" z=(" + bz1 + ", " + bz2 + ")", c="b", bold=False) 1685 1686 vedo.printc("scalar range".ljust(14) + ": ", c="b", bold=True, end="") 1687 vedo.printc(img.GetScalarRange(), c="b", bold=False) 1688 1689 print_histogram(obj, horizontal=True, spacer=" "*10, 1690 logscale=True, bins=8, height=15, c="b", bold=True) 1691 1692 elif isinstance(obj, vedo.Plotter) and obj.interactor: # dumps Plotter info 1693 axtype = { 1694 0: "(no axes)", 1695 1: "(three customizable gray grid walls)", 1696 2: "(cartesian axes from origin", 1697 3: "(positive range of cartesian axes from origin", 1698 4: "(axes triad at bottom left)", 1699 5: "(oriented cube at bottom left)", 1700 6: "(mark the corners of the bounding box)", 1701 7: "(3D ruler at each side of the cartesian axes)", 1702 8: "(the vtkCubeAxesActor object)", 1703 9: "(the bounding box outline)", 1704 10: "(circles of maximum bounding box range)", 1705 11: "(show a large grid on the x-y plane)", 1706 12: "(show polar axes)", 1707 13: "(simple ruler at the bottom of the window)", 1708 14: "(the vtkCameraOrientationWidget object)", 1709 } 1710 bns, totpt = [], 0 1711 for a in obj.actors: 1712 b = a.GetBounds() 1713 if a.GetBounds() is not None: 1714 if isinstance(a, vtk.vtkActor) and a.GetMapper(): 1715 totpt += a.GetMapper().GetInput().GetNumberOfPoints() 1716 bns.append(b) 1717 if len(bns) == 0: 1718 return 1719 vedo.printc("Plotter".ljust(70), invert=True, dim=1, c="c") 1720 otit = obj.title 1721 if not otit: 1722 otit = None 1723 vedo.printc("window title".ljust(14) + ":", otit, bold=False, c="c") 1724 vedo.printc( 1725 "window size".ljust(14) + ":", 1726 obj.window.GetSize(), 1727 "- full screen size:", 1728 obj.window.GetScreenSize(), 1729 bold=False, 1730 c="c", 1731 ) 1732 vedo.printc( 1733 "actv renderer".ljust(14) + ":", 1734 "nr.", 1735 obj.renderers.index(obj.renderer), 1736 f"(of {len(obj.renderers)} renderers)", 1737 bold=False, 1738 c="c", 1739 ) 1740 vedo.printc( 1741 "nr. of actors".ljust(14) + ":", len(obj.actors), bold=False, c="c", end="" 1742 ) 1743 vedo.printc(" (" + str(totpt), "vertices)", bold=False, c="c") 1744 max_bns = np.max(bns, axis=0) 1745 min_bns = np.min(bns, axis=0) 1746 vedo.printc("max bounds".ljust(14) + ": ", c="c", bold=False, end="") 1747 bx1, bx2 = precision(min_bns[0], 3), precision(max_bns[1], 3) 1748 vedo.printc("x=(" + bx1 + ", " + bx2 + ")", c="c", bold=False, end="") 1749 by1, by2 = precision(min_bns[2], 3), precision(max_bns[3], 3) 1750 vedo.printc(" y=(" + by1 + ", " + by2 + ")", c="c", bold=False, end="") 1751 bz1, bz2 = precision(min_bns[4], 3), precision(max_bns[5], 3) 1752 vedo.printc(" z=(" + bz1 + ", " + bz2 + ")", c="c", bold=False) 1753 if isinstance(obj.axes, dict): 1754 obj.axes = 1 1755 if obj.axes: 1756 vedo.printc( 1757 "axes style".ljust(14) + ":", 1758 obj.axes, 1759 axtype[obj.axes], 1760 bold=False, 1761 c="c", 1762 ) 1763 1764 elif isinstance(obj, vedo.Picture): # dumps Picture info 1765 vedo.printc("Picture".ljust(70), c="y", bold=True, invert=True) 1766 try: 1767 # generate a print thumbnail 1768 width, height = obj.dimensions() 1769 w = 45 1770 h = int(height / width * (w - 1) * 0.5 + 0.5) 1771 img_arr = obj.clone().resize([w, h]).tonumpy() 1772 h, w = img_arr.shape[:2] 1773 for x in range(h): 1774 for y in range(w): 1775 pix = img_arr[x][y] 1776 r, g, b = pix[:3] 1777 print(f"\x1b[48;2;{r};{g};{b}m", end=" ") 1778 print("\x1b[0m") 1779 except: 1780 pass 1781 1782 img = obj.GetMapper().GetInput() 1783 pos = obj.GetPosition() 1784 vedo.printc("position".ljust(14) + ": ", c="y", bold=True, end="") 1785 vedo.printc(pos, c="y", bold=False) 1786 1787 vedo.printc("dimensions".ljust(14) + ": ", c="y", bold=True, end="") 1788 vedo.printc(obj.shape, c="y", bold=False) 1789 1790 vedo.printc("memory size".ljust(14) + ": ", c="y", bold=True, end="") 1791 vedo.printc(int(img.GetActualMemorySize()), "kB", c="y", bold=False) 1792 1793 bnds = obj.GetBounds() 1794 vedo.printc("bounds".ljust(14) + ": ", c="y", bold=True, end="") 1795 bx1, bx2 = precision(bnds[0], 3), precision(bnds[1], 3) 1796 vedo.printc("x=(" + bx1 + ", " + bx2 + ")", c="y", bold=False, end="") 1797 by1, by2 = precision(bnds[2], 3), precision(bnds[3], 3) 1798 vedo.printc(" y=(" + by1 + ", " + by2 + ")", c="y", bold=False, end="") 1799 bz1, bz2 = precision(bnds[4], 3), precision(bnds[5], 3) 1800 vedo.printc(" z=(" + bz1 + ", " + bz2 + ")", c="y", bold=False) 1801 1802 vedo.printc("intensty range".ljust(14) + ": ", c="y", bold=True, end="") 1803 vedo.printc(img.GetScalarRange(), c="y", bold=False) 1804 vedo.printc("level / window".ljust(14) + ": ", c="y", bold=True, end="") 1805 vedo.printc(obj.level(), "/", obj.window(), c="y", bold=False) 1806 1807 else: 1808 vedo.printc(str(type(obj)).ljust(70), invert=True) 1809 vedo.printc(obj) 1810 1811 1812def print_histogram( 1813 data, 1814 bins=10, 1815 height=10, 1816 logscale=False, 1817 minbin=0, 1818 horizontal=False, 1819 char="\u2588", 1820 c=None, 1821 bold=True, 1822 title="histogram", 1823 spacer="", 1824): 1825 """ 1826 Ascii histogram printing. 1827 1828 Input can be a `vedo.Volume` or `vedo.Mesh`. 1829 Returns the raw data before binning (useful when passing vtk objects). 1830 1831 Arguments: 1832 bins : (int) 1833 number of histogram bins 1834 height : (int) 1835 height of the histogram in character units 1836 logscale : (bool) 1837 use logscale for frequencies 1838 minbin : (int) 1839 ignore bins before minbin 1840 horizontal : (bool) 1841 show histogram horizontally 1842 char : (str) 1843 character to be used 1844 bold : (bool) 1845 use boldface 1846 title : (str) 1847 histogram title 1848 spacer : (str) 1849 horizontal spacer 1850 1851 Example: 1852 ```python 1853 from vedo import print_histogram 1854 import numpy as np 1855 d = np.random.normal(size=1000) 1856 data = print_histogram(d, c='blue', logscale=True, title='my scalars') 1857 data = print_histogram(d, c=1, horizontal=1) 1858 print(np.mean(data)) # data here is same as d 1859 ``` 1860  1861 """ 1862 # credits: http://pyinsci.blogspot.com/2009/10/ascii-histograms.html 1863 # adapted for vedo by M.Musy, 2019 1864 1865 if not horizontal: # better aspect ratio 1866 bins *= 2 1867 1868 isimg = isinstance(data, vtk.vtkImageData) 1869 isvol = isinstance(data, vtk.vtkVolume) 1870 if isimg or isvol: 1871 if isvol: 1872 img = data.imagedata() 1873 else: 1874 img = data 1875 dims = img.GetDimensions() 1876 nvx = min(100000, dims[0] * dims[1] * dims[2]) 1877 idxs = np.random.randint(0, min(dims), size=(nvx, 3)) 1878 data = [] 1879 for ix, iy, iz in idxs: 1880 d = img.GetScalarComponentAsFloat(ix, iy, iz, 0) 1881 data.append(d) 1882 elif isinstance(data, vtk.vtkActor): 1883 arr = data.polydata().GetPointData().GetScalars() 1884 if not arr: 1885 arr = data.polydata().GetCellData().GetScalars() 1886 if not arr: 1887 return None 1888 1889 data = vtk2numpy(arr) 1890 1891 try: 1892 h = np.histogram(data, bins=bins) 1893 except TypeError as e: 1894 vedo.logger.error(f"cannot compute histogram: {e}") 1895 return "" 1896 1897 if minbin: 1898 hi = h[0][minbin:-1] 1899 else: 1900 hi = h[0] 1901 1902 if char == "\U00002589" and horizontal: 1903 char = "\U00002586" 1904 1905 title = title.ljust(14) + ":" 1906 entrs = " entries=" + str(len(data)) 1907 if logscale: 1908 h0 = np.log10(hi + 1) 1909 maxh0 = int(max(h0) * 100) / 100 1910 title = title + entrs + " (logscale)" 1911 else: 1912 h0 = hi 1913 maxh0 = max(h0) 1914 title = title + entrs 1915 1916 def _v(): 1917 his = "" 1918 if title: 1919 his += title + "\n" 1920 bars = h0 / maxh0 * height 1921 for l in reversed(range(1, height + 1)): 1922 line = "" 1923 if l == height: 1924 line = "%s " % maxh0 1925 else: 1926 line = " |" + " " * (len(str(maxh0)) - 3) 1927 for c in bars: 1928 if c >= np.ceil(l): 1929 line += char 1930 else: 1931 line += " " 1932 line += "\n" 1933 his += line 1934 his += "%.2f" % h[1][0] + "." * (bins) + "%.2f" % h[1][-1] + "\n" 1935 return his 1936 1937 def _h(): 1938 his = "" 1939 if title: 1940 his += title + "\n" 1941 xl = ["%.2f" % n for n in h[1]] 1942 lxl = [len(l) for l in xl] 1943 bars = h0 / maxh0 * height 1944 his += spacer + " " * int(max(bars) + 2 + max(lxl)) + "%s\n" % maxh0 1945 for i, c in enumerate(bars): 1946 line = xl[i] + " " * int(max(lxl) - lxl[i]) + "| " + char * int(c) + "\n" 1947 his += spacer + line 1948 return his 1949 1950 if horizontal: 1951 height *= 2 1952 vedo.printc(_h(), c=c, bold=bold) 1953 else: 1954 vedo.printc(_v(), c=c, bold=bold) 1955 return data 1956 1957def print_table(*columns, headers=None, c='g'): 1958 """ 1959 Print lists as tables. 1960 1961 Example: 1962 ```python 1963 list1 = [1, np.sqrt(2), 3] 1964 list2 = ["A", "B", "C"] 1965 list3 = [True, False, True] 1966 headers = ["First Column", "Second Column", "Third Column"] 1967 print_table(list1, list2, list3, headers=headers) 1968 ``` 1969 """ 1970 # If headers is not provided, use default header names 1971 corner='─' 1972 if headers is None: 1973 headers = [f"Column {i}" for i in range(1, len(columns)+1)] 1974 assert len(headers) == len(columns) 1975 1976 # Find the maximum length of the elements in each column and header 1977 max_lens = [max(len(str(x)) for x in column) for column in columns] 1978 max_len_headers = [max(len(str(header)), max_len) for header, max_len in zip(headers, max_lens)] 1979 1980 # Construct the table header 1981 header = "│ " + " │ ".join(header.ljust(max_len) for header, max_len in zip(headers, max_len_headers)) + " │" 1982 1983 # Construct the line separator 1984 line1 = "┌" + corner.join("─"*(max_len+2) for max_len in max_len_headers) + "┐" 1985 line2 = "└" + corner.join("─"*(max_len+2) for max_len in max_len_headers) + "┘" 1986 1987 # Print the table header 1988 vedo.printc(line1, c=c) 1989 vedo.printc(header, c=c) 1990 vedo.printc(line2, c=c) 1991 1992 # Print the data rows 1993 for row in zip(*columns): 1994 row = "│ " + " │ ".join(str(col).ljust(max_len) for col, max_len in zip(row, max_len_headers)) + " │" 1995 vedo.printc(row, bold=False, c=c) 1996 1997 # Print the line separator again to close the table 1998 vedo.printc(line2, c=c) 1999 2000 2001def make_bands(inputlist, n): 2002 """ 2003 Group values of a list into bands of equal value, where 2004 `n` is the number of bands, a positive integer > 2. 2005 2006 Returns a binned list of the same length as the input. 2007 """ 2008 if n < 2: 2009 return inputlist 2010 vmin = np.min(inputlist) 2011 vmax = np.max(inputlist) 2012 bb = np.linspace(vmin, vmax, n, endpoint=0) 2013 dr = bb[1] - bb[0] 2014 bb += dr / 2 2015 tol = dr / 2 * 1.001 2016 newlist = [] 2017 for s in inputlist: 2018 for b in bb: 2019 if abs(s - b) < tol: 2020 newlist.append(b) 2021 break 2022 return np.array(newlist) 2023 2024 2025################################################################# 2026# Functions adapted from: 2027# https://github.com/sdorkenw/MeshParty/blob/master/meshparty/trimesh_vtk.py 2028def camera_from_quaternion(pos, quaternion, distance=10000, ngl_correct=True): 2029 """ 2030 Define a `vtkCamera` with a particular orientation. 2031 2032 Arguments: 2033 pos: (np.array, list, tuple) 2034 an iterator of length 3 containing the focus point of the camera 2035 quaternion: (np.array, list, tuple) 2036 a len(4) quaternion `(x,y,z,w)` describing the rotation of the camera 2037 such as returned by neuroglancer `x,y,z,w` all in `[0,1]` range 2038 distance: (float) 2039 the desired distance from pos to the camera (default = 10000 nm) 2040 2041 Returns: 2042 `vtk.vtkCamera`, a vtk camera setup according to these rules. 2043 """ 2044 camera = vtk.vtkCamera() 2045 # define the quaternion in vtk, note the swapped order 2046 # w,x,y,z instead of x,y,z,w 2047 quat_vtk = vtk.vtkQuaterniond(quaternion[3], quaternion[0], quaternion[1], quaternion[2]) 2048 # use this to define a rotation matrix in x,y,z 2049 # right handed units 2050 M = np.zeros((3, 3), dtype=np.float32) 2051 quat_vtk.ToMatrix3x3(M) 2052 # the default camera orientation is y up 2053 up = [0, 1, 0] 2054 # calculate default camera position is backed off in positive z 2055 pos = [0, 0, distance] 2056 2057 # set the camera rototation by applying the rotation matrix 2058 camera.SetViewUp(*np.dot(M, up)) 2059 # set the camera position by applying the rotation matrix 2060 camera.SetPosition(*np.dot(M, pos)) 2061 if ngl_correct: 2062 # neuroglancer has positive y going down 2063 # so apply these azimuth and roll corrections 2064 # to fix orientatins 2065 camera.Azimuth(-180) 2066 camera.Roll(180) 2067 2068 # shift the camera posiiton and focal position 2069 # to be centered on the desired location 2070 p = camera.GetPosition() 2071 p_new = np.array(p) + pos 2072 camera.SetPosition(*p_new) 2073 camera.SetFocalPoint(*pos) 2074 return camera 2075 2076 2077def camera_from_neuroglancer(state, zoom=300): 2078 """ 2079 Define a `vtkCamera` from a neuroglancer state dictionary. 2080 2081 Arguments: 2082 state: (dict) 2083 an neuroglancer state dictionary. 2084 zoom: (float) 2085 how much to multiply zoom by to get camera backoff distance 2086 default = 300 > ngl_zoom = 1 > 300 nm backoff distance. 2087 2088 Returns: 2089 `vtk.vtkCamera`, a vtk camera setup that matches this state. 2090 """ 2091 orient = state.get("perspectiveOrientation", [0.0, 0.0, 0.0, 1.0]) 2092 pzoom = state.get("perspectiveZoom", 10.0) 2093 position = state["navigation"]["pose"]["position"] 2094 pos_nm = np.array(position["voxelCoordinates"]) * position["voxelSize"] 2095 return camera_from_quaternion(pos_nm, orient, pzoom * zoom, ngl_correct=True) 2096 2097 2098def oriented_camera( 2099 center=(0, 0, 0), up_vector=(0, 1, 0), backoff_vector=(0, 0, 1), backoff=1 2100): 2101 """ 2102 Generate a `vtkCamera` pointed at a specific location, 2103 oriented with a given up direction, set to a backoff. 2104 """ 2105 vup = np.array(up_vector) 2106 vup = vup / np.linalg.norm(vup) 2107 pt_backoff = center - backoff * np.array(backoff_vector) 2108 camera = vtk.vtkCamera() 2109 camera.SetFocalPoint(center[0], center[1], center[2]) 2110 camera.SetViewUp(vup[0], vup[1], vup[2]) 2111 camera.SetPosition(pt_backoff[0], pt_backoff[1], pt_backoff[2]) 2112 return camera 2113 2114 2115def camera_from_dict(camera, modify_inplace=None): 2116 """ 2117 Generate a `vtkCamera` from a dictionary. 2118 """ 2119 if modify_inplace: 2120 vcam = modify_inplace 2121 else: 2122 vcam = vtk.vtkCamera() 2123 2124 camera = dict(camera) # make a copy so input is not emptied by pop() 2125 cm_pos = camera.pop("position", camera.pop("pos", None)) 2126 cm_focal_point = camera.pop("focal_point", camera.pop("focalPoint", None)) 2127 cm_viewup = camera.pop("viewup", None) 2128 cm_distance = camera.pop("distance", None) 2129 cm_clipping_range = camera.pop("clipping_range", camera.pop("clippingRange", None)) 2130 cm_parallel_scale = camera.pop("parallel_scale", camera.pop("parallelScale", None)) 2131 cm_thickness = camera.pop("thickness", None) 2132 cm_view_angle = camera.pop("view_angle", camera.pop("viewAngle", None)) 2133 if len(camera.keys()): 2134 vedo.logger.warning(f"in camera_from_dict, key(s) not recognized: {camera.keys()}") 2135 if cm_pos is not None: vcam.SetPosition(cm_pos) 2136 if cm_focal_point is not None: vcam.SetFocalPoint(cm_focal_point) 2137 if cm_viewup is not None: vcam.SetViewUp(cm_viewup) 2138 if cm_distance is not None: vcam.SetDistance(cm_distance) 2139 if cm_clipping_range is not None: vcam.SetClippingRange(cm_clipping_range) 2140 if cm_parallel_scale is not None: vcam.SetParallelScale(cm_parallel_scale) 2141 if cm_thickness is not None: vcam.SetThickness(cm_thickness) 2142 if cm_view_angle is not None: vcam.SetViewAngle(cm_view_angle) 2143 return vcam 2144 2145 2146def vtkCameraToK3D(vtkcam): 2147 """ 2148 Convert a `vtkCamera` object into a 9-element list to be used by the K3D backend. 2149 2150 Output format is: 2151 `[posx,posy,posz, targetx,targety,targetz, upx,upy,upz]`. 2152 """ 2153 cpos = np.array(vtkcam.GetPosition()) 2154 kam = [cpos.tolist()] 2155 kam.append(vtkcam.GetFocalPoint()) 2156 kam.append(vtkcam.GetViewUp()) 2157 return np.array(kam).ravel() 2158 2159 2160def make_ticks(x0, x1, n=None, labels=None, digits=None, logscale=False, useformat=""): 2161 """ 2162 Generate numeric labels for the `[x0, x1]` range. 2163 2164 The format specifier could be expressed in the format: 2165 `:[[fill]align][sign][#][0][width][,][.precision][type]` 2166 2167 where, the options are: 2168 ``` 2169 fill = any character 2170 align = < | > | = | ^ 2171 sign = + | - | " " 2172 width = integer 2173 precision = integer 2174 type = b | c | d | e | E | f | F | g | G | n | o | s | x | X | % 2175 ``` 2176 2177 E.g.: useformat=":.2f" 2178 """ 2179 # Copyright M. Musy, 2021, license: MIT. 2180 # 2181 # useformat eg: ":.2f", check out: 2182 # https://mkaz.blog/code/python-string-format-cookbook/ 2183 # https://www.programiz.com/python-programming/methods/built-in/format 2184 2185 if x1 <= x0: 2186 # vedo.printc("Error in make_ticks(): x0 >= x1", x0,x1, c='r') 2187 return np.array([0.0, 1.0]), ["", ""] 2188 2189 ticks_str, ticks_float = [], [] 2190 baseline = (1, 2, 5, 10, 20, 50) 2191 2192 if logscale: 2193 if x0 <= 0 or x1 <= 0: 2194 vedo.logger.error("make_ticks: zero or negative range with log scale.") 2195 raise RuntimeError 2196 if n is None: 2197 n = int(abs(np.log10(x1) - np.log10(x0))) + 1 2198 x0, x1 = np.log10([x0, x1]) 2199 2200 if not n: 2201 n = 5 2202 2203 if labels is not None: 2204 # user is passing custom labels 2205 2206 ticks_float.append(0) 2207 ticks_str.append("") 2208 for tp, ts in labels: 2209 if tp == x1: 2210 continue 2211 ticks_str.append(str(ts)) 2212 tickn = lin_interpolate(tp, [x0, x1], [0, 1]) 2213 ticks_float.append(tickn) 2214 2215 else: 2216 # ..here comes one of the shortest and most painful pieces of code: 2217 # automatically choose the best natural axis subdivision based on multiples of 1,2,5 2218 dstep = (x1 - x0) / n # desired step size, begin of the nightmare 2219 2220 basestep = pow(10, np.floor(np.log10(dstep))) 2221 steps = np.array([basestep * i for i in baseline]) 2222 idx = (np.abs(steps - dstep)).argmin() 2223 s = steps[idx] # chosen step size 2224 2225 low_bound, up_bound = 0, 0 2226 if x0 < 0: 2227 low_bound = -pow(10, np.ceil(np.log10(-x0))) 2228 if x1 > 0: 2229 up_bound = pow(10, np.ceil(np.log10(x1))) 2230 2231 if low_bound < 0: 2232 if up_bound < 0: 2233 negaxis = np.arange(low_bound, int(up_bound / s) * s) 2234 else: 2235 if -low_bound / s > 1.0e06: 2236 return np.array([0.0, 1.0]), ["", ""] 2237 negaxis = np.arange(low_bound, 0, s) 2238 else: 2239 negaxis = np.array([]) 2240 2241 if up_bound > 0: 2242 if low_bound > 0: 2243 posaxis = np.arange(int(low_bound / s) * s, up_bound, s) 2244 else: 2245 if up_bound / s > 1.0e06: 2246 return np.array([0.0, 1.0]), ["", ""] 2247 posaxis = np.arange(0, up_bound, s) 2248 else: 2249 posaxis = np.array([]) 2250 2251 fulaxis = np.unique(np.clip(np.concatenate([negaxis, posaxis]), x0, x1)) 2252 # end of the nightmare 2253 2254 if useformat: 2255 sf = "{" + f"{useformat}" + "}" 2256 sas = "" 2257 for x in fulaxis: 2258 sas += sf.format(x) + " " 2259 elif digits is None: 2260 np.set_printoptions(suppress=True) # avoid zero precision 2261 sas = str(fulaxis).replace("[", "").replace("]", "") 2262 sas = sas.replace(".e", "e").replace("e+0", "e+").replace("e-0", "e-") 2263 np.set_printoptions(suppress=None) # set back to default 2264 else: 2265 sas = precision(fulaxis, digits, vrange=(x0, x1)) 2266 sas = sas.replace("[", "").replace("]", "").replace(")", "").replace(",", "") 2267 2268 sas2 = [] 2269 for s in sas.split(): 2270 if s.endswith("."): 2271 s = s[:-1] 2272 if s == "-0": 2273 s = "0" 2274 if digits is not None and "e" in s: 2275 s += " " # add space to terminate modifiers 2276 sas2.append(s) 2277 2278 for ts, tp in zip(sas2, fulaxis): 2279 if tp == x1: 2280 continue 2281 tickn = lin_interpolate(tp, [x0, x1], [0, 1]) 2282 ticks_float.append(tickn) 2283 if logscale: 2284 val = np.power(10, tp) 2285 if useformat: 2286 sf = "{" + f"{useformat}" + "}" 2287 ticks_str.append(sf.format(val)) 2288 else: 2289 if val >= 10: 2290 val = int(val + 0.5) 2291 else: 2292 val = round_to_digit(val, 2) 2293 ticks_str.append(str(val)) 2294 else: 2295 ticks_str.append(ts) 2296 2297 ticks_str.append("") 2298 ticks_float.append(1) 2299 ticks_float = np.array(ticks_float) 2300 return ticks_float, ticks_str 2301 2302 2303def grid_corners(i, nm, size, margin=0, yflip=True): 2304 """ 2305 Compute the 2 corners coordinates of the i-th box in a grid of shape n*m. 2306 The top-left square is square number 1. 2307 2308 Arguments: 2309 i : (int) 2310 input index of the desired grid square (to be used in `show(..., at=...)`). 2311 nm : (list) 2312 grid shape as (n,m). 2313 size : (list) 2314 total size of the grid along x and y. 2315 margin : (float) 2316 keep a small margin between boxes. 2317 yflip : (bool) 2318 y-coordinate points downwards 2319 2320 Returns: 2321 Two 2D points representing the bottom-left corner and the top-right corner 2322 of the `i`-nth box in the grid. 2323 2324 Example: 2325 ```python 2326 from vedo import * 2327 acts=[] 2328 n,m = 5,7 2329 for i in range(1, n*m + 1): 2330 c1,c2 = utils.grid_corners(i, [n,m], [1,1], 0.01) 2331 t = Text3D(i, (c1+c2)/2, c='k', s=0.02, justify='center').z(0.01) 2332 r = Rectangle(c1, c2, c=i) 2333 acts += [t,r] 2334 show(acts, axes=1).close() 2335 ``` 2336  2337 """ 2338 i -= 1 2339 n, m = nm 2340 sx, sy = size 2341 dx, dy = sx / n, sy / m 2342 nx = i % n 2343 ny = int((i - nx) / n) 2344 if yflip: 2345 ny = n - ny 2346 c1 = (dx * nx + margin, dy * ny + margin) 2347 c2 = (dx * (nx + 1) - margin, dy * (ny + 1) - margin) 2348 return np.array(c1), np.array(c2) 2349 2350 2351############################################################################ 2352# Trimesh support 2353# 2354# Install trimesh with: 2355# 2356# sudo apt install python3-rtree 2357# pip install rtree shapely 2358# conda install trimesh 2359# 2360# Check the example gallery in: examples/other/trimesh> 2361########################################################################### 2362def vedo2trimesh(mesh): 2363 """ 2364 Convert `vedo.mesh.Mesh` to `Trimesh.Mesh` object. 2365 """ 2366 if is_sequence(mesh): 2367 tms = [] 2368 for a in mesh: 2369 tms.append(vedo2trimesh(a)) 2370 return tms 2371 2372 from trimesh import Trimesh 2373 2374 tris = mesh.faces() 2375 carr = mesh.celldata["CellIndividualColors"] 2376 ccols = carr 2377 2378 points = mesh.points() 2379 varr = mesh.pointdata["VertexColors"] 2380 vcols = varr 2381 2382 if len(tris) == 0: 2383 tris = None 2384 2385 return Trimesh(vertices=points, faces=tris, face_colors=ccols, vertex_colors=vcols) 2386 2387 2388def trimesh2vedo(inputobj): 2389 """ 2390 Convert a `Trimesh` object to `vedo.Mesh` or `vedo.Assembly` object. 2391 """ 2392 if is_sequence(inputobj): 2393 vms = [] 2394 for ob in inputobj: 2395 vms.append(trimesh2vedo(ob)) 2396 return vms 2397 2398 inputobj_type = str(type(inputobj)) 2399 2400 if "Trimesh" in inputobj_type or "primitives" in inputobj_type: 2401 faces = inputobj.faces 2402 poly = buildPolyData(inputobj.vertices, faces) 2403 tact = vedo.Mesh(poly) 2404 if inputobj.visual.kind == "face": 2405 trim_c = inputobj.visual.face_colors 2406 else: 2407 trim_c = inputobj.visual.vertex_colors 2408 2409 if is_sequence(trim_c): 2410 if is_sequence(trim_c[0]): 2411 same_color = len(np.unique(trim_c, axis=0)) < 2 # all vtxs have same color 2412 2413 if same_color: 2414 tact.c(trim_c[0, [0, 1, 2]]).alpha(trim_c[0, 3]) 2415 else: 2416 if inputobj.visual.kind == "face": 2417 tact.cell_individual_colors(trim_c) 2418 return tact 2419 2420 if "PointCloud" in inputobj_type: 2421 2422 trim_cc, trim_al = "black", 1 2423 if hasattr(inputobj, "vertices_color"): 2424 trim_c = inputobj.vertices_color 2425 if len(trim_c): 2426 trim_cc = trim_c[:, [0, 1, 2]] / 255 2427 trim_al = trim_c[:, 3] / 255 2428 trim_al = np.sum(trim_al) / len(trim_al) # just the average 2429 return vedo.shapes.Points(inputobj.vertices, r=8, c=trim_cc, alpha=trim_al) 2430 2431 if "path" in inputobj_type: 2432 2433 lines = [] 2434 for e in inputobj.entities: 2435 # print('trimesh entity', e.to_dict()) 2436 l = vedo.shapes.Line(inputobj.vertices[e.points], c="k", lw=2) 2437 lines.append(l) 2438 return vedo.Assembly(lines) 2439 2440 return None 2441 2442 2443def vedo2meshlab(vmesh): 2444 """Convert a `vedo.Mesh` to a Meshlab object.""" 2445 try: 2446 import pymeshlab as mlab 2447 except RuntimeError: 2448 vedo.logger.error("Need pymeshlab to run:\npip install pymeshlab") 2449 2450 vertex_matrix = vmesh.points().astype(np.float64) 2451 2452 try: 2453 face_matrix = np.asarray(vmesh.faces(), dtype=np.float64) 2454 except: 2455 print("In vedo2meshlab, need to triangulate mesh first!") 2456 face_matrix = np.array(vmesh.clone().triangulate().faces(), dtype=np.float64) 2457 2458 v_normals_matrix = vmesh.normals(cells=False, recompute=False) 2459 if not v_normals_matrix.shape[0]: 2460 v_normals_matrix = np.empty((0, 3), dtype=np.float64) 2461 2462 f_normals_matrix = vmesh.normals(cells=True, recompute=False) 2463 if not f_normals_matrix.shape[0]: 2464 f_normals_matrix = np.empty((0, 3), dtype=np.float64) 2465 2466 v_color_matrix = vmesh.pointdata["RGBA"] 2467 if v_color_matrix is None: 2468 v_color_matrix = np.empty((0, 4), dtype=np.float64) 2469 else: 2470 v_color_matrix = v_color_matrix.astype(np.float64) / 255 2471 if v_color_matrix.shape[1] == 3: 2472 v_color_matrix = np.c_[ 2473 v_color_matrix, np.ones(v_color_matrix.shape[0], dtype=np.float64) 2474 ] 2475 2476 f_color_matrix = vmesh.celldata["RGBA"] 2477 if f_color_matrix is None: 2478 f_color_matrix = np.empty((0, 4), dtype=np.float64) 2479 else: 2480 f_color_matrix = f_color_matrix.astype(np.float64) / 255 2481 if f_color_matrix.shape[1] == 3: 2482 f_color_matrix = np.c_[ 2483 f_color_matrix, np.ones(f_color_matrix.shape[0], dtype=np.float64) 2484 ] 2485 2486 m = mlab.Mesh( 2487 vertex_matrix=vertex_matrix, 2488 face_matrix=face_matrix, 2489 v_normals_matrix=v_normals_matrix, 2490 f_normals_matrix=f_normals_matrix, 2491 v_color_matrix=v_color_matrix, 2492 f_color_matrix=f_color_matrix, 2493 ) 2494 2495 for k in vmesh.pointdata.keys(): 2496 data = vmesh.pointdata[k] 2497 if data is not None: 2498 if data.ndim == 1: # scalar 2499 m.add_vertex_custom_scalar_attribute(data.astype(np.float64), k) 2500 elif data.ndim == 2: # vectorial data 2501 m.add_vertex_custom_point_attribute(data.astype(np.float64), k) 2502 2503 for k in vmesh.celldata.keys(): 2504 data = vmesh.celldata[k] 2505 if data is not None: 2506 if data.ndim == 1: # scalar 2507 m.add_face_custom_scalar_attribute(data.astype(np.float64), k) 2508 elif data.ndim == 2: # vectorial data 2509 m.add_face_custom_point_attribute(data.astype(np.float64), k) 2510 2511 m.update_bounding_box() 2512 return m 2513 2514 2515def meshlab2vedo(mmesh): 2516 """Convert a Meshlab object to `vedo.Mesh`.""" 2517 inputtype = str(type(mmesh)) 2518 2519 if "MeshSet" in inputtype: 2520 mmesh = mmesh.current_mesh() 2521 2522 mpoints, mcells = mmesh.vertex_matrix(), mmesh.face_matrix() 2523 if len(mcells): 2524 polydata = buildPolyData(mpoints, mcells) 2525 else: 2526 polydata = buildPolyData(mpoints, None) 2527 2528 if mmesh.has_vertex_scalar(): 2529 parr = mmesh.vertex_scalar_array() 2530 parr_vtk = numpy_to_vtk(parr) 2531 parr_vtk.SetName("MeshLabScalars") 2532 x0, x1 = parr_vtk.GetRange() 2533 if x1 - x0: 2534 polydata.GetPointData().AddArray(parr_vtk) 2535 polydata.GetPointData().SetActiveScalars("MeshLabScalars") 2536 2537 if mmesh.has_face_scalar(): 2538 carr = mmesh.face_scalar_array() 2539 carr_vtk = numpy_to_vtk(carr) 2540 carr_vtk.SetName("MeshLabScalars") 2541 x0, x1 = carr_vtk.GetRange() 2542 if x1 - x0: 2543 polydata.GetCellData().AddArray(carr_vtk) 2544 polydata.GetCellData().SetActiveScalars("MeshLabScalars") 2545 2546 pnorms = mmesh.vertex_normal_matrix() 2547 if len(pnorms): 2548 polydata.GetPointData().SetNormals(numpy2vtk(pnorms)) 2549 2550 cnorms = mmesh.face_normal_matrix() 2551 if len(cnorms): 2552 polydata.GetCellData().SetNormals(numpy2vtk(cnorms)) 2553 return polydata 2554 2555def open3d2vedo(o3d_mesh): 2556 """Convert `open3d.geometry.TriangleMesh` to a `vedo.Mesh`.""" 2557 m = vedo.Mesh([np.array(o3d_mesh.vertices), np.array(o3d_mesh.triangles)]) 2558 # TODO: could also check whether normals and color are present in 2559 # order to port with the above vertices/faces 2560 return m 2561 2562def vedo2open3d(vedo_mesh): 2563 """ 2564 Return an `open3d.geometry.TriangleMesh` version of the current mesh. 2565 """ 2566 try: 2567 import open3d as o3d 2568 except RuntimeError: 2569 vedo.logger.error("Need open3d to run:\npip install open3d") 2570 2571 # create from numpy arrays 2572 o3d_mesh = o3d.geometry.TriangleMesh( 2573 vertices=o3d.utility.Vector3dVector(vedo_mesh.points()), 2574 triangles=o3d.utility.Vector3iVector(vedo_mesh.faces()), 2575 ) 2576 # TODO: need to add some if check here in case color and normals 2577 # info are not existing 2578 # o3d_mesh.vertex_colors = o3d.utility.Vector3dVector(vedo_mesh.pointdata["RGB"]/255) 2579 # o3d_mesh.vertex_normals= o3d.utility.Vector3dVector(vedo_mesh.pointdata["Normals"]) 2580 return o3d_mesh 2581 2582def vtk_version_at_least(major, minor=0, build=0): 2583 """ 2584 Check the installed VTK version. 2585 2586 Return `True` if the requested VTK version is greater or equal to the actual VTK version. 2587 2588 Arguments: 2589 major : (int) 2590 Major version. 2591 minor : (int) 2592 Minor version. 2593 build : (int) 2594 Build version. 2595 """ 2596 needed_version = 10000000000 * int(major) + 100000000 * int(minor) + int(build) 2597 try: 2598 vtk_version_number = vtk.VTK_VERSION_NUMBER 2599 except AttributeError: # as error: 2600 ver = vtk.vtkVersion() 2601 vtk_version_number = ( 2602 10000000000 * ver.GetVTKMajorVersion() 2603 + 100000000 * ver.GetVTKMinorVersion() 2604 + ver.GetVTKBuildVersion() 2605 ) 2606 return vtk_version_number >= needed_version 2607 2608 2609def ctf2lut(tvobj, logscale=False): 2610 """Internal use.""" 2611 # build LUT from a color transfer function for tmesh or volume 2612 pr = tvobj.GetProperty() 2613 if not isinstance(pr, vtk.vtkVolumeProperty): 2614 return None 2615 ctf = pr.GetRGBTransferFunction() 2616 otf = pr.GetScalarOpacity() 2617 x0, x1 = tvobj.inputdata().GetScalarRange() 2618 cols, alphas = [], [] 2619 for x in np.linspace(x0, x1, 256): 2620 cols.append(ctf.GetColor(x)) 2621 alphas.append(otf.GetValue(x)) 2622 2623 if logscale: 2624 lut = vtk.vtkLogLookupTable() 2625 else: 2626 lut = vtk.vtkLookupTable() 2627 2628 lut.SetRange(x0, x1) 2629 lut.SetNumberOfTableValues(len(cols)) 2630 for i, col in enumerate(cols): 2631 r, g, b = col 2632 lut.SetTableValue(i, r, g, b, alphas[i]) 2633 lut.Build() 2634 return lut
72class OperationNode: 73 """ 74 Keep track of the operations which led to a final object. 75 """ 76 # https://www.graphviz.org/doc/info/shapes.html#html 77 # Mesh #e9c46a 78 # Follower #d9ed92 79 # Volume, UGrid #4cc9f0 80 # TetMesh #9e2a2b 81 # File #8a817c 82 # Picture #f28482 83 # Assembly #f08080 84 85 def __init__( 86 self, 87 operation, 88 parents=(), 89 comment="", 90 shape="none", 91 c="#e9c46a", 92 style="filled", 93 ): 94 """ 95 Keep track of the operations which led to a final object. 96 This allows to show the `pipeline` tree for any `vedo` object with e.g.: 97 98 ```python 99 from vedo import * 100 sp = Sphere() 101 sp.clean().subdivide() 102 sp.pipeline.show() 103 ``` 104 105 Arguments: 106 operation : (str, class) 107 descriptor label, if a class is passed then grab its name 108 parents : (list) 109 list of the parent classes the object comes from 110 comment : (str) 111 a second-line text description 112 shape : (str) 113 shape of the frame, check out [this link.](https://graphviz.org/doc/info/shapes.html) 114 c : (hex) 115 hex color 116 style : (str) 117 comma-separated list of styles 118 119 Example: 120 ```python 121 from vedo.utils import OperationNode 122 123 op_node1 = OperationNode("Operation1", c="lightblue") 124 op_node2 = OperationNode("Operation2") 125 op_node3 = OperationNode("Operation3", shape='diamond') 126 op_node4 = OperationNode("Operation4") 127 op_node5 = OperationNode("Operation5") 128 op_node6 = OperationNode("Result", c="lightgreen") 129 130 op_node3.add_parent(op_node1) 131 op_node4.add_parent(op_node1) 132 op_node3.add_parent(op_node2) 133 op_node5.add_parent(op_node2) 134 op_node6.add_parent(op_node3) 135 op_node6.add_parent(op_node5) 136 op_node6.add_parent(op_node1) 137 138 op_node6.show(orientation="TB") 139 ``` 140  141 """ 142 if not vedo.settings.enable_pipeline: 143 return 144 145 if isinstance(operation, str): 146 self.operation = operation 147 else: 148 self.operation = operation.__class__.__name__ 149 self.operation_plain = str(self.operation) 150 151 pp = [] # filter out invalid stuff 152 for p in parents: 153 if hasattr(p, "pipeline"): 154 pp.append(p.pipeline) 155 self.parents = pp 156 157 if comment: 158 self.operation = f"<{self.operation}<BR/><SUB><I>{comment}</I></SUB>>" 159 160 self.dot = None 161 self.time = time.time() 162 self.shape = shape 163 self.style = style 164 self.color = c 165 166 def add_parent(self, parent): 167 self.parents.append(parent) 168 169 def _build_tree(self, dot): 170 dot.node( 171 str(id(self)), 172 label=self.operation, 173 shape=self.shape, 174 color=self.color, 175 style=self.style, 176 ) 177 for parent in self.parents: 178 if parent: 179 t = f"{self.time - parent.time: .1f}s" 180 dot.edge(str(id(parent)), str(id(self)), label=t) 181 parent._build_tree(dot) 182 183 def __repr__(self): 184 try: 185 from treelib import Tree 186 except ImportError: 187 vedo.logger.error("To use this functionality please install treelib:" 188 "\n pip install treelib") 189 return "" 190 191 def _build_tree(parent): 192 for par in parent.parents: 193 if par: 194 op = par.operation_plain 195 tree.create_node( 196 op, 197 op + str(par.time), 198 parent=parent.operation_plain + str(parent.time)) 199 _build_tree(par) 200 201 tree = Tree() 202 tree.create_node( 203 self.operation_plain, self.operation_plain + str(self.time) 204 ) 205 _build_tree(self) 206 return tree.show(reverse=True, stdout=False) 207 208 def show(self, orientation="LR", popup=True): 209 """Show the graphviz output for the pipeline of this object""" 210 if not vedo.settings.enable_pipeline: 211 return 212 213 try: 214 from graphviz import Digraph 215 except ImportError: 216 vedo.logger.error("please install graphviz with command\n pip install graphviz") 217 return 218 219 # visualize the entire tree 220 dot = Digraph( 221 node_attr={ 222 'fontcolor':'#201010', 223 'fontname': "Helvetica", 224 'fontsize': '12', 225 }, 226 edge_attr={ 227 'fontname': "Helvetica", 228 'fontsize': '6', 229 'arrowsize': '0.4', 230 } 231 ) 232 dot.attr(rankdir=orientation) 233 234 self.counts = 0 235 self._build_tree(dot) 236 self.dot = dot 237 dot.render('.vedo_pipeline_graphviz', view=popup)
Keep track of the operations which led to a final object.
85 def __init__( 86 self, 87 operation, 88 parents=(), 89 comment="", 90 shape="none", 91 c="#e9c46a", 92 style="filled", 93 ): 94 """ 95 Keep track of the operations which led to a final object. 96 This allows to show the `pipeline` tree for any `vedo` object with e.g.: 97 98 ```python 99 from vedo import * 100 sp = Sphere() 101 sp.clean().subdivide() 102 sp.pipeline.show() 103 ``` 104 105 Arguments: 106 operation : (str, class) 107 descriptor label, if a class is passed then grab its name 108 parents : (list) 109 list of the parent classes the object comes from 110 comment : (str) 111 a second-line text description 112 shape : (str) 113 shape of the frame, check out [this link.](https://graphviz.org/doc/info/shapes.html) 114 c : (hex) 115 hex color 116 style : (str) 117 comma-separated list of styles 118 119 Example: 120 ```python 121 from vedo.utils import OperationNode 122 123 op_node1 = OperationNode("Operation1", c="lightblue") 124 op_node2 = OperationNode("Operation2") 125 op_node3 = OperationNode("Operation3", shape='diamond') 126 op_node4 = OperationNode("Operation4") 127 op_node5 = OperationNode("Operation5") 128 op_node6 = OperationNode("Result", c="lightgreen") 129 130 op_node3.add_parent(op_node1) 131 op_node4.add_parent(op_node1) 132 op_node3.add_parent(op_node2) 133 op_node5.add_parent(op_node2) 134 op_node6.add_parent(op_node3) 135 op_node6.add_parent(op_node5) 136 op_node6.add_parent(op_node1) 137 138 op_node6.show(orientation="TB") 139 ``` 140  141 """ 142 if not vedo.settings.enable_pipeline: 143 return 144 145 if isinstance(operation, str): 146 self.operation = operation 147 else: 148 self.operation = operation.__class__.__name__ 149 self.operation_plain = str(self.operation) 150 151 pp = [] # filter out invalid stuff 152 for p in parents: 153 if hasattr(p, "pipeline"): 154 pp.append(p.pipeline) 155 self.parents = pp 156 157 if comment: 158 self.operation = f"<{self.operation}<BR/><SUB><I>{comment}</I></SUB>>" 159 160 self.dot = None 161 self.time = time.time() 162 self.shape = shape 163 self.style = style 164 self.color = c
Keep track of the operations which led to a final object.
This allows to show the pipeline
tree for any vedo
object with e.g.:
from vedo import *
sp = Sphere()
sp.clean().subdivide()
sp.pipeline.show()
Arguments:
- operation : (str, class) descriptor label, if a class is passed then grab its name
- parents : (list) list of the parent classes the object comes from
- comment : (str) a second-line text description
- shape : (str) shape of the frame, check out this link.
- c : (hex) hex color
- style : (str) comma-separated list of styles
Example:
from vedo.utils import OperationNode op_node1 = OperationNode("Operation1", c="lightblue") op_node2 = OperationNode("Operation2") op_node3 = OperationNode("Operation3", shape='diamond') op_node4 = OperationNode("Operation4") op_node5 = OperationNode("Operation5") op_node6 = OperationNode("Result", c="lightgreen") op_node3.add_parent(op_node1) op_node4.add_parent(op_node1) op_node3.add_parent(op_node2) op_node5.add_parent(op_node2) op_node6.add_parent(op_node3) op_node6.add_parent(op_node5) op_node6.add_parent(op_node1) op_node6.show(orientation="TB")
208 def show(self, orientation="LR", popup=True): 209 """Show the graphviz output for the pipeline of this object""" 210 if not vedo.settings.enable_pipeline: 211 return 212 213 try: 214 from graphviz import Digraph 215 except ImportError: 216 vedo.logger.error("please install graphviz with command\n pip install graphviz") 217 return 218 219 # visualize the entire tree 220 dot = Digraph( 221 node_attr={ 222 'fontcolor':'#201010', 223 'fontname': "Helvetica", 224 'fontsize': '12', 225 }, 226 edge_attr={ 227 'fontname': "Helvetica", 228 'fontsize': '6', 229 'arrowsize': '0.4', 230 } 231 ) 232 dot.attr(rankdir=orientation) 233 234 self.counts = 0 235 self._build_tree(dot) 236 self.dot = dot 237 dot.render('.vedo_pipeline_graphviz', view=popup)
Show the graphviz output for the pipeline of this object
241class ProgressBar: 242 """ 243 Class to print a progress bar. 244 """ 245 def __init__( 246 self, 247 start, 248 stop, 249 step=1, 250 c=None, 251 bold=True, 252 italic=False, 253 title="", 254 eta=True, 255 delay=0, 256 width=25, 257 char="\U00002501", 258 char_back="\U00002500", 259 ): 260 """ 261 Class to print a progress bar with optional text message. 262 263 Check out also function `progressbar()`. 264 265 Example: 266 ```python 267 import time 268 pb = ProgressBar(0,400, c='red') 269 for i in pb.range(): 270 time.sleep(0.1) 271 pb.print('some message') 272 ``` 273  274 """ 275 self.char = char 276 self.char_back = char_back 277 278 self.title = title + " " 279 if title: 280 self.title = " " + self.title 281 282 self.start = start 283 self.stop = stop 284 self.step = step 285 286 self.color = c 287 self.bold = bold 288 self.italic = italic 289 self.width = width 290 self.pbar = "" 291 self.percent = 0.0 292 self.percent_int = 0 293 self.eta = eta 294 self.delay = delay 295 296 self.t0 = time.time() 297 self._remaining = 1e10 298 299 self._update(0) 300 301 self._counts = 0 302 self._oldbar = "" 303 self._lentxt = 0 304 self._range = np.arange(start, stop, step) 305 306 def print(self, txt="", c=None): 307 """Print the progress bar with an optional message.""" 308 if not c: 309 c = self.color 310 311 self._update(self._counts + self.step) 312 313 if self.delay: 314 if time.time() - self.t0 < self.delay: 315 return 316 317 if self.pbar != self._oldbar: 318 self._oldbar = self.pbar 319 320 if self.eta and self._counts > 1: 321 322 tdenom = time.time() - self.t0 323 if tdenom: 324 vel = self._counts / tdenom 325 self._remaining = (self.stop - self._counts) / vel 326 else: 327 vel = 1 328 self._remaining = 0.0 329 330 if self._remaining > 60: 331 mins = int(self._remaining / 60) 332 secs = self._remaining - 60 * mins 333 mins = f"{mins}m" 334 secs = f"{int(secs + 0.5)}s " 335 else: 336 mins = "" 337 secs = f"{int(self._remaining + 0.5)}s " 338 339 vel = round(vel, 1) 340 eta = f"eta: {mins}{secs}({vel} it/s) " 341 if self._remaining < 0.5: 342 dt = time.time() - self.t0 343 if dt > 60: 344 mins = int(dt / 60) 345 secs = dt - 60 * mins 346 mins = f"{mins}m" 347 secs = f"{int(secs + 0.5)}s " 348 else: 349 mins = "" 350 secs = f"{int(dt + 0.5)}s " 351 eta = f"elapsed: {mins}{secs}({vel} it/s) " 352 txt = "" 353 else: 354 eta = "" 355 356 eraser = " " * self._lentxt + "\b" * self._lentxt 357 358 s = f"{self.pbar} {eraser}{eta}{txt}\r" 359 vedo.printc(s, c=c, bold=self.bold, italic=self.italic, end="") 360 if self.percent > 99.999: 361 print("") 362 363 self._lentxt = len(txt) 364 365 def range(self): 366 """Return the range iterator.""" 367 return self._range 368 369 def _update(self, counts): 370 if counts < self.start: 371 counts = self.start 372 elif counts > self.stop: 373 counts = self.stop 374 self._counts = counts 375 376 self.percent = (self._counts - self.start) * 100.0 377 378 delta = self.stop - self.start 379 if delta: 380 self.percent /= delta 381 else: 382 self.percent = 0.0 383 384 self.percent_int = int(round(self.percent)) 385 af = self.width - 2 386 nh = int(round(self.percent_int / 100 * af)) 387 pbar_background = "\x1b[2m" + self.char_back * (af - nh) 388 self.pbar = f"{self.title}{self.char * (nh-1)}{pbar_background}" 389 if self.percent < 100.0: 390 ps = f" {self.percent_int}%" 391 else: 392 ps = "" 393 self.pbar += ps
Class to print a progress bar.
245 def __init__( 246 self, 247 start, 248 stop, 249 step=1, 250 c=None, 251 bold=True, 252 italic=False, 253 title="", 254 eta=True, 255 delay=0, 256 width=25, 257 char="\U00002501", 258 char_back="\U00002500", 259 ): 260 """ 261 Class to print a progress bar with optional text message. 262 263 Check out also function `progressbar()`. 264 265 Example: 266 ```python 267 import time 268 pb = ProgressBar(0,400, c='red') 269 for i in pb.range(): 270 time.sleep(0.1) 271 pb.print('some message') 272 ``` 273  274 """ 275 self.char = char 276 self.char_back = char_back 277 278 self.title = title + " " 279 if title: 280 self.title = " " + self.title 281 282 self.start = start 283 self.stop = stop 284 self.step = step 285 286 self.color = c 287 self.bold = bold 288 self.italic = italic 289 self.width = width 290 self.pbar = "" 291 self.percent = 0.0 292 self.percent_int = 0 293 self.eta = eta 294 self.delay = delay 295 296 self.t0 = time.time() 297 self._remaining = 1e10 298 299 self._update(0) 300 301 self._counts = 0 302 self._oldbar = "" 303 self._lentxt = 0 304 self._range = np.arange(start, stop, step)
Class to print a progress bar with optional text message.
Check out also function progressbar()
.
Example:
import time pb = ProgressBar(0,400, c='red') for i in pb.range(): time.sleep(0.1) pb.print('some message')
306 def print(self, txt="", c=None): 307 """Print the progress bar with an optional message.""" 308 if not c: 309 c = self.color 310 311 self._update(self._counts + self.step) 312 313 if self.delay: 314 if time.time() - self.t0 < self.delay: 315 return 316 317 if self.pbar != self._oldbar: 318 self._oldbar = self.pbar 319 320 if self.eta and self._counts > 1: 321 322 tdenom = time.time() - self.t0 323 if tdenom: 324 vel = self._counts / tdenom 325 self._remaining = (self.stop - self._counts) / vel 326 else: 327 vel = 1 328 self._remaining = 0.0 329 330 if self._remaining > 60: 331 mins = int(self._remaining / 60) 332 secs = self._remaining - 60 * mins 333 mins = f"{mins}m" 334 secs = f"{int(secs + 0.5)}s " 335 else: 336 mins = "" 337 secs = f"{int(self._remaining + 0.5)}s " 338 339 vel = round(vel, 1) 340 eta = f"eta: {mins}{secs}({vel} it/s) " 341 if self._remaining < 0.5: 342 dt = time.time() - self.t0 343 if dt > 60: 344 mins = int(dt / 60) 345 secs = dt - 60 * mins 346 mins = f"{mins}m" 347 secs = f"{int(secs + 0.5)}s " 348 else: 349 mins = "" 350 secs = f"{int(dt + 0.5)}s " 351 eta = f"elapsed: {mins}{secs}({vel} it/s) " 352 txt = "" 353 else: 354 eta = "" 355 356 eraser = " " * self._lentxt + "\b" * self._lentxt 357 358 s = f"{self.pbar} {eraser}{eta}{txt}\r" 359 vedo.printc(s, c=c, bold=self.bold, italic=self.italic, end="") 360 if self.percent > 99.999: 361 print("") 362 363 self._lentxt = len(txt)
Print the progress bar with an optional message.
396def progressbar( 397 iterable, 398 c=None, 399 bold=True, 400 italic=False, 401 title="", 402 eta=True, 403 width=25, 404 delay=0, 405 ): 406 """ 407 Function to print a progress bar with optional text message. 408 409 Example: 410 ```python 411 import time 412 for i in progressbar(range(100), c='red'): 413 time.sleep(0.1) 414 ``` 415  416 """ 417 try: 418 if is_number(iterable): 419 total = int(iterable) 420 iterable = range(total) 421 else: 422 total = len(iterable) 423 except TypeError: 424 iterable = list(iterable) 425 total = len(iterable) 426 427 pb = ProgressBar( 428 0, total, 429 c=c, bold=bold, italic=italic, title=title, eta=eta, delay=delay, width=width, 430 ) 431 for item in iterable: 432 pb.print() 433 yield item
Function to print a progress bar with optional text message.
Example:
import time for i in progressbar(range(100), c='red'): time.sleep(0.1)
517def geometry(obj, extent=None): 518 """ 519 Apply the `vtkGeometryFilter` to the input object. 520 This is a general-purpose filter to extract geometry (and associated data) 521 from any type of dataset. 522 This filter also may be used to convert any type of data to polygonal type. 523 The conversion process may be less than satisfactory for some 3D datasets. 524 For example, this filter will extract the outer surface of a volume 525 or structured grid dataset. 526 527 Returns a `vedo.Mesh` object. 528 529 Set `extent` as the `[xmin,xmax, ymin,ymax, zmin,zmax]` bounding box to clip data. 530 """ 531 gf = vtk.vtkGeometryFilter() 532 gf.SetInputData(obj) 533 if extent is not None: 534 gf.SetExtent(extent) 535 gf.Update() 536 return vedo.Mesh(gf.GetOutput())
Apply the vtkGeometryFilter
to the input object.
This is a general-purpose filter to extract geometry (and associated data)
from any type of dataset.
This filter also may be used to convert any type of data to polygonal type.
The conversion process may be less than satisfactory for some 3D datasets.
For example, this filter will extract the outer surface of a volume
or structured grid dataset.
Returns a vedo.Mesh
object.
Set extent
as the [xmin,xmax, ymin,ymax, zmin,zmax]
bounding box to clip data.
539def extract_cells_by_type(obj, types=()): 540 """ 541 Extract cells of a specified type from a vtk dataset. 542 543 Given an input `vtkDataSet` and a list of cell types, produce an output 544 containing only cells of the specified type(s). 545 546 Find [here](https://vtk.org/doc/nightly/html/vtkCellType_8h_source.html) 547 the list of possible cell types. 548 549 Return: 550 a `vtkDataSet` object which can be wrapped. 551 """ 552 ef = vtk.vtkExtractCellsByType() 553 try: 554 ef.SetInputData(obj._data) 555 except: 556 ef.SetInputData(obj) 557 558 for ct in types: 559 ef.AddCellType(ct) 560 ef.Update() 561 return ef.GetOutput()
Extract cells of a specified type from a vtk dataset.
Given an input vtkDataSet
and a list of cell types, produce an output
containing only cells of the specified type(s).
Find here the list of possible cell types.
Return:
a
vtkDataSet
object which can be wrapped.
748def is_sequence(arg): 749 """Check if the input is iterable.""" 750 if hasattr(arg, "strip"): 751 return False 752 if hasattr(arg, "__getslice__"): 753 return True 754 if hasattr(arg, "__iter__"): 755 return True 756 return False
Check if the input is iterable.
1024def lin_interpolate(x, rangeX, rangeY): 1025 """ 1026 Interpolate linearly the variable `x` in `rangeX` onto the new `rangeY`. 1027 If `x` is a 3D vector the linear weight is the distance to the two 3D `rangeX` vectors. 1028 1029 E.g. if `x` runs in `rangeX=[x0,x1]` and I want it to run in `rangeY=[y0,y1]` then 1030 1031 `y = lin_interpolate(x, rangeX, rangeY)` will interpolate `x` onto `rangeY`. 1032 1033 Examples: 1034 - [lin_interpolate.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/lin_interpolate.py) 1035 1036  1037 """ 1038 if is_sequence(x): 1039 x = np.asarray(x) 1040 x0, x1 = np.asarray(rangeX) 1041 y0, y1 = np.asarray(rangeY) 1042 dx = x1 - x0 1043 dxn = np.linalg.norm(dx) 1044 if not dxn: 1045 return y0 1046 s = np.linalg.norm(x - x0) / dxn 1047 t = np.linalg.norm(x - x1) / dxn 1048 st = s + t 1049 out = y0 * (t / st) + y1 * (s / st) 1050 1051 else: # faster 1052 1053 x0 = rangeX[0] 1054 dx = rangeX[1] - x0 1055 if not dx: 1056 return rangeY[0] 1057 s = (x - x0) / dx 1058 out = rangeY[0] * (1 - s) + rangeY[1] * s 1059 return out
Interpolate linearly the variable x
in rangeX
onto the new rangeY
.
If x
is a 3D vector the linear weight is the distance to the two 3D rangeX
vectors.
E.g. if x
runs in rangeX=[x0,x1]
and I want it to run in rangeY=[y0,y1]
then
y = lin_interpolate(x, rangeX, rangeY)
will interpolate x
onto rangeY
.
Examples:
1136def vector(x, y=None, z=0.0, dtype=np.float64): 1137 """ 1138 Return a 3D numpy array representing a vector. 1139 1140 If `y` is `None`, assume input is already in the form `[x,y,z]`. 1141 """ 1142 if y is None: # assume x is already [x,y,z] 1143 return np.asarray(x, dtype=dtype) 1144 return np.array([x, y, z], dtype=dtype)
Return a 3D numpy array representing a vector.
If y
is None
, assume input is already in the form [x,y,z]
.
1155def mag(v): 1156 """Get the magnitude of a vector or array of vectors.""" 1157 v = np.asarray(v) 1158 if v.ndim == 1: 1159 return np.linalg.norm(v) 1160 return np.linalg.norm(v, axis=1)
Get the magnitude of a vector or array of vectors.
1163def mag2(v): 1164 """Get the squared magnitude of a vector or array of vectors.""" 1165 v = np.asarray(v) 1166 if v.ndim == 1: 1167 return np.square(v).sum() 1168 return np.square(v).sum(axis=1)
Get the squared magnitude of a vector or array of vectors.
1147def versor(x, y=None, z=0.0, dtype=np.float64): 1148 """Return the unit vector. Input can be a list of vectors.""" 1149 v = vector(x, y, z, dtype) 1150 if isinstance(v[0], np.ndarray): 1151 return np.divide(v, mag(v)[:, None]) 1152 return v / mag(v)
Return the unit vector. Input can be a list of vectors.
1244def precision(x, p, vrange=None, delimiter="e"): 1245 """ 1246 Returns a string representation of `x` formatted to precision `p`. 1247 1248 Set `vrange` to the range in which x exists (to snap x to '0' if below precision). 1249 """ 1250 # Based on the webkit javascript implementation 1251 # `from here <https://code.google.com/p/webkit-mirror/source/browse/JavaScriptCore/kjs/number_object.cpp>`_, 1252 # and implemented by `randlet <https://github.com/randlet/to-precision>`_. 1253 # Modified for vedo by M.Musy 2020 1254 1255 if isinstance(x, str): # do nothing 1256 return x 1257 1258 if is_sequence(x): 1259 out = "(" 1260 nn = len(x) - 1 1261 for i, ix in enumerate(x): 1262 1263 try: 1264 if np.isnan(ix): 1265 return "NaN" 1266 except: 1267 # cannot handle list of list 1268 continue 1269 1270 out += precision(ix, p) 1271 if i < nn: 1272 out += ", " 1273 return out + ")" ############ <-- 1274 1275 if np.isnan(x): 1276 return "NaN" 1277 1278 x = float(x) 1279 1280 if x == 0.0 or (vrange is not None and abs(x) < vrange / pow(10, p)): 1281 return "0" 1282 1283 out = [] 1284 if x < 0: 1285 out.append("-") 1286 x = -x 1287 1288 e = int(math.log10(x)) 1289 tens = math.pow(10, e - p + 1) 1290 n = math.floor(x / tens) 1291 1292 if n < math.pow(10, p - 1): 1293 e = e - 1 1294 tens = math.pow(10, e - p + 1) 1295 n = math.floor(x / tens) 1296 1297 if abs((n + 1.0) * tens - x) <= abs(n * tens - x): 1298 n = n + 1 1299 1300 if n >= math.pow(10, p): 1301 n = n / 10.0 1302 e = e + 1 1303 1304 m = "%.*g" % (p, n) 1305 if e < -2 or e >= p: 1306 out.append(m[0]) 1307 if p > 1: 1308 out.append(".") 1309 out.extend(m[1:p]) 1310 out.append(delimiter) 1311 if e > 0: 1312 out.append("+") 1313 out.append(str(e)) 1314 elif e == (p - 1): 1315 out.append(m) 1316 elif e >= 0: 1317 out.append(m[: e + 1]) 1318 if e + 1 < len(m): 1319 out.append(".") 1320 out.extend(m[e + 1 :]) 1321 else: 1322 out.append("0.") 1323 out.extend(["0"] * -(e + 1)) 1324 out.append(m) 1325 return "".join(out)
Returns a string representation of x
formatted to precision p
.
Set vrange
to the range in which x exists (to snap x to '0' if below precision).
1190def round_to_digit(x, p): 1191 """Round a real number to the specified number of significant digits.""" 1192 if not x: 1193 return 0 1194 r = np.round(x, p - int(np.floor(np.log10(abs(x)))) - 1) 1195 if int(r) == r: 1196 return int(r) 1197 return r
Round a real number to the specified number of significant digits.
827def point_in_triangle(p, p1, p2, p3): 828 """ 829 Return True if a point is inside (or above/below) a triangle defined by 3 points in space. 830 """ 831 p1 = np.array(p1) 832 u = p2 - p1 833 v = p3 - p1 834 n = np.cross(u, v) 835 w = p - p1 836 ln = np.dot(n, n) 837 if not ln: 838 return None # degenerate triangle 839 gamma = (np.dot(np.cross(u, w), n)) / ln 840 if 0 < gamma < 1: 841 beta = (np.dot(np.cross(w, v), n)) / ln 842 if 0 < beta < 1: 843 alpha = 1 - gamma - beta 844 if 0 < alpha < 1: 845 return True 846 return False
Return True if a point is inside (or above/below) a triangle defined by 3 points in space.
1008def point_line_distance(p, p1, p2): 1009 """ 1010 Compute the distance of a point to a line (not the segment) 1011 defined by `p1` and `p2`. 1012 """ 1013 d = np.sqrt(vtk.vtkLine.DistanceToLine(p, p1, p2)) 1014 return d
Compute the distance of a point to a line (not the segment)
defined by p1
and p2
.
1396def grep(filename, tag, first_occurrence_only=False): 1397 """Greps the line in a file that starts with a specific `tag` string inside the file.""" 1398 import re 1399 1400 with open(filename, "r", encoding="UTF-8") as afile: 1401 content = [] 1402 for line in afile: 1403 if re.search(tag, line): 1404 c = line.split() 1405 c[-1] = c[-1].replace("\n", "") 1406 content.append(c) 1407 if first_occurrence_only: 1408 break 1409 return content
Greps the line in a file that starts with a specific tag
string inside the file.
1412def print_info(obj): 1413 """Print information about a `vedo` object.""" 1414 1415 def _print_data(poly, mapper, c): 1416 ptdata = poly.GetPointData() 1417 cldata = poly.GetCellData() 1418 fldata = poly.GetFieldData() 1419 if ptdata.GetNumberOfArrays() + cldata.GetNumberOfArrays(): 1420 arrtypes = {} 1421 arrtypes[vtk.VTK_UNSIGNED_CHAR] = ("UNSIGNED_CHAR", "np.uint8") 1422 arrtypes[vtk.VTK_UNSIGNED_SHORT]= ("UNSIGNED_SHORT", "np.uint16") 1423 arrtypes[vtk.VTK_UNSIGNED_INT] = ("UNSIGNED_INT", "np.uint32") 1424 arrtypes[vtk.VTK_UNSIGNED_LONG_LONG] = ("UNSIGNED_LONG_LONG", "np.uint64") 1425 arrtypes[vtk.VTK_CHAR] = ("CHAR", "np.int8") 1426 arrtypes[vtk.VTK_SHORT] = ("SHORT", "np.int16") 1427 arrtypes[vtk.VTK_INT] = ("INT", "np.int32") 1428 arrtypes[vtk.VTK_LONG] = ("LONG", "") # ?? 1429 arrtypes[vtk.VTK_LONG_LONG] = ("LONG_LONG", "np.int64") 1430 arrtypes[vtk.VTK_FLOAT] = ("FLOAT", "np.float32") 1431 arrtypes[vtk.VTK_DOUBLE] = ("DOUBLE", "np.float64") 1432 arrtypes[vtk.VTK_SIGNED_CHAR] = ("SIGNED_CHAR", "np.int8") 1433 arrtypes[vtk.VTK_ID_TYPE] = ("ID", "np.int64") 1434 1435 if ptdata.GetScalars(): 1436 vedo.printc("active array".ljust(14)+": ", c=c, bold=True, end="") 1437 vedo.printc(ptdata.GetScalars().GetName(), "(pointdata) ", c=c, bold=False) 1438 1439 if cldata.GetScalars(): 1440 vedo.printc("active array".ljust(14)+": ", c=c, bold=True, end="") 1441 vedo.printc(cldata.GetScalars().GetName(), "(celldata)", c=c, bold=False) 1442 1443 for i in range(ptdata.GetNumberOfArrays()): 1444 name = ptdata.GetArrayName(i) 1445 if name and ptdata.GetArray(i): 1446 vedo.printc("pointdata".ljust(14) + ": ", c=c, bold=True, end="") 1447 try: 1448 tt, nptt = arrtypes[ptdata.GetArray(i).GetDataType()] 1449 except: 1450 tt = "VTKTYPE" + str(ptdata.GetArray(i).GetDataType()) 1451 nptt = "" 1452 ncomp = ptdata.GetArray(i).GetNumberOfComponents() 1453 rng = ptdata.GetArray(i).GetRange() 1454 vedo.printc(f'"{name}" ({ncomp} {tt}),', c=c, bold=False, end="") 1455 vedo.printc( 1456 " range=(" + precision(rng[0], 3) + "," + precision(rng[1], 3) + ")", 1457 c=c, 1458 bold=False, 1459 ) 1460 1461 for i in range(cldata.GetNumberOfArrays()): 1462 name = cldata.GetArrayName(i) 1463 if name and cldata.GetArray(i): 1464 vedo.printc("celldata".ljust(14) + ": ", c=c, bold=True, end="") 1465 try: 1466 tt, nptt = arrtypes[cldata.GetArray(i).GetDataType()] 1467 except: 1468 tt = cldata.GetArray(i).GetDataType() 1469 ncomp = cldata.GetArray(i).GetNumberOfComponents() 1470 rng = cldata.GetArray(i).GetRange() 1471 vedo.printc(f'"{name}" ({ncomp} {tt}),', c=c, bold=False, end="") 1472 vedo.printc( 1473 " range=(" + precision(rng[0], 4) + "," + precision(rng[1], 4) + ")", 1474 c=c, 1475 bold=False, 1476 ) 1477 1478 for i in range(fldata.GetNumberOfArrays()): 1479 name = fldata.GetArrayName(i) 1480 if name and fldata.GetAbstractArray(i): 1481 arr = fldata.GetAbstractArray(i) 1482 vedo.printc("metadata".ljust(14) + ": ", c=c, bold=True, end="") 1483 ncomp = arr.GetNumberOfComponents() 1484 nvals = arr.GetNumberOfValues() 1485 vedo.printc(f'"{name}" ({ncomp} components, {nvals} values)', c=c, bold=False) 1486 1487 else: 1488 vedo.printc("mesh data".ljust(14) + ":", c=c, bold=True, end=" ") 1489 vedo.printc("no point or cell data is present.", c=c, bold=False) 1490 1491 ################################ 1492 def _printvtkactor(actor): 1493 1494 if not actor.GetPickable(): 1495 return 1496 1497 mapper = actor.GetMapper() 1498 if hasattr(actor, "polydata"): 1499 poly = actor.polydata() 1500 else: 1501 poly = mapper.GetInput() 1502 1503 pro = actor.GetProperty() 1504 pos = actor.GetPosition() 1505 bnds = actor.bounds() 1506 col = precision(pro.GetColor(), 3) 1507 alpha = pro.GetOpacity() 1508 npt = poly.GetNumberOfPoints() 1509 ncl = poly.GetNumberOfCells() 1510 npl = poly.GetNumberOfPolys() 1511 nln = poly.GetNumberOfLines() 1512 1513 vedo.printc("Mesh/Points".ljust(70), c="g", bold=True, invert=True, dim=1, end="") 1514 1515 if hasattr(actor, "info") and "legend" in actor.info.keys() and actor.info["legend"]: 1516 vedo.printc("legend".ljust(14)+": ", c="g", bold=True, end="") 1517 vedo.printc(actor.info["legend"], c="g", bold=False) 1518 else: 1519 print() 1520 1521 if hasattr(actor, "name") and actor.name: 1522 vedo.printc("name".ljust(14) + ": ", c="g", bold=True, end="") 1523 vedo.printc(actor.name, c="g", bold=False) 1524 1525 if hasattr(actor, "filename") and actor.filename: 1526 vedo.printc("file name".ljust(14) + ": ", c="g", bold=True, end="") 1527 vedo.printc(actor.filename, c="g", bold=False) 1528 1529 if not actor.GetMapper().GetScalarVisibility(): 1530 vedo.printc("color".ljust(14) + ": ", c="g", bold=True, end="") 1531 cname = vedo.colors.get_color_name(pro.GetColor()) 1532 vedo.printc(f"{cname}, rgb={col}, alpha={alpha}", c="g", bold=False) 1533 1534 if actor.GetBackfaceProperty(): 1535 bcol = actor.GetBackfaceProperty().GetDiffuseColor() 1536 cname = vedo.colors.get_color_name(bcol) 1537 vedo.printc("back color".ljust(14) + ": ", c="g", bold=True, end="") 1538 vedo.printc(f"{cname}, rgb={precision(bcol,3)}", c="g", bold=False) 1539 1540 vedo.printc("points".ljust(14) + ":", npt, c="g", bold=True) 1541 # vedo.printc("cells".ljust(14)+":", ncl, c="g", bold=True) 1542 vedo.printc("polygons".ljust(14) + ":", npl, c="g", bold=True) 1543 if nln: 1544 vedo.printc("lines".ljust(14) + ":", nln, c="g", bold=True) 1545 vedo.printc("position".ljust(14) + ":", pos, c="g", bold=True) 1546 1547 if hasattr(actor, "GetScale"): 1548 vedo.printc("scale".ljust(14) + ":", c="g", bold=True, end=" ") 1549 vedo.printc(precision(actor.GetScale(), 3), c="g", bold=False) 1550 1551 if hasattr(actor, "polydata") and actor.npoints: 1552 vedo.printc("center of mass".ljust(14) + ":", c="g", bold=True, end=" ") 1553 cm = tuple(actor.center_of_mass()) 1554 vedo.printc(precision(cm, 3), c="g", bold=False) 1555 1556 vedo.printc("average size".ljust(14) + ":", c="g", bold=True, end=" ") 1557 vedo.printc(precision(actor.average_size(), 6), c="g", bold=False) 1558 1559 vedo.printc("diagonal size".ljust(14) + ":", c="g", bold=True, end=" ") 1560 vedo.printc(precision(actor.diagonal_size(), 6), c="g", bold=False) 1561 1562 vedo.printc("bounds".ljust(14) + ":", c="g", bold=True, end=" ") 1563 bx1, bx2 = precision(bnds[0], 3), precision(bnds[1], 3) 1564 vedo.printc("x=(" + bx1 + ", " + bx2 + ")", c="g", bold=False, end="") 1565 by1, by2 = precision(bnds[2], 3), precision(bnds[3], 3) 1566 vedo.printc(" y=(" + by1 + ", " + by2 + ")", c="g", bold=False, end="") 1567 bz1, bz2 = precision(bnds[4], 3), precision(bnds[5], 3) 1568 vedo.printc(" z=(" + bz1 + ", " + bz2 + ")", c="g", bold=False) 1569 1570 _print_data(poly, mapper, "g") 1571 1572 if hasattr(actor, "picked3d") and actor.picked3d is not None: 1573 idpt = actor.closest_point(actor.picked3d, return_point_id=True) 1574 idcell = actor.closest_point(actor.picked3d, return_cell_id=True) 1575 vedo.printc( 1576 "clicked point".ljust(14) + ":", 1577 precision(actor.picked3d, 6), 1578 f"pointID={idpt}, cellID={idcell}", 1579 c="g", 1580 bold=True, 1581 ) 1582 1583 if obj is None: 1584 return 1585 1586 if isinstance(obj, np.ndarray): 1587 obj = obj 1588 cf = "y" 1589 vedo.printc("Numpy Array".ljust(70), c=cf, invert=True) 1590 vedo.printc(obj, c=cf) 1591 vedo.printc("shape".ljust(8) + ":", obj.shape, c=cf) 1592 vedo.printc("range".ljust(8) + f": ({np.min(obj)}, {np.max(obj)})", c=cf) 1593 vedo.printc("mean".ljust(8) + ":", np.mean(obj), c=cf) 1594 vedo.printc("std_dev".ljust(8) + ":", np.std(obj), c=cf) 1595 if len(obj.shape) >= 2: 1596 vedo.printc("Axis 0".ljust(8) + ":", c=cf, italic=1) 1597 vedo.printc("\tmin :", np.min(obj, axis=0), c=cf) 1598 vedo.printc("\tmax :", np.max(obj, axis=0), c=cf) 1599 vedo.printc("\tmean:", np.mean(obj, axis=0), c=cf) 1600 if obj.shape[1] > 3: 1601 vedo.printc("Axis 1".ljust(8) + ":", c=cf, italic=1) 1602 tmin = str(np.min(obj, axis=1).tolist()[:2]).replace("]", ", ...") 1603 tmax = str(np.max(obj, axis=1).tolist()[:2]).replace("]", ", ...") 1604 tmea = str(np.mean(obj, axis=1).tolist()[:2]).replace("]", ", ...") 1605 vedo.printc(f"\tmin : {tmin}", c=cf) 1606 vedo.printc(f"\tmax : {tmax}", c=cf) 1607 vedo.printc(f"\tmean: {tmea}", c=cf) 1608 1609 elif isinstance(obj, vedo.Points): 1610 _printvtkactor(obj) 1611 1612 elif isinstance(obj, vedo.Assembly): 1613 vedo.printc("Assembly".ljust(75), c="g", bold=True, invert=True) 1614 1615 pos = obj.GetPosition() 1616 bnds = obj.GetBounds() 1617 vedo.printc("position".ljust(14) + ": ", c="g", bold=True, end="") 1618 vedo.printc(pos, c="g", bold=False) 1619 1620 vedo.printc("bounds".ljust(14) + ": ", c="g", bold=True, end="") 1621 bx1, bx2 = precision(bnds[0], 3), precision(bnds[1], 3) 1622 vedo.printc("x=(" + bx1 + ", " + bx2 + ")", c="g", bold=False, end="") 1623 by1, by2 = precision(bnds[2], 3), precision(bnds[3], 3) 1624 vedo.printc(" y=(" + by1 + ", " + by2 + ")", c="g", bold=False, end="") 1625 bz1, bz2 = precision(bnds[4], 3), precision(bnds[5], 3) 1626 vedo.printc(" z=(" + bz1 + ", " + bz2 + ")", c="g", bold=False) 1627 1628 cl = vtk.vtkPropCollection() 1629 obj.GetActors(cl) 1630 cl.InitTraversal() 1631 for _ in range(obj.GetNumberOfPaths()): 1632 act = vtk.vtkActor.SafeDownCast(cl.GetNextProp()) 1633 if isinstance(act, vtk.vtkActor): 1634 _printvtkactor(act) 1635 1636 elif isinstance(obj, vedo.TetMesh): 1637 cf = "m" 1638 vedo.printc("TetMesh".ljust(70), c=cf, bold=True, invert=True) 1639 pos = obj.GetPosition() 1640 bnds = obj.GetBounds() 1641 ug = obj.inputdata() 1642 vedo.printc("nr. of tetras".ljust(14) + ": ", c=cf, bold=True, end="") 1643 vedo.printc(ug.GetNumberOfCells(), c=cf, bold=False) 1644 vedo.printc("position".ljust(14) + ": ", c=cf, bold=True, end="") 1645 vedo.printc(pos, c=cf, bold=False) 1646 vedo.printc("bounds".ljust(14) + ": ", c=cf, bold=True, end="") 1647 bx1, bx2 = precision(bnds[0], 3), precision(bnds[1], 3) 1648 vedo.printc("x=(" + bx1 + ", " + bx2 + ")", c=cf, bold=False, end="") 1649 by1, by2 = precision(bnds[2], 3), precision(bnds[3], 3) 1650 vedo.printc(" y=(" + by1 + ", " + by2 + ")", c=cf, bold=False, end="") 1651 bz1, bz2 = precision(bnds[4], 3), precision(bnds[5], 3) 1652 vedo.printc(" z=(" + bz1 + ", " + bz2 + ")", c=cf, bold=False) 1653 _print_data(ug, obj._mapper, cf) 1654 1655 elif isinstance(obj, (vedo.volume.Volume, vedo.volume.VolumeSlice)): 1656 vedo.printc("Volume".ljust(70), c="b", bold=True, invert=True) 1657 1658 img = obj.GetMapper().GetInput() 1659 vedo.printc("origin".ljust(14) + ": ", c="b", bold=True, end="") 1660 vedo.printc(precision(obj.origin(),6), c="b", bold=False) 1661 1662 vedo.printc("center".ljust(14) + ": ", c="b", bold=True, end="") 1663 vedo.printc(precision(obj.center(),6), c="b", bold=False) 1664 1665 vedo.printc("dimensions".ljust(14) + ": ", c="b", bold=True, end="") 1666 vedo.printc(img.GetDimensions(), c="b", bold=False) 1667 vedo.printc("spacing".ljust(14) + ": ", c="b", bold=True, end="") 1668 vedo.printc(precision(img.GetSpacing(),6), c="b", bold=False) 1669 # vedo.printc("data dimension".ljust(14) + ": ", c="b", bold=True, end="") 1670 # vedo.printc(img.GetDataDimension(), c="b", bold=False) 1671 1672 vedo.printc("memory size".ljust(14) + ": ", c="b", bold=True, end="") 1673 vedo.printc(int(img.GetActualMemorySize() / 1024), "MB", c="b", bold=False) 1674 1675 vedo.printc("scalar #bytes".ljust(14) + ": ", c="b", bold=True, end="") 1676 vedo.printc(img.GetScalarSize(), c="b", bold=False) 1677 1678 bnds = obj.GetBounds() 1679 vedo.printc("bounds".ljust(14) + ": ", c="b", bold=True, end="") 1680 bx1, bx2 = precision(bnds[0], 4), precision(bnds[1], 4) 1681 vedo.printc("x=(" + bx1 + ", " + bx2 + ")", c="b", bold=False, end="") 1682 by1, by2 = precision<