vedo.utils

Utilities submodule.

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

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

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

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

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

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

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

op_node6.show(orientation="TB")

def add_parent(self, parent) -> None:
150    def add_parent(self, parent) -> None:
151        self.parents.append(parent)
def print(self) -> None:
194    def print(self) -> None:
195        """Print the tree of operations."""
196        print(self.__str__())

Print the tree of operations.

def show(self, orientation='LR', popup=True) -> None:
198    def show(self, orientation="LR", popup=True) -> None:
199        """Show the graphviz output for the pipeline of this object"""
200        if not vedo.settings.enable_pipeline:
201            return
202
203        try:
204            from graphviz import Digraph
205        except ImportError:
206            vedo.logger.error("please install graphviz with command\n pip install graphviz")
207            return
208
209        # visualize the entire tree
210        dot = Digraph(
211            node_attr={"fontcolor": "#201010", "fontname": "Helvetica", "fontsize": "12"},
212            edge_attr={"fontname": "Helvetica", "fontsize": "6", "arrowsize": "0.4"},
213        )
214        dot.attr(rankdir=orientation)
215
216        self.counts = 0
217        self._build_tree(dot)
218        self.dot = dot
219
220        home_dir = os.path.expanduser("~")
221        gpath = os.path.join(
222            home_dir, vedo.settings.cache_directory, "vedo", "pipeline_graphviz")
223
224        dot.render(gpath, view=popup)

Show the graphviz output for the pipeline of this object

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

Class to print a progress bar.

ProgressBar( start, stop, step=1, c=None, bold=True, italic=False, title='', eta=True, delay=-1, width=25, char='━', char_back='─')
233    def __init__(
234        self,
235        start,
236        stop,
237        step=1,
238        c=None,
239        bold=True,
240        italic=False,
241        title="",
242        eta=True,
243        delay=-1,
244        width=25,
245        char="\U00002501",
246        char_back="\U00002500",
247    ) -> None:
248        """
249        Class to print a progress bar with optional text message.
250
251        Check out also function `progressbar()`.
252
253        Arguments:
254            start : (int)
255                starting value
256            stop : (int)
257                stopping value
258            step : (int)
259                step value
260            c : (str)
261                color in hex format
262            title : (str)
263                title text
264            eta : (bool)
265                estimate time of arrival
266            delay : (float)
267                minimum time before printing anything,
268                if negative use the default value
269                as set in `vedo.settings.progressbar_delay`
270            width : (int)
271                width of the progress bar
272            char : (str)
273                character to use for the progress bar
274            char_back : (str)
275                character to use for the background of the progress bar
276
277        Example:
278            ```python
279            import time
280            from vedo import ProgressBar
281            pb = ProgressBar(0,40, c='r')
282            for i in pb.range():
283                time.sleep(0.1)
284                pb.print()
285            ```
286            ![](https://user-images.githubusercontent.com/32848391/51858823-ed1f4880-2335-11e9-8788-2d102ace2578.png)
287        """
288        self.char = char
289        self.char_back = char_back
290
291        self.title = title + " "
292        if title:
293            self.title = " " + self.title
294
295        if delay < 0:
296            delay = vedo.settings.progressbar_delay
297
298        self.start = start
299        self.stop = stop
300        self.step = step
301
302        self.color = c
303        self.bold = bold
304        self.italic = italic
305        self.width = width
306        self.pbar = ""
307        self.percent = 0.0
308        self.percent_int = 0
309        self.eta = eta
310        self.delay = delay
311
312        self.t0 = time.time()
313        self._remaining = 1e10
314
315        self._update(0)
316
317        self._counts = 0
318        self._oldbar = ""
319        self._lentxt = 0
320        self._range = np.arange(start, stop, step)

Class to print a progress bar with optional text message.

Check out also function progressbar().

Arguments:
  • start : (int) starting value
  • stop : (int) stopping value
  • step : (int) step value
  • c : (str) color in hex format
  • title : (str) title text
  • eta : (bool) estimate time of arrival
  • delay : (float) minimum time before printing anything, if negative use the default value as set in vedo.settings.progressbar_delay
  • width : (int) width of the progress bar
  • char : (str) character to use for the progress bar
  • char_back : (str) character to use for the background of the progress bar
Example:
import time
from vedo import ProgressBar
pb = ProgressBar(0,40, c='r')
for i in pb.range():
    time.sleep(0.1)
    pb.print()

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

Print the progress bar with an optional message.

def range(self) -> numpy.ndarray:
381    def range(self) -> np.ndarray:
382        """Return the range iterator."""
383        return self._range

Return the range iterator.

def progressbar( iterable, c=None, bold=True, italic=False, title='', eta=True, width=25, delay=-1):
413def progressbar(
414        iterable,
415        c=None, bold=True, italic=False, title="",
416        eta=True, width=25, delay=-1,
417    ):
418    """
419    Function to print a progress bar with optional text message.
420
421    Use delay to set a minimum time before printing anything.
422    If delay is negative, then use the default value
423    as set in `vedo.settings.progressbar_delay`.
424
425    Arguments:
426        start : (int)
427            starting value
428        stop : (int)
429            stopping value
430        step : (int)
431            step value
432        c : (str)
433            color in hex format
434        title : (str)
435            title text
436        eta : (bool)
437            estimate time of arrival
438        delay : (float)
439            minimum time before printing anything,
440            if negative use the default value
441            set in `vedo.settings.progressbar_delay`
442        width : (int)
443            width of the progress bar
444        char : (str)
445            character to use for the progress bar
446        char_back : (str)
447            character to use for the background of the progress bar
448
449    Example:
450        ```python
451        import time
452        for i in progressbar(range(100), c='r'):
453            time.sleep(0.1)
454        ```
455        ![](https://user-images.githubusercontent.com/32848391/51858823-ed1f4880-2335-11e9-8788-2d102ace2578.png)
456    """
457    try:
458        if is_number(iterable):
459            total = int(iterable)
460            iterable = range(total)
461        else:
462            total = len(iterable)
463    except TypeError:
464        iterable = list(iterable)
465        total = len(iterable)
466
467    pb = ProgressBar(
468        0, total, c=c, bold=bold, italic=italic, title=title,
469        eta=eta, delay=delay, width=width,
470    )
471    for item in iterable:
472        pb.print()
473        yield item

Function to print a progress bar with optional text message.

Use delay to set a minimum time before printing anything. If delay is negative, then use the default value as set in vedo.settings.progressbar_delay.

Arguments:
  • start : (int) starting value
  • stop : (int) stopping value
  • step : (int) step value
  • c : (str) color in hex format
  • title : (str) title text
  • eta : (bool) estimate time of arrival
  • delay : (float) minimum time before printing anything, if negative use the default value set in vedo.settings.progressbar_delay
  • width : (int) width of the progress bar
  • char : (str) character to use for the progress bar
  • char_back : (str) character to use for the background of the progress bar
Example:
import time
for i in progressbar(range(100), c='r'):
    time.sleep(0.1)

