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

Keep track of the operations which led to a final object.

OperationNode( operation, parents=(), comment='', shape='none', c='#e9c46a', style='filled')
103    def __init__(
104        self, operation, parents=(), comment="", shape="none", c="#e9c46a", style="filled"
105    ):
106        """
107        Keep track of the operations which led to a final object.
108        This allows to show the `pipeline` tree for any `vedo` object with e.g.:
109
110        ```python
111        from vedo import *
112        sp = Sphere()
113        sp.clean().subdivide()
114        sp.pipeline.show()
115        ```
116
117        Arguments:
118            operation : (str, class)
119                descriptor label, if a class is passed then grab its name
120            parents : (list)
121                list of the parent classes the object comes from
122            comment : (str)
123                a second-line text description
124            shape : (str)
125                shape of the frame, check out [this link.](https://graphviz.org/doc/info/shapes.html)
126            c : (hex)
127                hex color
128            style : (str)
129                comma-separated list of styles
130
131        Example:
132            ```python
133            from vedo.utils import OperationNode
134
135            op_node1 = OperationNode("Operation1", c="lightblue")
136            op_node2 = OperationNode("Operation2")
137            op_node3 = OperationNode("Operation3", shape='diamond')
138            op_node4 = OperationNode("Operation4")
139            op_node5 = OperationNode("Operation5")
140            op_node6 = OperationNode("Result", c="lightgreen")
141
142            op_node3.add_parent(op_node1)
143            op_node4.add_parent(op_node1)
144            op_node3.add_parent(op_node2)
145            op_node5.add_parent(op_node2)
146            op_node6.add_parent(op_node3)
147            op_node6.add_parent(op_node5)
148            op_node6.add_parent(op_node1)
149
150            op_node6.show(orientation="TB")
151            ```
152            ![](https://vedo.embl.es/images/feats/operation_node.png)
153        """
154        if not vedo.settings.enable_pipeline:
155            return
156
157        if isinstance(operation, str):
158            self.operation = operation
159        else:
160            self.operation = operation.__class__.__name__
161        self.operation_plain = str(self.operation)
162
163        pp = []  # filter out invalid stuff
164        for p in parents:
165            if hasattr(p, "pipeline"):
166                pp.append(p.pipeline)
167        self.parents = pp
168
169        if comment:
170            self.operation = f"<{self.operation}<BR/><SUB><I>{comment}</I></SUB>>"
171
172        self.dot = None
173        self.time = time.time()
174        self.shape = shape
175        self.style = style
176        self.color = c
177        self.counts = 0

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")

def add_parent(self, parent):
179    def add_parent(self, parent):
180        self.parents.append(parent)
def show(self, orientation='LR', popup=True):
219    def show(self, orientation="LR", popup=True):
220        """Show the graphviz output for the pipeline of this object"""
221        if not vedo.settings.enable_pipeline:
222            return
223
224        try:
225            from graphviz import Digraph
226        except ImportError:
227            vedo.logger.error("please install graphviz with command\n pip install graphviz")
228            return
229
230        # visualize the entire tree
231        dot = Digraph(
232            node_attr={"fontcolor": "#201010", "fontname": "Helvetica", "fontsize": "12"},
233            edge_attr={"fontname": "Helvetica", "fontsize": "6", "arrowsize": "0.4"},
234        )
235        dot.attr(rankdir=orientation)
236
237        self.counts = 0
238        self._build_tree(dot)
239        self.dot = dot
240        dot.render(".vedo_pipeline_graphviz", view=popup)

Show the graphviz output for the pipeline of this object

class ProgressBar:
244class ProgressBar:
245    """
246    Class to print a progress bar.
247    """
248
249    def __init__(
250        self,
251        start,
252        stop,
253        step=1,
254        c=None,
255        bold=True,
256        italic=False,
257        title="",
258        eta=True,
259        delay=0.0,
260        width=25,
261        char="\U00002501",
262        char_back="\U00002500",
263    ):
264        """
265        Class to print a progress bar with optional text message.
266
267        Check out also function `progressbar()`.
268
269        Example:
270            ```python
271            import time
272            pb = ProgressBar(0,400, c='red')
273            for i in pb.range():
274                time.sleep(0.1)
275                pb.print('some message')
276            ```
277            ![](https://user-images.githubusercontent.com/32848391/51858823-ed1f4880-2335-11e9-8788-2d102ace2578.png)
278        """
279        self.char = char
280        self.char_back = char_back
281
282        self.title = title + " "
283        if title:
284            self.title = " " + self.title
285
286        self.start = start
287        self.stop = stop
288        self.step = step
289
290        self.color = c
291        self.bold = bold
292        self.italic = italic
293        self.width = width
294        self.pbar = ""
295        self.percent = 0.0
296        self.percent_int = 0
297        self.eta = eta
298        self.delay = delay
299
300        self.t0 = time.time()
301        self._remaining = 1e10
302
303        self._update(0)
304
305        self._counts = 0
306        self._oldbar = ""
307        self._lentxt = 0
308        self._range = np.arange(start, stop, step)
309
310    def print(self, txt="", c=None):
311        """Print the progress bar with an optional message."""
312        if not c:
313            c = self.color
314
315        self._update(self._counts + self.step)
316
317        if self.delay:
318            if time.time() - self.t0 < self.delay:
319                return
320
321        if self.pbar != self._oldbar:
322            self._oldbar = self.pbar
323
324            if self.eta and self._counts > 1:
325
326                tdenom = time.time() - self.t0
327                if tdenom:
328                    vel = self._counts / tdenom
329                    self._remaining = (self.stop - self._counts) / vel
330                else:
331                    vel = 1
332                    self._remaining = 0.0
333
334                if self._remaining > 60:
335                    mins = int(self._remaining / 60)
336                    secs = self._remaining - 60 * mins
337                    mins = f"{mins}m"
338                    secs = f"{int(secs + 0.5)}s "
339                else:
340                    mins = ""
341                    secs = f"{int(self._remaining + 0.5)}s "
342
343                vel = round(vel, 1)
344                eta = f"eta: {mins}{secs}({vel} it/s) "
345                if self._remaining < 0.5:
346                    dt = time.time() - self.t0
347                    if dt > 60:
348                        mins = int(dt / 60)
349                        secs = dt - 60 * mins
350                        mins = f"{mins}m"
351                        secs = f"{int(secs + 0.5)}s "
352                    else:
353                        mins = ""
354                        secs = f"{int(dt + 0.5)}s "
355                    eta = f"elapsed: {mins}{secs}({vel} it/s)        "
356                    txt = ""
357            else:
358                eta = ""
359
360            eraser = " " * self._lentxt + "\b" * self._lentxt
361
362            s = f"{self.pbar} {eraser}{eta}{txt}\r"
363            vedo.printc(s, c=c, bold=self.bold, italic=self.italic, end="")
364            if self.percent > 99.999:
365                print("")
366
367            self._lentxt = len(txt)
368
369    def range(self):
370        """Return the range iterator."""
371        return self._range
372
373    def _update(self, counts):
374        if counts < self.start:
375            counts = self.start
376        elif counts > self.stop:
377            counts = self.stop
378        self._counts = counts
379
380        self.percent = (self._counts - self.start) * 100.0
381
382        delta = self.stop - self.start
383        if delta:
384            self.percent /= delta
385        else:
386            self.percent = 0.0
387
388        self.percent_int = int(round(self.percent))
389        af = self.width - 2
390        nh = int(round(self.percent_int / 100 * af))
391        pbar_background = "\x1b[2m" + self.char_back * (af - nh)
392        self.pbar = f"{self.title}{self.char * (nh-1)}{pbar_background}"
393        if self.percent < 100.0:
394            ps = f" {self.percent_int}%"
395        else:
396            ps = ""
397        self.pbar += ps

