vedo.file_io

Submodule to read/write meshes and other objects in different formats, and other I/O functionalities.

   1import glob
   2import os
   3import time
   4from tempfile import NamedTemporaryFile, TemporaryDirectory
   5
   6import numpy as np
   7
   8try:
   9    import vedo.vtkclasses as vtk
  10except ImportError:
  11    import vtkmodules.all as vtk
  12
  13import vedo
  14from vedo import settings
  15from vedo import colors
  16from vedo import utils
  17from vedo.assembly import Assembly
  18from vedo.picture import Picture
  19from vedo.pointcloud import Points
  20from vedo.mesh import Mesh
  21from vedo.volume import Volume
  22
  23__docformat__ = "google"
  24
  25__doc__ = """
  26Submodule to read/write meshes and other objects in different formats,
  27and other I/O functionalities.
  28"""
  29
  30__all__ = [
  31    "load",
  32    "download",
  33    "gunzip",
  34    "loadStructuredPoints",
  35    "loadStructuredGrid",
  36    "loadRectilinearGrid",
  37    "loadUnStructuredGrid",
  38    "load_transform",
  39    "write_transform",
  40    "write",
  41    "export_window",
  42    "import_window",
  43    "screenshot",
  44    "ask",
  45    "Video",
  46]
  47
  48
  49# example web page for X3D
  50_x3d_html = """
  51<!DOCTYPE html>
  52<html lang="en">
  53<head>
  54  <meta charset="UTF-8">
  55  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  56  <title> vedo with x3d </title>
  57
  58  <!-- THESE ARE THE RELEVANT LINES: -->
  59  <script src='https://www.x3dom.org/download/x3dom.js'> </script>
  60  <link rel='stylesheet' type='text/css' href='https://www.x3dom.org/download/x3dom.css'/>
  61
  62  <style>
  63     table, td, th { border: 1px solid black; background-color: powderblue;}
  64     table {width: 70%; border-collapse: collapse;}
  65     table th {width: 35%;}
  66  </style>
  67</head>
  68
  69<body style="font-family: Verdana">
  70  <h1>Example html generated by vedo</h1>
  71  This example loads a 3D scene from file ~fileoutput generated by
  72  <a href="https://github.com/marcomusy/vedo">vedo</a>
  73  (see <a href="https://github.com/marcomusy/vedo/tree/master/examples/other/export_x3d.py">export_x3d.py</a>).
  74  <br><br>
  75
  76
  77  <!-- THESE ARE THE RELEVANT LINES: -->
  78  <x3d width='~widthpx' height='~heightpx'>
  79     <scene>
  80        <Inline url="~fileoutput"> </Inline>
  81     </scene>
  82  </x3d>
  83
  84  <h3>Nothing shows up above this line?</h3>
  85  Enable your browser to load local files:
  86  <br><b>Firefox</b>: type <code>about:config</code> in the URL bar and
  87  change <code>privacy.file_unique_origin</code> from <code>True</code> to <code>False</code>
  88  <br><b>Chrome</b>: from terminal type:
  89  <code>google-chrome --enable-webgl --allow-file-access-from-files</code>
  90  (see <a href="https://cmatskas.com/interacting-with-local-data-files-using-chrome/">here</a>)
  91
  92  <br>
  93  <h3>Controls:</h3>
  94  <h4><strong>Examine Mode (activate with key 'e'):</strong></h4>
  95  <table>
  96     <tbody>
  97        <tr class="even description">
  98           <th>Button</th>
  99           <th>Function</th>
 100        </tr>
 101        <tr>
 102           <td>Left Button / Left Button + Shift</td>
 103           <td>Rotate</td>
 104        </tr>
 105        <tr>
 106           <td>Mid Button / Left Button + Ctl</td>
 107           <td>Pan</td>
 108        </tr>
 109        <tr>
 110           <td>Right Button / Wheel / Left Button + Alt</td>
 111           <td>Zoom</td>
 112        </tr>
 113        <tr>
 114           <td>Left double click</td>
 115           <td>Set center of rotation</td>
 116        </tr>
 117     </tbody>
 118  </table>
 119  <h4><strong>Walk Mode (activate with key 'w'):</strong></h4>
 120  <table>
 121     <tbody>
 122        <tr class="even description">
 123           <th>Button</th>
 124           <th>Function</th>
 125        </tr>
 126        <tr>
 127           <td>Left Button</td>
 128           <td>Move forward</td>
 129        </tr>
 130        <tr>
 131           <td>Right Button</td>
 132           <td>Move backward</td>
 133        </tr>
 134     </tbody>
 135  </table>
 136  <h4><strong>Fly Mode (activate with key 'f'):</strong></h4>
 137  <table>
 138     <tbody>
 139        <tr class="even description">
 140           <th>Button</th>
 141           <th>Function</th>
 142        </tr>
 143        <tr>
 144           <td>Left Button</td>
 145           <td>Move forward</td>
 146        </tr>
 147        <tr>
 148           <td>Right Button</td>
 149           <td>Move backward</td>
 150        </tr>
 151     </tbody>
 152  </table>
 153  <h3>Non-interactive camera movement</h3>
 154  <table>
 155     <tbody>
 156        <tr class="even description">
 157           <th>Key</th>
 158           <th>Function</th>
 159        </tr>
 160        <tr>
 161           <td>r</td>
 162           <td>reset view</td>
 163        </tr>
 164        <tr>
 165           <td>a</td>
 166           <td>show all</td>
 167        </tr>
 168        <tr>
 169           <td>u</td>
 170           <td>upright</td>
 171        </tr>
 172     </tbody>
 173  </table>
 174</body>
 175</html>
 176"""
 177
 178
 179def load(inputobj, unpack=True, force=False):
 180    """
 181    Load any vedo objects from file or from the web.
 182
 183    The output will depend on the file extension. See examples below.
 184    Unzip is made on the fly, if file ends with `.gz`.
 185    Can load an object directly from a URL address.
 186
 187    Arguments:
 188        unpack : bool
 189            unpack MultiBlockData into a flat list of objects.
 190
 191        force : bool
 192            when downloading a file ignore any previous cached downloads and force a new one.
 193
 194    Example:
 195        ```python
 196        from vedo import dataurl, load, show
 197        # Return a list of 2 meshes
 198        g = load([dataurl+'250.vtk', dataurl+'270.vtk'])
 199        show(g)
 200        # Return a list of meshes by reading all files in a directory
 201        # (if directory contains DICOM files then a Volume is returned)
 202        g = load('mydicomdir/')
 203        show(g)
 204        ```
 205    """
 206    acts = []
 207    if utils.is_sequence(inputobj):
 208        flist = inputobj
 209    elif isinstance(inputobj, str) and inputobj.startswith("https://"):
 210        flist = [inputobj]
 211    else:
 212        flist = sorted(glob.glob(inputobj))
 213
 214    for fod in flist:
 215
 216        if fod.startswith("https://"):
 217            fod = download(fod, force=force, verbose=False)
 218
 219        if os.path.isfile(fod):  ### it's a file
 220
 221            if fod.endswith(".gz"):
 222                fod = gunzip(fod)
 223
 224            a = _load_file(fod, unpack)
 225            acts.append(a)
 226
 227        elif os.path.isdir(fod):  ### it's a directory or DICOM
 228            flist = os.listdir(fod)
 229            if ".dcm" in flist[0]:  ### it's DICOM
 230                reader = vtk.vtkDICOMImageReader()
 231                reader.SetDirectoryName(fod)
 232                reader.Update()
 233                image = reader.GetOutput()
 234                actor = Volume(image)
 235
 236                actor.info["PixelSpacing"] = reader.GetPixelSpacing()
 237                actor.info["Width"] = reader.GetWidth()
 238                actor.info["Height"] = reader.GetHeight()
 239                actor.info["PositionPatient"] = reader.GetImagePositionPatient()
 240                actor.info["OrientationPatient"] = reader.GetImageOrientationPatient()
 241                actor.info["BitsAllocated"] = reader.GetBitsAllocated()
 242                actor.info["PixelRepresentation"] = reader.GetPixelRepresentation()
 243                actor.info["NumberOfComponents"] = reader.GetNumberOfComponents()
 244                actor.info["TransferSyntaxUID"] = reader.GetTransferSyntaxUID()
 245                actor.info["RescaleSlope"] = reader.GetRescaleSlope()
 246                actor.info["RescaleOffset"] = reader.GetRescaleOffset()
 247                actor.info["PatientName"] = reader.GetPatientName()
 248                actor.info["StudyUID"] = reader.GetStudyUID()
 249                actor.info["StudyID"] = reader.GetStudyID()
 250                actor.info["GantryAngle"] = reader.GetGantryAngle()
 251
 252                acts.append(actor)
 253
 254            else:  ### it's a normal directory
 255                utils.humansort(flist)
 256                for ifile in flist:
 257                    a = _load_file(fod + "/" + ifile, unpack)
 258                    acts.append(a)
 259        else:
 260            vedo.logger.error(f"in load(), cannot find {fod}")
 261
 262    if len(acts) == 1:
 263        if "numpy" in str(type(acts[0])):
 264            return acts[0]
 265        if not acts[0]:
 266            vedo.logger.error(f"in load(), cannot load {inputobj}")
 267        return acts[0]
 268
 269    if len(acts) == 0:
 270        vedo.logger.error(f"in load(), cannot load {inputobj}")
 271        return None
 272
 273    else:
 274        return acts
 275
 276
 277def _load_file(filename, unpack):
 278    fl = filename.lower()
 279
 280    ################################################################# other formats:
 281    if fl.endswith(".xml") or fl.endswith(".xml.gz") or fl.endswith(".xdmf"):
 282        # Fenics tetrahedral file
 283        actor = loadDolfin(filename)
 284    elif fl.endswith(".neutral") or fl.endswith(".neu"):  # neutral tetrahedral file
 285        actor = loadNeutral(filename)
 286    elif fl.endswith(".gmsh"):  # gmesh file
 287        actor = loadGmesh(filename)
 288    elif fl.endswith(".pcd"):  # PCL point-cloud format
 289        actor = loadPCD(filename)
 290        actor.GetProperty().SetPointSize(2)
 291    elif fl.endswith(".off"):
 292        actor = loadOFF(filename)
 293    elif fl.endswith(".3ds"):  # 3ds format
 294        actor = load3DS(filename)
 295    elif fl.endswith(".wrl"):
 296        importer = vtk.vtkVRMLImporter()
 297        importer.SetFileName(filename)
 298        importer.Read()
 299        importer.Update()
 300        actors = importer.GetRenderer().GetActors()  # vtkActorCollection
 301        actors.InitTraversal()
 302        wacts = []
 303        for i in range(actors.GetNumberOfItems()):
 304            act = actors.GetNextActor()
 305            wacts.append(act)
 306        actor = Assembly(wacts)
 307
 308        ################################################################# volumetric:
 309    elif (
 310        fl.endswith(".tif")
 311        or fl.endswith(".tiff")
 312        or fl.endswith(".slc")
 313        or fl.endswith(".vti")
 314        or fl.endswith(".mhd")
 315        or fl.endswith(".nrrd")
 316        or fl.endswith(".nii")
 317        or fl.endswith(".dem")
 318    ):
 319        img = loadImageData(filename)
 320        actor = Volume(img)
 321
 322        ################################################################# 2D images:
 323    elif (
 324        fl.endswith(".png")
 325        or fl.endswith(".jpg")
 326        or fl.endswith(".bmp")
 327        or fl.endswith(".jpeg")
 328        or fl.endswith(".gif")
 329    ):
 330        if ".png" in fl:
 331            picr = vtk.vtkPNGReader()
 332        elif ".jpg" in fl or ".jpeg" in fl:
 333            picr = vtk.vtkJPEGReader()
 334        elif ".bmp" in fl:
 335            picr = vtk.vtkBMPReader()
 336        elif ".gif" in fl:
 337            from PIL import Image, ImageSequence
 338
 339            img = Image.open(filename)
 340            frames = []
 341            for frame in ImageSequence.Iterator(img):
 342                a = np.array(frame.convert("RGB").getdata(), dtype=np.uint8)
 343                a = a.reshape([frame.size[1], frame.size[0], 3])
 344                frames.append(Picture(a))
 345            return frames
 346
 347        picr.SetFileName(filename)
 348        picr.Update()
 349        actor = Picture(picr.GetOutput())  # object derived from vtk.vtkImageActor()
 350
 351        ################################################################# multiblock:
 352    elif fl.endswith(".vtm") or fl.endswith(".vtmb"):
 353        read = vtk.vtkXMLMultiBlockDataReader()
 354        read.SetFileName(filename)
 355        read.Update()
 356        mb = read.GetOutput()
 357        if unpack:
 358            acts = []
 359            for i in range(mb.GetNumberOfBlocks()):
 360                b = mb.GetBlock(i)
 361                if isinstance(
 362                    b,
 363                    (
 364                        vtk.vtkPolyData,
 365                        vtk.vtkUnstructuredGrid,
 366                        vtk.vtkStructuredGrid,
 367                        vtk.vtkRectilinearGrid,
 368                    ),
 369                ):
 370                    acts.append(Mesh(b))
 371                elif isinstance(b, vtk.vtkImageData):
 372                    acts.append(Volume(b))
 373                elif isinstance(b, vtk.vtkUnstructuredGrid):
 374                    acts.append(vedo.UGrid(b))
 375            return acts
 376        return mb
 377
 378        ################################################################# numpy:
 379    elif fl.endswith(".npy") or fl.endswith(".npz"):
 380        acts = loadnumpy(filename)
 381
 382        if unpack is False:
 383            return Assembly(acts)
 384        return acts
 385
 386    elif fl.endswith(".geojson"):
 387        return loadGeoJSON(filename)
 388
 389    elif fl.endswith(".pvd"):
 390        return loadPVD(filename)
 391
 392        ################################################################# polygonal mesh:
 393    else:
 394        if fl.endswith(".vtk"):  # read all legacy vtk types
 395
 396            # output can be:
 397            # PolyData, StructuredGrid, StructuredPoints, UnstructuredGrid, RectilinearGrid
 398            reader = vtk.vtkDataSetReader()
 399            reader.ReadAllScalarsOn()
 400            reader.ReadAllVectorsOn()
 401            reader.ReadAllTensorsOn()
 402            reader.ReadAllFieldsOn()
 403            reader.ReadAllNormalsOn()
 404            reader.ReadAllColorScalarsOn()
 405
 406        elif fl.endswith(".ply"):
 407            reader = vtk.vtkPLYReader()
 408        elif fl.endswith(".obj"):
 409            reader = vtk.vtkOBJReader()
 410        elif fl.endswith(".stl"):
 411            reader = vtk.vtkSTLReader()
 412        elif fl.endswith(".byu") or fl.endswith(".g"):
 413            reader = vtk.vtkBYUReader()
 414        elif fl.endswith(".foam"):  # OpenFoam
 415            reader = vtk.vtkOpenFOAMReader()
 416        elif fl.endswith(".pvd"):
 417            reader = vtk.vtkXMLGenericDataObjectReader()
 418        elif fl.endswith(".vtp"):
 419            reader = vtk.vtkXMLPolyDataReader()
 420        elif fl.endswith(".vts"):
 421            reader = vtk.vtkXMLStructuredGridReader()
 422        elif fl.endswith(".vtu"):
 423            reader = vtk.vtkXMLUnstructuredGridReader()
 424        elif fl.endswith(".vtr"):
 425            reader = vtk.vtkXMLRectilinearGridReader()
 426        elif fl.endswith(".pvtk"):
 427            reader = vtk.vtkPDataSetReader()
 428        elif fl.endswith(".pvtr"):
 429            reader = vtk.vtkXMLPRectilinearGridReader()
 430        elif fl.endswith("pvtu"):
 431            reader = vtk.vtkXMLPUnstructuredGridReader()
 432        elif fl.endswith(".txt") or fl.endswith(".xyz"):
 433            reader = vtk.vtkParticleReader()  # (format is x, y, z, scalar)
 434        elif fl.endswith(".facet"):
 435            reader = vtk.vtkFacetReader()
 436        else:
 437            return None
 438
 439        reader.SetFileName(filename)
 440        reader.Update()
 441        routput = reader.GetOutput()
 442
 443        if not routput:
 444            vedo.logger.error(f"unable to load {filename}")
 445            return None
 446
 447        if isinstance(routput, vtk.vtkUnstructuredGrid):
 448            actor = vedo.TetMesh(routput)
 449
 450        else:
 451            actor = Mesh(routput)
 452            if fl.endswith(".txt") or fl.endswith(".xyz"):
 453                actor.GetProperty().SetPointSize(4)
 454
 455    actor.filename = filename
 456    actor.file_size, actor.created = file_info(filename)
 457    return actor
 458
 459
 460def download(url, force=False, verbose=True):
 461    """Retrieve a file from a URL, save it locally and return its path.
 462    Use `force` to force reload and discard cached copies."""
 463
 464    if not url.startswith("https://"):
 465        vedo.logger.error(f"Invalid URL (must start with https):\n{url}")
 466        return url
 467    url = url.replace("www.dropbox", "dl.dropbox")
 468
 469    if "github.com" in url:
 470        url = url.replace("/blob/", "/raw/")
 471
 472    basename = os.path.basename(url)
 473
 474    if "?" in basename:
 475        basename = basename.split("?")[0]
 476
 477    tmp_file = NamedTemporaryFile(delete=False)
 478    tmp_file.name = os.path.join(os.path.dirname(tmp_file.name), os.path.basename(basename))
 479
 480    if not force and os.path.exists(tmp_file.name):
 481        if verbose:
 482            colors.printc("reusing cached file:", tmp_file.name)
 483            # colors.printc("     (use force=True to force a new download)")
 484        return tmp_file.name
 485
 486    try:
 487        from urllib.request import urlopen, Request
 488
 489        req = Request(url, headers={"User-Agent": "Mozilla/5.0"})
 490        if verbose:
 491            colors.printc("reading", basename, "from", url.split("/")[2][:40], "...", end="")
 492
 493    except ImportError:
 494        import urllib2
 495        import contextlib
 496
 497        urlopen = lambda url_: contextlib.closing(urllib2.urlopen(url_))
 498        req = url
 499        if verbose:
 500            colors.printc("reading", basename, "from", url.split("/")[2][:40], "...", end="")
 501
 502    with urlopen(req) as response, open(tmp_file.name, "wb") as output:
 503        output.write(response.read())
 504
 505    if verbose:
 506        colors.printc(" done.")
 507    return tmp_file.name
 508
 509
 510def gunzip(filename):
 511    """Unzip a `.gz` file to a temporary file and returns its path."""
 512    if not filename.endswith(".gz"):
 513        # colors.printc("gunzip() error: file must end with .gz", c='r')
 514        return filename
 515    import gzip
 516
 517    tmp_file = NamedTemporaryFile(delete=False)
 518    tmp_file.name = os.path.join(
 519        os.path.dirname(tmp_file.name), os.path.basename(filename).replace(".gz", "")
 520    )
 521    inF = gzip.open(filename, "rb")
 522    with open(tmp_file.name, "wb") as outF:
 523        outF.write(inF.read())
 524    inF.close()
 525    return tmp_file.name
 526
 527
 528def file_info(file_path):
 529    """Return the file size and creation time of input file"""
 530    sz, created = "", ""
 531    if os.path.isfile(file_path):
 532        file_info = os.stat(file_path)
 533        num = file_info.st_size
 534        for x in ["B", "KB", "MB", "GB", "TB"]:
 535            if num < 1024.0:
 536                break
 537            num /= 1024.0
 538        sz = "%3.1f%s" % (num, x)
 539        created = time.ctime(os.path.getmtime(file_path))
 540    return sz, created
 541
 542
 543###################################################################
 544def loadStructuredPoints(filename):
 545    """Load and return a `vtkStructuredPoints` object from file."""
 546    reader = vtk.vtkStructuredPointsReader()
 547    reader.SetFileName(filename)
 548    reader.Update()
 549    return reader.GetOutput()
 550
 551
 552def loadStructuredGrid(filename):
 553    """Load and return a `vtkStructuredGrid` object from file."""
 554    if filename.endswith(".vts"):
 555        reader = vtk.vtkXMLStructuredGridReader()
 556    else:
 557        reader = vtk.vtkStructuredGridReader()
 558    reader.SetFileName(filename)
 559    reader.Update()
 560    return reader.GetOutput()
 561
 562
 563def loadUnStructuredGrid(filename):
 564    """Load and return a `vtkunStructuredGrid` object from file."""
 565    if filename.endswith(".vtu"):
 566        reader = vtk.vtkXMLUnstructuredGridReader()
 567    else:
 568        reader = vtk.vtkUnstructuredGridReader()
 569    reader.SetFileName(filename)
 570    reader.Update()
 571    return reader.GetOutput()
 572
 573
 574def loadRectilinearGrid(filename):
 575    """Load and return a `vtkRectilinearGrid` object from file."""
 576    if filename.endswith(".vtr"):
 577        reader = vtk.vtkXMLRectilinearGridReader()
 578    else:
 579        reader = vtk.vtkRectilinearGridReader()
 580    reader.SetFileName(filename)
 581    reader.Update()
 582    return reader.GetOutput()
 583
 584
 585def loadXMLData(filename):
 586    """Read any type of vtk data object encoded in XML format."""
 587    reader = vtk.vtkXMLGenericDataObjectReader()
 588    reader.SetFileName(filename)
 589    reader.Update()
 590    return reader.GetOutput()
 591
 592
 593###################################################################
 594def load3DS(filename):
 595    """Load `3DS` file format from file.
 596    Returns:
 597        `Assembly(vtkAssembly)` object.
 598    """
 599    renderer = vtk.vtkRenderer()
 600    renWin = vtk.vtkRenderWindow()
 601    renWin.AddRenderer(renderer)
 602
 603    importer = vtk.vtk3DSImporter()
 604    importer.SetFileName(filename)
 605    importer.ComputeNormalsOn()
 606    importer.SetRenderWindow(renWin)
 607    importer.Update()
 608
 609    actors = renderer.GetActors()  # vtkActorCollection
 610    acts = []
 611    for i in range(actors.GetNumberOfItems()):
 612        a = actors.GetItemAsObject(i)
 613        acts.append(a)
 614    del renWin
 615    return Assembly(acts)
 616
 617
 618def loadOFF(filename):
 619    """Read the OFF file format (polygonal mesh)."""
 620    with open(filename, "r", encoding="UTF-8") as f:
 621        lines = f.readlines()
 622
 623    vertices = []
 624    faces = []
 625    NumberOfVertices = None
 626    i = -1
 627    for text in lines:
 628        if len(text) == 0:
 629            continue
 630        if text == "\n":
 631            continue
 632        if "#" in text:
 633            continue
 634        if "OFF" in text:
 635            continue
 636
 637        ts = text.split()
 638        n = len(ts)
 639
 640        if not NumberOfVertices and n > 1:
 641            NumberOfVertices, NumberOfFaces = int(ts[0]), int(ts[1])
 642            continue
 643        i += 1
 644
 645        if i < NumberOfVertices and n == 3:
 646            x, y, z = float(ts[0]), float(ts[1]), float(ts[2])
 647            vertices.append([x, y, z])
 648
 649        ids = []
 650        if NumberOfVertices <= i < (NumberOfVertices + NumberOfFaces + 1) and n > 2:
 651            ids += [int(xx) for xx in ts[1:]]
 652            faces.append(ids)
 653
 654    return Mesh(utils.buildPolyData(vertices, faces))
 655
 656
 657def loadGeoJSON(filename):
 658    """Load GeoJSON files."""
 659    jr = vtk.vtkGeoJSONReader()
 660    jr.SetFileName(filename)
 661    jr.Update()
 662    return Mesh(jr.GetOutput())
 663
 664
 665def loadDolfin(filename, exterior=False):
 666    """Reads a `Fenics/Dolfin` file format (.xml or .xdmf).
 667    Return an `Mesh` object."""
 668    import dolfin
 669
 670    if filename.lower().endswith(".xdmf"):
 671        f = dolfin.XDMFFile(filename)
 672        m = dolfin.Mesh()
 673        f.read(m)
 674    else:
 675        m = dolfin.Mesh(filename)
 676
 677    bm = dolfin.BoundaryMesh(m, "exterior")
 678
 679    if exterior:
 680        poly = utils.buildPolyData(bm.coordinates(), bm.cells(), tetras=True)
 681    else:
 682        polyb = utils.buildPolyData(bm.coordinates(), bm.cells(), tetras=True)
 683        polym = utils.buildPolyData(m.coordinates(), m.cells(), tetras=True)
 684        app = vtk.vtkAppendPolyData()
 685        app.AddInputData(polym)
 686        app.AddInputData(polyb)
 687        app.Update()
 688        poly = app.GetOutput()
 689    return Mesh(poly).lw(0.1)
 690
 691
 692def loadPVD(filename):
 693    """Reads a paraview set of files."""
 694    import xml.etree.ElementTree as et
 695
 696    tree = et.parse(filename)
 697
 698    dname = os.path.dirname(filename)
 699    if not dname:
 700        dname = "."
 701
 702    listofobjs = []
 703    for coll in tree.getroot():
 704        for dataset in coll:
 705            fname = dataset.get("file")
 706            ob = load(dname + "/" + fname)
 707            tm = dataset.get("timestep")
 708            if tm:
 709                ob.time = tm
 710            listofobjs.append(ob)
 711    if len(listofobjs) == 1:
 712        return listofobjs[0]
 713    if len(listofobjs) == 0:
 714        return None
 715    return listofobjs
 716
 717
 718def loadNeutral(filename):
 719    """Reads a `Neutral` tetrahedral file format. Return an `Mesh` object."""
 720    with open(filename, "r", encoding="UTF-8") as f:
 721        lines = f.readlines()
 722
 723    ncoords = int(lines[0])
 724    coords = []
 725    for i in range(1, ncoords + 1):
 726        x, y, z = lines[i].split()
 727        coords.append([float(x), float(y), float(z)])
 728
 729    ntets = int(lines[ncoords + 1])
 730    idolf_tets = []
 731    for i in range(ncoords + 2, ncoords + ntets + 2):
 732        text = lines[i].split()
 733        v0, v1, v2, v3 = int(text[1]) - 1, int(text[2]) - 1, int(text[3]) - 1, int(text[4]) - 1
 734        idolf_tets.append([v0, v1, v2, v3])
 735
 736    poly = utils.buildPolyData(coords, idolf_tets)
 737    return Mesh(poly)
 738
 739
 740def loadGmesh(filename):
 741    """Reads a `gmesh` file format. Return an `Mesh` object."""
 742    with open(filename, "r", encoding="UTF-8") as f:
 743        lines = f.readlines()
 744
 745    nnodes = 0
 746    index_nodes = 0
 747    for i, line in enumerate(lines):
 748        if "$Nodes" in line:
 749            index_nodes = i + 1
 750            nnodes = int(lines[index_nodes])
 751            break
 752    node_coords = []
 753    for i in range(index_nodes + 1, index_nodes + 1 + nnodes):
 754        cn = lines[i].split()
 755        node_coords.append([float(cn[1]), float(cn[2]), float(cn[3])])
 756
 757    nelements = 0
 758    index_elements = 0
 759    for i, line in enumerate(lines):
 760        if "$Elements" in line:
 761            index_elements = i + 1
 762            nelements = int(lines[index_elements])
 763            break
 764    elements = []
 765    for i in range(index_elements + 1, index_elements + 1 + nelements):
 766        ele = lines[i].split()
 767        elements.append([int(ele[-3]), int(ele[-2]), int(ele[-1])])
 768
 769    poly = utils.buildPolyData(node_coords, elements, indexOffset=1)
 770    return Mesh(poly)
 771
 772
 773def loadPCD(filename):
 774    """Return a `Mesh` made of only vertex points
 775    from `Point Cloud` file format. Return an `Points` object."""
 776    with open(filename, "r", encoding="UTF-8") as f:
 777        lines = f.readlines()
 778
 779    start = False
 780    pts = []
 781    N, expN = 0, 0
 782    for text in lines:
 783        if start:
 784            if N >= expN:
 785                break
 786            l = text.split()
 787            pts.append([float(l[0]), float(l[1]), float(l[2])])
 788            N += 1
 789        if not start and "POINTS" in text:
 790            expN = int(text.split()[1])
 791        if not start and "DATA ascii" in text:
 792            start = True
 793    if expN != N:
 794        vedo.logger.warning(f"Mismatch in PCD file {expN} != {len(pts)}")
 795    poly = utils.buildPolyData(pts)
 796    return Points(poly).pointSize(4)
 797
 798
 799def tonumpy(obj):
 800    """Dump a vedo object to numpy format."""
 801
 802    adict = {}
 803    adict["type"] = "unknown"
 804
 805    ########################################################
 806    def _fillcommon(obj, adict):
 807        adict["filename"] = obj.filename
 808        adict["name"] = obj.name
 809        adict["time"] = obj.time
 810        adict["rendered_at"] = obj.rendered_at
 811        adict["position"] = obj.pos()
 812        adict["info"] = obj.info
 813
 814        try:
 815            # GetMatrix might not exist for non linear transforms
 816            m = np.eye(4)
 817            vm = obj.get_transform().GetMatrix()
 818            for i in [0, 1, 2, 3]:
 819                for j in [0, 1, 2, 3]:
 820                    m[i, j] = vm.GetElement(i, j)
 821            adict["transform"] = m
 822            minv = np.eye(4)
 823            vm.Invert()
 824            for i in [0, 1, 2, 3]:
 825                for j in [0, 1, 2, 3]:
 826                    minv[i, j] = vm.GetElement(i, j)
 827            adict["transform_inverse"] = minv
 828        except AttributeError:
 829            adict["transform"] = []
 830            adict["transform_inverse"] = []
 831
 832    ########################################################
 833    def _fillmesh(obj, adict):
 834
 835        adict["points"] = obj.points(transformed=True).astype(float)
 836        poly = obj.polydata()
 837
 838        adict["cells"] = None
 839        if poly.GetNumberOfPolys():
 840            try:
 841                adict["cells"] = np.array(obj.faces(), dtype=np.uint32)
 842            except ValueError:  # in case of inhomogeneous shape
 843                adict["cells"] = obj.faces()
 844
 845        adict["lines"] = None
 846        if poly.GetNumberOfLines():
 847            adict["lines"] = obj.lines()
 848
 849        adict["pointdata"] = []
 850        for iname in obj.pointdata.keys():
 851            if not iname:
 852                continue
 853            if "Normals" in iname.lower():
 854                continue
 855            arr = poly.GetPointData().GetArray(iname)
 856            adict["pointdata"].append([utils.vtk2numpy(arr), iname])
 857
 858        adict["celldata"] = []
 859        for iname in obj.celldata.keys():
 860            if not iname:
 861                continue
 862            if "Normals" in iname.lower():
 863                continue
 864            arr = poly.GetCellData().GetArray(iname)
 865            adict["celldata"].append([utils.vtk2numpy(arr), iname])
 866
 867        adict["activedata"] = None
 868        if poly.GetPointData().GetScalars():
 869            adict["activedata"] = ["pointdata", poly.GetPointData().GetScalars().GetName()]
 870        elif poly.GetCellData().GetScalars():
 871            adict["activedata"] = ["celldata", poly.GetCellData().GetScalars().GetName()]
 872
 873        adict["LUT"] = None
 874        adict["LUT_range"] = None
 875        lut = obj.mapper().GetLookupTable()
 876        if lut:
 877            nlut = lut.GetNumberOfTableValues()
 878            lutvals = []
 879            for i in range(nlut):
 880                v4 = lut.GetTableValue(i)  # r, g, b, alpha
 881                lutvals.append(v4)
 882            adict["LUT"] = lutvals
 883            adict["LUT_range"] = lut.GetRange()
 884
 885        prp = obj.GetProperty()
 886        adict["alpha"] = prp.GetOpacity()
 887        adict["representation"] = prp.GetRepresentation()
 888        adict["pointsize"] = prp.GetPointSize()
 889
 890        adict["linecolor"] = None
 891        adict["linewidth"] = None
 892        if prp.GetEdgeVisibility():
 893            adict["linewidth"] = obj.linewidth()
 894            adict["linecolor"] = prp.GetEdgeColor()
 895
 896        adict["ambient"] = prp.GetAmbient()
 897        adict["diffuse"] = prp.GetDiffuse()
 898        adict["specular"] = prp.GetSpecular()
 899        adict["specularpower"] = prp.GetSpecularPower()
 900        adict["specularcolor"] = prp.GetSpecularColor()
 901        adict["shading"] = prp.GetInterpolation()  # flat phong..:
 902        adict["color"] = prp.GetColor()
 903        adict["lighting_is_on"] = prp.GetLighting()
 904        adict["backcolor"] = None
 905        if obj.GetBackfaceProperty():
 906            adict["backcolor"] = obj.GetBackfaceProperty().GetColor()
 907
 908        adict["scalarvisibility"] = obj.mapper().GetScalarVisibility()
 909        adict["texture"] = None
 910
 911    ######################################################## Assembly
 912    if isinstance(obj, Assembly):
 913        pass
 914        # adict['type'] = 'Assembly'
 915        # _fillcommon(obj, adict)
 916        # adict['actors'] = []
 917        # for a in obj.unpack():
 918        #     assdict = dict()
 919        #     if isinstance(a, Mesh):
 920        #         _fillmesh(a, assdict)
 921        #         adict['actors'].append(assdict)
 922
 923    ######################################################## Points/Mesh
 924    elif isinstance(obj, Points):
 925        adict["type"] = "Mesh"
 926        _fillcommon(obj, adict)
 927        _fillmesh(obj, adict)
 928
 929    ######################################################## Volume
 930    elif isinstance(obj, Volume):
 931        adict["type"] = "Volume"
 932        _fillcommon(obj, adict)
 933        imgdata = obj.inputdata()
 934        arr = utils.vtk2numpy(imgdata.GetPointData().GetScalars())
 935        adict["array"] = arr.reshape(imgdata.GetDimensions())
 936        adict["mode"] = obj.mode()
 937        # adict['jittering'] = obj.mapper().GetUseJittering()
 938
 939        prp = obj.GetProperty()
 940        ctf = prp.GetRGBTransferFunction()
 941        otf = prp.GetScalarOpacity()
 942        gotf = prp.GetGradientOpacity()
 943        smin, smax = ctf.GetRange()
 944        xs = np.linspace(smin, smax, num=100, endpoint=True)
 945        cols, als, algrs = [], [], []
 946        for x in xs:
 947            cols.append(ctf.GetColor(x))
 948            als.append(otf.GetValue(x))
 949            if gotf:
 950                algrs.append(gotf.GetValue(x))
 951        adict["color"] = cols
 952        adict["alpha"] = als
 953        adict["alphagrad"] = algrs
 954
 955    ######################################################## Picture
 956    elif isinstance(obj, Picture):
 957        adict["type"] = "Picture"
 958        _fillcommon(obj, adict)
 959        adict["array"] = obj.tonumpy()
 960
 961    ######################################################## Text2D
 962    elif isinstance(obj, vedo.Text2D):
 963        adict["type"] = "Text2D"
 964        adict["rendered_at"] = obj.rendered_at
 965        adict["text"] = obj.text()
 966        adict["position"] = obj.GetPosition()
 967        adict["color"] = obj.property.GetColor()
 968        adict["font"] = obj.fontname
 969        adict["size"] = obj.property.GetFontSize() / 22.5
 970        adict["bgcol"] = obj.property.GetBackgroundColor()
 971        adict["alpha"] = obj.property.GetBackgroundOpacity()
 972        adict["frame"] = obj.property.GetFrame()
 973        # print('tonumpy(): vedo.Text2D', obj.text()[:10], obj.font(), obj.GetPosition())
 974
 975    else:
 976        pass
 977        # colors.printc('Unknown object type in tonumpy()', [obj], c='r')
 978
 979    return adict
 980
 981
 982def loadnumpy(inobj):
 983    """Load a vedo format file or scene."""
 984    # make sure the numpy file is not containing a scene
 985    if isinstance(inobj, str):  # user passing a file
 986
 987        if inobj.endswith(".npy"):
 988            data = np.load(inobj, allow_pickle=True, encoding="latin1")  # .flatten()
 989        elif inobj.endswith(".npz"):
 990            data = np.load(inobj, allow_pickle=True)["vedo_scenes"]
 991
 992        isdict = hasattr(data[0], "keys")
 993
 994        if isdict and "objects" in data[0].keys():  # loading a full scene!!
 995            return import_window(data[0])
 996
 997        # it's a very normal numpy data object? just return it!
 998        if not isdict:
 999            return data
