vedo.utils

Utilities submodule.

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

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

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

Keep track of the operations which led to a final object. This allows to show the pipeline tree for any vedo object with e.g.:

from vedo import *
sp = Sphere()
sp.clean().subdivide()
sp.pipeline.show()
Arguments:
  • operation : (str, class) descriptor label, if a class is passed then grab its name
  • parents : (list) list of the parent classes the object comes from
  • comment : (str) a second-line text description
  • shape : (str) shape of the frame, check out this link.
  • c : (hex) hex color
  • style : (str) comma-separated list of styles
Example:
from vedo.utils import OperationNode

op_node1 = OperationNode("Operation1", c="lightblue")
op_node2 = OperationNode("Operation2")
op_node3 = OperationNode("Operation3", shape='diamond')
op_node4 = OperationNode("Operation4")
op_node5 = OperationNode("Operation5")
op_node6 = OperationNode("Result", c="lightgreen")

op_node3.add_parent(op_node1)
op_node4.add_parent(op_node1)
op_node3.add_parent(op_node2)
op_node5.add_parent(op_node2)
op_node6.add_parent(op_node3)
op_node6.add_parent(op_node5)
op_node6.add_parent(op_node1)

op_node6.show(orientation="TB")

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

Show the graphviz output for the pipeline of this object

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

Class to print a progress bar.

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

Class to print a progress bar with optional text message.

Check out also function progressbar().

Example:
import time
pb = ProgressBar(0,400, c='red')
for i in pb.range():
    time.sleep(0.1)
    pb.print('some message')

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

Print the progress bar with an optional message.

def range(self):
365    def range(self):
366        """Return the range iterator."""
367        return self._range

Return the range iterator.

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

Function to print a progress bar with optional text message.

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

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

Apply the vtkGeometryFilter to the input object. This is a general-purpose filter to extract geometry (and associated data) from any type of dataset. This filter also may be used to convert any type of data to polygonal type. The conversion process may be less than satisfactory for some 3D datasets. For example, this filter will extract the outer surface of a volume or structured grid dataset.

Returns a vedo.Mesh object.

Set extent as the [xmin,xmax, ymin,ymax, zmin,zmax] bounding box to clip data.

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

Extract cells of a specified type from a vtk dataset.

Given an input vtkDataSet and a list of cell types, produce an output containing only cells of the specified type(s).

Find here the list of possible cell types.

Return:

a vtkDataSet object which can be wrapped.

def is_sequence(arg):
748def is_sequence(arg):
749    """Check if the input is iterable."""
750    if hasattr(arg, "strip"):
751        return False
752    if hasattr(arg, "__getslice__"):
753        return True
754    if hasattr(arg, "__iter__"):
755        return True
756    return False

Check if the input is iterable.

def lin_interpolate(x, rangeX, rangeY):
1024def lin_interpolate(x, rangeX, rangeY):
1025    """
1026    Interpolate linearly the variable `x` in `rangeX` onto the new `rangeY`.
1027    If `x` is a 3D vector the linear weight is the distance to the two 3D `rangeX` vectors.
1028
1029    E.g. if `x` runs in `rangeX=[x0,x1]` and I want it to run in `rangeY=[y0,y1]` then
1030
1031    `y = lin_interpolate(x, rangeX, rangeY)` will interpolate `x` onto `rangeY`.
1032
1033    Examples:
1034        - [lin_interpolate.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/lin_interpolate.py)
1035
1036            ![](https://vedo.embl.es/images/basic/linInterpolate.png)
1037    """
1038    if is_sequence(x):
1039        x = np.asarray(x)
1040        x0, x1 = np.asarray(rangeX)
1041        y0, y1 = np.asarray(rangeY)
1042        dx = x1 - x0
1043        dxn = np.linalg.norm(dx)
1044        if not dxn:
1045            return y0
1046        s = np.linalg.norm(x - x0) / dxn
1047        t = np.linalg.norm(x - x1) / dxn
1048        st = s + t
1049        out = y0 * (t / st) + y1 * (s / st)
1050
1051    else:  # faster
1052
1053        x0 = rangeX[0]
1054        dx = rangeX[1] - x0
1055        if not dx:
1056            return rangeY[0]
1057        s = (x - x0) / dx
1058        out = rangeY[0] * (1 - s) + rangeY[1] * s
1059    return out