Class to print a progress bar.

ProgressBar( start, stop, step=1, c=None, bold=True, italic=False, title='', eta=True, delay=0.0, width=25, char='━', char_back='─')
249    def __init__(
250        self,
251        start,
252        stop,
253        step=1,
254        c=None,
255        bold=True,
256        italic=False,
257        title="",
258        eta=True,
259        delay=0.0,
260        width=25,
261        char="\U00002501",
262        char_back="\U00002500",
263    ):
264        """
265        Class to print a progress bar with optional text message.
266
267        Check out also function `progressbar()`.
268
269        Example:
270            ```python
271            import time
272            pb = ProgressBar(0,400, c='red')
273            for i in pb.range():
274                time.sleep(0.1)
275                pb.print('some message')
276            ```
277            ![](https://user-images.githubusercontent.com/32848391/51858823-ed1f4880-2335-11e9-8788-2d102ace2578.png)
278        """
279        self.char = char
280        self.char_back = char_back
281
282        self.title = title + " "
283        if title:
284            self.title = " " + self.title
285
286        self.start = start
287        self.stop = stop
288        self.step = step
289
290        self.color = c
291        self.bold = bold
292        self.italic = italic
293        self.width = width
294        self.pbar = ""
295        self.percent = 0.0
296        self.percent_int = 0
297        self.eta = eta
298        self.delay = delay
299
300        self.t0 = time.time()
301        self._remaining = 1e10
302
303        self._update(0)
304
305        self._counts = 0
306        self._oldbar = ""
307        self._lentxt = 0
308        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')

def print(self, txt='', c=None):
310    def print(self, txt="", c=None):
311        """Print the progress bar with an optional message."""
312        if not c:
313            c = self.color
314
315        self._update(self._counts + self.step)
316
317        if self.delay:
318            if time.time() - self.t0 < self.delay:
319                return
320
321        if self.pbar != self._oldbar:
322            self._oldbar = self.pbar
323
324            if self.eta and self._counts > 1:
325
326                tdenom = time.time() - self.t0
327                if tdenom:
328                    vel = self._counts / tdenom
329                    self._remaining = (self.stop - self._counts) / vel
330                else:
331                    vel = 1
332                    self._remaining = 0.0
333
334                if self._remaining > 60:
335                    mins = int(self._remaining / 60)
336                    secs = self._remaining - 60 * mins
337                    mins = f"{mins}m"
338                    secs = f"{int(secs + 0.5)}s "
339                else:
340                    mins = ""
341                    secs = f"{int(self._remaining + 0.5)}s "
342
343                vel = round(vel, 1)
344                eta = f"eta: {mins}{secs}({vel} it/s) "
345                if self._remaining < 0.5:
346                    dt = time.time() - self.t0
347                    if dt > 60:
348                        mins = int(dt / 60)
349                        secs = dt - 60 * mins
350                        mins = f"{mins}m"
351                        secs = f"{int(secs + 0.5)}s "
352                    else:
353                        mins = ""
354                        secs = f"{int(dt + 0.5)}s "
355                    eta = f"elapsed: {mins}{secs}({vel} it/s)        "
356                    txt = ""
357            else:
358                eta = ""
359
360            eraser = " " * self._lentxt + "\b" * self._lentxt
361
362            s = f"{self.pbar} {eraser}{eta}{txt}\r"
363            vedo.printc(s, c=c, bold=self.bold, italic=self.italic, end="")
364            if self.percent > 99.999:
365                print("")
366
367            self._lentxt = len(txt)

Print the progress bar with an optional message.

def range(self):
369    def range(self):
370        """Return the range iterator."""
371        return self._range

Return the range iterator.

def progressbar( iterable, c=None, bold=True, italic=False, title='', eta=True, width=25, delay=0.5):
401def progressbar(iterable, c=None, bold=True, italic=False, title="", eta=True, width=25, delay=0.5):
402    """
403    Function to print a progress bar with optional text message.
404
405    Use delay to set a minimum time before printing anything.
406
407    Example:
408        ```python
409        import time
410        for i in progressbar(range(100), c='red'):
411            time.sleep(0.1)
412        ```
413        ![](https://user-images.githubusercontent.com/32848391/51858823-ed1f4880-2335-11e9-8788-2d102ace2578.png)
414    """
415    try:
416        if is_number(iterable):
417            total = int(iterable)
418            iterable = range(total)
419        else:
420            total = len(iterable)
421    except TypeError:
422        iterable = list(iterable)
423        total = len(iterable)
424
425    pb = ProgressBar(
426        0, total, c=c, bold=bold, italic=italic, title=title, eta=eta, delay=delay, width=width
427    )
428    for item in iterable:
429        pb.print()
430        yield item

Function to print a progress bar with optional text message.

Use delay to set a minimum time before printing anything.

Example:
import time
for i in progressbar(range(100), c='red'):
    time.sleep(0.1)