1000        if "type" not in data[0].keys():
1001            return data
1002
1003    else:
1004        data = inobj
1005
1006    ######################################################
1007    def _load_common(obj, d):
1008        keys = d.keys()
1009        if "time" in keys:
1010            obj.time = d["time"]
1011        if "name" in keys:
1012            obj.name = d["name"]
1013        if "filename" in keys:
1014            obj.filename = d["filename"]
1015        if "info" in keys:
1016            obj.info = d["info"]
1017
1018        # if "transform" in keys and len(d["transform"]) == 4:
1019        #     vm = vtk.vtkMatrix4x4()
1020        #     for i in [0, 1, 2, 3]:
1021        #         for j in [0, 1, 2, 3]:
1022        #             vm.SetElement(i, j, d["transform"][i, j])
1023        #     obj.apply_transform(vm)
1024
1025        elif "position" in keys:
1026            obj.pos(d["position"])
1027
1028    ######################################################
1029    def _buildmesh(d):
1030        keys = d.keys()
1031
1032        vertices = d["points"]
1033        if len(vertices) == 0:
1034            return None
1035
1036        cells = None
1037        if "cells" in keys:
1038            cells = d["cells"]
1039
1040        lines = None
1041        if "lines" in keys:
1042            lines = d["lines"]
1043
1044        poly = utils.buildPolyData(vertices, cells, lines)
1045        msh = Mesh(poly)
1046        _load_common(msh, d)
1047
1048        prp = msh.GetProperty()
1049        if 'ambient' in keys:        prp.SetAmbient(d['ambient'])
1050        if 'diffuse' in keys:        prp.SetDiffuse(d['diffuse'])
1051        if 'specular' in keys:       prp.SetSpecular(d['specular'])
1052        if 'specularpower' in keys:  prp.SetSpecularPower(d['specularpower'])
1053        if 'specularcolor' in keys:  prp.SetSpecularColor(d['specularcolor'])
1054        if 'lighting_is_on' in keys:   prp.SetLighting(d['lighting_is_on'])
1055        if 'shading' in keys:        prp.SetInterpolation(d['shading'])
1056        if 'alpha' in keys:          prp.SetOpacity(d['alpha'])
1057        if 'opacity' in keys:        prp.SetOpacity(d['opacity']) # synonym
1058        if 'representation' in keys: prp.SetRepresentation(d['representation'])
1059        if 'pointsize' in keys and d['pointsize']: prp.SetPointSize(d['pointsize'])
1060
1061        if 'linewidth' in keys and d['linewidth']: msh.linewidth(d['linewidth'])
1062        if 'linecolor' in keys and d['linecolor']: msh.linecolor(d['linecolor'])
1063
1064        if 'color' in keys and d['color'] is not None:
1065            msh.color(d['color'])
1066        if 'backcolor' in keys and d['backcolor'] is not None:
1067            msh.backcolor(d['backcolor'])
1068
1069        if "celldata" in keys:
1070            for csc, cscname in d["celldata"]:
1071                msh.celldata[cscname] = csc
1072        if "pointdata" in keys:
1073            for psc, pscname in d["pointdata"]:
1074                msh.pointdata[pscname] = psc
1075
1076        msh.mapper().ScalarVisibilityOff()  # deactivate scalars
1077
1078        if "LUT" in keys and "activedata" in keys and d["activedata"]:
1079            # print(d['activedata'],'', msh.filename)
1080            lut_list = d["LUT"]
1081            ncols = len(lut_list)
1082            lut = vtk.vtkLookupTable()
1083            lut.SetNumberOfTableValues(ncols)
1084            lut.SetRange(d["LUT_range"])
1085            for i in range(ncols):
1086                r, g, b, a = lut_list[i]
1087                lut.SetTableValue(i, r, g, b, a)
1088            lut.Build()
1089            msh.mapper().SetLookupTable(lut)
1090            msh.mapper().ScalarVisibilityOn()  # activate scalars
1091            msh.mapper().SetScalarRange(d["LUT_range"])
1092            if d["activedata"][0] == "celldata":
1093                poly.GetCellData().SetActiveScalars(d["activedata"][1])
1094            if d["activedata"][0] == "pointdata":
1095                poly.GetPointData().SetActiveScalars(d["activedata"][1])
1096
1097        if "shading" in keys and int(d["shading"]) > 0:
1098            msh.compute_normals(cells=0)  # otherwise cannot renderer phong
1099
1100        if "scalarvisibility" in keys:
1101            if d["scalarvisibility"]:
1102                msh.mapper().ScalarVisibilityOn()
1103            else:
1104                msh.mapper().ScalarVisibilityOff()
1105
1106        if "texture" in keys and d["texture"]:
1107            msh.texture(d["texture"])
1108
1109        return msh
1110
1111    ######################################################
1112
1113    objs = []
1114    for d in data:
1115        # print('loadnumpy:', d['type'], d)
1116
1117        ### Mesh
1118        if d['type'].lower() == 'mesh':
1119            a = _buildmesh(d)
1120            if a:
1121                objs.append(a)
1122
1123        ### Assembly
1124        elif d['type'].lower() == 'assembly':
1125            assacts = []
1126            for ad in d["actors"]:
1127                assacts.append(_buildmesh(ad))
1128            asse = Assembly(assacts)
1129            _load_common(asse, d)
1130            objs.append(asse)
1131
1132        ### Volume
1133        elif d['type'].lower() == 'volume':
1134            vol = Volume(d["array"])
1135            _load_common(vol, d)
1136            if "jittering" in d.keys():
1137                vol.jittering(d["jittering"])
1138            # print(d['mode'])
1139            vol.mode(d["mode"])
1140            vol.color(d["color"])
1141            vol.alpha(d["alpha"])
1142            vol.alpha_gradient(d["alphagrad"])
1143            objs.append(vol)
1144
1145        ### Picture
1146        elif d['type'].lower() == 'picture':
1147            vimg = Picture(d["array"])
1148            _load_common(vimg, d)
1149            objs.append(vimg)
1150
1151        ### Text2D
1152        elif d['type'].lower() == 'text2d':
1153            t = vedo.shapes.Text2D(d["text"], font=d["font"], c=d["color"])
1154            t.pos(d["position"]).size(d["size"])
1155            t.background(d["bgcol"], d["alpha"])
1156            if d["frame"]:
1157                t.frame(d["bgcol"])
1158            objs.append(t)
1159
1160        ### Annotation ## backward compatibility - will disappear
1161        elif d['type'].lower() == 'annotation':
1162
1163            pos = d["position"]
1164            if isinstance(pos, int):
1165                pos = "top-left"
1166                d["size"] *= 2.7
1167            t = vedo.shapes.Text2D(d["text"], font=d["font"], c=d["color"]).pos(pos)
1168            t.background(d["bgcol"], d["alpha"]).size(d["size"]).frame(d["bgcol"])
1169            objs.append(t)  ## backward compatibility
1170
1171    if len(objs) == 1:
1172        return objs[0]
1173    if len(objs) == 0:
1174        return None
1175    return objs
1176
1177
1178def loadImageData(filename):
1179    """Read and return a `vtkImageData` object from file."""
1180    if ".tif" in filename.lower():
1181        reader = vtk.vtkTIFFReader()
1182        # print("GetOrientationType ", reader.GetOrientationType())
1183        reader.SetOrientationType(settings.tiff_orientation_type)
1184    elif ".slc" in filename.lower():
1185        reader = vtk.vtkSLCReader()
1186        if not reader.CanReadFile(filename):
1187            vedo.logger.error(f"sorry, bad SLC file {filename}")
1188            return None
1189    elif ".vti" in filename.lower():
1190        reader = vtk.vtkXMLImageDataReader()
1191    elif ".mhd" in filename.lower():
1192        reader = vtk.vtkMetaImageReader()
1193    elif ".dem" in filename.lower():
1194        reader = vtk.vtkDEMReader()
1195    elif ".nii" in filename.lower():
1196        reader = vtk.vtkNIFTIImageReader()
1197    elif ".nrrd" in filename.lower():
1198        reader = vtk.vtkNrrdReader()
1199        if not reader.CanReadFile(filename):
1200            vedo.logger.error(f"sorry, bad NRRD file {filename}")
1201            return None
1202    else:
1203        vedo.logger.error(f"sorry, cannot read file {filename}")
1204        return None
1205    reader.SetFileName(filename)
1206    reader.Update()
1207    image = reader.GetOutput()
1208    return image
1209
1210
1211###########################################################
1212def write(objct, fileoutput, binary=True):
1213    """
1214    Write object to file.
1215
1216    Possile extensions are:
1217        - `vtk, vti, npy, npz, ply, obj, stl, byu, vtp, vti, mhd, xyz, tif, png, bmp`
1218    """
1219    obj = objct
1220    if isinstance(obj, Points):  # picks transformation
1221        obj = objct.polydata(True)
1222    elif isinstance(obj, (vtk.vtkActor, vtk.vtkVolume)):
1223        obj = objct.GetMapper().GetInput()
1224    elif isinstance(obj, (vtk.vtkPolyData, vtk.vtkImageData)):
1225        obj = objct
1226
1227    fr = fileoutput.lower()
1228    if fr.endswith(".vtk"):
1229        writer = vtk.vtkDataSetWriter()
1230    elif fr.endswith(".ply"):
1231        writer = vtk.vtkPLYWriter()
1232        writer.AddComment("PLY file generated by vedo")
1233        lut = objct.GetMapper().GetLookupTable()
1234        if lut:
1235            pscal = obj.GetPointData().GetScalars()
1236            if not pscal:
1237                pscal = obj.GetCellData().GetScalars()
1238            if pscal and pscal.GetName():
1239                writer.SetArrayName(pscal.GetName())
1240            writer.SetLookupTable(lut)
1241    elif fr.endswith(".stl"):
1242        writer = vtk.vtkSTLWriter()
1243    elif fr.endswith(".vtp"):
1244        writer = vtk.vtkXMLPolyDataWriter()
1245    elif fr.endswith(".vtu"):
1246        writer = vtk.vtkXMLUnstructuredGridWriter()
1247    elif fr.endswith(".vtm"):
1248        g = vtk.vtkMultiBlockDataGroupFilter()
1249        for ob in objct:
1250            if isinstance(ob, (Points, Volume)):  # picks transformation
1251                ob = ob.polydata(True)
1252                g.AddInputData(ob)
1253        g.Update()
1254        mb = g.GetOutputDataObject(0)
1255        wri = vtk.vtkXMLMultiBlockDataWriter()
1256        wri.SetInputData(mb)
1257        wri.SetFileName(fileoutput)
1258        wri.Write()
1259        return mb
1260    elif fr.endswith(".xyz"):
1261        writer = vtk.vtkSimplePointsWriter()
1262    elif fr.endswith(".facet"):
1263        writer = vtk.vtkFacetWriter()
1264    elif fr.endswith(".vti"):
1265        writer = vtk.vtkXMLImageDataWriter()
1266    elif fr.endswith(".mhd"):
1267        writer = vtk.vtkMetaImageWriter()
1268    elif fr.endswith(".nii"):
1269        writer = vtk.vtkNIFTIImageWriter()
1270    elif fr.endswith(".png"):
1271        writer = vtk.vtkPNGWriter()
1272    elif fr.endswith(".jpg"):
1273        writer = vtk.vtkJPEGWriter()
1274    elif fr.endswith(".bmp"):
1275        writer = vtk.vtkBMPWriter()
1276    elif fr.endswith(".tif") or fr.endswith(".tiff"):
1277        writer = vtk.vtkTIFFWriter()
1278        writer.SetFileDimensionality(len(obj.GetDimensions()))
1279    elif fr.endswith(".npy") or fr.endswith(".npz"):
1280        if utils.is_sequence(objct):
1281            objslist = objct
1282        else:
1283            objslist = [objct]
1284        dicts2save = []
1285        for obj in objslist:
1286            dicts2save.append(tonumpy(obj))
1287        np.save(fileoutput, dicts2save)
1288        return dicts2save
1289
1290    elif fr.endswith(".obj"):
1291        with open(fileoutput, "w", encoding="UTF-8") as outF:
1292            outF.write("# OBJ file format with ext .obj\n")
1293            outF.write("# File generated by vedo\n")
1294
1295            for p in objct.points():
1296                outF.write("v {:.5g} {:.5g} {:.5g}\n".format(*p))
1297
1298            ptxt = objct.polydata().GetPointData().GetTCoords()
1299            if ptxt:
1300                ntxt = utils.vtk2numpy(ptxt)
1301                for vt in ntxt:
1302                    outF.write("vt " + str(vt[0]) + " " + str(vt[1]) + " 0.0\n")
1303
1304            if isinstance(objct, Mesh):
1305
1306                for i, f in enumerate(objct.faces()):
1307                    fs = ""
1308                    for fi in f:
1309                        if ptxt:
1310                            fs += f" {fi+1}/{fi+1}"
1311                        else:
1312                            fs += f" {fi+1}"
1313                    outF.write(f"f{fs}\n")
1314
1315                for l in objct.lines():
1316                    ls = ""
1317                    for li in l:
1318                        ls += str(li + 1) + " "
1319                    outF.write(f"l {ls}\n")
1320
1321        return objct
1322
1323    elif fr.endswith(".xml"):  # write tetrahedral dolfin xml
1324        vertices = objct.points().astype(str)
1325        faces = np.array(objct.faces()).astype(str)
1326        ncoords = vertices.shape[0]
1327        with open(fileoutput, "w", encoding="UTF-8") as outF:
1328            outF.write('<?xml version="1.0" encoding="UTF-8"?>\n')
1329            outF.write('<dolfin xmlns:dolfin="http://www.fenicsproject.org">\n')
1330
1331            if len(faces[0]) == 4:  # write tetrahedral mesh
1332                ntets = faces.shape[0]
1333                outF.write('  <mesh celltype="tetrahedron" dim="3">\n')
1334                outF.write('    <vertices size="' + str(ncoords) + '">\n')
1335                for i in range(ncoords):
1336                    x, y, z = vertices[i]
1337                    outF.write('      <vertex index="'+str(i)+'" x="'+x+'" y="'+y+'" z="'+z+'"/>\n')
1338                outF.write('    </vertices>\n')
1339                outF.write('    <cells size="' + str(ntets) + '">\n')
1340                for i in range(ntets):
1341                    v0, v1, v2, v3 = faces[i]
1342                    outF.write('     <tetrahedron index="'+str(i)
1343                               + '" v0="'+v0+'" v1="'+v1+'" v2="'+v2+'" v3="'+v3+'"/>\n')
1344
1345            elif len(faces[0]) == 3:  # write triangle mesh
1346                ntri = faces.shape[0]
1347                outF.write('  <mesh celltype="triangle" dim="2">\n')
1348                outF.write('    <vertices size="' + str(ncoords) + '">\n')
1349                for i in range(ncoords):
1350                    x, y, dummy_z = vertices[i]
1351                    outF.write('      <vertex index="'+str(i)+'" x="'+x+'" y="'+y+'"/>\n')
1352                outF.write('    </vertices>\n')
1353                outF.write('    <cells size="' + str(ntri) + '">\n')
1354                for i in range(ntri):
1355                    v0, v1, v2 = faces[i]
1356                    outF.write('     <triangle index="'+str(i)+'" v0="'+v0+'" v1="'+v1+'" v2="'+v2+'"/>\n')
1357
1358            outF.write("    </cells>\n")
1359            outF.write("  </mesh>\n")
1360            outF.write("</dolfin>\n")
1361        return objct
1362
1363    else:
1364        vedo.logger.error(f"Unknown format {fileoutput}, file not saved")
1365        return objct
1366
1367    try:
1368        if binary:
1369            writer.SetFileTypeToBinary()
1370        else:
1371            writer.SetFileTypeToASCII()
1372    except AttributeError:
1373        pass
1374
1375    try:
1376        writer.SetInputData(obj)
1377        writer.SetFileName(fileoutput)
1378        writer.Write()
1379    except:
1380        vedo.logger.error(f"could not save {fileoutput}")
1381    return objct
1382
1383
1384def write_transform(inobj, filename="transform.mat", comment=""):
1385    """
1386    Save a transformation for a mesh or pointcloud to ASCII file.
1387
1388    Arguments:
1389        filename : (str)
1390            output file name
1391        comment : (str)
1392            some optional comment
1393    """
1394    if isinstance(inobj, Points):
1395        M = inobj.get_transform().GetMatrix()
1396    elif isinstance(inobj, vtk.vtkTransform):
1397        M = inobj.GetMatrix()
1398    elif isinstance(inobj, vtk.vtkMatrix4x4):
1399        M = inobj
1400    else:
1401        vedo.logger.error(f"in write_transform(), cannot understand input type {type(inobj)}")
1402
1403    with open(filename, "w", encoding="UTF-8") as f:
1404        if comment:
1405            f.write("# " + comment + "\n")
1406        for i in range(4):
1407            f.write(
1408                str(M.GetElement(i,0))+' '+
1409                str(M.GetElement(i,1))+' '+
1410                str(M.GetElement(i,2))+' '+
1411                str(M.GetElement(i,3))+'\n',
1412            )
1413        f.write('\n')
1414
1415
1416def load_transform(filename):
1417    """
1418    Load a transformation from a file `.mat`.
1419
1420    Returns:
1421        - `vtkTransform`
1422            The transformation to be applied to some object (`use apply_transform()`).
1423        - `str`, a comment string associated to this transformation file.
1424    """
1425    with open(filename, "r", encoding="UTF-8") as f:
1426        lines = f.readlines()
1427        M = vtk.vtkMatrix4x4()
1428        i = 0
1429        comment = ""
1430        for l in lines:
1431            if l.startswith("#"):
1432                comment = l.replace("#", "").replace("\n", "")
1433                continue
1434            vals = l.split(" ")
1435            if len(vals) == 4:
1436                for j in range(4):
1437                    v = vals[j].replace("\n", "")
1438                    M.SetElement(i, j, float(v))
1439                i += 1
1440        T = vtk.vtkTransform()
1441        T.SetMatrix(M)
1442    return (T, comment)
1443
1444
1445###############################################################################
1446def export_window(fileoutput, binary=False):
1447    """
1448    Exporter which writes out the rendered scene into an HTML, X3D
1449    or Numpy file.
1450
1451    Example:
1452        - [export_x3d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/export_x3d.py)
1453
1454        Check out the HTML generated webpage [here](https://vedo.embl.es/examples/embryo.html).
1455
1456        <img src='https://user-images.githubusercontent.com/32848391/57160341-c6ffbd80-6de8-11e9-95ff-7215ce642bc5.jpg' width="600"/>
1457
1458    .. note::
1459        the rendering window can also be exported to `numpy` file `scene.npz`
1460        by pressing `E` keyboard at any moment during visualization.
1461    """
1462    fr = fileoutput.lower()
1463
1464    ####################################################################
1465    if fr.endswith(".npy") or fr.endswith(".npz"):
1466        sdict = {}
1467        plt = vedo.plotter_instance
1468        sdict["shape"] = plt.shape
1469        sdict["sharecam"] = plt.sharecam
1470        sdict["camera"] = dict(
1471            pos=plt.camera.GetPosition(),
1472            focal_point=plt.camera.GetFocalPoint(),
1473            viewup=plt.camera.GetViewUp(),
1474            distance=plt.camera.GetDistance(),
1475            clipping_range=plt.camera.GetClippingRange(),
1476        )
1477        sdict["position"] = plt.pos
1478        sdict["size"] = plt.size
1479        sdict["axes"] = plt.axes
1480        sdict["title"] = plt.title
1481        sdict["backgrcol"] = colors.get_color(plt.renderer.GetBackground())
1482        sdict["backgrcol2"] = None
1483        if plt.renderer.GetGradientBackground():
1484            sdict["backgrcol2"] = plt.renderer.GetBackground2()
1485        sdict["use_depth_peeling"] = settings.use_depth_peeling
1486        sdict["render_lines_as_tubes"] = settings.render_lines_as_tubes
1487        sdict["hidden_line_removal"] = settings.hidden_line_removal
1488        sdict["visible_grid_edges"] = settings.visible_grid_edges
1489        sdict["use_parallel_projection"] = settings.use_parallel_projection
1490        sdict["default_font"] = settings.default_font
1491        sdict["objects"] = []
1492
1493        allobjs = plt.get_meshes(include_non_pickables=True) + plt.get_volumes(include_non_pickables=True)
1494        acts2d = plt.renderer.GetActors2D()
1495        acts2d.InitTraversal()
1496        for _ in range(acts2d.GetNumberOfItems()):
1497            a = acts2d.GetNextItem()
1498            if isinstance(a, vedo.Text2D):
1499                allobjs.append(a)
1500        allobjs += plt.actors
1501
1502        allobjs = list(set(allobjs))  # make sure its unique
1503
1504        for a in allobjs:
1505            if a.GetVisibility():
1506                sdict["objects"].append(tonumpy(a))
1507
1508        if fr.endswith(".npz"):
1509            np.savez_compressed(fileoutput, vedo_scenes=[sdict])
1510        else:
1511            np.save(fileoutput, [sdict])
1512
1513    ####################################################################
1514    elif fr.endswith(".x3d"):
1515        obj = list(set(vedo.plotter_instance.get_meshes() + vedo.plotter_instance.actors))
1516        if vedo.plotter_instance.axes_instances:
1517            obj.append(vedo.plotter_instance.axes_instances[0])
1518
1519        for a in obj:
1520            if isinstance(a, Mesh):
1521                newa = a.clone(transformed=True)
1522                vedo.plotter_instance.remove(a).add(newa)
1523
1524            elif isinstance(a, Assembly):
1525                vedo.plotter_instance.remove(a)
1526                for b in a.unpack():
1527                    if b:
1528                        if a.name == "Axes":
1529                            newb = b.clone(transformed=True)
1530                        else:
1531                            # newb = b.clone(transformed=True) # BUG??
1532
1533                            newb = b.clone(transformed=False)
1534                            tt = vtk.vtkTransform()
1535                            tt.Concatenate(a.GetMatrix())
1536                            tt.Concatenate(b.GetMatrix())
1537                            newb.PokeMatrix(vtk.vtkMatrix4x4())
1538                            newb.SetUserTransform(tt)
1539
1540                        vedo.plotter_instance.add(newb)
1541
1542        vedo.plotter_instance.render()
1543
1544        exporter = vtk.vtkX3DExporter()
1545        exporter.SetBinary(binary)
1546        exporter.FastestOff()
1547        exporter.SetInput(vedo.plotter_instance.window)
1548        exporter.SetFileName(fileoutput)
1549        # exporter.WriteToOutputStringOn() # see below
1550        exporter.Update()
1551        exporter.Write()
1552
1553        # this can reduce the size by more than half...
1554        #        outstring = exporter.GetOutputString().decode("utf-8") # this fails though
1555        #        from vedo.utils import isInteger, isNumber, precision
1556        #        newlines = []
1557        #        for l in outstring.splitlines(True):
1558        #            ls = l.lstrip()
1559        #            content = ls.split()
1560        #            newls = ""
1561        #            for c in content:
1562        #                c2 = c.replace(',','')
1563        #                if isNumber(c2) and not isInteger(c2):
1564        #                    newc = precision(float(c2), 4)
1565        #                    if ',' in c:
1566        #                        newls += newc + ','
1567        #                    else:
1568        #                        newls += newc + ' '
1569        #                else:
1570        #                    newls += c + ' '
1571        #        newlines.append(newls.lstrip()+'\n')
1572        #        with open("fileoutput", 'w', encoding='UTF-8') as f:
1573        #            l = "".join(newlines)
1574        #            f.write(l)
1575
1576        x3d_html = _x3d_html.replace("~fileoutput", fileoutput)
1577        wsize = vedo.plotter_instance.window.GetSize()
1578        x3d_html = x3d_html.replace("~width", str(wsize[0]))
1579        x3d_html = x3d_html.replace("~height", str(wsize[1]))
1580        with open(fileoutput.replace(".x3d", ".html"), "w", encoding="UTF-8") as outF:
1581            outF.write(x3d_html)
1582            vedo.logger.info(f"Saved files {fileoutput} and {fileoutput.replace('.x3d','.html')}")
1583
1584    ####################################################################
1585    elif fr.endswith(".html"):
1586        savebk = vedo.notebook_backend
1587        vedo.notebook_backend = "k3d"
1588        vedo.settings.default_backend = "k3d"
1589        plt = vedo.backends.get_notebook_backend(vedo.plotter_instance.actors)
1590
1591        with open(fileoutput, "w", encoding="UTF-8") as fp:
1592            fp.write(plt.get_snapshot())
1593
1594        vedo.notebook_backend = savebk
1595        vedo.settings.default_backend = savebk
1596
1597    else:
1598        vedo.logger.error(f"export extension {fr.split('.')[-1]} is not supported")
1599
1600    return vedo.plotter_instance
1601
1602
1603def import_window(fileinput, mtl_file=None, texture_path=None):
1604    """Import a whole scene from a Numpy or OBJ wavefront file.
1605
1606    Arguments:
1607        mtl_file : (str)
1608            MTL file for OBJ wavefront files
1609        texture_path : (str)
1610            path of the texture files directory
1611
1612    Returns:
1613        `Plotter` instance
1614    """
1615    data = None
1616    if isinstance(fileinput, dict):
1617        data = fileinput
1618    elif fileinput.endswith(".npy"):
1619        data = np.load(fileinput, allow_pickle=True, encoding="latin1").flatten()[0]
1620    elif fileinput.endswith(".npz"):
1621        data = np.load(fileinput, allow_pickle=True)["vedo_scenes"][0]
1622
1623    if data is not None:
1624        if "render_lines_as_tubes" in data.keys():
1625            settings.render_lines_as_tubes = data["render_lines_as_tubes"]
1626        if "hidden_line_removal" in data.keys():
1627            settings.hidden_line_removal = data["hidden_line_removal"]
1628        if "visible_grid_edges" in data.keys():
1629            settings.visible_grid_edges = data["visible_grid_edges"]
1630        if "use_parallel_projection" in data.keys():
1631            settings.use_parallel_projection = data["use_parallel_projection"]
1632        if "use_polygon_offset" in data.keys():
1633            settings.use_polygon_offset = data["use_polygon_offset"]
1634        if "polygon_offset_factor" in data.keys():
1635            settings.polygon_offset_factor = data["polygon_offset_factor"]
1636        if "polygon_offset_units" in data.keys():
1637            settings.polygon_offset_units = data["polygon_offset_units"]
1638        if "interpolate_scalars_before_mapping" in data.keys():
1639            settings.interpolate_scalars_before_mapping = data["interpolate_scalars_before_mapping"]
1640        if "default_font" in data.keys():
1641            settings.default_font = data["default_font"]
1642        if "use_depth_peeling" in data.keys():
1643            settings.use_depth_peeling = data["use_depth_peeling"]
1644
1645        axes = data.pop("axes", 4)
1646        title = data.pop("title", "")
1647        backgrcol = data.pop("backgrcol", "white")
1648        backgrcol2 = data.pop("backgrcol2", None)
1649        cam = data.pop("camera", None)
1650
1651        if data["shape"] != (1, 1):
1652            data["size"] = "auto"  # disable size
1653
1654        plt = vedo.Plotter(
1655            size=data["size"],  # not necessarily a good idea to set it
1656            # shape=data['shape'], # will need to create a Renderer class first
1657            axes=axes,
1658            title=title,
1659            bg=backgrcol,
1660            bg2=backgrcol2,
1661        )
1662
1663        if cam:
1664            if "pos" in cam.keys():
1665                plt.camera.SetPosition(cam["pos"])
1666            if "focalPoint" in cam.keys():
1667                plt.camera.SetFocalPoint(cam["focalPoint"])
1668            if "focal_point" in cam.keys():
1669                plt.camera.SetFocalPoint(cam["focal_point"])
1670            if "viewup" in cam.keys():
1671                plt.camera.SetViewUp(cam["viewup"])
1672            if "distance" in cam.keys():
1673                plt.camera.SetDistance(cam["distance"])
1674            if "clippingRange" in cam.keys():
1675                plt.camera.SetClippingRange(cam["clippingRange"])
1676            if "clipping_range" in cam.keys():
1677                plt.camera.SetClippingRange(cam["clipping_range"])
1678            plt.resetcam = False
1679
1680        if "objects" in data.keys():
1681            objs = loadnumpy(data["objects"])
1682            if not utils.is_sequence(objs):
1683                objs = [objs]
1684        else:
1685            # colors.printc("Trying to import a single mesh.. use load() instead.", c='r')
1686            # colors.printc(" -> try to load a single object with load().", c='r')
1687            objs = [loadnumpy(fileinput)]
1688
1689        plt.actors = objs
1690        plt.add(objs)
1691        return plt
1692
1693    elif ".obj" in fileinput.lower():
1694
1695        window = vtk.vtkRenderWindow()
1696        window.SetOffScreenRendering(1)
1697        renderer = vtk.vtkRenderer()
1698        window.AddRenderer(renderer)
1699
1700        importer = vtk.vtkOBJImporter()
1701        importer.SetFileName(fileinput)
1702        if mtl_file is not False:
1703            if mtl_file is None:
1704                mtl_file = fileinput.replace(".obj", ".mtl").replace(".OBJ", ".MTL")
1705            importer.SetFileNameMTL(mtl_file)
1706        if texture_path is not False:
1707            if texture_path is None:
1708                texture_path = fileinput.replace(".obj", ".txt").replace(".OBJ", ".TXT")
1709            importer.SetTexturePath(texture_path)
1710        importer.SetRenderWindow(window)
1711        importer.Update()
1712
1713        plt = vedo.Plotter()
1714
1715        actors = renderer.GetActors()
1716        actors.InitTraversal()
1717        for _ in range(actors.GetNumberOfItems()):
1718            vactor = actors.GetNextActor()
1719            act = Mesh(vactor)
1720            act_tu = vactor.GetTexture()
1721            if act_tu:
1722                act.texture(act_tu)
1723            plt.actors.append(act)
1724        return plt
1725
1726    return None
1727
1728
1729##########################################################
1730def screenshot(filename="screenshot.png", scale=1, asarray=False):
1731    """
1732    Save a screenshot of the current rendering window.
1733
1734    Arguments:
1735        scale : (int)
1736            Set image magnification as an integer multiplicative factor.
1737            E.g. setting a magnification of 2 produces an image twice as large,
1738            but 10x slower to generate.
1739        asarray : (bool)
1740            Return a numpy array of the image
1741    """
1742    if not vedo.plotter_instance or not vedo.plotter_instance.window:
1743        # vedo.logger.error("in screenshot(), rendering window is not present, skip.")
1744        return vedo.plotter_instance  ##########
1745
1746    if asarray and scale == 1:
1747        nx, ny = vedo.plotter_instance.window.GetSize()
1748        arr = vtk.vtkUnsignedCharArray()
1749        vedo.plotter_instance.window.GetRGBACharPixelData(0, 0, nx-1, ny-1, 0, arr)
1750        narr = vedo.vtk2numpy(arr).T[:3].T.reshape([ny, nx, 3])
1751        narr = np.flip(narr, axis=0)
1752        return narr  ##########
1753
1754    filename = str(filename)
1755
1756    if filename.endswith(".pdf"):
1757        writer = vtk.vtkGL2PSExporter()
1758        writer.SetRenderWindow(vedo.plotter_instance.window)
1759        writer.Write3DPropsAsRasterImageOff()
1760        writer.SilentOn()
1761        writer.SetSortToBSP()
1762        writer.SetFileFormatToPDF()
1763        writer.SetFilePrefix(filename.replace(".pdf", ""))
1764        writer.Write()
1765        return vedo.plotter_instance  ##########
1766
1767    elif filename.endswith(".svg"):
1768        writer = vtk.vtkGL2PSExporter()
1769        writer.SetRenderWindow(vedo.plotter_instance.window)
1770        writer.Write3DPropsAsRasterImageOff()
1771        writer.SilentOn()
1772        writer.SetSortToBSP()
1773        writer.SetFileFormatToSVG()
1774        writer.SetFilePrefix(filename.replace(".svg", ""))
1775        writer.Write()
1776        return vedo.plotter_instance  ##########
1777
1778    elif filename.endswith(".eps"):
1779        writer = vtk.vtkGL2PSExporter()
1780        writer.SetRenderWindow(vedo.plotter_instance.window)
1781        writer.Write3DPropsAsRasterImageOff()
1782        writer.SilentOn()
1783        writer.SetSortToBSP()
1784        writer.SetFileFormatToEPS()
1785        writer.SetFilePrefix(filename.replace(".eps", ""))
1786        writer.Write()
1787        return vedo.plotter_instance  ##########
1788
1789    if settings.screeshot_large_image:
1790        w2if = vtk.vtkRenderLargeImage()
1791        w2if.SetInput(vedo.plotter_instance.renderer)
1792        w2if.SetMagnification(scale)
1793    else:
1794        w2if = vtk.vtkWindowToImageFilter()
1795        w2if.SetInput(vedo.plotter_instance.window)
1796        if hasattr(w2if, "SetScale"):
1797            w2if.SetScale(int(scale), int(scale))
1798        if settings.screenshot_transparent_background:
1799            w2if.SetInputBufferTypeToRGBA()
1800        w2if.ReadFrontBufferOff()  # read from the back buffer
1801    w2if.Update()
1802
1803    if asarray and scale != 1:
1804        pd = w2if.GetOutput().GetPointData()
1805        npdata = utils.vtk2numpy(pd.GetArray("ImageScalars"))
1806        npdata = npdata[:, [0, 1, 2]]
1807        ydim, xdim, _ = w2if.GetOutput().GetDimensions()
1808        npdata = npdata.reshape([xdim, ydim, -1])
1809        npdata = np.flip(npdata, axis=0)
1810        return npdata
1811
1812    if filename.lower().endswith(".png"):
1813        writer = vtk.vtkPNGWriter()
1814        writer.SetFileName(filename)
1815        writer.SetInputData(w2if.GetOutput())
1816        writer.Write()
1817    elif filename.lower().endswith(".jpg") or filename.lower().endswith(".jpeg"):
1818        writer = vtk.vtkJPEGWriter()
1819        writer.SetFileName(filename)
1820        writer.SetInputData(w2if.GetOutput())
1821        writer.Write()
1822    else:  # add .png
1823        writer = vtk.vtkPNGWriter()
1824        writer.SetFileName(filename + ".png")
1825        writer.SetInputData(w2if.GetOutput())
1826        writer.Write()
1827    return vedo.plotter_instance
1828
1829
1830def ask(*question, **kwarg):
1831    """
1832    Ask a question from command line. Return the answer as a string.
1833    See function `colors.printc()` for the description of the keyword options.
1834
1835    Arguments:
1836        options : (list)
1837            a python list of possible answers to choose from.
1838        default : (str)
1839            the default answer when just hitting return.
1840
1841    Example:
1842        ```python
1843        import vedo
1844        res = vedo.file_io.ask("Continue?", options=['Y','n'], default='Y', c='g')
1845        print(res)
1846        ```
1847    """
1848    kwarg.update({"end": " "})
1849    if "invert" not in kwarg:
1850        kwarg.update({"invert": True})
1851    if "box" in kwarg:
1852        kwarg.update({"box": ""})
1853
1854    options = kwarg.pop("options", [])
1855    default = kwarg.pop("default", "")
1856    if options:
1857        opt = "["
1858        for o in options:
1859            opt += o + "/"
1860        opt = opt[:-1] + "]"
1861        colors.printc(*question, opt, **kwarg)
1862    else:
1863        colors.printc(*question, **kwarg)
1864
1865    resp = input()
1866
1867    if options:
1868        if resp not in options:
1869            if default and str(repr(resp)) == "''":
1870                return default
1871            colors.printc("Please choose one option in:", opt, italic=True, bold=False)
1872            kwarg["options"] = options
1873            return ask(*question, **kwarg)  # ask again
1874    return resp
1875
1876
1877##############################################################################################
1878class Video:
1879    """
1880    Generate a video from a rendering window.
1881    """
1882
1883    def __init__(self, name="movie.mp4", duration=None, fps=24, backend="imageio"):
1884        """
1885        Class to generate a video from the specified rendering window.
1886        Program `ffmpeg` is used to create video from each generated frame.
1887
1888        Arguments:
1889            name : (str)
1890                name of the output file.
1891            fps : (int)
1892                set the number of frames per second.
1893            duration : (float)
1894                set the total `duration` of the video and recalculates `fps` accordingly.
1895            backend : (str)
1896                the backend engine to be used `['imageio', 'ffmpeg', 'cv']`
1897
1898        Examples:
1899            - [make_video.py](https://github.com/marcomusy/vedo/tree/master/examples/other/make_video.py)
1900
1901            ![](https://user-images.githubusercontent.com/32848391/50739007-2bfc2b80-11da-11e9-97e6-620a3541a6fa.jpg)
1902        """
1903        self.name = name
1904        self.duration = duration
1905        self.backend = backend
1906        self.fps = float(fps)
1907        self.command = "ffmpeg -loglevel panic -y -r"
1908        self.options = "-b:v 8000k"
1909
1910        self.frames = []
1911        self.tmp_dir = TemporaryDirectory()
1912        self.get_filename = lambda x: os.path.join(self.tmp_dir.name, x)
1913        colors.printc(":video: Video file", self.name, "is open... ", c="m", end="")
1914
1915    def add_frame(self):
1916        """Add frame to current video."""
1917        fr = self.get_filename(str(len(self.frames)) + ".png")
1918        screenshot(fr)
1919        self.frames.append(fr)
1920        return self
1921
1922    def pause(self, pause=0):
1923        """Insert a `pause`, in seconds."""
1924        fr = self.frames[-1]
1925        n = int(self.fps * pause)
1926        for _ in range(n):
1927            fr2 = self.get_filename(str(len(self.frames)) + ".png")
1928            self.frames.append(fr2)
1929            os.system("cp -f %s %s" % (fr, fr2))
1930        return self
1931
1932    def action(self, elevation=(0, 80), azimuth=(0, 359), cameras=(), resetcam=False):
1933        """
1934        Automatic shooting of a static scene by specifying rotation and elevation ranges.
1935
1936        Arguments:
1937            elevation : list
1938                initial and final elevation angles
1939            azimuth_range : list
1940                initial and final azimuth angles
1941            cameras : list
1942                list of cameras to go through, each camera can be dictionary or a vtkCamera
1943        """
1944        if not self.duration:
1945            self.duration = 5
1946
1947        plt = vedo.plotter_instance
1948        n = int(self.fps * self.duration)
1949
1950        cams = []
1951        for cm in cameras:
1952            cams.append(utils.camera_from_dict(cm))
1953        nc = len(cams)
1954
1955        plt.show(resetcam=resetcam, interactive=False)
1956
1957        if nc:
1958            for i in range(n):
1959                plt.move_camera(cams, i / n)
1960                plt.show()
1961                self.add_frame()
1962
1963        else:  ########################################
1964
1965            for i in range(n):
1966                plt.camera.Elevation((elevation[1] - elevation[0]) / n)
1967                plt.camera.Azimuth((azimuth[1] - azimuth[0]) / n)
1968                plt.show()
1969                self.add_frame()
1970
1971        return self
1972
1973    def close(self):
1974        """
1975        Render the video and write it to file.
1976        """
1977        if self.duration:
1978            self.fps = int(len(self.frames) / float(self.duration) + 0.5)
1979            colors.printc("recalculated fps:", self.fps, c="m", end="")
1980        else:
1981            self.fps = int(self.fps)
1982
1983        ########################################
1984        if self.backend == "ffmpeg":
1985            out = os.system(
1986                self.command
1987                + " "
1988                + str(self.fps)
1989                + " -i "
1990                + f"'{self.tmp_dir.name}'"
1991                + os.sep
1992                + "%01d.png "
1993                + self.options
1994                + " "
1995                + f"'{self.name}'"
1996            )
1997            if out:
1998                vedo.logger.error(f":noentry: backend {self.backend} returning error: {out}")
1999            else:
2000                colors.printc(f":save: saved to {self.name}", c="m")
2001
2002        ########################################
2003        elif "cv" in self.backend:
2004            try:
2005                import cv2
2006            except ImportError:
2007                vedo.logger.error("opencv is not installed")
2008                return
2009
2010            cap = cv2.VideoCapture(os.path.join(self.tmp_dir.name, "%1d.png"))
2011            fourcc = cv2.VideoWriter_fourcc(*"mp4v")
2012            w, h = vedo.plotter_instance.window.GetSize()
2013            writer = cv2.VideoWriter(self.name, fourcc, self.fps, (w, h), True)
2014
2015            while True:
2016                ret, frame = cap.read()
2017                if not ret:
2018                    break
2019                writer.write(frame)
2020
2021            cap.release()
2022            writer.release()
2023
2024        ########################################
2025        elif "imageio" in self.backend:
2026            try:
2027                import imageio
2028            except ImportError:
2029                vedo.logger.error("Please install imageio with:\n pip install imageio[ffmpeg]")
2030                return
2031
2032            if self.name.endswith(".mp4"):
2033                writer = imageio.get_writer(self.name, fps=self.fps)
2034            elif self.name.endswith(".gif"):
2035                writer = imageio.get_writer(self.name, mode="I", duration=1 / self.fps)
2036            elif self.name.endswith(".webm"):
2037                writer = imageio.get_writer(self.name, format="webm", fps=self.fps)
2038            else:
2039                vedo.logger.error(f"Unknown format of {self.name}.")
2040                return
2041
2042            for f in utils.humansort(self.frames):
2043                image = imageio.v3.imread(f)
2044                writer.append_data(image)
2045            try:
2046                writer.close()
2047                colors.printc(f"... saved as {self.name}", c="m")
2048            except:
2049                colors.printc(f":noentry: Could not save video {self.name}", c="r")
2050
2051        # finalize cleanup
2052        self.tmp_dir.cleanup()
2053
2054    def split_frames(self, output_dir="video_frames", prefix="frame_", format="png"):
2055        """Split an existing video file into frames."""
2056        try:
2057            import imageio
2058        except ImportError:
2059            vedo.logger.error("\nPlease install imageio with:\n pip install imageio")
2060            return
2061
2062        # Create the output directory if it doesn't exist
2063        if not os.path.exists(output_dir):
2064            os.makedirs(output_dir)
2065
2066        # Create a reader object to read the video
2067        reader = imageio.get_reader(self.name)
2068
2069        # Loop through each frame of the video and save it as image
2070        print()
2071        for i, frame in utils.progressbar(
2072            enumerate(reader), title=f"writing {format} frames", c="m", width=20
2073        ):
2074            output_file = os.path.join(output_dir, f"{prefix}{str(i).zfill(5)}.{format}")
2075            imageio.imwrite(output_file, frame, format=format)
def load(inputobj, unpack=True, force=False):
180def load(inputobj, unpack=True, force=False):
181    """
182    Load any vedo objects from file or from the web.
183
184    The output will depend on the file extension. See examples below.
185    Unzip is made on the fly, if file ends with `.gz`.
186    Can load an object directly from a URL address.
187
188    Arguments:
189        unpack : bool
190            unpack MultiBlockData into a flat list of objects.
191
192        force : bool
193            when downloading a file ignore any previous cached downloads and force a new one.
194
195    Example:
196        ```python
197        from vedo import dataurl, load, show
198        # Return a list of 2 meshes
199        g = load([dataurl+'250.vtk', dataurl+'270.vtk'])
200        show(g)
201        # Return a list of meshes by reading all files in a directory
202        # (if directory contains DICOM files then a Volume is returned)
203        g = load('mydicomdir/')
204        show(g)
205        ```
206    """
207    acts = []
208    if utils.is_sequence(inputobj):
209        flist = inputobj
210    elif isinstance(inputobj, str) and inputobj.startswith("https://"):
211        flist = [inputobj]
212    else:
213        flist = sorted(glob.glob(inputobj))
214
215    for fod in flist:
216
217        if fod.startswith("https://"):
218            fod = download(fod, force=force, verbose=False)
219
220        if os.path.isfile(fod):  ### it's a file
221
222            if fod.endswith(".gz"):
223                fod = gunzip(fod)
224
225            a = _load_file(fod, unpack)
226            acts.append(a)
227
228        elif os.path.isdir(fod):  ### it's a directory or DICOM
229            flist = os.listdir(fod)
230            if ".dcm" in flist[0]:  ### it's DICOM
231                reader = vtk.vtkDICOMImageReader()
232                reader.SetDirectoryName(fod)
233                reader.Update()
234                image = reader.GetOutput()
235                actor = Volume(image)
236
237                actor.info["PixelSpacing"] = reader.GetPixelSpacing()
238                actor.info["Width"] = reader.GetWidth()
239                actor.info["Height"] = reader.GetHeight()
240                actor.info["PositionPatient"] = reader.GetImagePositionPatient()
241                actor.info["OrientationPatient"] = reader.GetImageOrientationPatient()
242                actor.info["BitsAllocated"] = reader.GetBitsAllocated()
243                actor.info["PixelRepresentation"] = reader.GetPixelRepresentation()
244                actor.info["NumberOfComponents"] = reader.GetNumberOfComponents()
245                actor.info["TransferSyntaxUID"] = reader.GetTransferSyntaxUID()
246                actor.info["RescaleSlope"] = reader.GetRescaleSlope()
247                actor.info["RescaleOffset"] = reader.GetRescaleOffset()
248                actor.info["PatientName"] = reader.GetPatientName()
249                actor.info["StudyUID"] = reader.GetStudyUID()
250                actor.info["StudyID"] = reader.GetStudyID()
251                actor.info["GantryAngle"] = reader.GetGantryAngle()
252
253                acts.append(actor)
254
255            else:  ### it's a normal directory
256                utils.humansort(flist)
257                for ifile in flist:
258                    a = _load_file(fod + "/" + ifile, unpack)
259                    acts.append(a)
260        else:
261            vedo.logger.error(f"in load(), cannot find {fod}")
262
263    if len(acts) == 1:
264        if "numpy" in str(type(acts[0])):
265            return acts[0]
266        if not acts[0]:
267            vedo.logger.error(f"in load(), cannot load {inputobj}")
268        return acts[0]
269
270    if len(acts) == 0:
271        vedo.logger.error(f"in load(), cannot load {inputobj}")
272        return None
273
274    else:
275        return acts