Interpolate linearly the variable x in rangeX onto the new rangeY. If x is a 3D vector the linear weight is the distance to the two 3D rangeX vectors.

E.g. if x runs in rangeX=[x0,x1] and I want it to run in rangeY=[y0,y1] then

y = lin_interpolate(x, rangeX, rangeY) will interpolate x onto rangeY.

Examples:
def vector(x, y=None, z=0.0, dtype=<class 'numpy.float64'>):
1136def vector(x, y=None, z=0.0, dtype=np.float64):
1137    """
1138    Return a 3D numpy array representing a vector.
1139
1140    If `y` is `None`, assume input is already in the form `[x,y,z]`.
1141    """
1142    if y is None:  # assume x is already [x,y,z]
1143        return np.asarray(x, dtype=dtype)
1144    return np.array([x, y, z], dtype=dtype)

Return a 3D numpy array representing a vector.

If y is None, assume input is already in the form [x,y,z].

def mag(v):
1155def mag(v):
1156    """Get the magnitude of a vector or array of vectors."""
1157    v = np.asarray(v)
1158    if v.ndim == 1:
1159        return np.linalg.norm(v)
1160    return np.linalg.norm(v, axis=1)

Get the magnitude of a vector or array of vectors.

def mag2(v):
1163def mag2(v):
1164    """Get the squared magnitude of a vector or array of vectors."""
1165    v = np.asarray(v)
1166    if v.ndim == 1:
1167        return np.square(v).sum()
1168    return np.square(v).sum(axis=1)

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

def versor(x, y=None, z=0.0, dtype=<class 'numpy.float64'>):
1147def versor(x, y=None, z=0.0, dtype=np.float64):
1148    """Return the unit vector. Input can be a list of vectors."""
1149    v = vector(x, y, z, dtype)
1150    if isinstance(v[0], np.ndarray):
1151        return np.divide(v, mag(v)[:, None])
1152    return v / mag(v)

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

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

Returns a string representation of x formatted to precision p.

Set vrange to the range in which x exists (to snap x to '0' if below precision).

def round_to_digit(x, p):
1190def round_to_digit(x, p):
1191    """Round a real number to the specified number of significant digits."""
1192    if not x:
1193        return 0
1194    r = np.round(x, p - int(np.floor(np.log10(abs(x)))) - 1)
1195    if int(r) == r:
1196        return int(r)
1197    return r

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

def point_in_triangle(p, p1, p2, p3):
827def point_in_triangle(p, p1, p2, p3):
828    """
829    Return True if a point is inside (or above/below) a triangle defined by 3 points in space.
830    """
831    p1 = np.array(p1)
832    u = p2 - p1
833    v = p3 - p1
834    n = np.cross(u, v)
835    w = p - p1
836    ln = np.dot(n, n)
837    if not ln:
838        return None  # degenerate triangle
839    gamma = (np.dot(np.cross(u, w), n)) / ln
840    if 0 < gamma < 1:
841        beta = (np.dot(np.cross(w, v), n)) / ln
842        if 0 < beta < 1:
843            alpha = 1 - gamma - beta
844            if 0 < alpha < 1:
845                return True
846    return False

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

def point_line_distance(p, p1, p2):
1008def point_line_distance(p, p1, p2):
1009    """
1010    Compute the distance of a point to a line (not the segment)
1011    defined by `p1` and `p2`.
1012    """
1013    d = np.sqrt(vtk.vtkLine.DistanceToLine(p, p1, p2))
1014    return d

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

def grep(filename, tag, first_occurrence_only=False):
1396def grep(filename, tag, first_occurrence_only=False):
1397    """Greps the line in a file that starts with a specific `tag` string inside the file."""
1398    import re
1399
1400    with open(filename, "r", encoding="UTF-8") as afile:
1401        content = []
1402        for line in afile:
1403            if re.search(tag, line):
1404                c = line.split()
1405                c[-1] = c[-1].replace("\n", "")
1406                content.append(c)
1407                if first_occurrence_only:
1408                    break
1409    return content

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