def geometry(obj, extent=None):
515def geometry(obj, extent=None):
516    """
517    Apply the `vtkGeometryFilter` to the input object.
518    This is a general-purpose filter to extract geometry (and associated data)
519    from any type of dataset.
520    This filter also may be used to convert any type of data to polygonal type.
521    The conversion process may be less than satisfactory for some 3D datasets.
522    For example, this filter will extract the outer surface of a volume
523    or structured grid dataset.
524
525    Returns a `vedo.Mesh` object.
526
527    Set `extent` as the `[xmin,xmax, ymin,ymax, zmin,zmax]` bounding box to clip data.
528    """
529    gf = vtk.vtkGeometryFilter()
530    gf.SetInputData(obj)
531    if extent is not None:
532        gf.SetExtent(extent)
533    gf.Update()
534    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.

def extract_cells_by_type(obj, types=()):
537def extract_cells_by_type(obj, types=()):
538    """
539    Extract cells of a specified type from a vtk dataset.
540
541    Given an input `vtkDataSet` and a list of cell types, produce an output
542    containing only cells of the specified type(s).
543
544    Find [here](https://vtk.org/doc/nightly/html/vtkCellType_8h_source.html)
545    the list of possible cell types.
546
547    Return:
548        a `vtkDataSet` object which can be wrapped.
549    """
550    ef = vtk.vtkExtractCellsByType()
551    try:
552        ef.SetInputData(obj.inputdata())
553    except:
554        ef.SetInputData(obj)
555
556    for ct in types:
557        ef.AddCellType(ct)
558    ef.Update()
559    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.

def is_sequence(arg):
739def is_sequence(arg):
740    """Check if the input is iterable."""
741    if hasattr(arg, "strip"):
742        return False
743    if hasattr(arg, "__getslice__"):
744        return True
745    if hasattr(arg, "__iter__"):
746        return True
747    return False

Check if the input is iterable.

def lin_interpolate(x, rangeX, rangeY):
1048def lin_interpolate(x, rangeX, rangeY):
1049    """
1050    Interpolate linearly the variable `x` in `rangeX` onto the new `rangeY`.
1051    If `x` is a 3D vector the linear weight is the distance to the two 3D `rangeX` vectors.
1052
1053    E.g. if `x` runs in `rangeX=[x0,x1]` and I want it to run in `rangeY=[y0,y1]` then
1054
1055    `y = lin_interpolate(x, rangeX, rangeY)` will interpolate `x` onto `rangeY`.
1056
1057    Examples:
1058        - [lin_interpolate.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/lin_interpolate.py)
1059
1060            ![](https://vedo.embl.es/images/basic/linInterpolate.png)
1061    """
1062    if is_sequence(x):
1063        x = np.asarray(x)
1064        x0, x1 = np.asarray(rangeX)
1065        y0, y1 = np.asarray(rangeY)
1066        dx = x1 - x0
1067        dxn = np.linalg.norm(dx)
1068        if not dxn:
1069            return y0
1070        s = np.linalg.norm(x - x0) / dxn
1071        t = np.linalg.norm(x - x1) / dxn
1072        st = s + t
1073        out = y0 * (t / st) + y1 * (s / st)
1074
1075    else:  # faster
1076
1077        x0 = rangeX[0]
1078        dx = rangeX[1] - x0
1079        if not dx:
1080            return rangeY[0]
1081        s = (x - x0) / dx
1082        out = rangeY[0] * (1 - s) + rangeY[1] * s
1083    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:
def vector(x, y=None, z=0.0, dtype=<class 'numpy.float64'>):
1160def vector(x, y=None, z=0.0, dtype=np.float64):
1161    """
1162    Return a 3D numpy array representing a vector.
1163
1164    If `y` is `None`, assume input is already in the form `[x,y,z]`.
1165    """
1166    if y is None:  # assume x is already [x,y,z]
1167        return np.asarray(x, dtype=dtype)
1168    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].

def mag(v):
1179def mag(v):
1180    """Get the magnitude of a vector or array of vectors."""
1181    v = np.asarray(v)
1182    if v.ndim == 1:
1183        return np.linalg.norm(v)
1184    return np.linalg.norm(v, axis=1)

Get the magnitude of a vector or array of vectors.

def mag2(v):
1187def mag2(v):
1188    """Get the squared magnitude of a vector or array of vectors."""
1189    v = np.asarray(v)
1190    if v.ndim == 1:
1191        return np.square(v).sum()
1192    return np.square(v).sum(axis=1)

Get the squared magnitude of a vector or array of vectors.

def versor(x, y=None, z=0.0, dtype=<class 'numpy.float64'>):
1171def versor(x, y=None, z=0.0, dtype=np.float64):
1172    """Return the unit vector. Input can be a list of vectors."""
1173    v = vector(x, y, z, dtype)
1174    if isinstance(v[0], np.ndarray):
1175        return np.divide(v, mag(v)[:, None])
1176    return v / mag(v)

Return the unit vector. Input can be a list of vectors.

def precision(x, p, vrange=None, delimiter='e'):
1261def precision(x, p, vrange=None, delimiter="e"):
1262    """
1263    Returns a string representation of `x` formatted to precision `p`.
1264
1265    Set `vrange` to the range in which x exists (to snap x to '0' if below precision).
1266    """
1267    # Based on the webkit javascript implementation
1268    # `from here <https://code.google.com/p/webkit-mirror/source/browse/JavaScriptCore/kjs/number_object.cpp>`_,
1269    # and implemented by `randlet <https://github.com/randlet/to-precision>`_.
1270    # Modified for vedo by M.Musy 2020
1271
1272    if isinstance(x, str):  # do nothing
1273        return x
1274
1275    if is_sequence(x):
1276        out = "("
1277        nn = len(x) - 1
1278        for i, ix in enumerate(x):
1279
1280            try:
1281                if np.isnan(ix):
1282                    return "NaN"
1283            except:
1284                # cannot handle list of list
1285                continue
1286
1287            out += precision(ix, p)
1288            if i < nn:
1289                out += ", "
1290        return out + ")"  ############ <--
1291
1292    if np.isnan(x):
1293        return "NaN"
1294
1295    x = float(x)
1296
1297    if x == 0.0 or (vrange is not None and abs(x) < vrange / pow(10, p)):
1298        return "0"
1299
1300    out = []
1301    if x < 0:
1302        out.append("-")
1303        x = -x
1304
1305    e = int(math.log10(x))
1306    tens = math.pow(10, e - p + 1)
1307    n = math.floor(x / tens)
1308
1309    if n < math.pow(10, p - 1):
1310        e = e - 1
1311        tens = math.pow(10, e - p + 1)
1312        n = math.floor(x / tens)
1313
1314    if abs((n + 1.0) * tens - x) <= abs(n * tens - x):
1315        n = n + 1
1316
1317    if n >= math.pow(10, p):
1318        n = n / 10.0
1319        e = e + 1
1320
1321    m = "%.*g" % (p, n)
1322    if e < -2 or e >= p:
1323        out.append(m[0])
1324        if p > 1:
1325            out.append(".")
1326            out.extend(m[1:p])
1327        out.append(delimiter)
1328        if e > 0:
1329            out.append("+")
1330        out.append(str(e))
1331    elif e == (p - 1):
1332        out.append(m)
1333    elif e >= 0:
1334        out.append(m[: e + 1])
1335        if e + 1 < len(m):
1336            out.append(".")
1337            out.extend(m[e + 1 :])
1338    else:
1339        out.append("0.")
1340        out.extend(["0"] * -(e + 1))
1341        out.append(m)
1342    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).