Load any vedo objects from file or from the web.

The output will depend on the file extension. See examples below. Unzip is made on the fly, if file ends with .gz. Can load an object directly from a URL address.

Arguments:
  • unpack : bool unpack MultiBlockData into a flat list of objects.
  • force : bool when downloading a file ignore any previous cached downloads and force a new one.
Example:
from vedo import dataurl, load, show
# Return a list of 2 meshes
g = load([dataurl+'250.vtk', dataurl+'270.vtk'])
show(g)
# Return a list of meshes by reading all files in a directory
# (if directory contains DICOM files then a Volume is returned)
g = load('mydicomdir/')
show(g)
def download(url, force=False, verbose=True):
461def download(url, force=False, verbose=True):
462    """Retrieve a file from a URL, save it locally and return its path.
463    Use `force` to force reload and discard cached copies."""
464
465    if not url.startswith("https://"):
466        vedo.logger.error(f"Invalid URL (must start with https):\n{url}")
467        return url
468    url = url.replace("www.dropbox", "dl.dropbox")
469
470    if "github.com" in url:
471        url = url.replace("/blob/", "/raw/")
472
473    basename = os.path.basename(url)
474
475    if "?" in basename:
476        basename = basename.split("?")[0]
477
478    tmp_file = NamedTemporaryFile(delete=False)
479    tmp_file.name = os.path.join(os.path.dirname(tmp_file.name), os.path.basename(basename))
480
481    if not force and os.path.exists(tmp_file.name):
482        if verbose:
483            colors.printc("reusing cached file:", tmp_file.name)
484            # colors.printc("     (use force=True to force a new download)")
485        return tmp_file.name
486
487    try:
488        from urllib.request import urlopen, Request
489
490        req = Request(url, headers={"User-Agent": "Mozilla/5.0"})
491        if verbose:
492            colors.printc("reading", basename, "from", url.split("/")[2][:40], "...", end="")
493
494    except ImportError:
495        import urllib2
496        import contextlib
497
498        urlopen = lambda url_: contextlib.closing(urllib2.urlopen(url_))
499        req = url
500        if verbose:
501            colors.printc("reading", basename, "from", url.split("/")[2][:40], "...", end="")
502
503    with urlopen(req) as response, open(tmp_file.name, "wb") as output:
504        output.write(response.read())
505
506    if verbose:
507        colors.printc(" done.")
508    return tmp_file.name

