vedo.file_io

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

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

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

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

Load and return a vtkStructuredPoints object from file.

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

Load and return a vtkStructuredGrid object from file.

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

Load and return a vtkRectilinearGrid object from file.

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

Load and return a vtkunStructuredGrid object from file.

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

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

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

Plotter instance

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

Save a screenshot of the current rendering window.

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

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

Arguments:
  • options : (list) a python list of possible answers to choose from.
  • default : (str) the default answer when just hitting return.
Example:
import vedo
res = vedo.file_io.ask("Continue?", options=['Y','n'], default='Y', c='g')
print(res)
class Video:
1880class Video:
1881    """
1882    Generate a video from a rendering window.
1883    """
1884
1885    def __init__(self, name="movie.mp4", duration=None, fps=24, backend="imageio"):
1886        """
1887        Class to generate a video from the specified rendering window.
1888        Program `ffmpeg` is used to create video from each generated frame.
1889
1890        Arguments:
1891            name : (str)
1892                name of the output file.
1893            fps : (int)
1894                set the number of frames per second.
1895            duration : (float)
1896                set the total `duration` of the video and recalculates `fps` accordingly.
1897            backend : (str)
1898                the backend engine to be used `['imageio', 'ffmpeg', 'cv']`
1899
1900        Examples:
1901            - [make_video.py](https://github.com/marcomusy/vedo/tree/master/examples/other/make_video.py)
1902
1903            ![](https://user-images.githubusercontent.com/32848391/50739007-2bfc2b80-11da-11e9-97e6-620a3541a6fa.jpg)
1904        """
1905        self.name = name
1906        self.duration = duration
1907        self.backend = backend
1908        self.fps = float(fps)
1909        self.command = "ffmpeg -loglevel panic -y -r"
1910        self.options = "-b:v 8000k"
1911
1912        self.frames = []
1913        self.tmp_dir = TemporaryDirectory()
1914        self.get_filename = lambda x: os.path.join(self.tmp_dir.name, x)
1915        colors.printc(":video: Video file", self.name, "is open... ", c="m", end="")
1916
1917    def add_frame(self):
1918        """Add frame to current video."""
1919        fr = self.get_filename(str(len(self.frames)) + ".png")
1920        screenshot(fr)
1921        self.frames.append(fr)
1922        return self
1923
1924    def pause(self, pause=0):
1925        """Insert a `pause`, in seconds."""
1926        fr = self.frames[-1]
1927        n = int(self.fps * pause)
1928        for _ in range(n):
1929            fr2 = self.get_filename(str(len(self.frames)) + ".png")
1930            self.frames.append(fr2)
1931            os.system("cp -f %s %s" % (fr, fr2))
1932        return self
1933
1934    def action(self, elevation=(0, 80), azimuth=(0, 359), cameras=(), resetcam=False):
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":noentry: backend {self.backend} returning error: {out}")
2001            else:
2002                colors.printc(f":save: saved to {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            writer = cv2.VideoWriter(self.name, fourcc, self.fps, (w, h), True)
2016
2017            while True:
2018                ret, frame = cap.read()
2019                if not ret:
2020                    break
2021                writer.write(frame)
2022
2023            cap.release()
2024            writer.release()
2025
2026        ########################################
2027        elif "imageio" in self.backend:
2028            try:
2029                import imageio
2030            except ImportError:
2031                vedo.logger.error("Please install imageio with:\n pip install imageio[ffmpeg]")
2032                return
2033
2034            if self.name.endswith(".mp4"):
2035                writer = imageio.get_writer(self.name, fps=self.fps)
2036            elif self.name.endswith(".gif"):
2037                writer = imageio.get_writer(self.name, mode="I", duration=1 / self.fps)
2038            elif self.name.endswith(".webm"):
2039                writer = imageio.get_writer(self.name, format="webm", fps=self.fps)
2040            else:
2041                vedo.logger.error(f"Unknown format of {self.name}.")
2042                return
2043
2044            for f in utils.humansort(self.frames):
2045                image = imageio.v3.imread(f)
2046                writer.append_data(image)
2047            try:
2048                writer.close()
2049                colors.printc(f"... saved as {self.name}", c="m")
2050            except:
2051                colors.printc(f":noentry: Could not save video {self.name}", c="r")
2052
2053        # finalize cleanup
2054        self.tmp_dir.cleanup()
2055
2056    def split_frames(self, output_dir="video_frames", prefix="frame_", format="png"):
2057        """Split an existing video file into frames."""
2058        try:
2059            import imageio
2060        except ImportError:
2061            vedo.logger.error("\nPlease install imageio with:\n pip install imageio")
2062            return
2063
2064        # Create the output directory if it doesn't exist
2065        if not os.path.exists(output_dir):
2066            os.makedirs(output_dir)
2067
2068        # Create a reader object to read the video
2069        reader = imageio.get_reader(self.name)
2070
2071        # Loop through each frame of the video and save it as image
2072        print()
2073        for i, frame in utils.progressbar(
2074            enumerate(reader), title=f"writing {format} frames", c="m", width=20
2075        ):
2076            output_file = os.path.join(output_dir, f"{prefix}{str(i).zfill(5)}.{format}")
2077            imageio.imwrite(output_file, frame, format=format)

Generate a video from a rendering window.

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

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

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

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

Add frame to current video.

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

Insert a pause, in seconds.

def action( self, elevation=(0, 80), azimuth=(0, 359), cameras=(), resetcam=False):
1934    def action(self, elevation=(0, 80), azimuth=(0, 359), cameras=(), resetcam=False):
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

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):
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":noentry: backend {self.backend} returning error: {out}")
2001            else:
2002                colors.printc(f":save: saved to {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            writer = cv2.VideoWriter(self.name, fourcc, self.fps, (w, h), True)
2016
2017            while True:
2018                ret, frame = cap.read()
2019                if not ret:
2020                    break
2021                writer.write(frame)
2022
2023            cap.release()
2024            writer.release()
2025
2026        ########################################
2027        elif "imageio" in self.backend:
2028            try:
2029                import imageio
2030            except ImportError:
2031                vedo.logger.error("Please install imageio with:\n pip install imageio[ffmpeg]")
2032                return
2033
2034            if self.name.endswith(".mp4"):
2035                writer = imageio.get_writer(self.name, fps=self.fps)
2036            elif self.name.endswith(".gif"):
2037                writer = imageio.get_writer(self.name, mode="I", duration=1 / self.fps)
2038            elif self.name.endswith(".webm"):
2039                writer = imageio.get_writer(self.name, format="webm", fps=self.fps)
2040            else:
2041                vedo.logger.error(f"Unknown format of {self.name}.")
2042                return
2043
2044            for f in utils.humansort(self.frames):
2045                image = imageio.v3.imread(f)
2046                writer.append_data(image)
2047            try:
2048                writer.close()
2049                colors.printc(f"... saved as {self.name}", c="m")
2050            except:
2051                colors.printc(f":noentry: Could not save video {self.name}", c="r")
2052
2053        # finalize cleanup
2054        self.tmp_dir.cleanup()

Render the video and write it to file.

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

Split an existing video file into frames.