def round_to_digit(x, p):
1214def round_to_digit(x, p):
1215    """Round a real number to the specified number of significant digits."""
1216    if not x:
1217        return 0
1218    r = np.round(x, p - int(np.floor(np.log10(abs(x)))) - 1)
1219    if int(r) == r:
1220        return int(r)
1221    return r

Round a real number to the specified number of significant digits.

def point_in_triangle(p, p1, p2, p3):
818def point_in_triangle(p, p1, p2, p3):
819    """
820    Return True if a point is inside (or above/below)
821    a triangle defined by 3 points in space.
822    """
823    p1 = np.array(p1)
824    u = p2 - p1
825    v = p3 - p1
826    n = np.cross(u, v)
827    w = p - p1
828    ln = np.dot(n, n)
829    if not ln:
830        return None  # degenerate triangle
831    gamma = (np.dot(np.cross(u, w), n)) / ln
832    if 0 < gamma < 1:
833        beta = (np.dot(np.cross(w, v), n)) / ln
834        if 0 < beta < 1:
835            alpha = 1 - gamma - beta
836            if 0 < alpha < 1:
837                return True
838    return False

Return True if a point is inside (or above/below) a triangle defined by 3 points in space.

def point_line_distance(p, p1, p2):
1002def point_line_distance(p, p1, p2):
1003    """
1004    Compute the distance of a point to a line (not the segment)
1005    defined by `p1` and `p2`.
1006    """
1007    return np.sqrt(vtk.vtkLine.DistanceToLine(p, p1, p2))

Compute the distance of a point to a line (not the segment) defined by p1 and p2.

def closest(point, points, n=1, return_ids=False, use_tree=False):
1010def closest(point, points, n=1, return_ids=False, use_tree=False):
1011    """
1012    Returns the distances and the closest point(s) to the given set of points.
1013    Needs `scipy.spatial` library.
1014
1015    Arguments:
1016        n : (int)
1017            the nr of closest points to return
1018        return_ids : (bool)
1019            return the ids instead of the points coordinates
1020        use_tree : (bool)
1021            build a `scipy.spatial.KDTree`.
1022            An already existing one can be passed to avoid rebuilding.
1023    """
1024    from scipy.spatial import distance, KDTree
1025
1026    points = np.asarray(points)
1027    if n == 1:
1028        dists = distance.cdist([point], points)
1029        closest_idx = np.argmin(dists)
1030    else:
1031        if use_tree:
1032            if isinstance(use_tree, KDTree):  # reuse
1033                tree = use_tree
1034            else:
1035                tree = KDTree(points)
1036            dists, closest_idx = tree.query([point], k=n)
1037            closest_idx = closest_idx[0]
1038        else:
1039            dists = distance.cdist([point], points)
1040            closest_idx = np.argsort(dists)[0][:n]
1041    if return_ids:
1042        return dists, closest_idx
1043    else:
1044        return dists, points[closest_idx]

Returns the distances and the closest point(s) to the given set of points. Needs scipy.spatial library.

Arguments:
  • n : (int) the nr of closest points to return
  • return_ids : (bool) return the ids instead of the points coordinates
  • use_tree : (bool) build a scipy.spatial.KDTree. An already existing one can be passed to avoid rebuilding.
def grep(filename, tag, column=None, first_occurrence_only=False):
1413def grep(filename, tag, column=None, first_occurrence_only=False):
1414    """Greps the line in a file that starts with a specific `tag` string inside the file."""
1415    import re
1416
1417    with open(filename, "r", encoding="UTF-8") as afile:
1418        content = []
1419        for line in afile:
1420            if re.search(tag, line):
1421                c = line.split()
1422                c[-1] = c[-1].replace("\n", "")
1423                if column is not None:
1424                    c = c[column]
1425                content.append(c)
1426                if first_occurrence_only:
1427                    break
1428    return content

Greps the line in a file that starts with a specific tag string inside the file.

def make_bands(inputlist, n):
2021def make_bands(inputlist, n):
2022    """
2023    Group values of a list into bands of equal value, where
2024    `n` is the number of bands, a positive integer > 2.
2025
2026    Returns a binned list of the same length as the input.
2027    """
2028    if n < 2:
2029        return inputlist
2030    vmin = np.min(inputlist)
2031    vmax = np.max(inputlist)
2032    bb = np.linspace(vmin, vmax, n, endpoint=0)
2033    dr = bb[1] - bb[0]
2034    bb += dr / 2
2035    tol = dr / 2 * 1.001
2036    newlist = []
2037    for s in inputlist:
2038        for b in bb:
2039            if abs(s - b) < tol:
2040                newlist.append(b)
2041                break
2042    return np.array(newlist)

Group values of a list into bands of equal value, where n is the number of bands, a positive integer > 2.

Returns a binned list of the same length as the input.

def pack_spheres(bounds, radius):
1224def pack_spheres(bounds, radius):
1225    """
1226    Packing spheres into a bounding box.
1227    Returns a numpy array of sphere centers.
1228    """
1229    h = 0.8164965 / 2
1230    d = 0.8660254
1231    a = 0.288675135
1232
1233    if is_sequence(bounds):
1234        x0, x1, y0, y1, z0, z1 = bounds
1235    else:
1236        x0, x1, y0, y1, z0, z1 = bounds.bounds()
1237
1238    x = np.arange(x0, x1, radius)
1239    nul = np.zeros_like(x)
1240    nz = int((z1 - z0) / radius / h / 2 + 1.5)
1241    ny = int((y1 - y0) / radius / d + 1.5)
1242
1243    pts = []
1244    for iz in range(nz):
1245        z = z0 + nul + iz * h * radius
1246        dx, dy, dz = [radius * 0.5, radius * a, iz * h * radius]
1247        for iy in range(ny):
1248            y = y0 + nul + iy * d * radius
1249            if iy % 2:
1250                xs = x
1251            else:
1252                xs = x + radius * 0.5
1253            if iz % 2:
1254                p = np.c_[xs, y, z] + [dx, dy, dz]
1255            else:
1256                p = np.c_[xs, y, z] + [0, 0, dz]
1257            pts += p.tolist()
1258    return np.array(pts)

