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

Read an object from file. Same as load().

def download(url: str, force=False, verbose=True) -> str:
447def download(url: str, force=False, verbose=True) -> str:
448    """
449    Retrieve a file from a URL, save it locally and return its path.
450    Use `force=True` to force a reload and discard cached copies.
451    """
452    if not url.startswith("https://"):
453        # assume it's a file so no need to download
454        return url
455    url = url.replace("www.dropbox", "dl.dropbox")
456
457    if "github.com" in url:
458        url = url.replace("/blob/", "/raw/")
459
460    basename = os.path.basename(url)
461
462    if "?" in basename:
463        basename = basename.split("?")[0]
464
465    home_directory = os.path.expanduser("~")
466    cachedir = os.path.join(home_directory, settings.cache_directory, "vedo")
467    fname = os.path.join(cachedir, basename)
468    # Create the directory if it does not exist
469    if not os.path.exists(cachedir):
470        os.makedirs(cachedir)
471
472    if not force and os.path.exists(fname):
473        if verbose:
474            colors.printc("reusing cached file:", fname)
475        return fname
476
477    try:
478        from urllib.request import urlopen, Request
479        req = Request(url, headers={"User-Agent": "Mozilla/5.0"})
480        if verbose:
481            colors.printc("reading", basename, "from", url.split("/")[2][:40], "...", end="")
482
483    except ImportError:
484        import urllib2
485        import contextlib
486        urlopen = lambda url_: contextlib.closing(urllib2.urlopen(url_))
487        req = url
488        if verbose:
489            colors.printc("reading", basename, "from", url.split("/")[2][:40], "...", end="")
490
491    with urlopen(req) as response, open(fname, "wb") as output:
492        output.write(response.read())
493
494    if verbose:
495        colors.printc(" done.")
496    return fname

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

def gunzip(filename: str) -> str:
576def gunzip(filename: str) -> str:
577    """Unzip a `.gz` file to a temporary file and returns its path."""
578    if not filename.endswith(".gz"):
579        # colors.printc("gunzip() error: file must end with .gz", c='r')
580        return filename
581
582    import gzip
583
584    tmp_file = NamedTemporaryFile(delete=False)
585    tmp_file.name = os.path.join(
586        os.path.dirname(tmp_file.name), os.path.basename(filename).replace(".gz", "")
587    )
588    inF = gzip.open(filename, "rb")
589    with open(tmp_file.name, "wb") as outF:
590        outF.write(inF.read())
591    inF.close()
592    return tmp_file.name

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

def loadStructuredPoints(filename, as_points=True):
611def loadStructuredPoints(filename, as_points=True):
612    """
613    Load and return a `vtkStructuredPoints` object from file.
614    
615    If `as_points` is True, return a `Points` object
616    instead of a `vtkStructuredPoints`.
617    """
618    reader = vtki.new("StructuredPointsReader")
619    reader.SetFileName(filename)
620    reader.Update()
621    if as_points:
622        v2p = vtki.new("ImageToPoints")
623        v2p.SetInputData(reader.GetOutput())
624        v2p.Update()
625        pts = Points(v2p.GetOutput())
626        return pts
627    return reader.GetOutput()

Load and return a vtkStructuredPoints object from file.

If as_points is True, return a Points object instead of a vtkStructuredPoints.

def loadStructuredGrid(filename):
630def loadStructuredGrid(filename):
631    """Load and return a `vtkStructuredGrid` object from file."""
632    if filename.endswith(".vts"):
633        reader = vtki.new("XMLStructuredGridReader")
634    else:
635        reader = vtki.new("StructuredGridReader")
636    reader.SetFileName(filename)
637    reader.Update()
638    return reader.GetOutput()

Load and return a vtkStructuredGrid object from file.