Retrieve a file from a URL, save it locally and return its path. Use force to force reload and discard cached copies.

def gunzip(filename):
511def gunzip(filename):
512    """Unzip a `.gz` file to a temporary file and returns its path."""
513    if not filename.endswith(".gz"):
514        # colors.printc("gunzip() error: file must end with .gz", c='r')
515        return filename
516    import gzip
517
518    tmp_file = NamedTemporaryFile(delete=False)
519    tmp_file.name = os.path.join(
520        os.path.dirname(tmp_file.name), os.path.basename(filename).replace(".gz", "")
521    )
522    inF = gzip.open(filename, "rb")
523    with open(tmp_file.name, "wb") as outF:
524        outF.write(inF.read())
525    inF.close()
526    return tmp_file.name

Unzip a .gz file to a temporary file and returns its path.

def loadStructuredPoints(filename):
545def loadStructuredPoints(filename):
546    """Load and return a `vtkStructuredPoints` object from file."""
547    reader = vtk.vtkStructuredPointsReader()
548    reader.SetFileName(filename)
549    reader.Update()
550    return reader.GetOutput()

Load and return a vtkStructuredPoints object from file.

def loadStructuredGrid(filename):
553def loadStructuredGrid(filename):
554    """Load and return a `vtkStructuredGrid` object from file."""
555    if filename.endswith(".vts"):
556        reader = vtk.vtkXMLStructuredGridReader()
557    else:
558        reader = vtk.vtkStructuredGridReader()
559    reader.SetFileName(filename)
560    reader.Update()
561    return reader.GetOutput()

