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

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

def loadStructuredPoints(filename):
538def loadStructuredPoints(filename):
539    """Load and return a `vtkStructuredPoints` object from file."""
540    reader = vtk.vtkStructuredPointsReader()
541    reader.SetFileName(filename)
542    reader.Update()
543    return reader.GetOutput()

Load and return a vtkStructuredPoints object from file.

def loadStructuredGrid(filename):
546def loadStructuredGrid(filename):
547    """Load and return a `vtkStructuredGrid` object from file."""
548    if filename.endswith(".vts"):
549        reader = vtk.vtkXMLStructuredGridReader()
550    else:
551        reader = vtk.vtkStructuredGridReader()
552    reader.SetFileName(filename)
553    reader.Update()
554    return reader.GetOutput()

Load and return a vtkStructuredGrid object from file.

def loadRectilinearGrid(filename):
568def loadRectilinearGrid(filename):
569    """Load and return a `vtkRectilinearGrid` object from file."""
570    if filename.endswith(".vtr"):
571        reader = vtk.vtkXMLRectilinearGridReader()
572    else:
573        reader = vtk.vtkRectilinearGridReader()
574    reader.SetFileName(filename)
575    reader.Update()
576    return reader.GetOutput()

Load and return a vtkRectilinearGrid object from file.

def loadUnStructuredGrid(filename):
557def loadUnStructuredGrid(filename):
558    """Load and return a `vtkunStructuredGrid` object from file."""
559    if filename.endswith(".vtu"):
560        reader = vtk.vtkXMLUnstructuredGridReader()
561    else:
562        reader = vtk.vtkUnstructuredGridReader()
563    reader.SetFileName(filename)
564    reader.Update()
565    return reader.GetOutput()

Load and return a vtkunStructuredGrid object from file.