def write(objct: Any, fileoutput: str, binary=True) -> Any:
1149def write(objct: Any, fileoutput: str, binary=True) -> Any:
1150    """
1151    Write object to file. Same as `save()`.
1152
1153    Supported extensions are:
1154    
1155    - `vtk, vti, ply, obj, stl, byu, vtp, vti, mhd, xyz, xml, tif, png, bmp`
1156    """
1157    obj = objct.dataset
1158
1159    try:
1160        # check if obj is a Mesh.actor and has a transform
1161        M = objct.actor.GetMatrix()
1162        if M and not M.IsIdentity():
1163            obj = objct.apply_transform_from_actor()
1164            obj = objct.dataset
1165            vedo.logger.info(
1166                f"object '{objct.name}' "
1167                "was manually moved. Writing uses current position."
1168            )
1169    except:
1170        pass
1171
1172    fr = fileoutput.lower()
1173    if fr.endswith(".vtk"):
1174        writer = vtki.new("DataSetWriter")
1175    elif fr.endswith(".ply"):
1176        writer = vtki.new("PLYWriter")
1177        writer.AddComment("PLY file generated by vedo")
1178        lut = objct.mapper.GetLookupTable()
1179        if lut:
1180            pscal = obj.GetPointData().GetScalars()
1181            if not pscal:
1182                pscal = obj.GetCellData().GetScalars()
1183            if pscal and pscal.GetName():
1184                writer.SetArrayName(pscal.GetName())
1185            writer.SetLookupTable(lut)
1186    elif fr.endswith(".stl"):
1187        writer = vtki.new("STLWriter")
1188    elif fr.endswith(".vtp"):
1189        writer = vtki.new("XMLPolyDataWriter")
1190    elif fr.endswith(".vtu"):
1191        writer = vtki.new("XMLUnstructuredGridWriter")
1192    elif fr.endswith(".xyz"):
1193        writer = vtki.new("SimplePointsWriter")
1194    elif fr.endswith(".facet"):
1195        writer = vtki.new("FacetWriter")
1196    elif fr.endswith(".vti"):
1197        writer = vtki.new("XMLImageDataWriter")
1198    elif fr.endswith(".vtr"):
1199        writer = vtki.new("XMLRectilinearGridWriter")
1200    elif fr.endswith(".vtm"):
1201        g = vtki.new("MultiBlockDataGroupFilter")
1202        for ob in objct:
1203            try:
1204                g.AddInputData(ob)
1205            except TypeError:
1206                vedo.logger.warning("cannot save object of type", type(ob))
1207        g.Update()
1208        mb = g.GetOutputDataObject(0)
1209        wri = vtki.new("vtkXMLMultiBlockDataWriter")
1210        wri.SetInputData(mb)
1211        wri.SetFileName(fileoutput)
1212        wri.Write()
1213        return objct
1214    elif fr.endswith(".mhd"):
1215        writer = vtki.new("MetaImageWriter")
1216    elif fr.endswith(".nii"):
1217        writer = vtki.new("NIFTIImageWriter")
1218    elif fr.endswith(".png"):
1219        writer = vtki.new("PNGWriter")
1220    elif fr.endswith(".jpg"):
1221        writer = vtki.new("JPEGWriter")
1222    elif fr.endswith(".bmp"):
1223        writer = vtki.new("BMPWriter")
1224    elif fr.endswith(".tif") or fr.endswith(".tiff"):
1225        writer = vtki.new("TIFFWriter")
1226        writer.SetFileDimensionality(len(obj.GetDimensions()))
1227    elif fr.endswith(".obj"):
1228        with open(fileoutput, "w", encoding="UTF-8") as outF:
1229            outF.write("# OBJ file format with ext .obj\n")
1230            outF.write("# File generated by vedo\n")
1231
1232            for p in objct.vertices:
1233                outF.write("v {:.5g} {:.5g} {:.5g}\n".format(*p))
1234
1235            ptxt = objct.dataset.GetPointData().GetTCoords()
1236            if ptxt:
1237                ntxt = utils.vtk2numpy(ptxt)
1238                for vt in ntxt:
1239                    outF.write("vt " + str(vt[0]) + " " + str(vt[1]) + " 0.0\n")
1240
1241            if isinstance(objct, Mesh):
1242                for i, f in enumerate(objct.cells):
1243                    fs = ""
1244                    for fi in f:
1245                        if ptxt:
1246                            fs += f" {fi+1}/{fi+1}"
1247                        else:
1248                            fs += f" {fi+1}"
1249                    outF.write(f"f{fs}\n")
1250
1251                for l in objct.lines:
1252                    ls = ""
1253                    for li in l:
1254                        ls += str(li + 1) + " "
1255                    outF.write(f"l {ls}\n")
1256        return objct
1257
1258    elif fr.endswith(".xml"):  # write tetrahedral dolfin xml
1259        vertices = objct.vertices.astype(str)
1260        faces = np.array(objct.cells).astype(str)
1261        ncoords = vertices.shape[0]
1262        with open(fileoutput, "w", encoding="UTF-8") as outF:
1263            outF.write('<?xml version="1.0" encoding="UTF-8"?>\n')
1264            outF.write('<dolfin xmlns:dolfin="http://www.fenicsproject.org">\n')
1265
1266            if len(faces[0]) == 4:  # write tetrahedral mesh
1267                ntets = faces.shape[0]
1268                outF.write('  <mesh celltype="tetrahedron" dim="3">\n')
1269                outF.write('    <vertices size="' + str(ncoords) + '">\n')
1270                for i in range(ncoords):
1271                    x, y, z = vertices[i]
1272                    outF.write('      <vertex index="'+str(i)+'" x="'+x+'" y="'+y+'" z="'+z+'"/>\n')
1273                outF.write('    </vertices>\n')
1274                outF.write('    <cells size="' + str(ntets) + '">\n')
1275                for i in range(ntets):
1276                    v0, v1, v2, v3 = faces[i]
1277                    outF.write('     <tetrahedron index="'+str(i)
1278                               + '" v0="'+v0+'" v1="'+v1+'" v2="'+v2+'" v3="'+v3+'"/>\n')
1279
1280            elif len(faces[0]) == 3:  # write triangle mesh
1281                ntri = faces.shape[0]
1282                outF.write('  <mesh celltype="triangle" dim="2">\n')
1283                outF.write('    <vertices size="' + str(ncoords) + '">\n')
1284                for i in range(ncoords):
1285                    x, y, _ = vertices[i]
1286                    outF.write('      <vertex index="'+str(i)+'" x="'+x+'" y="'+y+'"/>\n')
1287                outF.write('    </vertices>\n')
1288                outF.write('    <cells size="' + str(ntri) + '">\n')
1289                for i in range(ntri):
1290                    v0, v1, v2 = faces[i]
1291                    outF.write('     <triangle index="'+str(i)+'" v0="'+v0+'" v1="'+v1+'" v2="'+v2+'"/>\n')
1292
1293            outF.write("    </cells>\n")
1294            outF.write("  </mesh>\n")
1295            outF.write("</dolfin>\n")
1296        return objct
1297
1298    else:
1299        vedo.logger.error(f"Unknown format {fileoutput}, file not saved")
1300        return objct
1301
1302    try:
1303        if binary:
1304            writer.SetFileTypeToBinary()
1305        else:
1306            writer.SetFileTypeToASCII()
1307    except AttributeError:
1308        pass
1309
1310    try:
1311        writer.SetInputData(obj)
1312        writer.SetFileName(fileoutput)
1313        writer.Write()
1314    except:
1315        vedo.logger.error(f"could not save {fileoutput}")
1316    return objct