Load and return a vtkStructuredGrid object from file.

def loadRectilinearGrid(filename):
575def loadRectilinearGrid(filename):
576    """Load and return a `vtkRectilinearGrid` object from file."""
577    if filename.endswith(".vtr"):
578        reader = vtk.vtkXMLRectilinearGridReader()
579    else:
580        reader = vtk.vtkRectilinearGridReader()
581    reader.SetFileName(filename)
582    reader.Update()
583    return reader.GetOutput()

Load and return a vtkRectilinearGrid object from file.

def loadUnStructuredGrid(filename):
564def loadUnStructuredGrid(filename):
565    """Load and return a `vtkunStructuredGrid` object from file."""
566    if filename.endswith(".vtu"):
567        reader = vtk.vtkXMLUnstructuredGridReader()
568    else:
569        reader = vtk.vtkUnstructuredGridReader()
570    reader.SetFileName(filename)
571    reader.Update()
572    return reader.GetOutput()

Load and return a vtkunStructuredGrid object from file.

def load_transform(filename):
1417def load_transform(filename):
1418    """
1419    Load a transformation from a file `.mat`.
1420
1421    Returns:
1422        - `vtkTransform`
1423            The transformation to be applied to some object (`use apply_transform()`).
1424        - `str`, a comment string associated to this transformation file.
1425    """
1426    with open(filename, "r", encoding="UTF-8") as f:
1427        lines = f.readlines()
1428        M = vtk.vtkMatrix4x4()
1429        i = 0
1430        comment = ""
1431        for l in lines:
1432            if l.startswith("#"):
1433                comment = l.replace("#", "").replace("\n", "")
1434                continue
1435            vals = l.split(" ")
1436            if len(vals) == 4:
1437                for j in range(4):
1438                    v = vals[j].replace("\n", "")
1439                    M.SetElement(i, j, float(v))
1440                i += 1
1441        T = vtk.vtkTransform()
1442        T.SetMatrix(M)
1443    return (T, comment)