Packing spheres into a bounding box. Returns a numpy array of sphere centers.

def spher2cart(rho, theta, phi):
1371def spher2cart(rho, theta, phi):
1372    """3D Spherical to Cartesian coordinate conversion."""
1373    st = np.sin(theta)
1374    sp = np.sin(phi)
1375    ct = np.cos(theta)
1376    cp = np.cos(phi)
1377    rst = rho * st
1378    x = rst * cp
1379    y = rst * sp
1380    z = rho * ct
1381    return np.array([x, y, z])

3D Spherical to Cartesian coordinate conversion.

def cart2spher(x, y, z):
1362def cart2spher(x, y, z):
1363    """3D Cartesian to Spherical coordinate conversion."""
1364    hxy = np.hypot(x, y)
1365    rho = np.hypot(hxy, z)
1366    theta = np.arctan2(hxy, z)
1367    phi = np.arctan2(y, x)
1368    return np.array([rho, theta, phi])

3D Cartesian to Spherical coordinate conversion.

def cart2cyl(x, y, z):
1384def cart2cyl(x, y, z):
1385    """3D Cartesian to Cylindrical coordinate conversion."""
1386    rho = np.sqrt(x * x + y * y)
1387    theta = np.arctan2(y, x)
1388    return np.array([rho, theta, z])

3D Cartesian to Cylindrical coordinate conversion.

def cyl2cart(rho, theta, z):
1391def cyl2cart(rho, theta, z):
1392    """3D Cylindrical to Cartesian coordinate conversion."""
1393    x = rho * np.cos(theta)
1394    y = rho * np.sin(theta)
1395    return np.array([x, y, z])

3D Cylindrical to Cartesian coordinate conversion.

def cyl2spher(rho, theta, z):
1398def cyl2spher(rho, theta, z):
1399    """3D Cylindrical to Spherical coordinate conversion."""
1400    rhos = np.sqrt(rho * rho + z * z)
1401    phi = np.arctan2(rho, z)
1402    return np.array([rhos, phi, theta])

3D Cylindrical to Spherical coordinate conversion.

def spher2cyl(rho, theta, phi):
1405def spher2cyl(rho, theta, phi):
1406    """3D Spherical to Cylindrical coordinate conversion."""
1407    rhoc = rho * np.sin(theta)
1408    z = rho * np.cos(theta)
1409    return np.array([rhoc, phi, z])

3D Spherical to Cylindrical coordinate conversion.

def cart2pol(x, y):
1347def cart2pol(x, y):
1348    """2D Cartesian to Polar coordinates conversion."""
1349    theta = np.arctan2(y, x)
1350    rho = np.hypot(x, y)
1351    return np.array([rho, theta])

2D Cartesian to Polar coordinates conversion.

def pol2cart(rho, theta):
1354def pol2cart(rho, theta):
1355    """2D Polar to Cartesian coordinates conversion."""
1356    x = rho * np.cos(theta)
1357    y = rho * np.sin(theta)
1358    return np.array([x, y])

2D Polar to Cartesian coordinates conversion.

def humansort(alist):
785def humansort(alist):
786    """
787    Sort in place a given list the way humans expect.
788
789    E.g. `['file11', 'file1'] -> ['file1', 'file11']`
790
791    .. warning:: input list is modified in-place by this function.
792    """
793    import re
794
795    def alphanum_key(s):
796        # Turn a string into a list of string and number chunks.
797        # e.g. "z23a" -> ["z", 23, "a"]
798        def tryint(s):
799            if s.isdigit():
800                return int(s)
801            return s
802
803        return [tryint(c) for c in re.split("([0-9]+)", s)]
804
805    alist.sort(key=alphanum_key)
806    return alist  # NB: input list is modified

Sort in place a given list the way humans expect.

E.g. ['file11', 'file1'] -> ['file1', 'file11']

input list is modified in-place by this function.
def camera_from_quaternion(pos, quaternion, distance=10000, ngl_correct=True):
2048def camera_from_quaternion(pos, quaternion, distance=10000, ngl_correct=True):
2049    """
2050    Define a `vtkCamera` with a particular orientation.
2051
2052    Arguments:
2053        pos: (np.array, list, tuple)
2054            an iterator of length 3 containing the focus point of the camera
2055        quaternion: (np.array, list, tuple)
2056            a len(4) quaternion `(x,y,z,w)` describing the rotation of the camera
2057            such as returned by neuroglancer `x,y,z,w` all in `[0,1]` range
2058        distance: (float)
2059            the desired distance from pos to the camera (default = 10000 nm)
2060
2061    Returns:
2062        `vtk.vtkCamera`, a vtk camera setup according to these rules.
2063    """
2064    camera = vtk.vtkCamera()
2065    # define the quaternion in vtk, note the swapped order
2066    # w,x,y,z instead of x,y,z,w
2067    quat_vtk = vtk.vtkQuaterniond(quaternion[3], quaternion[0], quaternion[1], quaternion[2])
2068    # use this to define a rotation matrix in x,y,z
2069    # right handed units
2070    M = np.zeros((3, 3), dtype=np.float32)
2071    quat_vtk.ToMatrix3x3(M)
2072    # the default camera orientation is y up
2073    up = [0, 1, 0]
2074    # calculate default camera position is backed off in positive z
2075    pos = [0, 0, distance]
2076
2077    # set the camera rototation by applying the rotation matrix
2078    camera.SetViewUp(*np.dot(M, up))
2079    # set the camera position by applying the rotation matrix
2080    camera.SetPosition(*np.dot(M, pos))
2081    if ngl_correct:
2082        # neuroglancer has positive y going down
2083        # so apply these azimuth and roll corrections
2084        # to fix orientatins
2085        camera.Azimuth(-180)
2086        camera.Roll(180)
2087
2088    # shift the camera posiiton and focal position
2089    # to be centered on the desired location
2090    p = camera.GetPosition()
2091    p_new = np.array(p) + pos
2092    camera.SetPosition(*p_new)
2093    camera.SetFocalPoint(*pos)
2094    return camera

Define a vtkCamera with a particular orientation.