Write object to file. Same as save().

Supported extensions are:

  • vtk, vti, ply, obj, stl, byu, vtp, vti, mhd, xyz, xml, tif, png, bmp
def save(obj: Any, fileoutput='out.png', binary=True) -> Any:
1318def save(obj: Any, fileoutput="out.png", binary=True) -> Any:
1319    """Save an object to file. Same as `write()`."""
1320    return write(obj, fileoutput, binary)

Save an object to file. Same as write().

def export_window(fileoutput: str, binary=False, plt=None) -> vedo.plotter.Plotter:
1327def export_window(fileoutput: str, binary=False, plt=None) -> "vedo.Plotter":
1328    """
1329    Exporter which writes out the rendered scene into an HTML, X3D or Numpy file.
1330
1331    Example:
1332        - [export_x3d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/export_x3d.py)
1333
1334        Check out the HTML generated webpage [here](https://vedo.embl.es/examples/embryo.html).
1335
1336        <img src='https://user-images.githubusercontent.com/32848391/57160341-c6ffbd80-6de8-11e9-95ff-7215ce642bc5.jpg' width="600"/>
1337
1338    .. note::
1339        the rendering window can also be exported to `numpy` file `scene.npz`
1340        by pressing `E` key at any moment during visualization.
1341    """
1342    if plt is None:
1343        plt = vedo.plotter_instance
1344
1345    fr = fileoutput.lower()
1346    ####################################################################
1347    if fr.endswith(".npy") or fr.endswith(".npz"):
1348        _export_npy(plt, fileoutput)
1349
1350    ####################################################################
1351    elif fr.endswith(".x3d"):
1352        # obj = plt.get_actors()
1353        # if plt.axes_instances:
1354        #     obj.append(plt.axes_instances[0])
1355
1356        # for a in obj:
1357        #     if isinstance(a, Assembly):
1358        #         plt.remove(a)
1359        #         plt.add(a.unpack())
1360
1361        plt.render()
1362
1363        exporter = vtki.new("X3DExporter")
1364        exporter.SetBinary(binary)
1365        exporter.FastestOff()
1366        exporter.SetInput(plt.window)
1367        exporter.SetFileName(fileoutput)
1368        # exporter.WriteToOutputStringOn()
1369        exporter.Update()
1370        exporter.Write()
1371
1372        wsize = plt.window.GetSize()
1373        x3d_html = _x3d_html_template.replace("~fileoutput", fileoutput)
1374        x3d_html = x3d_html.replace("~width",  str(wsize[0]))
1375        x3d_html = x3d_html.replace("~height", str(wsize[1]))
1376        with open(fileoutput.replace(".x3d", ".html"), "w", encoding="UTF-8") as outF:
1377            outF.write(x3d_html)
1378
1379    ####################################################################
1380    elif fr.endswith(".html"):
1381        savebk = vedo.notebook_backend
1382        vedo.notebook_backend = "k3d"
1383        vedo.settings.default_backend = "k3d"
1384        # acts = plt.get_actors()
1385        plt = vedo.backends.get_notebook_backend(plt.objects)
1386
1387        with open(fileoutput, "w", encoding="UTF-8") as fp:
1388            fp.write(plt.get_snapshot())
1389
1390        vedo.notebook_backend = savebk
1391        vedo.settings.default_backend = savebk
1392
1393    else:
1394        vedo.logger.error(f"export extension {fr.split('.')[-1]} is not supported")
1395
1396    return plt

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 key at any moment during visualization.