Load a transformation from a file .mat.

Returns:
  • vtkTransform The transformation to be applied to some object (use apply_transform()).
  • str, a comment string associated to this transformation file.
def write_transform(inobj, filename='transform.mat', comment=''):
1385def write_transform(inobj, filename="transform.mat", comment=""):
1386    """
1387    Save a transformation for a mesh or pointcloud to ASCII file.
1388
1389    Arguments:
1390        filename : (str)
1391            output file name
1392        comment : (str)
1393            some optional comment
1394    """
1395    if isinstance(inobj, Points):
1396        M = inobj.get_transform().GetMatrix()
1397    elif isinstance(inobj, vtk.vtkTransform):
1398        M = inobj.GetMatrix()
1399    elif isinstance(inobj, vtk.vtkMatrix4x4):
1400        M = inobj
1401    else:
1402        vedo.logger.error(f"in write_transform(), cannot understand input type {type(inobj)}")
1403
1404    with open(filename, "w", encoding="UTF-8") as f:
1405        if comment:
1406            f.write("# " + comment + "\n")
1407        for i in range(4):
1408            f.write(
1409                str(M.GetElement(i,0))+' '+
1410                str(M.GetElement(i,1))+' '+
1411                str(M.GetElement(i,2))+' '+
1412                str(M.GetElement(i,3))+'\n',
1413            )
1414        f.write('\n')

Save a transformation for a mesh or pointcloud to ASCII file.

Arguments:
  • filename : (str) output file name
  • comment : (str) some optional comment
def write(objct, fileoutput, binary=True):
1213def write(objct, fileoutput, binary=True):
1214    """
1215    Write object to file.
1216
1217    Possile extensions are:
1218        - `vtk, vti, npy, npz, ply, obj, stl, byu, vtp, vti, mhd, xyz, tif, png, bmp`
1219    """
1220    obj = objct
1221    if isinstance(obj, Points):  # picks transformation
1222        obj = objct.polydata(True)
1223    elif isinstance(obj, (vtk.vtkActor, vtk.vtkVolume)):
1224        obj = objct.GetMapper().GetInput()
1225    elif isinstance(obj, (vtk.vtkPolyData, vtk.vtkImageData)):
1226        obj = objct
1227
1228    fr = fileoutput.lower()
1229    if fr.endswith(".vtk"):
1230        writer = vtk.vtkDataSetWriter()
1231    elif fr.endswith(".ply"):
1232        writer = vtk.vtkPLYWriter()
1233        writer.AddComment("PLY file generated by vedo")
1234        lut = objct.GetMapper().GetLookupTable()
1235        if lut:
1236            pscal = obj.GetPointData().GetScalars()
1237            if not pscal:
1238                pscal = obj.GetCellData().GetScalars()
1239            if pscal and pscal.GetName():
1240                writer.SetArrayName(pscal.GetName())
1241            writer.SetLookupTable(lut)
1242    elif fr.endswith(".stl"):
1243        writer = vtk.vtkSTLWriter()
1244    elif fr.endswith(".vtp"):
1245        writer = vtk.vtkXMLPolyDataWriter()
1246    elif fr.endswith(".vtu"):
1247        writer = vtk.vtkXMLUnstructuredGridWriter()
1248    elif fr.endswith(".vtm"):
1249        g = vtk.vtkMultiBlockDataGroupFilter()
1250        for ob in objct:
1251            if isinstance(ob, (Points, Volume)):  # picks transformation
1252                ob = ob.polydata(True)
1253                g.AddInputData(ob)
1254        g.Update()
1255        mb = g.GetOutputDataObject(0)
1256        wri = vtk.vtkXMLMultiBlockDataWriter()
1257        wri.SetInputData(mb)
1258        wri.SetFileName(fileoutput)
1259        wri.Write()
1260        return mb
1261    elif fr.endswith(".xyz"):
1262        writer = vtk.vtkSimplePointsWriter()
1263    elif fr.endswith(".facet"):
1264        writer = vtk.vtkFacetWriter()
1265    elif fr.endswith(".vti"):
1266        writer = vtk.vtkXMLImageDataWriter()
1267    elif fr.endswith(".mhd"):
1268        writer = vtk.vtkMetaImageWriter()
1269    elif fr.endswith(".nii"):
1270        writer = vtk.vtkNIFTIImageWriter()
1271    elif fr.endswith(".png"):
1272        writer = vtk.vtkPNGWriter()
1273    elif fr.endswith(".jpg"):
1274        writer = vtk.vtkJPEGWriter()
1275    elif fr.endswith(".bmp"):
1276        writer = vtk.vtkBMPWriter()
1277    elif fr.endswith(".tif") or fr.endswith(".tiff"):
1278        writer = vtk.vtkTIFFWriter()
1279        writer.SetFileDimensionality(len(obj.GetDimensions()))
1280    elif fr.endswith(".npy") or fr.endswith(".npz"):
1281        if utils.is_sequence(objct):
1282            objslist = objct
1283        else:
1284            objslist = [objct]
1285        dicts2save = []
1286        for obj in objslist:
1287            dicts2save.append(tonumpy(obj))
1288        np.save(fileoutput, dicts2save)
1289        return dicts2save
1290
1291    elif fr.endswith(".obj"):
1292        with open(fileoutput, "w", encoding="UTF-8") as outF:
1293            outF.write("# OBJ file format with ext .obj\n")
1294            outF.write("# File generated by vedo\n")
1295
1296            for p in objct.points():
1297                outF.write("v {:.5g} {:.5g} {:.5g}\n".format(*p))
1298
1299            ptxt = objct.polydata().GetPointData().GetTCoords()
1300            if ptxt:
1301                ntxt = utils.vtk2numpy(ptxt)
1302                for vt in ntxt:
1303                    outF.write("vt " + str(vt[0]) + " " + str(vt[1]) + " 0.0\n")
1304
1305            if isinstance(objct, Mesh):
1306
1307                for i, f in enumerate(objct.faces()):
1308                    fs = ""
1309                    for fi in f:
1310                        if ptxt:
1311                            fs += f" {fi+1}/{fi+1}"
1312                        else:
1313                            fs += f" {fi+1}"
1314                    outF.write(f"f{fs}\n")
1315
1316                for l in objct.lines():
1317                    ls = ""
1318                    for li in l:
1319                        ls += str(li + 1) + " "
1320                    outF.write(f"l {ls}\n")
1321
1322        return objct
1323
1324    elif fr.endswith(".xml"):  # write tetrahedral dolfin xml
1325        vertices = objct.points().astype(str)
1326        faces = np.array(objct.faces()).astype(str)
1327        ncoords = vertices.shape[0]
1328        with open(fileoutput, "w", encoding="UTF-8") as outF:
1329            outF.write('<?xml version="1.0" encoding="UTF-8"?>\n')
1330            outF.write('<dolfin xmlns:dolfin="http://www.fenicsproject.org">\n')
1331
1332            if len(faces[0]) == 4:  # write tetrahedral mesh
1333                ntets = faces.shape[0]
1334                outF.write('  <mesh celltype="tetrahedron" dim="3">\n')
1335                outF.write('    <vertices size="' + str(ncoords) + '">\n')
1336                for i in range(ncoords):
1337                    x, y, z = vertices[i]
1338                    outF.write('      <vertex index="'+str(i)+'" x="'+x+'" y="'+y+'" z="'+z+'"/>\n')
1339                outF.write('    </vertices>\n')
1340                outF.write('    <cells size="' + str(ntets) + '">\n')
1341                for i in range(ntets):
1342                    v0, v1, v2, v3 = faces[i]
1343                    outF.write('     <tetrahedron index="'+str(i)
1344                               + '" v0="'+v0+'" v1="'+v1+'" v2="'+v2+'" v3="'+v3+'"/>\n')
1345
1346            elif len(faces[0]) == 3:  # write triangle mesh
1347                ntri = faces.shape[0]
1348                outF.write('  <mesh celltype="triangle" dim="2">\n')
1349                outF.write('    <vertices size="' + str(ncoords) + '">\n')
1350                for i in range(ncoords):
1351                    x, y, dummy_z = vertices[i]
1352                    outF.write('      <vertex index="'+str(i)+'" x="'+x+'" y="'+y+'"/>\n')
1353                outF.write('    </vertices>\n')
1354                outF.write('    <cells size="' + str(ntri) + '">\n')
1355                for i in range(ntri):
1356                    v0, v1, v2 = faces[i]
1357                    outF.write('     <triangle index="'+str(i)+'" v0="'+v0+'" v1="'+v1+'" v2="'+v2+'"/>\n')
1358
1359            outF.write("    </cells>\n")
1360            outF.write("  </mesh>\n")
1361            outF.write("</dolfin>\n")
1362        return objct
1363
1364    else:
1365        vedo.logger.error(f"Unknown format {fileoutput}, file not saved")
1366        return objct
1367
1368    try:
1369        if binary:
1370            writer.SetFileTypeToBinary()
1371        else:
1372            writer.SetFileTypeToASCII()
1373    except AttributeError:
1374        pass
1375
1376    try:
1377        writer.SetInputData(obj)
1378        writer.SetFileName(fileoutput)
1379        writer.Write()
1380    except:
1381        vedo.logger.error(f"could not save {fileoutput}")
1382    return objct

Write object to file.

Possile extensions are:
  • vtk, vti, npy, npz, ply, obj, stl, byu, vtp, vti, mhd, xyz, tif, png, bmp