Arguments:
  • pos: (np.array, list, tuple) an iterator of length 3 containing the focus point of the camera
  • quaternion: (np.array, list, tuple) a len(4) quaternion (x,y,z,w) describing the rotation of the camera such as returned by neuroglancer x,y,z,w all in [0,1] range
  • distance: (float) the desired distance from pos to the camera (default = 10000 nm)
Returns:

vtk.vtkCamera, a vtk camera setup according to these rules.

def camera_from_neuroglancer(state, zoom=300):
2097def camera_from_neuroglancer(state, zoom=300):
2098    """
2099    Define a `vtkCamera` from a neuroglancer state dictionary.
2100
2101    Arguments:
2102        state: (dict)
2103            an neuroglancer state dictionary.
2104        zoom: (float)
2105            how much to multiply zoom by to get camera backoff distance
2106            default = 300 > ngl_zoom = 1 > 300 nm backoff distance.
2107
2108    Returns:
2109        `vtk.vtkCamera`, a vtk camera setup that matches this state.
2110    """
2111    orient = state.get("perspectiveOrientation", [0.0, 0.0, 0.0, 1.0])
2112    pzoom = state.get("perspectiveZoom", 10.0)
2113    position = state["navigation"]["pose"]["position"]
2114    pos_nm = np.array(position["voxelCoordinates"]) * position["voxelSize"]
2115    return camera_from_quaternion(pos_nm, orient, pzoom * zoom, ngl_correct=True)

Define a vtkCamera from a neuroglancer state dictionary.

Arguments:
  • state: (dict) an neuroglancer state dictionary.
  • zoom: (float) how much to multiply zoom by to get camera backoff distance default = 300 > ngl_zoom = 1 > 300 nm backoff distance.
Returns:

vtk.vtkCamera, a vtk camera setup that matches this state.

def oriented_camera( center=(0, 0, 0), up_vector=(0, 1, 0), backoff_vector=(0, 0, 1), backoff=1.0):
2118def oriented_camera(center=(0, 0, 0), up_vector=(0, 1, 0), backoff_vector=(0, 0, 1), backoff=1.0):
2119    """
2120    Generate a `vtkCamera` pointed at a specific location,
2121    oriented with a given up direction, set to a backoff.
2122    """
2123    vup = np.array(up_vector)
2124    vup = vup / np.linalg.norm(vup)
2125    pt_backoff = center - backoff * np.array(backoff_vector)
2126    camera = vtk.vtkCamera()
2127    camera.SetFocalPoint(center[0], center[1], center[2])
2128    camera.SetViewUp(vup[0], vup[1], vup[2])
2129    camera.SetPosition(pt_backoff[0], pt_backoff[1], pt_backoff[2])
2130    return camera

Generate a vtkCamera pointed at a specific location, oriented with a given up direction, set to a backoff.

def vedo2trimesh(mesh):
2380def vedo2trimesh(mesh):
2381    """
2382    Convert `vedo.mesh.Mesh` to `Trimesh.Mesh` object.
2383    """
2384    if is_sequence(mesh):
2385        tms = []
2386        for a in mesh:
2387            tms.append(vedo2trimesh(a))
2388        return tms
2389
2390    from trimesh import Trimesh
2391
2392    tris = mesh.faces()
2393    carr = mesh.celldata["CellIndividualColors"]
2394    ccols = carr
2395
2396    points = mesh.points()
2397    varr = mesh.pointdata["VertexColors"]
2398    vcols = varr
2399
2400    if len(tris) == 0:
2401        tris = None
2402
2403    return Trimesh(vertices=points, faces=tris, face_colors=ccols, vertex_colors=vcols)

Convert vedo.mesh.Mesh to Trimesh.Mesh object.

def trimesh2vedo(inputobj):
2406def trimesh2vedo(inputobj):
2407    """
2408    Convert a `Trimesh` object to `vedo.Mesh` or `vedo.Assembly` object.
2409    """
2410    if is_sequence(inputobj):
2411        vms = []
2412        for ob in inputobj:
2413            vms.append(trimesh2vedo(ob))
2414        return vms
2415
2416    inputobj_type = str(type(inputobj))
2417
2418    if "Trimesh" in inputobj_type or "primitives" in inputobj_type:
2419        faces = inputobj.faces
2420        poly = buildPolyData(inputobj.vertices, faces)
2421        tact = vedo.Mesh(poly)
2422        if inputobj.visual.kind == "face":
2423            trim_c = inputobj.visual.face_colors
2424        else:
2425            trim_c = inputobj.visual.vertex_colors
2426
2427        if is_sequence(trim_c):
2428            if is_sequence(trim_c[0]):
2429                same_color = len(np.unique(trim_c, axis=0)) < 2  # all vtxs have same color
2430
2431                if same_color:
2432                    tact.c(trim_c[0, [0, 1, 2]]).alpha(trim_c[0, 3])
2433                else:
2434                    if inputobj.visual.kind == "face":
2435                        tact.cell_individual_colors(trim_c)
2436        return tact
2437
2438    if "PointCloud" in inputobj_type:
2439
2440        trim_cc, trim_al = "black", 1
2441        if hasattr(inputobj, "vertices_color"):
2442            trim_c = inputobj.vertices_color
2443            if len(trim_c) > 0:
2444                trim_cc = trim_c[:, [0, 1, 2]] / 255
2445                trim_al = trim_c[:, 3] / 255
2446                trim_al = np.sum(trim_al) / len(trim_al)  # just the average
2447        return vedo.shapes.Points(inputobj.vertices, r=8, c=trim_cc, alpha=trim_al)
2448
2449    if "path" in inputobj_type:
2450
2451        lines = []
2452        for e in inputobj.entities:
2453            # print('trimesh entity', e.to_dict())
2454            l = vedo.shapes.Line(inputobj.vertices[e.points], c="k", lw=2)
2455            lines.append(l)
2456        return vedo.Assembly(lines)
2457
2458    return None

Convert a Trimesh object to vedo.Mesh or vedo.Assembly object.