def import_window(fileinput: str) -> Optional[vedo.plotter.Plotter]:
1666def import_window(fileinput: str) -> Union["vedo.Plotter", None]:
1667    """
1668    Import a whole scene from a Numpy NPZ file.
1669
1670    Returns:
1671        `vedo.Plotter` instance
1672    """
1673    if fileinput.endswith(".npy") or fileinput.endswith(".npz"):
1674        return _import_npy(fileinput)
1675    
1676    # elif ".obj" in fileinput.lower():
1677    #     meshes = load_obj(fileinput, mtl_file, texture_path)
1678    #     plt = vedo.Plotter()
1679    #     plt.add(meshes)
1680    #     return plt
1681
1682    # elif fileinput.endswith(".h5") or fileinput.endswith(".hdf5"):
1683    #     return _import_hdf5(fileinput) # in store/file_io_HDF5.py
1684
1685    return None

Import a whole scene from a Numpy NPZ file.

Returns:

vedo.Plotter instance

def load_obj(fileinput: str, mtl_file=None, texture_path=None) -> List[vedo.mesh.Mesh]:
1688def load_obj(fileinput: str, mtl_file=None, texture_path=None) -> List[Mesh]:
1689    """
1690    Import a set of meshes from a OBJ wavefront file.
1691
1692    Arguments:
1693        mtl_file : (str)
1694            MTL file for OBJ wavefront files
1695        texture_path : (str)
1696            path of the texture files directory
1697
1698    Returns:
1699        `list(Mesh)`
1700    """
1701    window = vtki.vtkRenderWindow()
1702    window.SetOffScreenRendering(1)
1703    renderer = vtki.vtkRenderer()
1704    window.AddRenderer(renderer)
1705
1706    importer = vtki.new("OBJImporter")
1707    importer.SetFileName(fileinput)
1708    if mtl_file is None:
1709        mtl_file = fileinput.replace(".obj", ".mtl").replace(".OBJ", ".MTL")
1710    if os.path.isfile(mtl_file):
1711        importer.SetFileNameMTL(mtl_file)
1712    if texture_path is None:
1713        texture_path = fileinput.replace(".obj", ".txt").replace(".OBJ", ".TXT")
1714    # since the texture_path may be a directory which contains textures
1715    if os.path.exists(texture_path):
1716        importer.SetTexturePath(texture_path)
1717    importer.SetRenderWindow(window)
1718    importer.Update()
1719
1720    actors = renderer.GetActors()
1721    actors.InitTraversal()
1722    objs = []
1723    for _ in range(actors.GetNumberOfItems()):
1724        vactor = actors.GetNextActor()
1725        msh = Mesh(vactor)
1726        msh.name = "OBJMesh"
1727        tx = vactor.GetTexture()
1728        if tx:
1729            msh.texture(tx)
1730        objs.append(msh)
1731    return objs