def export_window(fileoutput, binary=False):
1447def export_window(fileoutput, binary=False):
1448    """
1449    Exporter which writes out the rendered scene into an HTML, X3D
1450    or Numpy file.
1451
1452    Example:
1453        - [export_x3d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/export_x3d.py)
1454
1455        Check out the HTML generated webpage [here](https://vedo.embl.es/examples/embryo.html).
1456
1457        <img src='https://user-images.githubusercontent.com/32848391/57160341-c6ffbd80-6de8-11e9-95ff-7215ce642bc5.jpg' width="600"/>
1458
1459    .. note::
1460        the rendering window can also be exported to `numpy` file `scene.npz`
1461        by pressing `E` keyboard at any moment during visualization.
1462    """
1463    fr = fileoutput.lower()
1464
1465    ####################################################################
1466    if fr.endswith(".npy") or fr.endswith(".npz"):
1467        sdict = {}
1468        plt = vedo.plotter_instance
1469        sdict["shape"] = plt.shape
1470        sdict["sharecam"] = plt.sharecam
1471        sdict["camera"] = dict(
1472            pos=plt.camera.GetPosition(),
1473            focal_point=plt.camera.GetFocalPoint(),
1474            viewup=plt.camera.GetViewUp(),
1475            distance=plt.camera.GetDistance(),
1476            clipping_range=plt.camera.GetClippingRange(),
1477        )
1478        sdict["position"] = plt.pos
1479        sdict["size"] = plt.size
1480        sdict["axes"] = plt.axes
1481        sdict["title"] = plt.title
1482        sdict["backgrcol"] = colors.get_color(plt.renderer.GetBackground())
1483        sdict["backgrcol2"] = None
1484        if plt.renderer.GetGradientBackground():
1485            sdict["backgrcol2"] = plt.renderer.GetBackground2()
1486        sdict["use_depth_peeling"] = settings.use_depth_peeling
1487        sdict["render_lines_as_tubes"] = settings.render_lines_as_tubes
1488        sdict["hidden_line_removal"] = settings.hidden_line_removal
1489        sdict["visible_grid_edges"] = settings.visible_grid_edges
1490        sdict["use_parallel_projection"] = settings.use_parallel_projection
1491        sdict["default_font"] = settings.default_font
1492        sdict["objects"] = []
1493
1494        allobjs = plt.get_meshes(include_non_pickables=True) + plt.get_volumes(include_non_pickables=True)
1495        acts2d = plt.renderer.GetActors2D()
1496        acts2d.InitTraversal()
1497        for _ in range(acts2d.GetNumberOfItems()):
1498            a = acts2d.GetNextItem()
1499            if isinstance(a, vedo.Text2D):
1500                allobjs.append(a)
1501        allobjs += plt.actors
1502
1503        allobjs = list(set(allobjs))  # make sure its unique
1504
1505        for a in allobjs:
1506            if a.GetVisibility():
1507                sdict["objects"].append(tonumpy(a))
1508
1509        if fr.endswith(".npz"):
1510            np.savez_compressed(fileoutput, vedo_scenes=[sdict])
1511        else:
1512            np.save(fileoutput, [sdict])
1513
1514    ####################################################################
1515    elif fr.endswith(".x3d"):
1516        obj = list(set(vedo.plotter_instance.get_meshes() + vedo.plotter_instance.actors))
1517        if vedo.plotter_instance.axes_instances:
1518            obj.append(vedo.plotter_instance.axes_instances[0])
1519
1520        for a in obj:
1521            if isinstance(a, Mesh):
1522                newa = a.clone(transformed=True)
1523                vedo.plotter_instance.remove(a).add(newa)
1524
1525            elif isinstance(a, Assembly):
1526                vedo.plotter_instance.remove(a)
1527                for b in a.unpack():
1528                    if b:
1529                        if a.name == "Axes":
1530                            newb = b.clone(transformed=True)
1531                        else:
1532                            # newb = b.clone(transformed=True) # BUG??
1533
1534                            newb = b.clone(transformed=False)
1535                            tt = vtk.vtkTransform()
1536                            tt.Concatenate(a.GetMatrix())
1537                            tt.Concatenate(b.GetMatrix())
1538                            newb.PokeMatrix(vtk.vtkMatrix4x4())
1539                            newb.SetUserTransform(tt)
1540
1541                        vedo.plotter_instance.add(newb)
1542
1543        vedo.plotter_instance.render()
1544
1545        exporter = vtk.vtkX3DExporter()
1546        exporter.SetBinary(binary)
1547        exporter.FastestOff()
1548        exporter.SetInput(vedo.plotter_instance.window)
1549        exporter.SetFileName(fileoutput)
1550        # exporter.WriteToOutputStringOn() # see below
1551        exporter.Update()
1552        exporter.Write()
1553
1554        # this can reduce the size by more than half...
1555        #        outstring = exporter.GetOutputString().decode("utf-8") # this fails though
1556        #        from vedo.utils import isInteger, isNumber, precision
1557        #        newlines = []
1558        #        for l in outstring.splitlines(True):
1559        #            ls = l.lstrip()
1560        #            content = ls.split()
1561        #            newls = ""
1562        #            for c in content:
1563        #                c2 = c.replace(',','')
1564        #                if isNumber(c2) and not isInteger(c2):
1565        #                    newc = precision(float(c2), 4)
1566        #                    if ',' in c:
1567        #                        newls += newc + ','
1568        #                    else:
1569        #                        newls += newc + ' '
1570        #                else:
1571        #                    newls += c + ' '
1572        #        newlines.append(newls.lstrip()+'\n')
1573        #        with open("fileoutput", 'w', encoding='UTF-8') as f:
1574        #            l = "".join(newlines)
1575        #            f.write(l)
1576
1577        x3d_html = _x3d_html.replace("~fileoutput", fileoutput)
1578        wsize = vedo.plotter_instance.window.GetSize()
1579        x3d_html = x3d_html.replace("~width", str(wsize[0]))
1580        x3d_html = x3d_html.replace("~height", str(wsize[1]))
1581        with open(fileoutput.replace(".x3d", ".html"), "w", encoding="UTF-8") as outF:
1582            outF.write(x3d_html)
1583            vedo.logger.info(f"Saved files {fileoutput} and {fileoutput.replace('.x3d','.html')}")
1584
1585    ####################################################################
1586    elif fr.endswith(".html"):
1587        savebk = vedo.notebook_backend
1588        vedo.notebook_backend = "k3d"
1589        vedo.settings.default_backend = "k3d"
1590        plt = vedo.backends.get_notebook_backend(vedo.plotter_instance.actors)
1591
1592        with open(fileoutput, "w", encoding="UTF-8") as fp:
1593            fp.write(plt.get_snapshot())
1594
1595        vedo.notebook_backend = savebk
1596        vedo.settings.default_backend = savebk
1597
1598    else:
1599        vedo.logger.error(f"export extension {fr.split('.')[-1]} is not supported")
1600
1601    return vedo.plotter_instance

Exporter which writes out the rendered scene into an HTML, X3D or Numpy file.

Example:

Check out the HTML generated webpage here.

the rendering window can also be exported to numpy file scene.npz by pressing E keyboard at any moment during visualization.

def import_window(fileinput, mtl_file=None, texture_path=None):
1604def import_window(fileinput, mtl_file=None, texture_path=None):
1605    """Import a whole scene from a Numpy or OBJ wavefront file.
1606
1607    Arguments:
1608        mtl_file : (str)
1609            MTL file for OBJ wavefront files
1610        texture_path : (str)
1611            path of the texture files directory
1612
1613    Returns:
1614        `Plotter` instance
1615    """
1616    data = None
1617    if isinstance(fileinput, dict):
1618        data = fileinput
1619    elif fileinput.endswith(".npy"):
1620        data = np.load(fileinput, allow_pickle=True, encoding="latin1").flatten()[0]
1621    elif fileinput.endswith(".npz"):
1622        data = np.load(fileinput, allow_pickle=True)["vedo_scenes"][0]
1623
1624    if data is not None:
1625        if "render_lines_as_tubes" in data.keys():
1626            settings.render_lines_as_tubes = data["render_lines_as_tubes"]
1627        if "hidden_line_removal" in data.keys():
1628            settings.hidden_line_removal = data["hidden_line_removal"]
1629        if "visible_grid_edges" in data.keys():
1630            settings.visible_grid_edges = data["visible_grid_edges"]
1631        if "use_parallel_projection" in data.keys():
1632            settings.use_parallel_projection = data["use_parallel_projection"]
1633        if "use_polygon_offset" in data.keys():
1634            settings.use_polygon_offset = data["use_polygon_offset"]
1635        if "polygon_offset_factor" in data.keys():
1636            settings.polygon_offset_factor = data["polygon_offset_factor"]
1637        if "polygon_offset_units" in data.keys():
1638            settings.polygon_offset_units = data["polygon_offset_units"]
1639        if "interpolate_scalars_before_mapping" in data.keys():
1640            settings.interpolate_scalars_before_mapping = data["interpolate_scalars_before_mapping"]
1641        if "default_font" in data.keys():
1642            settings.default_font = data["default_font"]
1643        if "use_depth_peeling" in data.keys():
1644            settings.use_depth_peeling = data["use_depth_peeling"]
1645
1646        axes = data.pop("axes", 4)
1647        title = data.pop("title", "")
1648        backgrcol = data.pop("backgrcol", "white")
1649        backgrcol2 = data.pop("backgrcol2", None)
1650        cam = data.pop("camera", None)
1651
1652        if data["shape"] != (1, 1):
1653            data["size"] = "auto"  # disable size
1654
1655        plt = vedo.Plotter(
1656            size=data["size"],  # not necessarily a good idea to set it
1657            # shape=data['shape'], # will need to create a Renderer class first
1658            axes=axes,
1659            title=title,
1660            bg=backgrcol,
1661            bg2=backgrcol2,
1662        )
1663
1664        if cam:
1665            if "pos" in cam.keys():
1666                plt.camera.SetPosition(cam["pos"])
1667            if "focalPoint" in cam.keys():
1668                plt.camera.SetFocalPoint(cam["focalPoint"])
1669            if "focal_point" in cam.keys():
1670                plt.camera.SetFocalPoint(cam["focal_point"])
1671            if "viewup" in cam.keys():
1672                plt.camera.SetViewUp(cam["viewup"])
1673            if "distance" in cam.keys():
1674                plt.camera.SetDistance(cam["distance"])
1675            if "clippingRange" in cam.keys():
1676                plt.camera.SetClippingRange(cam["clippingRange"])
1677            if "clipping_range" in cam.keys():
1678                plt.camera.SetClippingRange(cam["clipping_range"])
1679            plt.resetcam = False
1680
1681        if "objects" in data.keys():
1682            objs = loadnumpy(data["objects"])
1683            if not utils.is_sequence(objs):
1684                objs = [objs]
1685        else:
1686            # colors.printc("Trying to import a single mesh.. use load() instead.", c='r')
1687            # colors.printc(" -> try to load a single object with load().", c='r')
1688            objs = [loadnumpy(fileinput)]
1689
1690        plt.actors = objs
1691        plt.add(objs)
1692        return plt
1693
1694    elif ".obj" in fileinput.lower():
1695
1696        window = vtk.vtkRenderWindow()
1697        window.SetOffScreenRendering(1)
1698        renderer = vtk.vtkRenderer()
1699        window.AddRenderer(renderer)
1700
1701        importer = vtk.vtkOBJImporter()
1702        importer.SetFileName(fileinput)
1703        if mtl_file is not False:
1704            if mtl_file is None:
1705                mtl_file = fileinput.replace(".obj", ".mtl").replace(".OBJ", ".MTL")
1706            importer.SetFileNameMTL(mtl_file)
1707        if texture_path is not False:
1708            if texture_path is None:
1709                texture_path = fileinput.replace(".obj", ".txt").replace(".OBJ", ".TXT")
1710            importer.SetTexturePath(texture_path)
1711        importer.SetRenderWindow(window)
1712        importer.Update()
1713
1714        plt = vedo.Plotter()
1715
1716        actors = renderer.GetActors()
1717        actors.InitTraversal()
1718        for _ in range(actors.GetNumberOfItems()):
1719            vactor = actors.GetNextActor()
1720            act = Mesh(vactor)
1721            act_tu = vactor.GetTexture()
1722            if act_tu:
1723                act.texture(act_tu)
1724            plt.actors.append(act)
1725        return plt
1726
1727    return None

Import a whole scene from a Numpy or OBJ wavefront file.

Arguments:
  • mtl_file : (str) MTL file for OBJ wavefront files
  • texture_path : (str) path of the texture files directory
Returns:

Plotter instance

def screenshot(filename='screenshot.png', scale=1, asarray=False):
1731def screenshot(filename="screenshot.png", scale=1, asarray=False):
1732    """
1733    Save a screenshot of the current rendering window.
1734
1735    Arguments:
1736        scale : (int)
1737            Set image magnification as an integer multiplicative factor.
1738            E.g. setting a magnification of 2 produces an image twice as large,
1739            but 10x slower to generate.
1740        asarray : (bool)
1741            Return a numpy array of the image
1742    """
1743    if not vedo.plotter_instance or not vedo.plotter_instance.window:
1744        # vedo.logger.error("in screenshot(), rendering window is not present, skip.")
1745        return vedo.plotter_instance  ##########
1746
1747    if asarray and scale == 1:
1748        nx, ny = vedo.plotter_instance.window.GetSize()
1749        arr = vtk.vtkUnsignedCharArray()
1750        vedo.plotter_instance.window.GetRGBACharPixelData(0, 0, nx-1, ny-1, 0, arr)
1751        narr = vedo.vtk2numpy(arr).T[:3].T.reshape([ny, nx, 3])
1752        narr = np.flip(narr, axis=0)
1753        return narr  ##########
1754
1755    filename = str(filename)
1756
1757    if filename.endswith(".pdf"):
1758        writer = vtk.vtkGL2PSExporter()
1759        writer.SetRenderWindow(vedo.plotter_instance.window)
1760        writer.Write3DPropsAsRasterImageOff()
1761        writer.SilentOn()
1762        writer.SetSortToBSP()
1763        writer.SetFileFormatToPDF()
1764        writer.SetFilePrefix(filename.replace(".pdf", ""))
1765        writer.Write()
1766        return vedo.plotter_instance  ##########
1767
1768    elif filename.endswith(".svg"):
1769        writer = vtk.vtkGL2PSExporter()
1770        writer.SetRenderWindow(vedo.plotter_instance.window)
1771        writer.Write3DPropsAsRasterImageOff()
1772        writer.SilentOn()
1773        writer.SetSortToBSP()
1774        writer.SetFileFormatToSVG()
1775        writer.SetFilePrefix(filename.replace(".svg", ""))
1776        writer.Write()
1777        return vedo.plotter_instance  ##########
1778
1779    elif filename.endswith(".eps"):
1780        writer = vtk.vtkGL2PSExporter()
1781        writer.SetRenderWindow(vedo.plotter_instance.window)
1782        writer.Write3DPropsAsRasterImageOff()
1783        writer.SilentOn()
1784        writer.SetSortToBSP()
1785        writer.SetFileFormatToEPS()
1786        writer.SetFilePrefix(filename.replace(".eps", ""))
1787        writer.Write()
1788        return vedo.plotter_instance  ##########
1789
1790    if settings.screeshot_large_image:
1791        w2if = vtk.vtkRenderLargeImage()
1792        w2if.SetInput(vedo.plotter_instance.renderer)
1793        w2if.SetMagnification(scale)
1794    else:
1795        w2if = vtk.vtkWindowToImageFilter()
1796        w2if.SetInput(vedo.plotter_instance.window)
1797        if hasattr(w2if, "SetScale"):
1798            w2if.SetScale(int(scale), int(scale))
1799        if settings.screenshot_transparent_background:
1800            w2if.SetInputBufferTypeToRGBA()
1801        w2if.ReadFrontBufferOff()  # read from the back buffer
1802    w2if.Update()
1803
1804    if asarray and scale != 1:
1805        pd = w2if.GetOutput().GetPointData()
1806        npdata = utils.vtk2numpy(pd.GetArray("ImageScalars"))
1807        npdata = npdata[:, [0, 1, 2]]
1808        ydim, xdim, _ = w2if.GetOutput().GetDimensions()
1809        npdata = npdata.reshape([xdim, ydim, -1])
1810        npdata = np.flip(npdata, axis=0)
1811        return npdata
1812
1813    if filename.lower().endswith(".png"):
1814        writer = vtk.vtkPNGWriter()
1815        writer.SetFileName(filename)
1816        writer.SetInputData(w2if.GetOutput())
1817        writer.Write()
1818    elif filename.lower().endswith(".jpg") or filename.lower().endswith(".jpeg"):
1819        writer = vtk.vtkJPEGWriter()
1820        writer.SetFileName(filename)
1821        writer.SetInputData(w2if.GetOutput())
1822        writer.Write()
1823    else:  # add .png
1824        writer = vtk.vtkPNGWriter()
1825        writer.SetFileName(filename + ".png")
1826        writer.SetInputData(w2if.GetOutput())
1827        writer.Write()
1828    return vedo.plotter_instance

Save a screenshot of the current rendering window.

Arguments:
  • scale : (int) Set image magnification as an integer multiplicative factor. E.g. setting a magnification of 2 produces an image twice as large, but 10x slower to generate.
  • asarray : (bool) Return a numpy array of the image