def vedo2meshlab(vmesh):
2461def vedo2meshlab(vmesh):
2462    """Convert a `vedo.Mesh` to a Meshlab object."""
2463    try:
2464        import pymeshlab as mlab
2465    except RuntimeError:
2466        vedo.logger.error("Need pymeshlab to run:\npip install pymeshlab")
2467
2468    vertex_matrix = vmesh.points().astype(np.float64)
2469
2470    try:
2471        face_matrix = np.asarray(vmesh.faces(), dtype=np.float64)
2472    except:
2473        print("In vedo2meshlab, need to triangulate mesh first!")
2474        face_matrix = np.array(vmesh.clone().triangulate().faces(), dtype=np.float64)
2475
2476    v_normals_matrix = vmesh.normals(cells=False, recompute=False)
2477    if not v_normals_matrix.shape[0]:
2478        v_normals_matrix = np.empty((0, 3), dtype=np.float64)
2479
2480    f_normals_matrix = vmesh.normals(cells=True, recompute=False)
2481    if not f_normals_matrix.shape[0]:
2482        f_normals_matrix = np.empty((0, 3), dtype=np.float64)
2483
2484    v_color_matrix = vmesh.pointdata["RGBA"]
2485    if v_color_matrix is None:
2486        v_color_matrix = np.empty((0, 4), dtype=np.float64)
2487    else:
2488        v_color_matrix = v_color_matrix.astype(np.float64) / 255
2489        if v_color_matrix.shape[1] == 3:
2490            v_color_matrix = np.c_[
2491                v_color_matrix, np.ones(v_color_matrix.shape[0], dtype=np.float64)
2492            ]
2493
2494    f_color_matrix = vmesh.celldata["RGBA"]
2495    if f_color_matrix is None:
2496        f_color_matrix = np.empty((0, 4), dtype=np.float64)
2497    else:
2498        f_color_matrix = f_color_matrix.astype(np.float64) / 255
2499        if f_color_matrix.shape[1] == 3:
2500            f_color_matrix = np.c_[
2501                f_color_matrix, np.ones(f_color_matrix.shape[0], dtype=np.float64)
2502            ]
2503
2504    m = mlab.Mesh(
2505        vertex_matrix=vertex_matrix,
2506        face_matrix=face_matrix,
2507        v_normals_matrix=v_normals_matrix,
2508        f_normals_matrix=f_normals_matrix,
2509        v_color_matrix=v_color_matrix,
2510        f_color_matrix=f_color_matrix,
2511    )
2512
2513    for k in vmesh.pointdata.keys():
2514        data = vmesh.pointdata[k]
2515        if data is not None:
2516            if data.ndim == 1:  # scalar
2517                m.add_vertex_custom_scalar_attribute(data.astype(np.float64), k)
2518            elif data.ndim == 2:  # vectorial data
2519                m.add_vertex_custom_point_attribute(data.astype(np.float64), k)
2520
2521    for k in vmesh.celldata.keys():
2522        data = vmesh.celldata[k]
2523        if data is not None:
2524            if data.ndim == 1:  # scalar
2525                m.add_face_custom_scalar_attribute(data.astype(np.float64), k)
2526            elif data.ndim == 2:  # vectorial data
2527                m.add_face_custom_point_attribute(data.astype(np.float64), k)
2528
2529    m.update_bounding_box()
2530    return m

Convert a vedo.Mesh to a Meshlab object.

def meshlab2vedo(mmesh):
2533def meshlab2vedo(mmesh):
2534    """Convert a Meshlab object to `vedo.Mesh`."""
2535    inputtype = str(type(mmesh))
2536
2537    if "MeshSet" in inputtype:
2538        mmesh = mmesh.current_mesh()
2539
2540    mpoints, mcells = mmesh.vertex_matrix(), mmesh.face_matrix()
2541    if len(mcells) > 0:
2542        polydata = buildPolyData(mpoints, mcells)
2543    else:
2544        polydata = buildPolyData(mpoints, None)
2545
2546    if mmesh.has_vertex_scalar():
2547        parr = mmesh.vertex_scalar_array()
2548        parr_vtk = numpy_to_vtk(parr)
2549        parr_vtk.SetName("MeshLabScalars")
2550        x0, x1 = parr_vtk.GetRange()
2551        if x1 - x0:
2552            polydata.GetPointData().AddArray(parr_vtk)
2553            polydata.GetPointData().SetActiveScalars("MeshLabScalars")
2554
2555    if mmesh.has_face_scalar():
2556        carr = mmesh.face_scalar_array()
2557        carr_vtk = numpy_to_vtk(carr)
2558        carr_vtk.SetName("MeshLabScalars")
2559        x0, x1 = carr_vtk.GetRange()
2560        if x1 - x0:
2561            polydata.GetCellData().AddArray(carr_vtk)
2562            polydata.GetCellData().SetActiveScalars("MeshLabScalars")
2563
2564    pnorms = mmesh.vertex_normal_matrix()
2565    if len(pnorms) > 0:
2566        polydata.GetPointData().SetNormals(numpy2vtk(pnorms))
2567
2568    cnorms = mmesh.face_normal_matrix()
2569    if len(cnorms) > 0:
2570        polydata.GetCellData().SetNormals(numpy2vtk(cnorms))
2571    return polydata

Convert a Meshlab object to vedo.Mesh.

def vedo2open3d(vedo_mesh):
2582def vedo2open3d(vedo_mesh):
2583    """
2584    Return an `open3d.geometry.TriangleMesh` version of the current mesh.
2585    """
2586    try:
2587        import open3d as o3d
2588    except RuntimeError:
2589        vedo.logger.error("Need open3d to run:\npip install open3d")
2590
2591    # create from numpy arrays
2592    o3d_mesh = o3d.geometry.TriangleMesh(
2593        vertices=o3d.utility.Vector3dVector(vedo_mesh.points()),
2594        triangles=o3d.utility.Vector3iVector(vedo_mesh.faces()),
2595    )
2596    # TODO: need to add some if check here in case color and normals
2597    #  info are not existing
2598    # o3d_mesh.vertex_colors = o3d.utility.Vector3dVector(vedo_mesh.pointdata["RGB"]/255)
2599    # o3d_mesh.vertex_normals= o3d.utility.Vector3dVector(vedo_mesh.pointdata["Normals"])
2600    return o3d_mesh

Return an open3d.geometry.TriangleMesh version of the current mesh.

def open3d2vedo(o3d_mesh):
2574def open3d2vedo(o3d_mesh):
2575    """Convert `open3d.geometry.TriangleMesh` to a `vedo.Mesh`."""
2576    m = vedo.Mesh([np.array(o3d_mesh.vertices), np.array(o3d_mesh.triangles)])
2577    # TODO: could also check whether normals and color are present in
2578    # order to port with the above vertices/faces
2579    return m

Convert open3d.geometry.TriangleMesh to a vedo.Mesh.