class Minimizer:
477class Minimizer:
478    """
479    A function minimizer that uses the Nelder-Mead method.
480
481    The algorithm constructs an n-dimensional simplex in parameter
482    space (i.e. a tetrahedron if the number or parameters is 3)
483    and moves the vertices around parameter space until
484    a local minimum is found. The amoeba method is robust,
485    reasonably efficient, but is not guaranteed to find
486    the global minimum if several local minima exist.
487    
488    Arguments:
489        function : (callable)
490            the function to minimize
491        max_iterations : (int)
492            the maximum number of iterations
493        contraction_ratio : (float)
494            The contraction ratio.
495            The default value of 0.5 gives fast convergence,
496            but larger values such as 0.6 or 0.7 provide greater stability.
497        expansion_ratio : (float)
498            The expansion ratio.
499            The default value is 2.0, which provides rapid expansion.
500            Values between 1.1 and 2.0 are valid.
501        tol : (float)
502            the tolerance for convergence
503    
504    Example:
505        - [nelder-mead.py](https://github.com/marcomusy/vedo/blob/master/examples/others/nelder-mead.py)
506    """
507    def __init__(
508            self,
509            function=None,
510            max_iterations=10000,
511            contraction_ratio=0.5,
512            expansion_ratio=2.0,
513            tol=1e-5,
514        ) -> None:
515        self.function = function
516        self.tolerance = tol
517        self.contraction_ratio = contraction_ratio
518        self.expansion_ratio = expansion_ratio
519        self.max_iterations = max_iterations
520        self.minimizer = vtki.new("AmoebaMinimizer")
521        self.minimizer.SetFunction(self._vtkfunc)
522        self.results = {}
523        self.parameters_path = []
524        self.function_path = []
525
526    def _vtkfunc(self):
527        n = self.minimizer.GetNumberOfParameters()
528        ain = [self.minimizer.GetParameterValue(i) for i in range(n)]
529        r = self.function(ain)
530        self.minimizer.SetFunctionValue(r)
531        self.parameters_path.append(ain)
532        self.function_path.append(r)
533        return r
534    
535    def eval(self, parameters=()) -> float:
536        """
537        Evaluate the function at the current or given parameters.
538        """
539        if len(parameters) == 0:
540            return self.minimizer.EvaluateFunction()
541        self.set_parameters(parameters)
542        return self.function(parameters)
543    
544    def set_parameter(self, name, value, scale=1.0) -> None:
545        """
546        Set the parameter value.
547        The initial amount by which the parameter
548        will be modified during the search for the minimum. 
549        """
550        self.minimizer.SetParameterValue(name, value)
551        self.minimizer.SetParameterScale(name, scale)
552    
553    def set_parameters(self, parameters) -> None:
554        """
555        Set the parameters names and values from a dictionary.
556        """
557        for name, value in parameters.items():
558            if len(value) == 2:
559                self.set_parameter(name, value[0], value[1])
560            else:
561                self.set_parameter(name, value)
562    
563    def minimize(self) -> dict:
564        """
565        Minimize the input function.
566
567        Returns:
568            dict : 
569                the minimization results
570            init_parameters : (dict)
571                the initial parameters
572            parameters : (dict)
573                the final parameters
574            min_value : (float)
575                the minimum value
576            iterations : (int)
577                the number of iterations
578            max_iterations : (int)
579                the maximum number of iterations
580            tolerance : (float)
581                the tolerance for convergence
582            convergence_flag : (int)
583                zero if the tolerance stopping
584                criterion has been met.
585            parameters_path : (np.array)
586                the path of the minimization
587                algorithm in parameter space
588            function_path : (np.array)
589                the path of the minimization
590                algorithm in function space
591            hessian : (np.array)
592                the Hessian matrix of the
593                function at the minimum
594            parameter_errors : (np.array)
595                the errors on the parameters
596        """
597        n = self.minimizer.GetNumberOfParameters()
598        out = [(
599            self.minimizer.GetParameterName(i),
600            (self.minimizer.GetParameterValue(i),
601             self.minimizer.GetParameterScale(i))
602        ) for i in range(n)]
603        self.results["init_parameters"] = dict(out)
604
605        self.minimizer.SetTolerance(self.tolerance)
606        self.minimizer.SetContractionRatio(self.contraction_ratio)
607        self.minimizer.SetExpansionRatio(self.expansion_ratio)
608        self.minimizer.SetMaxIterations(self.max_iterations)
609
610        self.minimizer.Minimize()
611        self.results["convergence_flag"] = not bool(self.minimizer.Iterate())
612
613        out = [(
614            self.minimizer.GetParameterName(i),
615            self.minimizer.GetParameterValue(i),
616        ) for i in range(n)]
617
618        self.results["parameters"] = dict(out)
619        self.results["min_value"] = self.minimizer.GetFunctionValue()
620        self.results["iterations"] = self.minimizer.GetIterations()
621        self.results["max_iterations"] = self.minimizer.GetMaxIterations()
622        self.results["tolerance"] = self.minimizer.GetTolerance()
623        self.results["expansion_ratio"] = self.expansion_ratio
624        self.results["contraction_ratio"] = self.contraction_ratio
625        self.results["parameters_path"] = np.array(self.parameters_path)
626        self.results["function_path"] = np.array(self.function_path)
627        self.results["hessian"] = np.zeros((n,n))
628        self.results["parameter_errors"] = np.zeros(n)
629        return self.results
630
631    def compute_hessian(self, epsilon=0) -> np.array:
632        """
633        Compute the Hessian matrix of `function` at the
634        minimum numerically.
635
636        Arguments:
637            epsilon : (float)
638                Step size used for numerical approximation.
639
640        Returns:
641            array: Hessian matrix of `function` at minimum.
642        """
643        if not epsilon:
644            epsilon = self.tolerance * 10
645        n = self.minimizer.GetNumberOfParameters()
646        x0 = [self.minimizer.GetParameterValue(i) for i in range(n)]
647        hessian = np.zeros((n, n))
648        for i in vedo.progressbar(n, title="Computing Hessian", delay=2):
649            for j in range(n):
650                xijp = np.copy(x0)
651                xijp[i] += epsilon
652                xijp[j] += epsilon
653                xijm = np.copy(x0)
654                xijm[i] += epsilon
655                xijm[j] -= epsilon
656                xjip = np.copy(x0)
657                xjip[i] -= epsilon
658                xjip[j] += epsilon
659                xjim = np.copy(x0)
660                xjim[i] -= epsilon
661                xjim[j] -= epsilon
662                # Second derivative approximation
663                fijp = self.function(xijp)
664                fijm = self.function(xijm)
665                fjip = self.function(xjip)
666                fjim = self.function(xjim)
667                hessian[i, j] = (fijp - fijm - fjip + fjim) / (2 * epsilon**2)
668        self.results["hessian"] = hessian
669        try:
670            ihess = np.linalg.inv(hessian)
671            self.results["parameter_errors"] = np.sqrt(np.diag(ihess))
672        except:
673            vedo.logger.warning("Cannot compute hessian for parameter errors")
674            self.results["parameter_errors"] = np.zeros(n)
675        return hessian
676
677    def __str__(self) -> str:
678        out = vedo.printc(
679            f"vedo.utils.Minimizer at ({hex(id(self))})".ljust(75),
680            bold=True, invert=True, return_string=True,
681        )
682        out += "Function name".ljust(20) + self.function.__name__ + "()\n"
683        out += "-------- parameters initial value -----------\n"
684        out += "Name".ljust(20) + "Value".ljust(20) + "Scale\n"
685        for name, value in self.results["init_parameters"].items():
686            out += name.ljust(20) + str(value[0]).ljust(20) + str(value[1]) + "\n"
687        out += "-------- parameters final value --------------\n"
688        for name, value in self.results["parameters"].items():
689            out += name.ljust(20) + f"{value:.6f}"
690            ierr = list(self.results["parameters"]).index(name)
691            err = self.results["parameter_errors"][ierr]
692            if err:
693                out += f" ± {err:.4f}"
694            out += "\n"
695        out += "Value at minimum".ljust(20)+ f'{self.results["min_value"]}\n'
696        out += "Iterations".ljust(20)      + f'{self.results["iterations"]}\n'
697        out += "Max iterations".ljust(20)  + f'{self.results["max_iterations"]}\n'
698        out += "Convergence flag".ljust(20)+ f'{self.results["convergence_flag"]}\n'
699        out += "Tolerance".ljust(20)       + f'{self.results["tolerance"]}\n'
700        try:
701            arr = np.array2string(
702                self.compute_hessian(),
703                separator=', ', precision=6, suppress_small=True,
704            )
705            out += "Hessian Matrix:\n" + arr
706        except:
707            out += "Hessian Matrix: (not available)"
708        return out