def load_transform(filename):
1408def load_transform(filename):
1409    """
1410    Load a transformation from a file `.mat`.
1411
1412    Returns:
1413        - `vtkTransform`
1414            The transformation to be applied to some object (`use apply_transform()`).
1415        - `str`, a comment string associated to this transformation file.
1416    """
1417    with open(filename, "r", encoding='UTF-8') as f:
1418        lines = f.readlines()
1419        M = vtk.vtkMatrix4x4()
1420        i = 0
1421        comment = ""
1422        for l in lines:
1423            if l.startswith("#"):
1424                comment = l.replace("#", "").replace("\n", "")
1425                continue
1426            vals = l.split(" ")
1427            if len(vals) == 4:
1428                for j in range(4):
1429                    v = vals[j].replace("\n", "")
1430                    M.SetElement(i, j, float(v))
1431                i += 1
1432        T = vtk.vtkTransform()
1433        T.SetMatrix(M)
1434    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=''):
1374def write_transform(inobj, filename="transform.mat", comment=""):
1375    """
1376    Save a transformation for a mesh or pointcloud to ASCII file.
1377
1378    Arguments:
1379        filename : (str)
1380            output file name
1381        comment : (str)
1382            some optional comment
1383    """
1384    if isinstance(inobj, Points):
1385        M = inobj.get_transform().GetMatrix()
1386    elif isinstance(inobj, vtk.vtkTransform):
1387        M = inobj.GetMatrix()
1388    elif isinstance(inobj, vtk.vtkMatrix4x4):
1389        M = inobj
1390    else:
1391        vedo.logger.error(
1392            f"in write_transform(), cannot understand input type {type(inobj)}"
1393        )
1394
1395    with open(filename, "w", encoding='UTF-8') as f:
1396        if comment:
1397            f.write("# " + comment + "\n")
1398        for i in range(4):
1399            f.write(
1400                str(M.GetElement(i,0))+' '+
1401                str(M.GetElement(i,1))+' '+
1402                str(M.GetElement(i,2))+' '+
1403                str(M.GetElement(i,3))+'\n',
1404            )
1405        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):
1204def write(objct, fileoutput, binary=True):
1205    """
1206    Write object to file.
1207
1208    Possile extensions are:
1209        - `vtk, vti, npy, npz, ply, obj, stl, byu, vtp, vti, mhd, xyz, tif, png, bmp`
1210    """
1211    obj = objct
1212    if isinstance(obj, Points):  # picks transformation
1213        obj = objct.polydata(True)
1214    elif isinstance(obj, (vtk.vtkActor, vtk.vtkVolume)):
1215        obj = objct.GetMapper().GetInput()
1216    elif isinstance(obj, (vtk.vtkPolyData, vtk.vtkImageData)):
1217        obj = objct
1218
1219    fr = fileoutput.lower()
1220    if fr.endswith(".vtk"):
1221        writer = vtk.vtkDataSetWriter()
1222    elif fr.endswith(".ply"):
1223        writer = vtk.vtkPLYWriter()
1224        writer.AddComment("PLY file generated by vedo")
1225        lut = objct.GetMapper().GetLookupTable()
1226        if lut:
1227            pscal = obj.GetPointData().GetScalars()
1228            if not pscal:
1229                pscal = obj.GetCellData().GetScalars()
1230            if pscal and pscal.GetName():
1231                writer.SetArrayName(pscal.GetName())
1232            writer.SetLookupTable(lut)
1233    elif fr.endswith(".stl"):
1234        writer = vtk.vtkSTLWriter()
1235    elif fr.endswith(".vtp"):
1236        writer = vtk.vtkXMLPolyDataWriter()
1237    elif fr.endswith(".vtu"):
1238        writer = vtk.vtkXMLUnstructuredGridWriter()
1239    elif fr.endswith(".vtm"):
1240        g = vtk.vtkMultiBlockDataGroupFilter()
1241        for ob in objct:
1242            if isinstance(ob, (Points, Volume)):  # picks transformation
1243                ob = ob.polydata(True)
1244                g.AddInputData(ob)
1245        g.Update()
1246        mb = g.GetOutputDataObject(0)
1247        wri = vtk.vtkXMLMultiBlockDataWriter()
1248        wri.SetInputData(mb)
1249        wri.SetFileName(fileoutput)
1250        wri.Write()
1251        return mb
1252    elif fr.endswith(".xyz"):
1253        writer = vtk.vtkSimplePointsWriter()
1254    elif fr.endswith(".facet"):
1255        writer = vtk.vtkFacetWriter()
1256    elif fr.endswith(".vti"):
1257        writer = vtk.vtkXMLImageDataWriter()
1258    elif fr.endswith(".mhd"):
1259        writer = vtk.vtkMetaImageWriter()
1260    elif fr.endswith(".nii"):
1261        writer = vtk.vtkNIFTIImageWriter()
1262    elif fr.endswith(".png"):
1263        writer = vtk.vtkPNGWriter()
1264    elif fr.endswith(".jpg"):
1265        writer = vtk.vtkJPEGWriter()
1266    elif fr.endswith(".bmp"):
1267        writer = vtk.vtkBMPWriter()
1268    elif fr.endswith(".tif") or fr.endswith(".tiff"):
1269        writer = vtk.vtkTIFFWriter()
1270        writer.SetFileDimensionality(len(obj.GetDimensions()))
1271    elif fr.endswith(".npy") or fr.endswith(".npz"):
1272        if utils.is_sequence(objct):
1273            objslist = objct
1274        else:
1275            objslist = [objct]
1276        dicts2save = []
1277        for obj in objslist:
1278            dicts2save.append(tonumpy(obj))
1279        np.save(fileoutput, dicts2save)
1280        return dicts2save
1281
1282    elif fr.endswith(".obj"):
1283        with open(fileoutput, "w", encoding='UTF-8') as outF:
1284            outF.write("# OBJ file format with ext .obj\n")
1285            outF.write("# File generated by vedo\n")
1286
1287            for p in objct.points():
1288                outF.write("v {:.5g} {:.5g} {:.5g}\n".format(*p))
1289
1290            ptxt = objct.polydata().GetPointData().GetTCoords()
1291            if ptxt:
1292                ntxt = utils.vtk2numpy(ptxt)
1293                for vt in ntxt:
1294                    outF.write('vt '+ str(vt[0]) +" "+ str(vt[1])+ ' 0.0\n')
1295
1296            for i, f in enumerate(objct.faces()):
1297                fs = ""
1298                for fi in f:
1299                    if ptxt:
1300                        fs += f" {fi+1}/{fi+1}"
1301                    else:
1302                        fs += f" {fi+1}"
1303                outF.write(f"f{fs}\n")
1304
1305            for l in objct.lines():
1306                ls = ""
1307                for li in l:
1308                    ls += str(li + 1) + " "
1309                outF.write(f"l {ls}\n")
1310
1311        return objct
1312
1313
1314    elif fr.endswith(".xml"):  # write tetrahedral dolfin xml
1315        vertices = objct.points().astype(str)
1316        faces = np.array(objct.faces()).astype(str)
1317        ncoords = vertices.shape[0]
1318        with open(fileoutput, "w", encoding='UTF-8') as outF:
1319            outF.write('<?xml version="1.0" encoding="UTF-8"?>\n')
1320            outF.write('<dolfin xmlns:dolfin="http://www.fenicsproject.org">\n')
1321
1322            if len(faces[0]) == 4:  # write tetrahedral mesh
1323                ntets = faces.shape[0]
1324                outF.write('  <mesh celltype="tetrahedron" dim="3">\n')
1325                outF.write('    <vertices size="' + str(ncoords) + '">\n')
1326                for i in range(ncoords):
1327                    x, y, z = vertices[i]
1328                    outF.write('      <vertex index="'+str(i)+'" x="'+x+'" y="'+y+'" z="'+z+'"/>\n')
1329                outF.write('    </vertices>\n')
1330                outF.write('    <cells size="' + str(ntets) + '">\n')
1331                for i in range(ntets):
1332                    v0, v1, v2, v3 = faces[i]
1333                    outF.write('     <tetrahedron index="'+str(i)
1334                               + '" v0="'+v0+'" v1="'+v1+'" v2="'+v2+'" v3="'+v3+'"/>\n')
1335
1336            elif len(faces[0]) == 3:  # write triangle mesh
1337                ntri = faces.shape[0]
1338                outF.write('  <mesh celltype="triangle" dim="2">\n')
1339                outF.write('    <vertices size="' + str(ncoords) + '">\n')
1340                for i in range(ncoords):
1341                    x, y, dummy_z = vertices[i]
1342                    outF.write('      <vertex index="'+str(i)+'" x="'+x+'" y="'+y+'"/>\n')
1343                outF.write('    </vertices>\n')
1344                outF.write('    <cells size="' + str(ntri) + '">\n')
1345                for i in range(ntri):
1346                    v0, v1, v2 = faces[i]
1347                    outF.write('     <triangle index="'+str(i)+'" v0="'+v0+'" v1="'+v1+'" v2="'+v2+'"/>\n')
1348
1349            outF.write("    </cells>\n")
1350            outF.write("  </mesh>\n")
1351            outF.write("</dolfin>\n")
1352        return objct
1353
1354    else:
1355        vedo.logger.error(f"Unknown format {fileoutput}, file not saved")
1356        return objct
1357
1358    try:
1359        if binary:
1360            writer.SetFileTypeToBinary()
1361        else:
1362            writer.SetFileTypeToASCII()
1363    except AttributeError:
1364        pass
1365
1366    try:
1367        writer.SetInputData(obj)
1368        writer.SetFileName(fileoutput)
1369        writer.Write()
1370    except:
1371        vedo.logger.error(f"could not save {fileoutput}")
1372    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):
1438def export_window(fileoutput, binary=False):
1439    """
1440    Exporter which writes out the rendered scene into an HTML, X3D
1441    or Numpy file.
1442
1443    Example:
1444        - [export_x3d.py](examples/other/export_x3d.py)
1445
1446        Check out the HTML generated webpage [here](https://vedo.embl.es/examples/embryo.html).
1447
1448        <img src='https://user-images.githubusercontent.com/32848391/57160341-c6ffbd80-6de8-11e9-95ff-7215ce642bc5.jpg' width="600"/>
1449
1450    .. note::
1451        the rendering window can also be exported to `numpy` file `scene.npz`
1452        by pressing `E` keyboard at any moment during visualization.
1453    """
1454    fr = fileoutput.lower()
1455
1456    ####################################################################
1457    if fr.endswith(".npy") or fr.endswith(".npz"):
1458        sdict = {}
1459        plt = vedo.plotter_instance
1460        sdict["shape"] = plt.shape
1461        sdict["sharecam"] = plt.sharecam
1462        sdict["camera"] = dict(
1463            pos=plt.camera.GetPosition(),
1464            focal_point=plt.camera.GetFocalPoint(),
1465            viewup=plt.camera.GetViewUp(),
1466            distance=plt.camera.GetDistance(),
1467            clipping_range=plt.camera.GetClippingRange(),
1468        )
1469        sdict["position"] = plt.pos
1470        sdict["size"] = plt.size
1471        sdict["axes"] = plt.axes
1472        sdict["title"] = plt.title
1473        sdict["backgrcol"] = colors.get_color(plt.renderer.GetBackground())
1474        sdict["backgrcol2"] = None
1475        if plt.renderer.GetGradientBackground():
1476            sdict["backgrcol2"] = plt.renderer.GetBackground2()
1477        sdict["use_depth_peeling"] = settings.use_depth_peeling
1478        sdict["render_lines_as_tubes"] = settings.render_lines_as_tubes
1479        sdict["hidden_line_removal"] = settings.hidden_line_removal
1480        sdict["visible_grid_edges"] = settings.visible_grid_edges
1481        sdict["use_parallel_projection"] = settings.use_parallel_projection
1482        sdict["default_font"] = settings.default_font
1483        sdict["objects"] = []
1484
1485        allobjs = plt.get_meshes(include_non_pickables=True) + plt.get_volumes(include_non_pickables=True)
1486        acts2d = plt.renderer.GetActors2D()
1487        acts2d.InitTraversal()
1488        for _ in range(acts2d.GetNumberOfItems()):
1489            a = acts2d.GetNextItem()
1490            if isinstance(a, vedo.Text2D):
1491                allobjs.append(a)
1492        allobjs += plt.actors
1493
1494        allobjs = list(set(allobjs))  # make sure its unique
1495
1496        for a in allobjs:
1497            if a.GetVisibility():
1498                sdict["objects"].append(tonumpy(a))
1499
1500        if fr.endswith(".npz"):
1501            np.savez_compressed(fileoutput, vedo_scenes=[sdict])
1502        else:
1503            np.save(fileoutput, [sdict])
1504
1505    ####################################################################
1506    elif fr.endswith(".x3d"):
1507        obj = list(
1508            set(vedo.plotter_instance.get_meshes() + vedo.plotter_instance.actors)
1509        )
1510        if vedo.plotter_instance.axes_instances:
1511            obj.append(vedo.plotter_instance.axes_instances[0])
1512
1513        for a in obj:
1514            if isinstance(a, Mesh):
1515                newa = a.clone(transformed=True)
1516                vedo.plotter_instance.remove(a).add(newa, render=False)
1517
1518            elif isinstance(a, Assembly):
1519                vedo.plotter_instance.remove(a)
1520                for b in a.unpack():
1521                    if b:
1522                        if a.name == "Axes":
1523                            newb = b.clone(transformed=True)
1524                        else:
1525                            # newb = b.clone(transformed=True) # BUG??
1526
1527                            newb = b.clone(transformed=False)
1528                            tt = vtk.vtkTransform()
1529                            tt.Concatenate(a.GetMatrix())
1530                            tt.Concatenate(b.GetMatrix())
1531                            newb.PokeMatrix(vtk.vtkMatrix4x4())
1532                            newb.SetUserTransform(tt)
1533
1534                        vedo.plotter_instance.add(newb, render=False)
1535
1536        vedo.plotter_instance.render()
1537
1538        exporter = vtk.vtkX3DExporter()
1539        exporter.SetBinary(binary)
1540        exporter.FastestOff()
1541        exporter.SetInput(vedo.plotter_instance.window)
1542        exporter.SetFileName(fileoutput)
1543        # exporter.WriteToOutputStringOn() # see below
1544        exporter.Update()
1545        exporter.Write()
1546
1547# this can reduce the size by more than half...
1548#        outstring = exporter.GetOutputString().decode("utf-8") # this fails though
1549#        from vedo.utils import isInteger, isNumber, precision
1550#        newlines = []
1551#        for l in outstring.splitlines(True):
1552#            ls = l.lstrip()
1553#            content = ls.split()
1554#            newls = ""
1555#            for c in content:
1556#                c2 = c.replace(',','')
1557#                if isNumber(c2) and not isInteger(c2):
1558#                    newc = precision(float(c2), 4)
1559#                    if ',' in c:
1560#                        newls += newc + ','
1561#                    else:
1562#                        newls += newc + ' '
1563#                else:
1564#                    newls += c + ' '
1565#        newlines.append(newls.lstrip()+'\n')
1566#        with open("fileoutput", 'w', encoding='UTF-8') as f:
1567#            l = "".join(newlines)
1568#            f.write(l)
1569
1570        x3d_html = _x3d_html.replace("~fileoutput", fileoutput)
1571        wsize = vedo.plotter_instance.window.GetSize()
1572        x3d_html = x3d_html.replace("~width", str(wsize[0]))
1573        x3d_html = x3d_html.replace("~height", str(wsize[1]))
1574        with open(fileoutput.replace(".x3d", ".html"), "w", encoding='UTF-8') as outF:
1575            outF.write(x3d_html)
1576            vedo.logger.info(
1577                f"Saved files {fileoutput} and {fileoutput.replace('.x3d','.html')}"
1578            )
1579
1580    ####################################################################
1581    elif fr.endswith(".html"):
1582        savebk = vedo.notebook_backend
1583        vedo.notebook_backend = "k3d"
1584        vedo.settings.default_backend = "k3d"
1585        plt = vedo.backends.get_notebook_backend(vedo.plotter_instance.actors)
1586 
1587        with open(fileoutput, "w", encoding='UTF-8') as fp:
1588             fp.write(plt.get_snapshot())
1589 
1590        vedo.notebook_backend = savebk
1591        vedo.settings.default_backend = savebk
1592
1593    else:
1594        vedo.logger.error(f"export extension {fr.split('.')[-1]} is not supported")
1595
1596    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):
1599def import_window(fileinput, mtl_file=None, texture_path=None):
1600    """Import a whole scene from a Numpy or OBJ wavefront file.
1601
1602    Arguments:
1603        mtl_file : (str)
1604            MTL file for OBJ wavefront files
1605        texture_path : (str)
1606            path of the texture files directory
1607
1608    Returns:
1609        `Plotter` instance
1610    """
1611    data = None
1612    if isinstance(fileinput, dict):
1613        data = fileinput
1614    elif fileinput.endswith(".npy"):
1615        data = np.load(fileinput, allow_pickle=True, encoding="latin1").flatten()[0]
1616    elif fileinput.endswith(".npz"):
1617        data = np.load(fileinput, allow_pickle=True)["vedo_scenes"][0]
1618
1619    if data is not None:
1620        if "render_lines_as_tubes" in data.keys():
1621            settings.render_lines_as_tubes = data["render_lines_as_tubes"]
1622        if "hidden_line_removal" in data.keys():
1623            settings.hidden_line_removal = data["hidden_line_removal"]
1624        if "visible_grid_edges" in data.keys():
1625            settings.visible_grid_edges = data["visible_grid_edges"]
1626        if "use_parallel_projection" in data.keys():
1627            settings.use_parallel_projection = data["use_parallel_projection"]
1628        if "use_polygon_offset" in data.keys():
1629            settings.use_polygon_offset = data["use_polygon_offset"]
1630        if "polygon_offset_factor" in data.keys():
1631            settings.polygon_offset_factor = data["polygon_offset_factor"]
1632        if "polygon_offset_units" in data.keys():
1633            settings.polygon_offset_units = data["polygon_offset_units"]
1634        if "interpolate_scalars_before_mapping" in data.keys():
1635            settings.interpolate_scalars_before_mapping = data["interpolate_scalars_before_mapping"]
1636        if "default_font" in data.keys():
1637            settings.default_font = data["default_font"]
1638        if "use_depth_peeling" in data.keys():
1639            settings.use_depth_peeling = data["use_depth_peeling"]
1640
1641        axes = data.pop("axes", 4)
1642        title = data.pop("title", "")
1643        backgrcol = data.pop("backgrcol", "white")
1644        backgrcol2 = data.pop("backgrcol2", None)
1645        cam = data.pop("camera", None)
1646
1647        if data["shape"] != (1, 1):
1648            data["size"] = "auto"  # disable size
1649
1650        plt = vedo.Plotter(
1651            size=data["size"],  # not necessarily a good idea to set it
1652            # shape=data['shape'], # will need to create a Renderer class first
1653            axes=axes,
1654            title=title,
1655            bg=backgrcol,
1656            bg2=backgrcol2,
1657        )
1658
1659        if cam:
1660            if "pos" in cam.keys():
1661                plt.camera.SetPosition(cam["pos"])
1662            if "focalPoint" in cam.keys():
1663                plt.camera.SetFocalPoint(cam["focalPoint"])
1664            if "focal_point" in cam.keys():
1665                plt.camera.SetFocalPoint(cam["focal_point"])
1666            if "viewup" in cam.keys():
1667                plt.camera.SetViewUp(cam["viewup"])
1668            if "distance" in cam.keys():
1669                plt.camera.SetDistance(cam["distance"])
1670            if "clippingRange" in cam.keys():
1671                plt.camera.SetClippingRange(cam["clippingRange"])
1672            if "clipping_range" in cam.keys():
1673                plt.camera.SetClippingRange(cam["clipping_range"])
1674            plt.resetcam = False
1675
1676        if "objects" in data.keys():
1677            objs = loadnumpy(data["objects"])
1678            if not utils.is_sequence(objs):
1679                objs = [objs]
1680        else:
1681            # colors.printc("Trying to import a single mesh.. use load() instead.", c='r')
1682            # colors.printc(" -> try to load a single object with load().", c='r')
1683            objs = [loadnumpy(fileinput)]
1684
1685        plt.actors = objs
1686        plt.add(objs, render=False)
1687        return plt
1688
1689    elif ".obj" in fileinput.lower():
1690
1691        plt = vedo.Plotter()
1692
1693        importer = vtk.vtkOBJImporter()
1694        importer.SetFileName(fileinput)
1695        if mtl_file is not False:
1696            if mtl_file is None:
1697                mtl_file = fileinput.replace(".obj", ".mtl").replace(".OBJ", ".MTL")
1698            importer.SetFileNameMTL(mtl_file)
1699        if texture_path is not False:
1700            if texture_path is None:
1701                texture_path = fileinput.replace(".obj", ".txt").replace(".OBJ", ".TXT")
1702            importer.SetTexturePath(texture_path)
1703        importer.SetRenderWindow(plt.window)
1704        importer.Update()
1705
1706        actors = plt.renderer.GetActors()
1707        actors.InitTraversal()
1708        for _ in range(actors.GetNumberOfItems()):
1709            vactor = actors.GetNextActor()
1710            act = Mesh(vactor)
1711            act_tu = vactor.GetTexture()
1712            if act_tu:
1713                act_tu.InterpolateOn()
1714                act.texture(act_tu)
1715            plt.actors.append(act)
1716        return plt
1717    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=None, asarray=False):
1721def screenshot(filename="screenshot.png", scale=None, asarray=False):
1722    """
1723    Save a screenshot of the current rendering window.
1724
1725    Arguments:
1726        scale : (int)
1727            set image magnification as an integer multiplicative factor
1728        asarray : (bool)
1729            return a numpy array of the image
1730    """
1731    if not vedo.plotter_instance or not vedo.plotter_instance.window:
1732        #vedo.logger.error("in screenshot(), rendering window is not present, skip.")
1733        return vedo.plotter_instance  ##########
1734
1735    if asarray:
1736        nx, ny = vedo.plotter_instance.window.GetSize()
1737        arr = vtk.vtkUnsignedCharArray()
1738        vedo.plotter_instance.window.GetRGBACharPixelData(0, 0, nx-1, ny-1, 0, arr)
1739        narr = vedo.vtk2numpy(arr).T[:3].T.reshape([ny,nx,3])
1740        narr = np.flip(narr, axis=0)
1741        return narr  ##########
1742
1743    filename = str(filename)
1744
1745    if filename.endswith(".pdf"):
1746        writer = vtk.vtkGL2PSExporter()
1747        writer.SetRenderWindow(vedo.plotter_instance.window)
1748        writer.Write3DPropsAsRasterImageOff()
1749        writer.SilentOn()
1750        writer.SetSortToBSP()
1751        writer.SetFileFormatToPDF()
1752        writer.SetFilePrefix(filename.replace(".pdf", ""))
1753        writer.Write()
1754        return vedo.plotter_instance  ##########
1755    
1756    elif filename.endswith(".svg"):
1757        writer = vtk.vtkGL2PSExporter()
1758        writer.SetRenderWindow(vedo.plotter_instance.window)
1759        writer.Write3DPropsAsRasterImageOff()
1760        writer.SilentOn()
1761        writer.SetSortToBSP()
1762        writer.SetFileFormatToSVG()
1763        writer.SetFilePrefix(filename.replace(".svg", ""))
1764        writer.Write()
1765        return vedo.plotter_instance  ##########
1766    
1767    elif filename.endswith(".eps"):
1768        writer = vtk.vtkGL2PSExporter()
1769        writer.SetRenderWindow(vedo.plotter_instance.window)
1770        writer.Write3DPropsAsRasterImageOff()
1771        writer.SilentOn()
1772        writer.SetSortToBSP()
1773        writer.SetFileFormatToEPS()
1774        writer.SetFilePrefix(filename.replace(".eps", ""))
1775        writer.Write()
1776        return vedo.plotter_instance  ##########
1777
1778    if scale is None:
1779        scale = settings.screeshot_scale
1780
1781    if settings.screeshot_large_image:
1782        w2if = vtk.vtkRenderLargeImage()
1783        w2if.SetInput(vedo.plotter_instance.renderer)
1784        w2if.SetMagnification(scale)
1785        w2if.Update()
1786    else:
1787        w2if = vtk.vtkWindowToImageFilter()
1788        w2if.SetInput(vedo.plotter_instance.window)
1789        if hasattr(w2if, "SetScale"):
1790            w2if.SetScale(scale, scale)
1791        if settings.screenshot_transparent_background:
1792            w2if.SetInputBufferTypeToRGBA()
1793        w2if.ReadFrontBufferOff()  # read from the back buffer
1794        w2if.Update()
1795
1796    # if asarray:
1797    #     npdata = utils.vtk2numpy(w2if.GetOutput().GetPointData().GetArray("ImageScalars"))
1798    #     npdata = npdata[:, [0, 1, 2]]
1799    #     ydim, xdim, _ = w2if.GetOutput().GetDimensions()
1800    #     npdata = npdata.reshape([xdim, ydim, -1])
1801    #     npdata = np.flip(npdata, axis=0)
1802    #     return npdata
1803
1804    if filename.lower().endswith(".png"):
1805        writer = vtk.vtkPNGWriter()
1806        writer.SetFileName(filename)
1807        writer.SetInputData(w2if.GetOutput())
1808        writer.Write()
1809    elif filename.lower().endswith(".jpg") or filename.lower().endswith(".jpeg"):
1810        writer = vtk.vtkJPEGWriter()
1811        writer.SetFileName(filename)
1812        writer.SetInputData(w2if.GetOutput())
1813        writer.Write()
1814    else:  # add .png
1815        writer = vtk.vtkPNGWriter()
1816        writer.SetFileName(filename + ".png")
1817        writer.SetInputData(w2if.GetOutput())
1818        writer.Write()
1819    return vedo.plotter_instance