Import a set of meshes from a OBJ wavefront file.

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

list(Mesh)

def screenshot( filename='screenshot.png', scale=1, asarray=False) -> Union[vedo.plotter.Plotter, numpy.ndarray, NoneType]:
1735def screenshot(filename="screenshot.png", scale=1, asarray=False) -> Union["vedo.Plotter", np.ndarray, None]:
1736    """
1737    Save a screenshot of the current rendering window.
1738
1739    Alternatively, press key `Shift-S` in the rendering window to save a screenshot.
1740    You can also use keyword `screenshot` in `show(..., screenshot="pic.png")`.
1741
1742    Arguments:
1743        scale : (int)
1744            Set image magnification as an integer multiplicative factor.
1745            E.g. setting a magnification of 2 produces an image twice as large,
1746            but 10x slower to generate.
1747        asarray : (bool)
1748            Return a numpy array of the image
1749    """
1750    # print("calling screenshot", filename, scale, asarray)
1751
1752    if not vedo.plotter_instance or not vedo.plotter_instance.window:
1753        # vedo.logger.error("in screenshot(), rendering window is not present, skip.")
1754        return vedo.plotter_instance  ##########
1755
1756    if asarray and scale == 1 and not vedo.plotter_instance.offscreen:
1757        nx, ny = vedo.plotter_instance.window.GetSize()
1758        arr = vtki.vtkUnsignedCharArray()
1759        vedo.plotter_instance.window.GetRGBACharPixelData(0, 0, nx-1, ny-1, 0, arr)
1760        narr = vedo.vtk2numpy(arr).T[:3].T.reshape([ny, nx, 3])
1761        narr = np.flip(narr, axis=0)
1762        return narr  ##########
1763
1764    filename = str(filename)
1765
1766    if filename.endswith(".pdf"):
1767        writer = vtki.new("GL2PSExporter")
1768        writer.SetRenderWindow(vedo.plotter_instance.window)
1769        writer.Write3DPropsAsRasterImageOff()
1770        writer.SilentOn()
1771        writer.SetSortToBSP()
1772        writer.SetFileFormatToPDF()
1773        writer.SetFilePrefix(filename.replace(".pdf", ""))
1774        writer.Write()
1775        return vedo.plotter_instance  ##########
1776
1777    elif filename.endswith(".svg"):
1778        writer = vtki.new("GL2PSExporter")
1779        writer.SetRenderWindow(vedo.plotter_instance.window)
1780        writer.Write3DPropsAsRasterImageOff()
1781        writer.SilentOn()
1782        writer.SetSortToBSP()
1783        writer.SetFileFormatToSVG()
1784        writer.SetFilePrefix(filename.replace(".svg", ""))
1785        writer.Write()
1786        return vedo.plotter_instance  ##########
1787
1788    elif filename.endswith(".eps"):
1789        writer = vtki.new("GL2PSExporter")
1790        writer.SetRenderWindow(vedo.plotter_instance.window)
1791        writer.Write3DPropsAsRasterImageOff()
1792        writer.SilentOn()
1793        writer.SetSortToBSP()
1794        writer.SetFileFormatToEPS()
1795        writer.SetFilePrefix(filename.replace(".eps", ""))
1796        writer.Write()
1797        return vedo.plotter_instance  ##########
1798
1799    if settings.screeshot_large_image:
1800        w2if = vtki.new("RenderLargeImage")
1801        w2if.SetInput(vedo.plotter_instance.renderer)
1802        w2if.SetMagnification(scale)
1803    else:
1804        w2if = vtki.new("WindowToImageFilter")
1805        w2if.SetInput(vedo.plotter_instance.window)
1806        if hasattr(w2if, "SetScale"):
1807            w2if.SetScale(int(scale), int(scale))
1808        if settings.screenshot_transparent_background:
1809            w2if.SetInputBufferTypeToRGBA()
1810        w2if.ReadFrontBufferOff()  # read from the back buffer
1811    w2if.Update()
1812
1813    if asarray:
1814        pd = w2if.GetOutput().GetPointData()
1815        npdata = utils.vtk2numpy(pd.GetArray("ImageScalars"))
1816        npdata = npdata[:, [0, 1, 2]]
1817        ydim, xdim, _ = w2if.GetOutput().GetDimensions()
1818        npdata = npdata.reshape([xdim, ydim, -1])
1819        npdata = np.flip(npdata, axis=0)
1820        return npdata ###########################
1821
1822    # elif settings.default_backend == "2d" and vedo.notebook_plotter:
1823    #     vedo.notebook_plotter.save(filename) # a PIL Image
1824    #     return vedo.notebook_plotter  ##########
1825
1826    if filename.lower().endswith(".png"):
1827        writer = vtki.new("PNGWriter")
1828        writer.SetFileName(filename)
1829        writer.SetInputData(w2if.GetOutput())
1830        writer.Write()
1831    elif filename.lower().endswith(".jpg") or filename.lower().endswith(".jpeg"):
1832        writer = vtki.new("JPEGWriter")
1833        writer.SetFileName(filename)
1834        writer.SetInputData(w2if.GetOutput())
1835        writer.Write()
1836    else:  # add .png
1837        writer = vtki.new("PNGWriter")
1838        writer.SetFileName(filename + ".png")
1839        writer.SetInputData(w2if.GetOutput())
1840        writer.Write()
1841    return vedo.plotter_instance