A function minimizer that uses the Nelder-Mead method.

The algorithm constructs an n-dimensional simplex in parameter space (i.e. a tetrahedron if the number or parameters is 3) and moves the vertices around parameter space until a local minimum is found. The amoeba method is robust, reasonably efficient, but is not guaranteed to find the global minimum if several local minima exist.

Arguments:
  • function : (callable) the function to minimize
  • max_iterations : (int) the maximum number of iterations
  • contraction_ratio : (float) The contraction ratio. The default value of 0.5 gives fast convergence, but larger values such as 0.6 or 0.7 provide greater stability.
  • expansion_ratio : (float) The expansion ratio. The default value is 2.0, which provides rapid expansion. Values between 1.1 and 2.0 are valid.
  • tol : (float) the tolerance for convergence
Example:
Minimizer( function=None, max_iterations=10000, contraction_ratio=0.5, expansion_ratio=2.0, tol=1e-05)
507    def __init__(
508            self,
509            function=None,
510            max_iterations=10000,
511            contraction_ratio=0.5,
512            expansion_ratio=2.0,
513            tol=1e-5,
514        ) -> None:
515        self.function = function
516        self.tolerance = tol
517        self.contraction_ratio = contraction_ratio
518        self.expansion_ratio = expansion_ratio
519        self.max_iterations = max_iterations
520        self.minimizer = vtki.new("AmoebaMinimizer")
521        self.minimizer.SetFunction(self._vtkfunc)
522        self.results = {}
523        self.parameters_path = []
524        self.function_path = []
def eval(self, parameters=()) -> float:
535    def eval(self, parameters=()) -> float:
536        """
537        Evaluate the function at the current or given parameters.
538        """
539        if len(parameters) == 0:
540            return self.minimizer.EvaluateFunction()
541        self.set_parameters(parameters)
542        return self.function(parameters)

Evaluate the function at the current or given parameters.

def set_parameter(self, name, value, scale=1.0) -> None:
544    def set_parameter(self, name, value, scale=1.0) -> None:
545        """
546        Set the parameter value.
547        The initial amount by which the parameter
548        will be modified during the search for the minimum. 
549        """
550        self.minimizer.SetParameterValue(name, value)
551        self.minimizer.SetParameterScale(name, scale)

Set the parameter value. The initial amount by which the parameter will be modified during the search for the minimum.

def set_parameters(self, parameters) -> None:
553    def set_parameters(self, parameters) -> None:
554        """
555        Set the parameters names and values from a dictionary.
556        """
557        for name, value in parameters.items():
558            if len(value) == 2:
559                self.set_parameter(name, value[0], value[1])
560            else:
561                self.set_parameter(name, value)

Set the parameters names and values from a dictionary.

def minimize(self) -> dict:
563    def minimize(self) -> dict:
564        """
565        Minimize the input function.
566
567        Returns:
568            dict : 
569                the minimization results
570            init_parameters : (dict)
571                the initial parameters
572            parameters : (dict)
573                the final parameters
574            min_value : (float)
575                the minimum value
576            iterations : (int)
577                the number of iterations
578            max_iterations : (int)
579                the maximum number of iterations
580            tolerance : (float)
581                the tolerance for convergence
582            convergence_flag : (int)
583                zero if the tolerance stopping
584                criterion has been met.
585            parameters_path : (np.array)
586                the path of the minimization
587                algorithm in parameter space
588            function_path : (np.array)
589                the path of the minimization
590                algorithm in function space
591            hessian : (np.array)
592                the Hessian matrix of the
593                function at the minimum
594            parameter_errors : (np.array)
595                the errors on the parameters
596        """
597        n = self.minimizer.GetNumberOfParameters()
598        out = [(
599            self.minimizer.GetParameterName(i),
600            (self.minimizer.GetParameterValue(i),
601             self.minimizer.GetParameterScale(i))
602        ) for i in range(n)]
603        self.results["init_parameters"] = dict(out)
604
605        self.minimizer.SetTolerance(self.tolerance)
606        self.minimizer.SetContractionRatio(self.contraction_ratio)
607        self.minimizer.SetExpansionRatio(self.expansion_ratio)
608        self.minimizer.SetMaxIterations(self.max_iterations)
609
610        self.minimizer.Minimize()
611        self.results["convergence_flag"] = not bool(self.minimizer.Iterate())
612
613        out = [(
614            self.minimizer.GetParameterName(i),
615            self.minimizer.GetParameterValue(i),
616        ) for i in range(n)]
617
618        self.results["parameters"] = dict(out)
619        self.results["min_value"] = self.minimizer.GetFunctionValue()
620        self.results["iterations"] = self.minimizer.GetIterations()
621        self.results["max_iterations"] = self.minimizer.GetMaxIterations()
622        self.results["tolerance"] = self.minimizer.GetTolerance()
623        self.results["expansion_ratio"] = self.expansion_ratio
624        self.results["contraction_ratio"] = self.contraction_ratio
625        self.results["parameters_path"] = np.array(self.parameters_path)
626        self.results["function_path"] = np.array(self.function_path)
627        self.results["hessian"] = np.zeros((n,n))
628        self.results["parameter_errors"] = np.zeros(n)
629        return self.results

Minimize the input function.

Returns:

dict : the minimization results init_parameters : (dict) the initial parameters parameters : (dict) the final parameters min_value : (float) the minimum value iterations : (int) the number of iterations max_iterations : (int) the maximum number of iterations tolerance : (float) the tolerance for convergence convergence_flag : (int) zero if the tolerance stopping criterion has been met. parameters_path : (np.array) the path of the minimization algorithm in parameter space function_path : (np.array) the path of the minimization algorithm in function space hessian : (np.array) the Hessian matrix of the function at the minimum parameter_errors : (np.array) the errors on the parameters

def compute_hessian(self, epsilon=0) -> <built-in function array>:
631    def compute_hessian(self, epsilon=0) -> np.array:
632        """
633        Compute the Hessian matrix of `function` at the
634        minimum numerically.
635
636        Arguments:
637            epsilon : (float)
638                Step size used for numerical approximation.
639
640        Returns:
641            array: Hessian matrix of `function` at minimum.
642        """
643        if not epsilon:
644            epsilon = self.tolerance * 10
645        n = self.minimizer.GetNumberOfParameters()
646        x0 = [self.minimizer.GetParameterValue(i) for i in range(n)]
647        hessian = np.zeros((n, n))
648        for i in vedo.progressbar(n, title="Computing Hessian", delay=2):
649            for j in range(n):
650                xijp = np.copy(x0)
651                xijp[i] += epsilon
652                xijp[j] += epsilon
653                xijm = np.copy(x0)
654                xijm[i] += epsilon
655                xijm[j] -= epsilon
656                xjip = np.copy(x0)
657                xjip[i] -= epsilon
658                xjip[j] += epsilon
659                xjim = np.copy(x0)
660                xjim[i] -= epsilon
661                xjim[j] -= epsilon
662                # Second derivative approximation
663                fijp = self.function(xijp)
664                fijm = self.function(xijm)
665                fjip = self.function(xjip)
666                fjim = self.function(xjim)
667                hessian[i, j] = (fijp - fijm - fjip + fjim) / (2 * epsilon**2)
668        self.results["hessian"] = hessian
669        try:
670            ihess = np.linalg.inv(hessian)
671            self.results["parameter_errors"] = np.sqrt(np.diag(ihess))
672        except:
673            vedo.logger.warning("Cannot compute hessian for parameter errors")
674            self.results["parameter_errors"] = np.zeros(n)
675        return hessian