def vtk2numpy(varr):
458def vtk2numpy(varr):
459    """Convert a `vtkDataArray`, `vtkIdList` or `vtTransform` into a numpy array."""
460    if isinstance(varr, vtk.vtkIdList):
461        return np.array([varr.GetId(i) for i in range(varr.GetNumberOfIds())])
462    elif isinstance(varr, vtk.vtkBitArray):
463        carr = vtk.vtkCharArray()
464        carr.DeepCopy(varr)
465        varr = carr
466    elif isinstance(varr, vtk.vtkHomogeneousTransform):
467        try:
468            varr = varr.GetMatrix()
469        except AttributeError:
470            pass
471        n = 4
472        M = [[varr.GetElement(i, j) for j in range(n)] for i in range(n)]
473        return np.array(M)
474
475    return vtk_to_numpy(varr)

Convert a vtkDataArray, vtkIdList or vtTransform into a numpy array.

def numpy2vtk(arr, dtype=None, deep=True, name=''):
434def numpy2vtk(arr, dtype=None, deep=True, name=""):
435    """
436    Convert a numpy array into a `vtkDataArray`.
437    Use `dtype='id'` for `vtkIdTypeArray` objects.
438    """
439    # https://github.com/Kitware/VTK/blob/master/Wrapping/Python/vtkmodules/util/numpy_support.py
440    if arr is None:
441        return None
442
443    arr = np.ascontiguousarray(arr)
444
445    if dtype == "id":
446        varr = numpy_to_vtkIdTypeArray(arr.astype(np.int64), deep=deep)
447    elif dtype:
448        varr = numpy_to_vtk(arr.astype(dtype), deep=deep)
449    else:
450        # let numpy_to_vtk() decide what is best type based on arr type
451        varr = numpy_to_vtk(arr, deep=deep)
452
453    if name:
454        varr.SetName(name)
455    return varr

Convert a numpy array into a vtkDataArray. Use dtype='id' for vtkIdTypeArray objects.

def get_uv(p, x, v):
1086def get_uv(p, x, v):
1087    """
1088    Obtain the texture uv-coords of a point p belonging to a face that has point
1089    coordinates (x0, x1, x2) with the corresponding uv-coordinates v=(v0, v1, v2).
1090    All p and x0,x1,x2 are 3D-vectors, while v are their 2D uv-coordinates.
1091
1092    Example:
1093        ```python
1094        from vedo import *
1095
1096        pic = Picture(dataurl+"coloured_cube_faces.jpg")
1097        cb = Mesh(dataurl+"coloured_cube.obj").lighting("off").texture(pic)
1098
1099        cbpts = cb.points()
1100        faces = cb.faces()
1101        uv = cb.pointdata["Material"]
1102
1103        pt = [-0.2, 0.75, 2]
1104        pr = cb.closest_point(pt)
1105
1106        idface = cb.closest_point(pt, return_cell_id=True)
1107        idpts = faces[idface]
1108        uv_face = uv[idpts]
1109
1110        uv_pr = utils.get_uv(pr, cbpts[idpts], uv_face)
1111        print("interpolated uv =", uv_pr)
1112
1113        sx, sy = pic.dimensions()
1114        i_interp_uv = uv_pr * [sy, sx]
1115        ix, iy = i_interp_uv.astype(int)
1116        mpic = pic.tomesh()
1117        rgba = mpic.pointdata["RGBA"].reshape(sy, sx, 3)
1118        print("color =", rgba[ix, iy])
1119
1120        show(
1121            [[cb, Point(pr), cb.labels("Material")],
1122                [pic, Point(i_interp_uv)]],
1123            N=2, axes=1, sharecam=False,
1124        ).close()
1125        ```
1126        ![](https://vedo.embl.es/images/feats/utils_get_uv.png)
1127    """
1128    # Vector vp=p-x0 is representable as alpha*s + beta*t,
1129    # where s = x1-x0 and t = x2-x0, in matrix form
1130    # vp = [alpha, beta] . matrix(s,t)
1131    # M = matrix(s,t) is 2x3 matrix, so (alpha, beta) can be found by
1132    # inverting any of its minor A with non-zero determinant.
1133    # Once found, uv-coords of p are vt0 + alpha (vt1-v0) + beta (vt2-v0)
1134
1135    p = np.asarray(p)
1136    x0, x1, x2 = np.asarray(x)[:3]
1137    vt0, vt1, vt2 = np.asarray(v)[:3]
1138
1139    s = x1 - x0
1140    t = x2 - x0
1141    vs = vt1 - vt0
1142    vt = vt2 - vt0
1143    vp = p - x0
1144
1145    # finding a minor with independent rows
1146    M = np.matrix([s, t])
1147    mnr = [0, 1]
1148    A = M[:, mnr]
1149    if np.abs(np.linalg.det(A)) < 0.000001:
1150        mnr = [0, 2]
1151        A = M[:, mnr]
1152        if np.abs(np.linalg.det(A)) < 0.000001:
1153            mnr = [1, 2]
1154            A = M[:, mnr]
1155    Ainv = np.linalg.inv(A)
1156    alpha_beta = vp[mnr].dot(Ainv)  # [alpha, beta]
1157    return np.asarray(vt0 + alpha_beta.dot(np.matrix([vs, vt])))[0]

Obtain the texture uv-coords of a point p belonging to a face that has point coordinates (x0, x1, x2) with the corresponding uv-coordinates v=(v0, v1, v2). All p and x0,x1,x2 are 3D-vectors, while v are their 2D uv-coordinates.

Example:
from vedo import *

pic = Picture(dataurl+"coloured_cube_faces.jpg")
cb = Mesh(dataurl+"coloured_cube.obj").lighting("off").texture(pic)

cbpts = cb.points()
faces = cb.faces()
uv = cb.pointdata["Material"]

pt = [-0.2, 0.75, 2]
pr = cb.closest_point(pt)

idface = cb.closest_point(pt, return_cell_id=True)
idpts = faces[idface]
uv_face = uv[idpts]

uv_pr = utils.get_uv(pr, cbpts[idpts], uv_face)
print("interpolated uv =", uv_pr)

sx, sy = pic.dimensions()
i_interp_uv = uv_pr * [sy, sx]
ix, iy = i_interp_uv.astype(int)
mpic = pic.tomesh()
rgba = mpic.pointdata["RGBA"].reshape(sy, sx, 3)
print("color =", rgba[ix, iy])

show(
    [[cb, Point(pr), cb.labels("Material")],
        [pic, Point(i_interp_uv)]],
    N=2, axes=1, sharecam=False,
).close()