Save a screenshot of the current rendering window.

Alternatively, press key Shift-S in the rendering window to save a screenshot. You can also use keyword screenshot in show(..., screenshot="pic.png").

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

Generate a video from a rendering window.

Video(name='movie.mp4', duration=None, fps=24, backend='imageio')
1901    def __init__(self, name="movie.mp4", duration=None, fps=24, backend="imageio"):
1902        """
1903        Class to generate a video from the specified rendering window.
1904        Program `ffmpeg` is used to create video from each generated frame.
1905
1906        Arguments:
1907            name : (str)
1908                name of the output file.
1909            fps : (int)
1910                set the number of frames per second.
1911            duration : (float)
1912                set the total `duration` of the video and recalculates `fps` accordingly.
1913            backend : (str)
1914                the backend engine to be used `['imageio', 'ffmpeg', 'cv']`
1915
1916        Examples:
1917            - [make_video.py](https://github.com/marcomusy/vedo/tree/master/examples/other/make_video.py)
1918
1919            ![](https://user-images.githubusercontent.com/32848391/50739007-2bfc2b80-11da-11e9-97e6-620a3541a6fa.jpg)
1920        """
1921        self.name = name
1922        self.duration = duration
1923        self.backend = backend
1924        self.fps = float(fps)
1925        self.command = "ffmpeg -loglevel panic -y -r"
1926        self.options = "-b:v 8000k"
1927
1928        self.frames = []
1929        self.tmp_dir = TemporaryDirectory()
1930        self.get_filename = lambda x: os.path.join(self.tmp_dir.name, x)
1931        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) -> Video:
1933    def add_frame(self) -> "Video":
1934        """Add frame to current video."""
1935        fr = self.get_filename(str(len(self.frames)) + ".png")
1936        screenshot(fr)
1937        self.frames.append(fr)
1938        return self

Add frame to current video.

def pause(self, pause=0) -> Video:
1940    def pause(self, pause=0) -> "Video":
1941        """Insert a `pause`, in seconds."""
1942        fr = self.frames[-1]
1943        n = int(self.fps * pause)
1944        for _ in range(n):
1945            fr2 = self.get_filename(str(len(self.frames)) + ".png")
1946            self.frames.append(fr2)
1947            os.system("cp -f %s %s" % (fr, fr2))
1948        return self

Insert a pause, in seconds.

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

Render the video and write it to file.

def split_frames(self, output_dir='video_frames', prefix='frame_', format='png') -> None:
2079    def split_frames(self, output_dir="video_frames", prefix="frame_", format="png") -> None:
2080        """Split an existing video file into frames."""
2081        try:
2082            import imageio
2083        except ImportError:
2084            vedo.logger.error("\nPlease install imageio with:\n pip install imageio")
2085            return
2086
2087        # Create the output directory if it doesn't exist
2088        if not os.path.exists(output_dir):
2089            os.makedirs(output_dir)
2090
2091        # Create a reader object to read the video
2092        reader = imageio.get_reader(self.name)
2093
2094        # Loop through each frame of the video and save it as image
2095        print()
2096        for i, frame in utils.progressbar(
2097            enumerate(reader), title=f"writing {format} frames", c="m", width=20
2098        ):
2099            output_file = os.path.join(output_dir, f"{prefix}{str(i).zfill(5)}.{format}")
2100            imageio.imwrite(output_file, frame, format=format)

Split an existing video file into frames.