Compute the Hessian matrix of function at the minimum numerically.

Arguments:
  • epsilon : (float) Step size used for numerical approximation.
Returns:

array: Hessian matrix of function at minimum.

def geometry(obj, extent=None) -> vedo.mesh.Mesh:
851def geometry(obj, extent=None) -> "vedo.Mesh":
852    """
853    Apply the `vtkGeometryFilter` to the input object.
854    This is a general-purpose filter to extract geometry (and associated data)
855    from any type of dataset.
856    This filter also may be used to convert any type of data to polygonal type.
857    The conversion process may be less than satisfactory for some 3D datasets.
858    For example, this filter will extract the outer surface of a volume
859    or structured grid dataset.
860
861    Returns a `vedo.Mesh` object.
862
863    Set `extent` as the `[xmin,xmax, ymin,ymax, zmin,zmax]` bounding box to clip data.
864    """
865    gf = vtki.new("GeometryFilter")
866    gf.SetInputData(obj)
867    if extent is not None:
868        gf.SetExtent(extent)
869    gf.Update()
870    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 is_sequence(arg) -> bool:
1039def is_sequence(arg) -> bool:
1040    """Check if the input is iterable."""
1041    if hasattr(arg, "strip"):
1042        return False
1043    if hasattr(arg, "__getslice__"):
1044        return True
1045    if hasattr(arg, "__iter__"):
1046        return True
1047    return False

Check if the input is iterable.

def lin_interpolate(x, rangeX, rangeY):
1413def lin_interpolate(x, rangeX, rangeY):
1414    """
1415    Interpolate linearly the variable `x` in `rangeX` onto the new `rangeY`.
1416    If `x` is a 3D vector the linear weight is the distance to the two 3D `rangeX` vectors.
1417
1418    E.g. if `x` runs in `rangeX=[x0,x1]` and I want it to run in `rangeY=[y0,y1]` then
1419
1420    `y = lin_interpolate(x, rangeX, rangeY)` will interpolate `x` onto `rangeY`.
1421
1422    Examples:
1423        - [lin_interpolate.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/lin_interpolate.py)
1424
1425            ![](https://vedo.embl.es/images/basic/linInterpolate.png)
1426    """
1427    if is_sequence(x):
1428        x = np.asarray(x)
1429        x0, x1 = np.asarray(rangeX)
1430        y0, y1 = np.asarray(rangeY)
1431        dx = x1 - x0
1432        dxn = np.linalg.norm(dx)
1433        if not dxn:
1434            return y0
1435        s = np.linalg.norm(x - x0) / dxn
1436        t = np.linalg.norm(x - x1) / dxn
1437        st = s + t
1438        out = y0 * (t / st) + y1 * (s / st)
1439
1440    else:  # faster
1441
1442        x0 = rangeX[0]
1443        dx = rangeX[1] - x0
1444        if not dx:
1445            return rangeY[0]
1446        s = (x - x0) / dx
1447        out = rangeY[0] * (1 - s) + rangeY[1] * s
1448    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'>) -> numpy.ndarray:
1525def vector(x, y=None, z=0.0, dtype=np.float64) -> np.ndarray:
1526    """
1527    Return a 3D numpy array representing a vector.
1528
1529    If `y` is `None`, assume input is already in the form `[x,y,z]`.
1530    """
1531    if y is None:  # assume x is already [x,y,z]
1532        return np.asarray(x, dtype=dtype)
1533    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):
1544def mag(v):
1545    """Get the magnitude of a vector or array of vectors."""
1546    v = np.asarray(v)
1547    if v.ndim == 1:
1548        return np.linalg.norm(v)
1549    return np.linalg.norm(v, axis=1)

Get the magnitude of a vector or array of vectors.

def mag2(v) -> numpy.ndarray:
1552def mag2(v) -> np.ndarray:
1553    """Get the squared magnitude of a vector or array of vectors."""
1554    v = np.asarray(v)
1555    if v.ndim == 1:
1556        return np.square(v).sum()
1557    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'>) -> numpy.ndarray:
1536def versor(x, y=None, z=0.0, dtype=np.float64) -> np.ndarray:
1537    """Return the unit vector. Input can be a list of vectors."""
1538    v = vector(x, y, z, dtype)
1539    if isinstance(v[0], np.ndarray):
1540        return np.divide(v, mag(v)[:, None])
1541    return v / mag(v)

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

def precision(x, p: int, vrange=None, delimiter='e') -> str:
1626def precision(x, p: int, vrange=None, delimiter="e") -> str:
1627    """
1628    Returns a string representation of `x` formatted to precision `p`.
1629
1630    Set `vrange` to the range in which x exists (to snap x to '0' if below precision).
1631    """
1632    # Based on the webkit javascript implementation
1633    # `from here <https://code.google.com/p/webkit-mirror/source/browse/JavaScriptCore/kjs/number_object.cpp>`_,
1634    # and implemented by `randlet <https://github.com/randlet/to-precision>`_.
1635    # Modified for vedo by M.Musy 2020
1636
1637    if isinstance(x, str):  # do nothing
1638        return x
1639
1640    if is_sequence(x):
1641        out = "("
1642        nn = len(x) - 1
1643        for i, ix in enumerate(x):
1644
1645            try:
1646                if np.isnan(ix):
1647                    return "NaN"
1648            except:
1649                # cannot handle list of list
1650                continue
1651
1652            out += precision(ix, p)
1653            if i < nn:
1654                out += ", "
1655        return out + ")"  ############ <--
1656
1657    try:
1658        if np.isnan(x):
1659            return "NaN"
1660    except TypeError:
1661        return "NaN"
1662
1663    x = float(x)
1664
1665    if x == 0.0 or (vrange is not None and abs(x) < vrange / pow(10, p)):
1666        return "0"
1667
1668    out = []
1669    if x < 0:
1670        out.append("-")
1671        x = -x
1672
1673    e = int(np.log10(x))
1674    # tens = np.power(10, e - p + 1)
1675    tens = 10 ** (e - p + 1)
1676    n = np.floor(x / tens)
1677
1678    # if n < np.power(10, p - 1):
1679    if n < 10 ** (p - 1):
1680        e = e - 1
1681        # tens = np.power(10, e - p + 1)
1682        tens = 10 ** (e - p + 1)
1683        n = np.floor(x / tens)
1684
1685    if abs((n + 1.0) * tens - x) <= abs(n * tens - x):
1686        n = n + 1
1687
1688    # if n >= np.power(10, p):
1689    if n >= 10 ** p:
1690        n = n / 10.0
1691        e = e + 1
1692
1693    m = "%.*g" % (p, n)
1694    if e < -2 or e >= p:
1695        out.append(m[0])
1696        if p > 1:
1697            out.append(".")
1698            out.extend(m[1:p])
1699        out.append(delimiter)
1700        if e > 0:
1701            out.append("+")
1702        out.append(str(e))
1703    elif e == (p - 1):
1704        out.append(m)
1705    elif e >= 0:
1706        out.append(m[: e + 1])
1707        if e + 1 < len(m):
1708            out.append(".")
1709            out.extend(m[e + 1 :])
1710    else:
1711        out.append("0.")
1712        out.extend(["0"] * -(e + 1))
1713        out.append(m)
1714    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) -> float:
1579def round_to_digit(x, p) -> float:
1580    """Round a real number to the specified number of significant digits."""
1581    if not x:
1582        return 0
1583    r = np.round(x, p - int(np.floor(np.log10(abs(x)))) - 1)
1584    if int(r) == r:
1585        return int(r)
1586    return r

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