Save a screenshot of the current rendering window.

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

Generate a video from a rendering window.

Video(name='movie.mp4', duration=None, fps=24, backend='imageio')
1874    def __init__(
1875            self,
1876            name="movie.mp4",
1877            duration=None,
1878            fps=24,
1879            backend="imageio",
1880        ):
1881        """
1882        Class to generate a video from the specified rendering window.
1883        Program `ffmpeg` is used to create video from each generated frame.
1884
1885        Arguments:
1886            name : (str)
1887                name of the output file.
1888            fps : (int)
1889                set the number of frames per second.
1890            duration : (float)
1891                set the total `duration` of the video and recalculates `fps` accordingly.
1892            backend : (str)
1893                the backend engine to be used `['imageio', 'ffmpeg', 'cv']`
1894
1895        Examples:
1896            - [make_video.py](examples/other/make_video.py)
1897
1898            ![](https://user-images.githubusercontent.com/32848391/50739007-2bfc2b80-11da-11e9-97e6-620a3541a6fa.jpg)
1899        """
1900        self.name = name
1901        self.duration = duration
1902        self.backend = backend
1903        self.fps = float(fps)
1904        self.command = "ffmpeg -loglevel panic -y -r"
1905        self.options = "-b:v 8000k"
1906
1907        self.frames = []
1908        self.tmp_dir = TemporaryDirectory()
1909        self.get_filename = lambda x: os.path.join(self.tmp_dir.name, x)
1910        colors.printc("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):
1912    def add_frame(self):
1913        """Add frame to current video."""
1914        fr = self.get_filename(str(len(self.frames)) + ".png")
1915        screenshot(fr)
1916        self.frames.append(fr)
1917        return self

Add frame to current video.

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

Insert a pause, in seconds.

def action( self, elevation=(0, 80), azimuth=(0, 359), cameras=(), resetcam=False):
1929    def action(
1930        self,
1931        elevation=(0, 80),
1932        azimuth=(0, 359),
1933        cameras=(),
1934        resetcam=False,
1935    ):
1936        """
1937        Automatic shooting of a static scene by specifying rotation and elevation ranges.
1938
1939        Arguments:
1940            elevation : list
1941                initial and final elevation angles
1942            azimuth_range : list
1943                initial and final azimuth angles
1944            cameras : list
1945                list of cameras to go through, each camera can be dictionary or a vtkCamera
1946        """
1947        if not self.duration:
1948            self.duration = 5
1949
1950        plt = vedo.plotter_instance
1951        n = int(self.fps * self.duration)
1952
1953        cams = []
1954        for cm in cameras:
1955            cams.append(utils.camera_from_dict(cm))
1956        nc = len(cams)
1957
1958        plt.show(resetcam=resetcam, interactive=False)
1959
1960        if nc:
1961            for i in range(n):
1962                plt.move_camera(cams, i / n)
1963                plt.show()
1964                self.add_frame()
1965
1966        else:  ########################################
1967
1968            for i in range(n):
1969                plt.camera.Elevation((elevation[1] - elevation[0]) / n)
1970                plt.camera.Azimuth((azimuth[1] - azimuth[0]) / n)
1971                plt.show()
1972                self.add_frame()
1973
1974        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):
1976    def close(self):
1977        """
1978        Render the video and write it to file.
1979        """
1980        if self.duration:
1981            self.fps = int(len(self.frames) / float(self.duration) +0.5)
1982            colors.printc("recalculated fps:", self.fps, c="m", end='')
1983        else:
1984            self.fps = int(self.fps)
1985
1986        ########################################
1987        if self.backend == "ffmpeg":
1988            out = os.system(
1989                self.command
1990                + " "
1991                + str(self.fps)
1992                + " -i "
1993                + f"'{self.tmp_dir.name}'"
1994                + os.sep
1995                + "%01d.png "
1996                + self.options
1997                + " "
1998                + f"'{self.name}'"
1999            )
2000            if out:
2001                vedo.logger.error(f"backend {self.backend} returning error: {out}")
2002            else:
2003                colors.printc(f"saved as {self.name}", c="m")
2004
2005        ########################################
2006        elif "cv" in self.backend:
2007            try:
2008                import cv2
2009            except ImportError:
2010                vedo.logger.error("opencv is not installed")
2011                return
2012
2013            cap = cv2.VideoCapture(os.path.join(self.tmp_dir.name, "%1d.png"))
2014            fourcc = cv2.VideoWriter_fourcc(*"mp4v")
2015            w, h = vedo.plotter_instance.window.GetSize()
2016            w, h = w * settings.screeshot_scale, h * settings.screeshot_scale
2017            writer = cv2.VideoWriter(self.name, fourcc, self.fps, (w, h), True)
2018
2019            found = False
2020            while True:
2021                ret, frame = cap.read()
2022                if not ret:
2023                    break
2024                writer.write(frame)
2025                found = True
2026
2027            cap.release()
2028            writer.release()
2029
2030        ########################################
2031        elif "imageio" in self.backend:
2032            try:
2033                import imageio
2034            except ImportError:
2035                vedo.logger.error("Please install imageio with:\n pip install imageio[ffmpeg]")
2036                return
2037
2038            if self.name.endswith(".mp4"):
2039                writer = imageio.get_writer(self.name, fps=self.fps)
2040            elif self.name.endswith(".gif"):
2041                writer = imageio.get_writer(self.name, mode='I', duration=1/self.fps)
2042            elif self.name.endswith(".webm"):
2043                writer = imageio.get_writer(self.name, format="webm", fps=self.fps)
2044            else:
2045                vedo.logger.error(f"Unknown format of {self.name}.")
2046                return
2047
2048            for f in utils.humansort(self.frames):
2049                image = imageio.v3.imread(f)
2050                writer.append_data(image)
2051            try:
2052                writer.close()
2053                colors.printc(f"... saved as {self.name}", c="m")
2054            except:
2055                colors.printc(f"Could not save video {self.name}", c="r")
2056
2057        # finalize cleanup
2058        self.tmp_dir.cleanup()

Render the video and write it to file.

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

Split an existing video file into frames.