def ask(*question, **kwarg):
1831def ask(*question, **kwarg):
1832    """
1833    Ask a question from command line. Return the answer as a string.
1834    See function `colors.printc()` for the description of the keyword options.
1835
1836    Arguments:
1837        options : (list)
1838            a python list of possible answers to choose from.
1839        default : (str)
1840            the default answer when just hitting return.
1841
1842    Example:
1843        ```python
1844        import vedo
1845        res = vedo.file_io.ask("Continue?", options=['Y','n'], default='Y', c='g')
1846        print(res)
1847        ```
1848    """
1849    kwarg.update({"end": " "})
1850    if "invert" not in kwarg:
1851        kwarg.update({"invert": True})
1852    if "box" in kwarg:
1853        kwarg.update({"box": ""})
1854
1855    options = kwarg.pop("options", [])
1856    default = kwarg.pop("default", "")
1857    if options:
1858        opt = "["
1859        for o in options:
1860            opt += o + "/"
1861        opt = opt[:-1] + "]"
1862        colors.printc(*question, opt, **kwarg)
1863    else:
1864        colors.printc(*question, **kwarg)
1865
1866    resp = input()
1867
1868    if options:
1869        if resp not in options:
1870            if default and str(repr(resp)) == "''":
1871                return default
1872            colors.printc("Please choose one option in:", opt, italic=True, bold=False)
1873            kwarg["options"] = options
1874            return ask(*question, **kwarg)  # ask again
1875    return resp

Ask a question from command line. Return the answer as a string. See function colors.printc() for the description of the keyword options.

Arguments:
  • options : (list) a python list of possible answers to choose from.
  • default : (str) the default answer when just hitting return.
Example:
import vedo
res = vedo.file_io.ask("Continue?", options=['Y','n'], default='Y', c='g')
print(res)
class Video:
1879class Video:
1880    """
1881    Generate a video from a rendering window.
1882    """
1883
1884    def __init__(self, name="movie.mp4", duration=None, fps=24, backend="imageio"):
1885        """
1886        Class to generate a video from the specified rendering window.
1887        Program `ffmpeg` is used to create video from each generated frame.
1888
1889        Arguments:
1890            name : (str)
1891                name of the output file.
1892            fps : (int)
1893                set the number of frames per second.
1894            duration : (float)
1895                set the total `duration` of the video and recalculates `fps` accordingly.
1896            backend : (str)
1897                the backend engine to be used `['imageio', 'ffmpeg', 'cv']`
1898
1899        Examples:
1900            - [make_video.py](https://github.com/marcomusy/vedo/tree/master/examples/other/make_video.py)
1901
1902            ![](https://user-images.githubusercontent.com/32848391/50739007-2bfc2b80-11da-11e9-97e6-620a3541a6fa.jpg)
1903        """
1904        self.name = name
1905        self.duration = duration
1906        self.backend = backend
1907        self.fps = float(fps)
1908        self.command = "ffmpeg -loglevel panic -y -r"
1909        self.options = "-b:v 8000k"
1910
1911        self.frames = []
1912        self.tmp_dir = TemporaryDirectory()
1913        self.get_filename = lambda x: os.path.join(self.tmp_dir.name, x)
1914        colors.printc(":video: Video file", self.name, "is open... ", c="m", end="")
1915
1916    def add_frame(self):
1917        """Add frame to current video."""
1918        fr = self.get_filename(str(len(self.frames)) + ".png")
1919        screenshot(fr)
1920        self.frames.append(fr)
1921        return self
1922
1923    def pause(self, pause=0):
1924        """Insert a `pause`, in seconds."""
1925        fr = self.frames[-1]
1926        n = int(self.fps * pause)
1927        for _ in range(n):
1928            fr2 = self.get_filename(str(len(self.frames)) + ".png")
1929            self.frames.append(fr2)
1930            os.system("cp -f %s %s" % (fr, fr2))
1931        return self
1932
1933    def action(self, elevation=(0, 80), azimuth=(0, 359), cameras=(), resetcam=False):
1934        """
1935        Automatic shooting of a static scene by specifying rotation and elevation ranges.
1936
1937        Arguments:
1938            elevation : list
1939                initial and final elevation angles
1940            azimuth_range : list
1941                initial and final azimuth angles
1942            cameras : list
1943                list of cameras to go through, each camera can be dictionary or a vtkCamera
1944        """
1945        if not self.duration:
1946            self.duration = 5
1947
1948        plt = vedo.plotter_instance
1949        n = int(self.fps * self.duration)
1950
1951        cams = []
1952        for cm in cameras:
1953            cams.append(utils.camera_from_dict(cm))
1954        nc = len(cams)
1955
1956        plt.show(resetcam=resetcam, interactive=False)
1957
1958        if nc:
1959            for i in range(n):
1960                plt.move_camera(cams, i / n)
1961                plt.show()
1962                self.add_frame()
1963
1964        else:  ########################################
1965
1966            for i in range(n):
1967                plt.camera.Elevation((elevation[1] - elevation[0]) / n)
1968                plt.camera.Azimuth((azimuth[1] - azimuth[0]) / n)
1969                plt.show()
1970                self.add_frame()
1971
1972        return self
1973
1974    def close(self):
1975        """
1976        Render the video and write it to file.
1977        """
1978        if self.duration:
1979            self.fps = int(len(self.frames) / float(self.duration) + 0.5)
1980            colors.printc("recalculated fps:", self.fps, c="m", end="")
1981        else:
1982            self.fps = int(self.fps)
1983
1984        ########################################
1985        if self.backend == "ffmpeg":
1986            out = os.system(
1987                self.command
1988                + " "
1989                + str(self.fps)
1990                + " -i "
1991                + f"'{self.tmp_dir.name}'"
1992                + os.sep
1993                + "%01d.png "
1994                + self.options
1995                + " "
1996                + f"'{self.name}'"
1997            )
1998            if out:
1999                vedo.logger.error(f":noentry: backend {self.backend} returning error: {out}")
2000            else:
2001                colors.printc(f":save: saved to {self.name}", c="m")
2002
2003        ########################################
2004        elif "cv" in self.backend:
2005            try:
2006                import cv2
2007            except ImportError:
2008                vedo.logger.error("opencv is not installed")
2009                return
2010
2011            cap = cv2.VideoCapture(os.path.join(self.tmp_dir.name, "%1d.png"))
2012            fourcc = cv2.VideoWriter_fourcc(*"mp4v")
2013            w, h = vedo.plotter_instance.window.GetSize()
2014            writer = cv2.VideoWriter(self.name, fourcc, self.fps, (w, h), True)
2015
2016            while True:
2017                ret, frame = cap.read()
2018                if not ret:
2019                    break
2020                writer.write(frame)
2021
2022            cap.release()
2023            writer.release()
2024
2025        ########################################
2026        elif "imageio" in self.backend:
2027            try:
2028                import imageio
2029            except ImportError:
2030                vedo.logger.error("Please install imageio with:\n pip install imageio[ffmpeg]")
2031                return
2032
2033            if self.name.endswith(".mp4"):
2034                writer = imageio.get_writer(self.name, fps=self.fps)
2035            elif self.name.endswith(".gif"):
2036                writer = imageio.get_writer(self.name, mode="I", duration=1 / self.fps)
2037            elif self.name.endswith(".webm"):
2038                writer = imageio.get_writer(self.name, format="webm", fps=self.fps)
2039            else:
2040                vedo.logger.error(f"Unknown format of {self.name}.")
2041                return
2042
2043            for f in utils.humansort(self.frames):
2044                image = imageio.v3.imread(f)
2045                writer.append_data(image)
2046            try:
2047                writer.close()
2048                colors.printc(f"... saved as {self.name}", c="m")
2049            except:
2050                colors.printc(f":noentry: Could not save video {self.name}", c="r")
2051
2052        # finalize cleanup
2053        self.tmp_dir.cleanup()
2054
2055    def split_frames(self, output_dir="video_frames", prefix="frame_", format="png"):
2056        """Split an existing video file into frames."""
2057        try:
2058            import imageio
2059        except ImportError:
2060            vedo.logger.error("\nPlease install imageio with:\n pip install imageio")
2061            return
2062
2063        # Create the output directory if it doesn't exist
2064        if not os.path.exists(output_dir):
2065            os.makedirs(output_dir)
2066
2067        # Create a reader object to read the video
2068        reader = imageio.get_reader(self.name)
2069
2070        # Loop through each frame of the video and save it as image
2071        print()
2072        for i, frame in utils.progressbar(
2073            enumerate(reader), title=f"writing {format} frames", c="m", width=20
2074        ):
2075            output_file = os.path.join(output_dir, f"{prefix}{str(i).zfill(5)}.{format}")
2076            imageio.imwrite(output_file, frame, format=format)

Generate a video from a rendering window.

Video(name='movie.mp4', duration=None, fps=24, backend='imageio')
1884    def __init__(self, name="movie.mp4", duration=None, fps=24, backend="imageio"):
1885        """
1886        Class to generate a video from the specified rendering window.
1887        Program `ffmpeg` is used to create video from each generated frame.
1888
1889        Arguments:
1890            name : (str)
1891                name of the output file.
1892            fps : (int)
1893                set the number of frames per second.
1894            duration : (float)
1895                set the total `duration` of the video and recalculates `fps` accordingly.
1896            backend : (str)
1897                the backend engine to be used `['imageio', 'ffmpeg', 'cv']`
1898
1899        Examples:
1900            - [make_video.py](https://github.com/marcomusy/vedo/tree/master/examples/other/make_video.py)
1901
1902            ![](https://user-images.githubusercontent.com/32848391/50739007-2bfc2b80-11da-11e9-97e6-620a3541a6fa.jpg)
1903        """
1904        self.name = name
1905        self.duration = duration
1906        self.backend = backend
1907        self.fps = float(fps)
1908        self.command = "ffmpeg -loglevel panic -y -r"
1909        self.options = "-b:v 8000k"
1910
1911        self.frames = []
1912        self.tmp_dir = TemporaryDirectory()
1913        self.get_filename = lambda x: os.path.join(self.tmp_dir.name, x)
1914        colors.printc(":video: Video file", self.name, "is open... ", c="m", end="")

Class to generate a video from the specified rendering window. Program ffmpeg is used to create video from each generated frame.

Arguments:
  • name : (str) name of the output file.
  • fps : (int) set the number of frames per second.
  • duration : (float) set the total duration of the video and recalculates fps accordingly.
  • backend : (str) the backend engine to be used ['imageio', 'ffmpeg', 'cv']
Examples:

def add_frame(self):
1916    def add_frame(self):
1917        """Add frame to current video."""
1918        fr = self.get_filename(str(len(self.frames)) + ".png")
1919        screenshot(fr)
1920        self.frames.append(fr)
1921        return self

Add frame to current video.

def pause(self, pause=0):
1923    def pause(self, pause=0):
1924        """Insert a `pause`, in seconds."""
1925        fr = self.frames[-1]
1926        n = int(self.fps * pause)
1927        for _ in range(n):
1928            fr2 = self.get_filename(str(len(self.frames)) + ".png")
1929            self.frames.append(fr2)
1930            os.system("cp -f %s %s" % (fr, fr2))
1931        return self

Insert a pause, in seconds.

def action( self, elevation=(0, 80), azimuth=(0, 359), cameras=(), resetcam=False):
1933    def action(self, elevation=(0, 80), azimuth=(0, 359), cameras=(), resetcam=False):
1934        """
1935        Automatic shooting of a static scene by specifying rotation and elevation ranges.
1936
1937        Arguments:
1938            elevation : list
1939                initial and final elevation angles
1940            azimuth_range : list
1941                initial and final azimuth angles
1942            cameras : list
1943                list of cameras to go through, each camera can be dictionary or a vtkCamera
1944        """
1945        if not self.duration:
1946            self.duration = 5
1947
1948        plt = vedo.plotter_instance
1949        n = int(self.fps * self.duration)
1950
1951        cams = []
1952        for cm in cameras:
1953            cams.append(utils.camera_from_dict(cm))
1954        nc = len(cams)
1955
1956        plt.show(resetcam=resetcam, interactive=False)
1957
1958        if nc:
1959            for i in range(n):
1960                plt.move_camera(cams, i / n)
1961                plt.show()
1962                self.add_frame()
1963
1964        else:  ########################################
1965
1966            for i in range(n):
1967                plt.camera.Elevation((elevation[1] - elevation[0]) / n)
1968                plt.camera.Azimuth((azimuth[1] - azimuth[0]) / n)
1969                plt.show()
1970                self.add_frame()
1971
1972        return self

Automatic shooting of a static scene by specifying rotation and elevation ranges.

Arguments:
  • elevation : list initial and final elevation angles
  • azimuth_range : list initial and final azimuth angles
  • cameras : list list of cameras to go through, each camera can be dictionary or a vtkCamera
def close(self):
1974    def close(self):
1975        """
1976        Render the video and write it to file.
1977        """
1978        if self.duration:
1979            self.fps = int(len(self.frames) / float(self.duration) + 0.5)
1980            colors.printc("recalculated fps:", self.fps, c="m", end="")
1981        else:
1982            self.fps = int(self.fps)
1983
1984        ########################################
1985        if self.backend == "ffmpeg":
1986            out = os.system(
1987                self.command
1988                + " "
1989                + str(self.fps)
1990                + " -i "
1991                + f"'{self.tmp_dir.name}'"
1992                + os.sep
1993                + "%01d.png "
1994                + self.options
1995                + " "
1996                + f"'{self.name}'"
1997            )
1998            if out:
1999                vedo.logger.error(f":noentry: backend {self.backend} returning error: {out}")
2000            else:
2001                colors.printc(f":save: saved to {self.name}", c="m")
2002
2003        ########################################
2004        elif "cv" in self.backend:
2005            try:
2006                import cv2
2007            except ImportError:
2008                vedo.logger.error("opencv is not installed")
2009                return
2010
2011            cap = cv2.VideoCapture(os.path.join(self.tmp_dir.name, "%1d.png"))
2012            fourcc = cv2.VideoWriter_fourcc(*"mp4v")
2013            w, h = vedo.plotter_instance.window.GetSize()
2014            writer = cv2.VideoWriter(self.name, fourcc, self.fps, (w, h), True)
2015
2016            while True:
2017                ret, frame = cap.read()
2018                if not ret:
2019                    break
2020                writer.write(frame)
2021
2022            cap.release()
2023            writer.release()
2024
2025        ########################################
2026        elif "imageio" in self.backend:
2027            try:
2028                import imageio
2029            except ImportError:
2030                vedo.logger.error("Please install imageio with:\n pip install imageio[ffmpeg]")
2031                return
2032
2033            if self.name.endswith(".mp4"):
2034                writer = imageio.get_writer(self.name, fps=self.fps)
2035            elif self.name.endswith(".gif"):
2036                writer = imageio.get_writer(self.name, mode="I", duration=1 / self.fps)
2037            elif self.name.endswith(".webm"):
2038                writer = imageio.get_writer(self.name, format="webm", fps=self.fps)
2039            else:
2040                vedo.logger.error(f"Unknown format of {self.name}.")
2041                return
2042
2043            for f in utils.humansort(self.frames):
2044                image = imageio.v3.imread(f)
2045                writer.append_data(image)
2046            try:
2047                writer.close()
2048                colors.printc(f"... saved as {self.name}", c="m")
2049            except:
2050                colors.printc(f":noentry: Could not save video {self.name}", c="r")
2051
2052        # finalize cleanup
2053        self.tmp_dir.cleanup()

Render the video and write it to file.

def split_frames(self, output_dir='video_frames', prefix='frame_', format='png'):
2055    def split_frames(self, output_dir="video_frames", prefix="frame_", format="png"):
2056        """Split an existing video file into frames."""
2057        try:
2058            import imageio
2059        except ImportError:
2060            vedo.logger.error("\nPlease install imageio with:\n pip install imageio")
2061            return
2062
2063        # Create the output directory if it doesn't exist
2064        if not os.path.exists(output_dir):
2065            os.makedirs(output_dir)
2066
2067        # Create a reader object to read the video
2068        reader = imageio.get_reader(self.name)
2069
2070        # Loop through each frame of the video and save it as image
2071        print()
2072        for i, frame in utils.progressbar(
2073            enumerate(reader), title=f"writing {format} frames", c="m", width=20
2074        ):
2075            output_file = os.path.join(output_dir, f"{prefix}{str(i).zfill(5)}.{format}")
2076            imageio.imwrite(output_file, frame, format=format)

Split an existing video file into frames.