def point_in_triangle(p, p1, p2, p3) -> Optional[bool]:
1122def point_in_triangle(p, p1, p2, p3) -> Union[bool, None]:
1123    """
1124    Return True if a point is inside (or above/below)
1125    a triangle defined by 3 points in space.
1126    """
1127    p1 = np.array(p1)
1128    u = p2 - p1
1129    v = p3 - p1
1130    n = np.cross(u, v)
1131    w = p - p1
1132    ln = np.dot(n, n)
1133    if not ln:
1134        return None  # degenerate triangle
1135    gamma = (np.dot(np.cross(u, w), n)) / ln
1136    if 0 < gamma < 1:
1137        beta = (np.dot(np.cross(w, v), n)) / ln
1138        if 0 < beta < 1:
1139            alpha = 1 - gamma - beta
1140            if 0 < alpha < 1:
1141                return True
1142    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) -> float:
1335def point_line_distance(p, p1, p2) -> float:
1336    """
1337    Compute the distance of a point to a line (not the segment)
1338    defined by `p1` and `p2`.
1339    """
1340    return np.sqrt(vtki.vtkLine.DistanceToLine(p, p1, p2))

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

def closest(point, points, n=1, return_ids=False, use_tree=False):
1375def closest(point, points, n=1, return_ids=False, use_tree=False):
1376    """
1377    Returns the distances and the closest point(s) to the given set of points.
1378    Needs `scipy.spatial` library.
1379
1380    Arguments:
1381        n : (int)
1382            the nr of closest points to return
1383        return_ids : (bool)
1384            return the ids instead of the points coordinates
1385        use_tree : (bool)
1386            build a `scipy.spatial.KDTree`.
1387            An already existing one can be passed to avoid rebuilding.
1388    """
1389    from scipy.spatial import distance, KDTree
1390
1391    points = np.asarray(points)
1392    if n == 1:
1393        dists = distance.cdist([point], points)
1394        closest_idx = np.argmin(dists)
1395    else:
1396        if use_tree:
1397            if isinstance(use_tree, KDTree):  # reuse
1398                tree = use_tree
1399            else:
1400                tree = KDTree(points)
1401            dists, closest_idx = tree.query([point], k=n)
1402            closest_idx = closest_idx[0]
1403        else:
1404            dists = distance.cdist([point], points)
1405            closest_idx = np.argsort(dists)[0][:n]
1406    if return_ids:
1407        return dists, closest_idx
1408    else:
1409        return dists, points[closest_idx]

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

Arguments:
  • n : (int) the nr of closest points to return
  • return_ids : (bool) return the ids instead of the points coordinates
  • use_tree : (bool) build a scipy.spatial.KDTree. An already existing one can be passed to avoid rebuilding.
def grep( filename: str, tag: str, column=None, first_occurrence_only=False) -> list:
1718def grep(filename: str, tag: str, column=None, first_occurrence_only=False) -> list:
1719    """Greps the line in a file that starts with a specific `tag` string inside the file."""
1720    import re
1721
1722    with open(filename, "r", encoding="UTF-8") as afile:
1723        content = []
1724        for line in afile:
1725            if re.search(tag, line):
1726                c = line.split()
1727                c[-1] = c[-1].replace("\n", "")
1728                if column is not None:
1729                    c = c[column]
1730                content.append(c)
1731                if first_occurrence_only:
1732                    break
1733    return content

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

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

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

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

def pack_spheres(bounds, radius) -> numpy.ndarray:
1589def pack_spheres(bounds, radius) -> np.ndarray:
1590    """
1591    Packing spheres into a bounding box.
1592    Returns a numpy array of sphere centers.
1593    """
1594    h = 0.8164965 / 2
1595    d = 0.8660254
1596    a = 0.288675135
1597
1598    if is_sequence(bounds):
1599        x0, x1, y0, y1, z0, z1 = bounds
1600    else:
1601        x0, x1, y0, y1, z0, z1 = bounds.bounds()
1602
1603    x = np.arange(x0, x1, radius)
1604    nul = np.zeros_like(x)
1605    nz = int((z1 - z0) / radius / h / 2 + 1.5)
1606    ny = int((y1 - y0) / radius / d + 1.5)
1607
1608    pts = []
1609    for iz in range(nz):
1610        z = z0 + nul + iz * h * radius
1611        dx, dy, dz = [radius * 0.5, radius * a, iz * h * radius]
1612        for iy in range(ny):
1613            y = y0 + nul + iy * d * radius
1614            if iy % 2:
1615                xs = x
1616            else:
1617                xs = x + radius * 0.5
1618            if iz % 2:
1619                p = np.c_[xs, y, z] + [dx, dy, dz]
1620            else:
1621                p = np.c_[xs, y, z] + [0, 0, dz]
1622            pts += p.tolist()
1623    return np.array(pts)

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

def humansort(alist) -> list:
1089def humansort(alist) -> list:
1090    """
1091    Sort in place a given list the way humans expect.
1092
1093    E.g. `['file11', 'file1'] -> ['file1', 'file11']`
1094
1095    .. warning:: input list is modified in-place by this function.
1096    """
1097    import re
1098
1099    def alphanum_key(s):
1100        # Turn a string into a list of string and number chunks.
1101        # e.g. "z23a" -> ["z", 23, "a"]
1102        def tryint(s):
1103            if s.isdigit():
1104                return int(s)
1105            return s
1106
1107        return [tryint(c) for c in re.split("([0-9]+)", s)]
1108
1109    alist.sort(key=alphanum_key)
1110    return alist  # NB: input list is modified

Sort in place a given list the way humans expect.

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

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

Define a vtkCamera with a particular orientation.

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

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

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

Define a vtkCamera from a neuroglancer state dictionary.

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

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

def camera_from_dict(camera, modify_inplace=None) -> vtkmodules.vtkRenderingCore.vtkCamera:
2129def camera_from_dict(camera, modify_inplace=None) -> vtki.vtkCamera:
2130    """
2131    Generate a `vtkCamera` object from a python dictionary.
2132
2133    Parameters of the camera are:
2134        - `position` or `pos` (3-tuple)
2135        - `focal_point` (3-tuple)
2136        - `viewup` (3-tuple)
2137        - `distance` (float)
2138        - `clipping_range` (2-tuple)
2139        - `parallel_scale` (float)
2140        - `thickness` (float)
2141        - `view_angle` (float)
2142        - `roll` (float)
2143
2144    Exaplanation of the parameters can be found in the
2145    [vtkCamera documentation](https://vtk.org/doc/nightly/html/classvtkCamera.html).
2146
2147    Arguments:
2148        camera: (dict)
2149            a python dictionary containing camera parameters.
2150        modify_inplace: (vtkCamera)
2151            an existing `vtkCamera` object to modify in place.
2152
2153    Returns:
2154        `vtki.vtkCamera`, a vtk camera setup that matches this state.
2155    """
2156    if modify_inplace:
2157        vcam = modify_inplace
2158    else:
2159        vcam = vtki.vtkCamera()
2160
2161    camera = dict(camera)  # make a copy so input is not emptied by pop()
2162
2163    cm_pos         = camera.pop("position", camera.pop("pos", None))
2164    cm_focal_point = camera.pop("focal_point", camera.pop("focalPoint", None))
2165    cm_viewup      = camera.pop("viewup", None)
2166    cm_distance    = camera.pop("distance", None)
2167    cm_clipping_range = camera.pop("clipping_range", camera.pop("clippingRange", None))
2168    cm_parallel_scale = camera.pop("parallel_scale", camera.pop("parallelScale", None))
2169    cm_thickness   = camera.pop("thickness", None)
2170    cm_view_angle  = camera.pop("view_angle", camera.pop("viewAngle", None))
2171    cm_roll        = camera.pop("roll", None)
2172
2173    if len(camera.keys()) > 0:
2174        vedo.logger.warning(f"in camera_from_dict, key(s) not recognized: {camera.keys()}")
2175    if cm_pos is not None:            vcam.SetPosition(cm_pos)
2176    if cm_focal_point is not None:    vcam.SetFocalPoint(cm_focal_point)
2177    if cm_viewup is not None:         vcam.SetViewUp(cm_viewup)
2178    if cm_distance is not None:       vcam.SetDistance(cm_distance)
2179    if cm_clipping_range is not None: vcam.SetClippingRange(cm_clipping_range)
2180    if cm_parallel_scale is not None: vcam.SetParallelScale(cm_parallel_scale)
2181    if cm_thickness is not None:      vcam.SetThickness(cm_thickness)
2182    if cm_view_angle is not None:     vcam.SetViewAngle(cm_view_angle)
2183    if cm_roll is not None:           vcam.SetRoll(cm_roll)
2184    return vcam

Generate a vtkCamera object from a python dictionary.

Parameters of the camera are:
  • position or pos (3-tuple)
  • focal_point (3-tuple)
  • viewup (3-tuple)
  • distance (float)
  • clipping_range (2-tuple)
  • parallel_scale (float)
  • thickness (float)
  • view_angle (float)
  • roll (float)

Exaplanation of the parameters can be found in the vtkCamera documentation.

Arguments:
  • camera: (dict) a python dictionary containing camera parameters.
  • modify_inplace: (vtkCamera) an existing vtkCamera object to modify in place.
Returns:

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

def camera_to_dict(vtkcam) -> dict:
2186def camera_to_dict(vtkcam) -> dict:
2187    """
2188    Convert a [vtkCamera](https://vtk.org/doc/nightly/html/classvtkCamera.html)
2189    object into a python dictionary.
2190
2191    Parameters of the camera are:
2192        - `position` (3-tuple)
2193        - `focal_point` (3-tuple)
2194        - `viewup` (3-tuple)
2195        - `distance` (float)
2196        - `clipping_range` (2-tuple)
2197        - `parallel_scale` (float)
2198        - `thickness` (float)
2199        - `view_angle` (float)
2200        - `roll` (float)
2201
2202    Arguments:
2203        vtkcam: (vtkCamera)
2204            a `vtkCamera` object to convert.
2205    """
2206    cam = dict()
2207    cam["position"] = np.array(vtkcam.GetPosition())
2208    cam["focal_point"] = np.array(vtkcam.GetFocalPoint())
2209    cam["viewup"] = np.array(vtkcam.GetViewUp())
2210    cam["distance"] = vtkcam.GetDistance()
2211    cam["clipping_range"] = np.array(vtkcam.GetClippingRange())
2212    cam["parallel_scale"] = vtkcam.GetParallelScale()
2213    cam["thickness"] = vtkcam.GetThickness()
2214    cam["view_angle"] = vtkcam.GetViewAngle()
2215    cam["roll"] = vtkcam.GetRoll()
2216    return cam

Convert a vtkCamera object into a python dictionary.

Parameters of the camera are:
  • position (3-tuple)
  • focal_point (3-tuple)
  • viewup (3-tuple)
  • distance (float)
  • clipping_range (2-tuple)
  • parallel_scale (float)
  • thickness (float)
  • view_angle (float)
  • roll (float)
Arguments:
  • vtkcam: (vtkCamera) a vtkCamera object to convert.
def oriented_camera( center=(0, 0, 0), up_vector=(0, 1, 0), backoff_vector=(0, 0, 1), backoff=1.0) -> vtkmodules.vtkRenderingCore.vtkCamera:
2114def oriented_camera(center=(0, 0, 0), up_vector=(0, 1, 0), backoff_vector=(0, 0, 1), backoff=1.0) -> vtki.vtkCamera:
2115    """
2116    Generate a `vtkCamera` pointed at a specific location,
2117    oriented with a given up direction, set to a backoff.
2118    """
2119    vup = np.array(up_vector)
2120    vup = vup / np.linalg.norm(vup)
2121    pt_backoff = center - backoff * np.array(backoff_vector)
2122    camera = vtki.vtkCamera()
2123    camera.SetFocalPoint(center[0], center[1], center[2])
2124    camera.SetViewUp(vup[0], vup[1], vup[2])
2125    camera.SetPosition(pt_backoff[0], pt_backoff[1], pt_backoff[2])
2126    return camera

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

def vedo2trimesh(mesh):
2442def vedo2trimesh(mesh):
2443    """
2444    Convert `vedo.mesh.Mesh` to `Trimesh.Mesh` object.
2445    """
2446    if is_sequence(mesh):
2447        tms = []
2448        for a in mesh:
2449            tms.append(vedo2trimesh(a))
2450        return tms
2451
2452    try:
2453        from trimesh import Trimesh
2454    except ModuleNotFoundError:
2455        vedo.logger.error("Need trimesh to run:\npip install trimesh")
2456        return None
2457
2458    tris = mesh.cells
2459    carr = mesh.celldata["CellIndividualColors"]
2460    ccols = carr
2461
2462    points = mesh.vertices
2463    varr = mesh.pointdata["VertexColors"]
2464    vcols = varr
2465
2466    if len(tris) == 0:
2467        tris = None
2468
2469    return Trimesh(vertices=points, faces=tris, face_colors=ccols, vertex_colors=vcols)

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

def trimesh2vedo(inputobj):
2472def trimesh2vedo(inputobj):
2473    """
2474    Convert a `Trimesh` object to `vedo.Mesh` or `vedo.Assembly` object.
2475    """
2476    if is_sequence(inputobj):
2477        vms = []
2478        for ob in inputobj:
2479            vms.append(trimesh2vedo(ob))
2480        return vms
2481
2482    inputobj_type = str(type(inputobj))
2483
2484    if "Trimesh" in inputobj_type or "primitives" in inputobj_type:
2485        faces = inputobj.faces
2486        poly = buildPolyData(inputobj.vertices, faces)
2487        tact = vedo.Mesh(poly)
2488        if inputobj.visual.kind == "face":
2489            trim_c = inputobj.visual.face_colors
2490        elif inputobj.visual.kind == "texture":
2491            trim_c = inputobj.visual.to_color().vertex_colors
2492        else:
2493            trim_c = inputobj.visual.vertex_colors
2494
2495        if is_sequence(trim_c):
2496            if is_sequence(trim_c[0]):
2497                same_color = len(np.unique(trim_c, axis=0)) < 2  # all vtxs have same color
2498
2499                if same_color:
2500                    tact.c(trim_c[0, [0, 1, 2]]).alpha(trim_c[0, 3])
2501                else:
2502                    if inputobj.visual.kind == "face":
2503                        tact.cellcolors = trim_c
2504        return tact
2505
2506    if "PointCloud" in inputobj_type:
2507
2508        vdpts = vedo.shapes.Points(inputobj.vertices, r=8, c='k')
2509        if hasattr(inputobj, "vertices_color"):
2510            vcols = (inputobj.vertices_color * 1).astype(np.uint8)
2511            vdpts.pointcolors = vcols
2512        return vdpts
2513
2514    if "path" in inputobj_type:
2515
2516        lines = []
2517        for e in inputobj.entities:
2518            # print('trimesh entity', e.to_dict())
2519            l = vedo.shapes.Line(inputobj.vertices[e.points], c="k", lw=2)
2520            lines.append(l)
2521        return vedo.Assembly(lines)
2522
2523    return None

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

def vedo2meshlab(vmesh):
2526def vedo2meshlab(vmesh):
2527    """Convert a `vedo.Mesh` to a Meshlab object."""
2528    try:
2529        import pymeshlab as mlab
2530    except ModuleNotFoundError:
2531        vedo.logger.error("Need pymeshlab to run:\npip install pymeshlab")
2532
2533    vertex_matrix = vmesh.vertices.astype(np.float64)
2534
2535    try:
2536        face_matrix = np.asarray(vmesh.cells, dtype=np.float64)
2537    except:
2538        print("WARNING: in vedo2meshlab(), need to triangulate mesh first!")
2539        face_matrix = np.array(vmesh.clone().triangulate().cells, dtype=np.float64)
2540
2541    # v_normals_matrix = vmesh.normals(cells=False, recompute=False)
2542    v_normals_matrix = vmesh.vertex_normals
2543    if not v_normals_matrix.shape[0]:
2544        v_normals_matrix = np.empty((0, 3), dtype=np.float64)
2545
2546    # f_normals_matrix = vmesh.normals(cells=True, recompute=False)
2547    f_normals_matrix = vmesh.cell_normals
2548    if not f_normals_matrix.shape[0]:
2549        f_normals_matrix = np.empty((0, 3), dtype=np.float64)
2550
2551    v_color_matrix = vmesh.pointdata["RGBA"]
2552    if v_color_matrix is None:
2553        v_color_matrix = np.empty((0, 4), dtype=np.float64)
2554    else:
2555        v_color_matrix = v_color_matrix.astype(np.float64) / 255
2556        if v_color_matrix.shape[1] == 3:
2557            v_color_matrix = np.c_[
2558                v_color_matrix, np.ones(v_color_matrix.shape[0], dtype=np.float64)
2559            ]
2560
2561    f_color_matrix = vmesh.celldata["RGBA"]
2562    if f_color_matrix is None:
2563        f_color_matrix = np.empty((0, 4), dtype=np.float64)
2564    else:
2565        f_color_matrix = f_color_matrix.astype(np.float64) / 255
2566        if f_color_matrix.shape[1] == 3:
2567            f_color_matrix = np.c_[
2568                f_color_matrix, np.ones(f_color_matrix.shape[0], dtype=np.float64)
2569            ]
2570
2571    m = mlab.Mesh(
2572        vertex_matrix=vertex_matrix,
2573        face_matrix=face_matrix,
2574        v_normals_matrix=v_normals_matrix,
2575        f_normals_matrix=f_normals_matrix,
2576        v_color_matrix=v_color_matrix,
2577        f_color_matrix=f_color_matrix,
2578    )
2579
2580    for k in vmesh.pointdata.keys():
2581        data = vmesh.pointdata[k]
2582        if data is not None:
2583            if data.ndim == 1:  # scalar
2584                m.add_vertex_custom_scalar_attribute(data.astype(np.float64), k)
2585            elif data.ndim == 2:  # vectorial data
2586                if "tcoord" not in k.lower() and k not in ["Normals", "TextureCoordinates"]:
2587                    m.add_vertex_custom_point_attribute(data.astype(np.float64), k)
2588
2589    for k in vmesh.celldata.keys():
2590        data = vmesh.celldata[k]
2591        if data is not None:
2592            if data.ndim == 1:  # scalar
2593                m.add_face_custom_scalar_attribute(data.astype(np.float64), k)
2594            elif data.ndim == 2 and k != "Normals":  # vectorial data
2595                m.add_face_custom_point_attribute(data.astype(np.float64), k)
2596
2597    m.update_bounding_box()
2598    return m

Convert a vedo.Mesh to a Meshlab object.

def meshlab2vedo(mmesh, pointdata_keys=(), celldata_keys=()):
2601def meshlab2vedo(mmesh, pointdata_keys=(), celldata_keys=()):
2602    """Convert a Meshlab object to `vedo.Mesh`."""
2603    inputtype = str(type(mmesh))
2604
2605    if "MeshSet" in inputtype:
2606        mmesh = mmesh.current_mesh()
2607
2608    mpoints, mcells = mmesh.vertex_matrix(), mmesh.face_matrix()
2609    if len(mcells) > 0:
2610        polydata = buildPolyData(mpoints, mcells)
2611    else:
2612        polydata = buildPolyData(mpoints, None)
2613
2614    if mmesh.has_vertex_scalar():
2615        parr = mmesh.vertex_scalar_array()
2616        parr_vtk = numpy_to_vtk(parr)
2617        parr_vtk.SetName("MeshLabScalars")
2618        polydata.GetPointData().AddArray(parr_vtk)
2619        polydata.GetPointData().SetActiveScalars("MeshLabScalars")
2620
2621    if mmesh.has_face_scalar():
2622        carr = mmesh.face_scalar_array()
2623        carr_vtk = numpy_to_vtk(carr)
2624        carr_vtk.SetName("MeshLabScalars")
2625        x0, x1 = carr_vtk.GetRange()
2626        polydata.GetCellData().AddArray(carr_vtk)
2627        polydata.GetCellData().SetActiveScalars("MeshLabScalars")
2628
2629    for k in pointdata_keys:
2630        parr = mmesh.vertex_custom_scalar_attribute_array(k)
2631        parr_vtk = numpy_to_vtk(parr)
2632        parr_vtk.SetName(k)
2633        polydata.GetPointData().AddArray(parr_vtk)
2634        polydata.GetPointData().SetActiveScalars(k)
2635
2636    for k in celldata_keys:
2637        carr = mmesh.face_custom_scalar_attribute_array(k)
2638        carr_vtk = numpy_to_vtk(carr)
2639        carr_vtk.SetName(k)
2640        polydata.GetCellData().AddArray(carr_vtk)
2641        polydata.GetCellData().SetActiveScalars(k)
2642
2643    pnorms = mmesh.vertex_normal_matrix()
2644    if len(pnorms) > 0:
2645        polydata.GetPointData().SetNormals(numpy2vtk(pnorms, name="Normals"))
2646
2647    cnorms = mmesh.face_normal_matrix()
2648    if len(cnorms) > 0:
2649        polydata.GetCellData().SetNormals(numpy2vtk(cnorms, name="Normals"))
2650    return vedo.Mesh(polydata)

Convert a Meshlab object to vedo.Mesh.

def vedo2open3d(vedo_mesh):
2661def vedo2open3d(vedo_mesh):
2662    """
2663    Return an `open3d.geometry.TriangleMesh` version of the current mesh.
2664    """
2665    try:
2666        import open3d as o3d
2667    except RuntimeError:
2668        vedo.logger.error("Need open3d to run:\npip install open3d")
2669
2670    # create from numpy arrays
2671    o3d_mesh = o3d.geometry.TriangleMesh(
2672        vertices=o3d.utility.Vector3dVector(vedo_mesh.vertices),
2673        triangles=o3d.utility.Vector3iVector(vedo_mesh.cells),
2674    )
2675    # TODO: need to add some if check here in case color and normals
2676    #  info are not existing
2677    # o3d_mesh.vertex_colors = o3d.utility.Vector3dVector(vedo_mesh.pointdata["RGB"]/255)
2678    # o3d_mesh.vertex_normals= o3d.utility.Vector3dVector(vedo_mesh.pointdata["Normals"])
2679    return o3d_mesh

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

def open3d2vedo(o3d_mesh):
2653def open3d2vedo(o3d_mesh):
2654    """Convert `open3d.geometry.TriangleMesh` to a `vedo.Mesh`."""
2655    m = vedo.Mesh([np.array(o3d_mesh.vertices), np.array(o3d_mesh.triangles)])
2656    # TODO: could also check whether normals and color are present in
2657    # order to port with the above vertices/faces
2658    return m

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

def vtk2numpy(varr):
792def vtk2numpy(varr):
793    """Convert a `vtkDataArray`, `vtkIdList` or `vtTransform` into a numpy array."""
794    if varr is None:
795        return np.array([])
796    if isinstance(varr, vtki.vtkIdList):
797        return np.array([varr.GetId(i) for i in range(varr.GetNumberOfIds())])
798    elif isinstance(varr, vtki.vtkBitArray):
799        carr = vtki.vtkCharArray()
800        carr.DeepCopy(varr)
801        varr = carr
802    elif isinstance(varr, vtki.vtkHomogeneousTransform):
803        try:
804            varr = varr.GetMatrix()
805        except AttributeError:
806            pass
807        n = 4
808        M = [[varr.GetElement(i, j) for j in range(n)] for i in range(n)]
809        return np.array(M)
810    return vtk_to_numpy(varr)

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

def numpy2vtk(arr, dtype=None, deep=True, name=''):
767def numpy2vtk(arr, dtype=None, deep=True, name=""):
768    """
769    Convert a numpy array into a `vtkDataArray`.
770    Use `dtype='id'` for `vtkIdTypeArray` objects.
771    """
772    # https://github.com/Kitware/VTK/blob/master/Wrapping/Python/vtkmodules/util/numpy_support.py
773    if arr is None:
774        return None
775
776    arr = np.ascontiguousarray(arr)
777
778    if dtype == "id":
779        varr = numpy_to_vtkIdTypeArray(arr.astype(np.int64), deep=deep)
780    elif dtype:
781        varr = numpy_to_vtk(arr.astype(dtype), deep=deep)
782    else:
783        # let numpy_to_vtk() decide what is best type based on arr type
784        if arr.dtype == np.bool_:
785            arr = arr.astype(np.uint8)
786        varr = numpy_to_vtk(arr, deep=deep)
787
788    if name:
789        varr.SetName(name)
790    return varr

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

def get_uv(p, x, v):
1451def get_uv(p, x, v):
1452    """
1453    Obtain the texture uv-coords of a point p belonging to a face that has point
1454    coordinates (x0, x1, x2) with the corresponding uv-coordinates v=(v0, v1, v2).
1455    All p and x0,x1,x2 are 3D-vectors, while v are their 2D uv-coordinates.
1456
1457    Example:
1458        ```python
1459        from vedo import *
1460
1461        pic = Image(dataurl+"coloured_cube_faces.jpg")
1462        cb = Mesh(dataurl+"coloured_cube.obj").lighting("off").texture(pic)
1463
1464        cbpts = cb.vertices
1465        faces = cb.cells
1466        uv = cb.pointdata["Material"]
1467
1468        pt = [-0.2, 0.75, 2]
1469        pr = cb.closest_point(pt)
1470
1471        idface = cb.closest_point(pt, return_cell_id=True)
1472        idpts = faces[idface]
1473        uv_face = uv[idpts]
1474
1475        uv_pr = utils.get_uv(pr, cbpts[idpts], uv_face)
1476        print("interpolated uv =", uv_pr)
1477
1478        sx, sy = pic.dimensions()
1479        i_interp_uv = uv_pr * [sy, sx]
1480        ix, iy = i_interp_uv.astype(int)
1481        mpic = pic.tomesh()
1482        rgba = mpic.pointdata["RGBA"].reshape(sy, sx, 3)
1483        print("color =", rgba[ix, iy])
1484
1485        show(
1486            [[cb, Point(pr), cb.labels("Material")],
1487                [pic, Point(i_interp_uv)]],
1488            N=2, axes=1, sharecam=False,
1489        ).close()
1490        ```
1491        ![](https://vedo.embl.es/images/feats/utils_get_uv.png)
1492    """
1493    # Vector vp=p-x0 is representable as alpha*s + beta*t,
1494    # where s = x1-x0 and t = x2-x0, in matrix form
1495    # vp = [alpha, beta] . matrix(s,t)
1496    # M = matrix(s,t) is 2x3 matrix, so (alpha, beta) can be found by
1497    # inverting any of its minor A with non-zero determinant.
1498    # Once found, uv-coords of p are vt0 + alpha (vt1-v0) + beta (vt2-v0)
1499
1500    p = np.asarray(p)
1501    x0, x1, x2 = np.asarray(x)[:3]
1502    vt0, vt1, vt2 = np.asarray(v)[:3]
1503
1504    s = x1 - x0
1505    t = x2 - x0
1506    vs = vt1 - vt0
1507    vt = vt2 - vt0
1508    vp = p - x0
1509
1510    # finding a minor with independent rows
1511    M = np.matrix([s, t])
1512    mnr = [0, 1]
1513    A = M[:, mnr]
1514    if np.abs(np.linalg.det(A)) < 0.000001:
1515        mnr = [0, 2]
1516        A = M[:, mnr]
1517        if np.abs(np.linalg.det(A)) < 0.000001:
1518            mnr = [1, 2]
1519            A = M[:, mnr]
1520    Ainv = np.linalg.inv(A)
1521    alpha_beta = vp[mnr].dot(Ainv)  # [alpha, beta]
1522    return np.asarray(vt0 + alpha_beta.dot(np.matrix([vs, vt])))[0]

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

Example:
from vedo import *

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

cbpts = cb.vertices
faces = cb.cells
uv = cb.pointdata["Material"]

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

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

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

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

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

def andrews_curves(M, res=100) -> numpy.ndarray:
712def andrews_curves(M, res=100) -> np.ndarray:
713    """
714    Computes the [Andrews curves](https://en.wikipedia.org/wiki/Andrews_plot)
715    for the provided data.
716
717    The input array is an array of shape (n,m) where n is the number of
718    features and m is the number of observations.
719    
720    Arguments:
721        M : (ndarray)
722            the data matrix (or data vector).
723        res : (int)
724            the resolution (n. of points) of the output curve.
725    
726    Example:
727        - [andrews_cluster.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/andrews_cluster.py)
728    
729        ![](https://vedo.embl.es/images/pyplot/andrews_cluster.png)
730    """
731    # Credits:
732    # https://gist.github.com/ryuzakyl/12c221ff0e54d8b1ac171c69ea552c0a
733    M = np.asarray(M)
734    m = int(res + 0.5)
735
736    # getting data vectors
737    X = np.reshape(M, (1, -1)) if len(M.shape) == 1 else M.copy()
738    _rows, n = X.shape
739
740    # andrews curve dimension (n. theta angles)
741    t = np.linspace(-np.pi, np.pi, m)
742
743    # m: range of values for angle theta
744    # n: amount of components of the Fourier expansion
745    A = np.empty((m, n))
746
747    # setting first column of A
748    A[:, 0] = [1/np.sqrt(2)] * m
749
750    # filling columns of A
751    for i in range(1, n):
752        # computing the scaling coefficient for angle theta
753        c = np.ceil(i / 2)
754        # computing i-th column of matrix A
755        col = np.sin(c * t) if i % 2 == 1 else np.cos(c * t)
756        # setting column in matrix A
757        A[:, i] = col[:]
758
759    # computing Andrews curves for provided data
760    andrew_curves = np.dot(A, X.T).T
761
762    # returning the Andrews Curves (raveling if needed)
763    return np.ravel(andrew_curves) if andrew_curves.shape[0] == 1 else andrew_curves

Computes the Andrews curves for the provided data.

The input array is an array of shape (n,m) where n is the number of features and m is the number of observations.

Arguments:
  • M : (ndarray) the data matrix (or data vector).
  • res : (int) the resolution (n. of points) of the output curve.
Example: