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 {:.8g} {:.8g} {:.8g}\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(".off"):
1258        with open(fileoutput, "w", encoding="UTF-8") as outF:
1259            outF.write("OFF\n")
1260            outF.write(str(objct.npoints) + " " + str(objct.ncells) + " 0\n\n")
1261            for p in objct.vertices:
1262                outF.write(" ".join([str(i) for i in p]) + "\n")
1263            for c in objct.cells:
1264                outF.write(str(len(c)) + " " + " ".join([str(i) for i in c]) + "\n")
1265        return objct
1266
1267    elif fr.endswith(".xml"):  # write tetrahedral dolfin xml
1268        vertices = objct.vertices.astype(str)
1269        faces = np.array(objct.cells).astype(str)
1270        ncoords = vertices.shape[0]
1271        with open(fileoutput, "w", encoding="UTF-8") as outF:
1272            outF.write('<?xml version="1.0" encoding="UTF-8"?>\n')
1273            outF.write('<dolfin xmlns:dolfin="http://www.fenicsproject.org">\n')
1274
1275            if len(faces[0]) == 4:  # write tetrahedral mesh
1276                ntets = faces.shape[0]
1277                outF.write('  <mesh celltype="tetrahedron" dim="3">\n')
1278                outF.write('    <vertices size="' + str(ncoords) + '">\n')
1279                for i in range(ncoords):
1280                    x, y, z = vertices[i]
1281                    outF.write('      <vertex index="'+str(i)+'" x="'+x+'" y="'+y+'" z="'+z+'"/>\n')
1282                outF.write('    </vertices>\n')
1283                outF.write('    <cells size="' + str(ntets) + '">\n')
1284                for i in range(ntets):
1285                    v0, v1, v2, v3 = faces[i]
1286                    outF.write('     <tetrahedron index="'+str(i)
1287                               + '" v0="'+v0+'" v1="'+v1+'" v2="'+v2+'" v3="'+v3+'"/>\n')
1288
1289            elif len(faces[0]) == 3:  # write triangle mesh
1290                ntri = faces.shape[0]
1291                outF.write('  <mesh celltype="triangle" dim="2">\n')
1292                outF.write('    <vertices size="' + str(ncoords) + '">\n')
1293                for i in range(ncoords):
1294                    x, y, _ = vertices[i]
1295                    outF.write('      <vertex index="'+str(i)+'" x="'+x+'" y="'+y+'"/>\n')
1296                outF.write('    </vertices>\n')
1297                outF.write('    <cells size="' + str(ntri) + '">\n')
1298                for i in range(ntri):
1299                    v0, v1, v2 = faces[i]
1300                    outF.write('     <triangle index="'+str(i)+'" v0="'+v0+'" v1="'+v1+'" v2="'+v2+'"/>\n')
1301
1302            outF.write("    </cells>\n")
1303            outF.write("  </mesh>\n")
1304            outF.write("</dolfin>\n")
1305        return objct
1306
1307    else:
1308        vedo.logger.error(f"Unknown format {fileoutput}, file not saved")
1309        return objct
1310
1311    try:
1312        if binary:
1313            writer.SetFileTypeToBinary()
1314        else:
1315            writer.SetFileTypeToASCII()
1316    except AttributeError:
1317        pass
1318
1319    try:
1320        writer.SetInputData(obj)
1321        writer.SetFileName(fileoutput)
1322        writer.Write()
1323    except:
1324        vedo.logger.error(f"could not save {fileoutput}")
1325    return objct
1326
1327def save(obj: Any, fileoutput="out.png", binary=True) -> Any:
1328    """Save an object to file. Same as `write()`."""
1329    return write(obj, fileoutput, binary)
1330
1331def read(obj: Any, unpack=True, force=False) -> Any:
1332    """Read an object from file. Same as `load()`."""
1333    return load(obj, unpack, force)
1334
1335###############################################################################
1336def export_window(fileoutput: str, binary=False, plt=None) -> "vedo.Plotter":
1337    """
1338    Exporter which writes out the rendered scene into an HTML, X3D or Numpy file.
1339
1340    Example:
1341        - [export_x3d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/export_x3d.py)
1342
1343        Check out the HTML generated webpage [here](https://vedo.embl.es/examples/embryo.html).
1344
1345        <img src='https://user-images.githubusercontent.com/32848391/57160341-c6ffbd80-6de8-11e9-95ff-7215ce642bc5.jpg' width="600"/>
1346
1347    .. note::
1348        the rendering window can also be exported to `numpy` file `scene.npz`
1349        by pressing `E` key at any moment during visualization.
1350    """
1351    if plt is None:
1352        plt = vedo.plotter_instance
1353
1354    fr = fileoutput.lower()
1355    ####################################################################
1356    if fr.endswith(".npy") or fr.endswith(".npz"):
1357        _export_npy(plt, fileoutput)
1358
1359    ####################################################################
1360    elif fr.endswith(".x3d"):
1361        # obj = plt.get_actors()
1362        # if plt.axes_instances:
1363        #     obj.append(plt.axes_instances[0])
1364
1365        # for a in obj:
1366        #     if isinstance(a, Assembly):
1367        #         plt.remove(a)
1368        #         plt.add(a.unpack())
1369
1370        plt.render()
1371
1372        exporter = vtki.new("X3DExporter")
1373        exporter.SetBinary(binary)
1374        exporter.FastestOff()
1375        exporter.SetInput(plt.window)
1376        exporter.SetFileName(fileoutput)
1377        # exporter.WriteToOutputStringOn()
1378        exporter.Update()
1379        exporter.Write()
1380
1381        wsize = plt.window.GetSize()
1382        x3d_html = _x3d_html_template.replace("~fileoutput", fileoutput)
1383        x3d_html = x3d_html.replace("~width",  str(wsize[0]))
1384        x3d_html = x3d_html.replace("~height", str(wsize[1]))
1385        with open(fileoutput.replace(".x3d", ".html"), "w", encoding="UTF-8") as outF:
1386            outF.write(x3d_html)
1387
1388    ####################################################################
1389    elif fr.endswith(".html"):
1390        savebk = vedo.notebook_backend
1391        vedo.notebook_backend = "k3d"
1392        vedo.settings.default_backend = "k3d"
1393        # acts = plt.get_actors()
1394        plt = vedo.backends.get_notebook_backend(plt.objects)
1395
1396        with open(fileoutput, "w", encoding="UTF-8") as fp:
1397            fp.write(plt.get_snapshot())
1398
1399        vedo.notebook_backend = savebk
1400        vedo.settings.default_backend = savebk
1401
1402    else:
1403        vedo.logger.error(f"export extension {fr.split('.')[-1]} is not supported")
1404
1405    return plt
1406
1407#########################################################################
1408def _to_numpy(act: Any) -> dict:
1409    """Encode a vedo object to numpy format."""
1410
1411    ########################################################
1412    def _fillcommon(obj, adict):
1413        adict["filename"] = obj.filename
1414        adict["name"] = obj.name
1415        adict["time"] = obj.time
1416        adict["rendered_at"] = obj.rendered_at
1417        try:
1418            adict["transform"] = obj.transform.matrix
1419        except AttributeError:
1420            adict["transform"] = np.eye(4)
1421
1422    ####################################################################
1423    try:
1424        obj = act.retrieve_object()
1425    except AttributeError:
1426        obj = act
1427
1428    adict = {}
1429    adict["type"] = "unknown"
1430
1431    ######################################################## Points/Mesh
1432    if isinstance(obj, (Points, vedo.UnstructuredGrid)):
1433        adict["type"] = "Mesh"
1434        _fillcommon(obj, adict)
1435
1436        if isinstance(obj, vedo.UnstructuredGrid):
1437            # adict["type"] = "UnstructuredGrid"
1438            # adict["cells"] = obj.cells_as_flat_array
1439            poly = obj._actor.GetMapper().GetInput()
1440            mapper = obj._actor.GetMapper()
1441        else:
1442            poly = obj.dataset
1443            mapper = obj.mapper
1444
1445        adict["points"] = obj.vertices.astype(float)
1446
1447        adict["cells"] = None
1448        if poly.GetNumberOfPolys():
1449            adict["cells"] = obj.cells_as_flat_array
1450
1451        adict["lines"] = None
1452        if poly.GetNumberOfLines():
1453            adict["lines"] = obj.lines#_as_flat_array
1454
1455        adict["pointdata"] = {}
1456        for iname in obj.pointdata.keys():
1457            if "normals" in iname.lower():
1458                continue
1459            adict["pointdata"][iname] = obj.pointdata[iname]
1460
1461        adict["celldata"] = {}
1462        for iname in obj.celldata.keys():
1463            if "normals" in iname.lower():
1464                continue
1465            adict["celldata"][iname] = obj.celldata[iname]
1466        
1467        adict["metadata"] = {}
1468        for iname in obj.metadata.keys():
1469            adict["metadata"][iname] = obj.metadata[iname]
1470
1471        # NEW in vedo 5.0
1472        adict["scalar_mode"] = mapper.GetScalarMode()
1473        adict["array_name_to_color_by"] = mapper.GetArrayName()
1474        adict["color_mode"] = mapper.GetColorMode()
1475        adict["interpolate_scalars_before_mapping"] = mapper.GetInterpolateScalarsBeforeMapping()
1476        adict["use_lookup_table_scalar_range"] = mapper.GetUseLookupTableScalarRange()
1477        adict["scalar_range"] = mapper.GetScalarRange()
1478        adict["scalar_visibility"] = mapper.GetScalarVisibility()
1479        adict["pickable"] = obj.actor.GetPickable()
1480        adict["dragable"] = obj.actor.GetDragable()
1481
1482        # adict["color_map_colors"]  = mapper.GetColorMapColors()   #vtkUnsignedCharArray
1483        # adict["color_coordinates"] = mapper.GetColorCoordinates() #vtkFloatArray
1484        texmap = mapper.GetColorTextureMap()  #vtkImageData
1485        if texmap:
1486            adict["color_texture_map"] = vedo.Image(texmap).tonumpy()
1487            # print("color_texture_map", adict["color_texture_map"].shape)
1488
1489        adict["texture_array"] = None
1490        texture = obj.actor.GetTexture()
1491        if texture:
1492            adict["texture_array"] = vedo.Image(texture.GetInput()).tonumpy()
1493            adict["texture_interpolate"] = texture.GetInterpolate()
1494            adict["texture_repeat"] = texture.GetRepeat()
1495            adict["texture_quality"] = texture.GetQuality()
1496            adict["texture_color_mode"] = texture.GetColorMode()
1497            adict["texture_mipmap"] = texture.GetMipmap()
1498            adict["texture_blending_mode"] = texture.GetBlendingMode()
1499            adict["texture_edge_clamp"] = texture.GetEdgeClamp()
1500            adict["texture_border_color"] = texture.GetBorderColor()
1501            # print("tonumpy: texture", obj.name, adict["texture_array"].shape)
1502
1503        adict["LUT"] = None
1504        adict["LUT_range"] = None
1505        lut = mapper.GetLookupTable()
1506        if lut:
1507            nlut = lut.GetNumberOfTableValues()
1508            lutvals = []
1509            for i in range(nlut):
1510                v4 = lut.GetTableValue(i)  # (r, g, b, alpha)
1511                lutvals.append(v4)
1512            adict["LUT"] = np.array(lutvals, dtype=np.float32)
1513            adict["LUT_range"] = np.array(lut.GetRange())
1514
1515        prp = obj.properties
1516        adict["alpha"] = prp.GetOpacity()
1517        adict["representation"] = prp.GetRepresentation()
1518        adict["pointsize"] = prp.GetPointSize()
1519
1520        adict["linecolor"] = None
1521        adict["linewidth"] = None
1522        adict["edge_visibility"] = prp.GetEdgeVisibility() # new in vedo 5.0
1523        if prp.GetEdgeVisibility():
1524            adict["linewidth"] = prp.GetLineWidth()
1525            adict["linecolor"] = prp.GetEdgeColor()
1526
1527        adict["ambient"] = prp.GetAmbient()
1528        adict["diffuse"] = prp.GetDiffuse()
1529        adict["specular"] = prp.GetSpecular()
1530        adict["specularpower"] = prp.GetSpecularPower()
1531        adict["specularcolor"] = prp.GetSpecularColor()
1532        adict["shading"] = prp.GetInterpolation()  # flat phong..:
1533        adict["color"] = prp.GetColor()
1534        adict["lighting_is_on"] = prp.GetLighting()
1535        adict["backcolor"] = None
1536        if obj.actor.GetBackfaceProperty():
1537            adict["backcolor"] = obj.actor.GetBackfaceProperty().GetColor()
1538
1539    ######################################################## Volume
1540    elif isinstance(obj, Volume):
1541        adict["type"] = "Volume"
1542        _fillcommon(obj, adict)
1543        adict["array"] = obj.tonumpy()
1544        adict["mode"] = obj.mode()
1545        adict["spacing"] = obj.spacing()
1546        adict["origin"] = obj.origin()        
1547
1548        prp = obj.properties
1549        ctf = prp.GetRGBTransferFunction()
1550        otf = prp.GetScalarOpacity()
1551        gotf = prp.GetGradientOpacity()
1552        smin, smax = ctf.GetRange()
1553        xs = np.linspace(smin, smax, num=256, endpoint=True)
1554        cols, als, algrs = [], [], []
1555        for x in xs:
1556            cols.append(ctf.GetColor(x))
1557            als.append(otf.GetValue(x))
1558            if gotf:
1559                algrs.append(gotf.GetValue(x))
1560        adict["color"] = cols
1561        adict["alpha"] = als
1562        adict["alphagrad"] = algrs
1563
1564    ######################################################## Image
1565    elif isinstance(obj, Image):
1566        adict["type"] = "Image"
1567        _fillcommon(obj, adict)
1568        adict["array"] = obj.tonumpy()
1569        adict["scale"] = obj.actor.GetScale()
1570        adict["position"] = obj.actor.GetPosition()
1571        adict["orientation"] = obj.actor.GetOrientation()
1572        adict['origin'] = obj.actor.GetOrigin()
1573        adict["alpha"] = obj.alpha()
1574
1575    ######################################################## Text2D
1576    elif isinstance(obj, vedo.Text2D):
1577        adict["type"] = "Text2D"
1578        adict["rendered_at"] = obj.rendered_at
1579        adict["text"] = obj.text()
1580        adict["position"] = obj.GetPosition()
1581        adict["color"] = obj.properties.GetColor()
1582        adict["font"] = obj.fontname
1583        adict["size"] = obj.properties.GetFontSize() / 22.5
1584        adict["bgcol"] = obj.properties.GetBackgroundColor()
1585        adict["alpha"] = obj.properties.GetBackgroundOpacity()
1586        adict["frame"] = obj.properties.GetFrame()
1587
1588    else:
1589        # vedo.logger.warning(f"to_numpy: cannot export object of type {type(obj)}")
1590        pass
1591
1592    return adict
1593
1594
1595#########################################################################
1596def _export_npy(plt, fileoutput="scene.npz") -> None:
1597
1598    sdict = {}
1599    sdict["shape"] = plt.shape
1600    sdict["sharecam"] = plt.sharecam
1601    sdict["camera"] = dict(
1602        pos=plt.camera.GetPosition(),
1603        focal_point=plt.camera.GetFocalPoint(),
1604        viewup=plt.camera.GetViewUp(),
1605        distance=plt.camera.GetDistance(),
1606        clipping_range=plt.camera.GetClippingRange(),
1607        parallel_scale=plt.camera.GetParallelScale(),
1608    )
1609    sdict["position"] = plt.pos
1610    sdict["size"] = plt.size
1611    sdict["axes"] = 0
1612    sdict["title"] = plt.title
1613    sdict["backgrcol"] = colors.get_color(plt.renderer.GetBackground())
1614    sdict["backgrcol2"] = None
1615    if plt.renderer.GetGradientBackground():
1616        sdict["backgrcol2"] = plt.renderer.GetBackground2()
1617    sdict["use_depth_peeling"] = plt.renderer.GetUseDepthPeeling()
1618    sdict["use_parallel_projection"] = plt.camera.GetParallelProjection()
1619    sdict["default_font"] = vedo.settings.default_font
1620
1621    sdict["objects"] = []
1622
1623    actors = plt.get_actors(include_non_pickables=True)
1624    # this ^ also retrieves Actors2D
1625    allobjs = []
1626    for i, a in enumerate(actors):
1627
1628        if not a.GetVisibility():
1629            continue
1630
1631        try:
1632            ob = a.retrieve_object()
1633            # print("get_actors",[ob], ob.name)
1634            if isinstance(ob, Assembly):
1635                asse_scale = ob.GetScale()
1636                asse_pos = ob.GetPosition()
1637                asse_ori = ob.GetOrientation()
1638                asse_org = ob.GetOrigin()
1639                for elem in ob.unpack():
1640                    elem.name = f"ASSEMBLY{i}_{ob.name}_{elem.name}"
1641                    # elem.info.update({"assembly": ob.name}) # TODO
1642                    # elem.info.update({"assembly_scale": asse_scale})
1643                    # elem.info.update({"assembly_position": asse_pos})
1644                    # elem.info.update({"assembly_orientation": asse_ori})
1645                    # elem.info.update({"assembly_origin": asse_org})
1646                    elem.metadata["assembly"] = ob.name
1647                    elem.metadata["assembly_scale"] = asse_scale
1648                    elem.metadata["assembly_position"] = asse_pos
1649                    elem.metadata["assembly_orientation"] = asse_ori
1650                    elem.metadata["assembly_origin"] = asse_org
1651                    allobjs.append(elem)
1652            else:
1653                allobjs.append(ob)
1654
1655        except AttributeError:
1656            # print()
1657            # vedo.logger.warning(f"Cannot retrieve object of type {type(a)}")
1658            pass
1659
1660    for a in allobjs:
1661        # print("to_numpy(): dumping", [a], a.name)
1662        # try:
1663        npobj = _to_numpy(a)
1664        sdict["objects"].append(npobj)
1665        # except AttributeError:
1666        #     vedo.logger.warning(f"Cannot export object of type {type(a)}")
1667
1668    if fileoutput.endswith(".npz"):
1669        np.savez_compressed(fileoutput, vedo_scenes=[sdict])
1670    else:
1671        np.save(fileoutput, [sdict])
1672
1673
1674########################################################################
1675def import_window(fileinput: str) -> Union["vedo.Plotter", None]:
1676    """
1677    Import a whole scene from a Numpy NPZ file.
1678
1679    Returns:
1680        `vedo.Plotter` instance
1681    """
1682    if fileinput.endswith(".npy") or fileinput.endswith(".npz"):
1683        return _import_npy(fileinput)
1684    
1685    # elif ".obj" in fileinput.lower():
1686    #     meshes = load_obj(fileinput, mtl_file, texture_path)
1687    #     plt = vedo.Plotter()
1688    #     plt.add(meshes)
1689    #     return plt
1690
1691    # elif fileinput.endswith(".h5") or fileinput.endswith(".hdf5"):
1692    #     return _import_hdf5(fileinput) # in store/file_io_HDF5.py
1693
1694    return None
1695
1696
1697def load_obj(fileinput: str, mtl_file=None, texture_path=None) -> List[Mesh]:
1698    """
1699    Import a set of meshes from a OBJ wavefront file.
1700
1701    Arguments:
1702        mtl_file : (str)
1703            MTL file for OBJ wavefront files
1704        texture_path : (str)
1705            path of the texture files directory
1706
1707    Returns:
1708        `list(Mesh)`
1709    """
1710    window = vtki.vtkRenderWindow()
1711    window.SetOffScreenRendering(1)
1712    renderer = vtki.vtkRenderer()
1713    window.AddRenderer(renderer)
1714
1715    importer = vtki.new("OBJImporter")
1716    importer.SetFileName(fileinput)
1717    if mtl_file is None:
1718        mtl_file = fileinput.replace(".obj", ".mtl").replace(".OBJ", ".MTL")
1719    if os.path.isfile(mtl_file):
1720        importer.SetFileNameMTL(mtl_file)
1721    if texture_path is None:
1722        texture_path = fileinput.replace(".obj", ".txt").replace(".OBJ", ".TXT")
1723    # since the texture_path may be a directory which contains textures
1724    if os.path.exists(texture_path):
1725        importer.SetTexturePath(texture_path)
1726    importer.SetRenderWindow(window)
1727    importer.Update()
1728
1729    actors = renderer.GetActors()
1730    actors.InitTraversal()
1731    objs = []
1732    for _ in range(actors.GetNumberOfItems()):
1733        vactor = actors.GetNextActor()
1734        msh = Mesh(vactor)
1735        msh.name = "OBJMesh"
1736        tx = vactor.GetTexture()
1737        if tx:
1738            msh.texture(tx)
1739        objs.append(msh)
1740    return objs
1741
1742
1743##########################################################
1744def screenshot(filename="screenshot.png", scale=1, asarray=False) -> Union["vedo.Plotter", np.ndarray, None]:
1745    """
1746    Save a screenshot of the current rendering window.
1747
1748    Alternatively, press key `Shift-S` in the rendering window to save a screenshot.
1749    You can also use keyword `screenshot` in `show(..., screenshot="pic.png")`.
1750
1751    Arguments:
1752        scale : (int)
1753            Set image magnification as an integer multiplicative factor.
1754            E.g. setting a magnification of 2 produces an image twice as large,
1755            but 10x slower to generate.
1756        asarray : (bool)
1757            Return a numpy array of the image
1758    """
1759    # print("calling screenshot", filename, scale, asarray)
1760
1761    if not vedo.plotter_instance or not vedo.plotter_instance.window:
1762        # vedo.logger.error("in screenshot(), rendering window is not present, skip.")
1763        return vedo.plotter_instance  ##########
1764
1765    if asarray and scale == 1 and not vedo.plotter_instance.offscreen:
1766        nx, ny = vedo.plotter_instance.window.GetSize()
1767        arr = vtki.vtkUnsignedCharArray()
1768        vedo.plotter_instance.window.GetRGBACharPixelData(0, 0, nx-1, ny-1, 0, arr)
1769        narr = vedo.vtk2numpy(arr).T[:3].T.reshape([ny, nx, 3])
1770        narr = np.flip(narr, axis=0)
1771        return narr  ##########
1772
1773    filename = str(filename)
1774
1775    if filename.endswith(".pdf"):
1776        writer = vtki.new("GL2PSExporter")
1777        writer.SetRenderWindow(vedo.plotter_instance.window)
1778        writer.Write3DPropsAsRasterImageOff()
1779        writer.SilentOn()
1780        writer.SetSortToBSP()
1781        writer.SetFileFormatToPDF()
1782        writer.SetFilePrefix(filename.replace(".pdf", ""))
1783        writer.Write()
1784        return vedo.plotter_instance  ##########
1785
1786    elif filename.endswith(".svg"):
1787        writer = vtki.new("GL2PSExporter")
1788        writer.SetRenderWindow(vedo.plotter_instance.window)
1789        writer.Write3DPropsAsRasterImageOff()
1790        writer.SilentOn()
1791        writer.SetSortToBSP()
1792        writer.SetFileFormatToSVG()
1793        writer.SetFilePrefix(filename.replace(".svg", ""))
1794        writer.Write()
1795        return vedo.plotter_instance  ##########
1796
1797    elif filename.endswith(".eps"):
1798        writer = vtki.new("GL2PSExporter")
1799        writer.SetRenderWindow(vedo.plotter_instance.window)
1800        writer.Write3DPropsAsRasterImageOff()
1801        writer.SilentOn()
1802        writer.SetSortToBSP()
1803        writer.SetFileFormatToEPS()
1804        writer.SetFilePrefix(filename.replace(".eps", ""))
1805        writer.Write()
1806        return vedo.plotter_instance  ##########
1807
1808    if settings.screeshot_large_image:
1809        w2if = vtki.new("RenderLargeImage")
1810        w2if.SetInput(vedo.plotter_instance.renderer)
1811        w2if.SetMagnification(scale)
1812    else:
1813        w2if = vtki.new("WindowToImageFilter")
1814        w2if.SetInput(vedo.plotter_instance.window)
1815        if hasattr(w2if, "SetScale"):
1816            w2if.SetScale(int(scale), int(scale))
1817        if settings.screenshot_transparent_background:
1818            w2if.SetInputBufferTypeToRGBA()
1819        w2if.ReadFrontBufferOff()  # read from the back buffer
1820    w2if.Update()
1821
1822    if asarray:
1823        pd = w2if.GetOutput().GetPointData()
1824        npdata = utils.vtk2numpy(pd.GetArray("ImageScalars"))
1825        npdata = npdata[:, [0, 1, 2]]
1826        ydim, xdim, _ = w2if.GetOutput().GetDimensions()
1827        npdata = npdata.reshape([xdim, ydim, -1])
1828        npdata = np.flip(npdata, axis=0)
1829        return npdata ###########################
1830
1831    # elif settings.default_backend == "2d" and vedo.notebook_plotter:
1832    #     vedo.notebook_plotter.save(filename) # a PIL Image
1833    #     return vedo.notebook_plotter  ##########
1834
1835    if filename.lower().endswith(".png"):
1836        writer = vtki.new("PNGWriter")
1837        writer.SetFileName(filename)
1838        writer.SetInputData(w2if.GetOutput())
1839        writer.Write()
1840    elif filename.lower().endswith(".jpg") or filename.lower().endswith(".jpeg"):
1841        writer = vtki.new("JPEGWriter")
1842        writer.SetFileName(filename)
1843        writer.SetInputData(w2if.GetOutput())
1844        writer.Write()
1845    else:  # add .png
1846        writer = vtki.new("PNGWriter")
1847        writer.SetFileName(filename + ".png")
1848        writer.SetInputData(w2if.GetOutput())
1849        writer.Write()
1850    return vedo.plotter_instance
1851
1852
1853def ask(*question, **kwarg) -> str:
1854    """
1855    Ask a question from command line. Return the answer as a string.
1856    See function `colors.printc()` for the description of the keyword options.
1857
1858    Arguments:
1859        options : (list)
1860            a python list of possible answers to choose from.
1861        default : (str)
1862            the default answer when just hitting return.
1863
1864    Example:
1865    ```python
1866    import vedo
1867    res = vedo.ask("Continue?", options=['Y','n'], default='Y', c='g')
1868    print(res)
1869    ```
1870    """
1871    kwarg.update({"end": " "})
1872    if "invert" not in kwarg:
1873        kwarg.update({"invert": True})
1874    if "box" in kwarg:
1875        kwarg.update({"box": ""})
1876
1877    options = kwarg.pop("options", [])
1878    default = kwarg.pop("default", "")
1879    if options:
1880        opt = "["
1881        for o in options:
1882            opt += o + "/"
1883        opt = opt[:-1] + "]"
1884        colors.printc(*question, opt, **kwarg)
1885    else:
1886        colors.printc(*question, **kwarg)
1887
1888    try:
1889        resp = input()
1890    except Exception:
1891        resp = ""
1892        return resp
1893
1894    if options:
1895        if resp not in options:
1896            if default and str(repr(resp)) == "''":
1897                return default
1898            colors.printc("Please choose one option in:", opt, italic=True, bold=False)
1899            kwarg["options"] = options
1900            return ask(*question, **kwarg)  # ask again
1901    return resp
1902
1903
1904##############################################################################################
1905class Video:
1906    """
1907    Generate a video from a rendering window.
1908    """
1909
1910    def __init__(self, name="movie.mp4", duration=None, fps=24, backend="imageio"):
1911        """
1912        Class to generate a video from the specified rendering window.
1913        Program `ffmpeg` is used to create video from each generated frame.
1914
1915        Arguments:
1916            name : (str)
1917                name of the output file.
1918            fps : (int)
1919                set the number of frames per second.
1920            duration : (float)
1921                set the total `duration` of the video and recalculates `fps` accordingly.
1922            backend : (str)
1923                the backend engine to be used `['imageio', 'ffmpeg', 'cv']`
1924
1925        Examples:
1926            - [make_video.py](https://github.com/marcomusy/vedo/tree/master/examples/other/make_video.py)
1927
1928            ![](https://user-images.githubusercontent.com/32848391/50739007-2bfc2b80-11da-11e9-97e6-620a3541a6fa.jpg)
1929        """
1930        self.name = name
1931        self.duration = duration
1932        self.backend = backend
1933        self.fps = float(fps)
1934        self.command = "ffmpeg -loglevel panic -y -r"
1935        self.options = "-b:v 8000k"
1936
1937        self.frames = []
1938        self.tmp_dir = TemporaryDirectory()
1939        self.get_filename = lambda x: os.path.join(self.tmp_dir.name, x)
1940        colors.printc(":video:  Video file", self.name, "is open... ", c="m", end="")
1941
1942    def add_frame(self) -> "Video":
1943        """Add frame to current video."""
1944        fr = self.get_filename(str(len(self.frames)) + ".png")
1945        screenshot(fr)
1946        self.frames.append(fr)
1947        return self
1948
1949    def pause(self, pause=0) -> "Video":
1950        """Insert a `pause`, in seconds."""
1951        fr = self.frames[-1]
1952        n = int(self.fps * pause)
1953        for _ in range(n):
1954            fr2 = self.get_filename(str(len(self.frames)) + ".png")
1955            self.frames.append(fr2)
1956            os.system("cp -f %s %s" % (fr, fr2))
1957        return self
1958
1959    def action(self, elevation=(0, 80), azimuth=(0, 359), cameras=(), resetcam=False) -> "Video":
1960        """
1961        Automatic shooting of a static scene by specifying rotation and elevation ranges.
1962
1963        Arguments:
1964            elevation : list
1965                initial and final elevation angles
1966            azimuth_range : list
1967                initial and final azimuth angles
1968            cameras : list
1969                list of cameras to go through, each camera can be dictionary or a vtkCamera
1970        """
1971        if not self.duration:
1972            self.duration = 5
1973
1974        plt = vedo.plotter_instance
1975        if not plt:
1976            vedo.logger.error("No vedo plotter found, cannot make video.")
1977            return self
1978        n = int(self.fps * self.duration)
1979
1980        cams = []
1981        for cm in cameras:
1982            cams.append(utils.camera_from_dict(cm))
1983        nc = len(cams)
1984
1985        plt.show(resetcam=resetcam, interactive=False)
1986
1987        if nc:
1988            for i in range(n):
1989                plt.move_camera(cams, i / n)
1990                plt.show()
1991                self.add_frame()
1992
1993        else:  ########################################
1994
1995            for i in range(n):
1996                plt.camera.Elevation((elevation[1] - elevation[0]) / n)
1997                plt.camera.Azimuth((azimuth[1] - azimuth[0]) / n)
1998                plt.show()
1999                self.add_frame()
2000
2001        return self
2002
2003    def close(self) -> None:
2004        """
2005        Render the video and write it to file.
2006        """
2007        if self.duration:
2008            self.fps = int(len(self.frames) / float(self.duration) + 0.5)
2009            colors.printc("recalculated fps:", self.fps, c="m", end="")
2010        else:
2011            self.fps = int(self.fps)
2012
2013        ########################################
2014        if self.backend == "ffmpeg":
2015            out = os.system(
2016                self.command
2017                + " "
2018                + str(self.fps)
2019                + " -i "
2020                + f"'{self.tmp_dir.name}'"
2021                + os.sep
2022                + "%01d.png "
2023                + self.options
2024                + " "
2025                + f"'{self.name}'"
2026            )
2027            if out:
2028                vedo.logger.error(f":noentry: backend {self.backend} returning error: {out}")
2029            else:
2030                colors.printc(f":save: saved to {self.name}", c="m")
2031
2032        ########################################
2033        elif "cv" in self.backend:
2034            try:
2035                import cv2
2036            except ImportError:
2037                vedo.logger.error("opencv is not installed")
2038                return
2039
2040            cap = cv2.VideoCapture(os.path.join(self.tmp_dir.name, "%1d.png"))
2041            fourcc = cv2.VideoWriter_fourcc(*"mp4v")
2042            if vedo.plotter_instance:
2043                w, h = vedo.plotter_instance.window.GetSize()
2044                writer = cv2.VideoWriter(self.name, fourcc, self.fps, (w, h), True)
2045            else:
2046                vedo.logger.error("No vedo plotter found, cannot make video.")
2047                return
2048
2049            while True:
2050                ret, frame = cap.read()
2051                if not ret:
2052                    break
2053                writer.write(frame)
2054
2055            cap.release()
2056            writer.release()
2057
2058        ########################################
2059        elif "imageio" in self.backend:
2060            try:
2061                import imageio
2062            except ImportError:
2063                vedo.logger.error("Please install imageio with:\n pip install imageio[ffmpeg]")
2064                return
2065
2066            if self.name.endswith(".mp4"):
2067                writer = imageio.get_writer(self.name, fps=self.fps)
2068            elif self.name.endswith(".gif"):
2069                writer = imageio.get_writer(self.name, mode="I", duration=1 / self.fps)
2070            elif self.name.endswith(".webm"):
2071                writer = imageio.get_writer(self.name, format="webm", fps=self.fps)
2072            else:
2073                vedo.logger.error(f"Unknown format of {self.name}.")
2074                return
2075
2076            for f in utils.humansort(self.frames):
2077                image = imageio.v3.imread(f)
2078                try:
2079                    writer.append_data(image)
2080                except TypeError:
2081                    vedo.logger.error(f"Could not append data to video {self.name}")
2082                    vedo.logger.error("Please install imageio with: pip install imageio[ffmpeg]")
2083                    break
2084            try:
2085                writer.close()
2086                colors.printc(f"... saved as {self.name}", c="m")
2087            except:
2088                colors.printc(f":noentry: Could not save video {self.name}", c="r")
2089
2090        # finalize cleanup
2091        self.tmp_dir.cleanup()
2092
2093    def split_frames(self, output_dir="video_frames", prefix="frame_", format="png") -> None:
2094        """Split an existing video file into frames."""
2095        try:
2096            import imageio
2097        except ImportError:
2098            vedo.logger.error("\nPlease install imageio with:\n pip install imageio")
2099            return
2100
2101        # Create the output directory if it doesn't exist
2102        if not os.path.exists(output_dir):
2103            os.makedirs(output_dir)
2104
2105        # Create a reader object to read the video
2106        reader = imageio.get_reader(self.name)
2107
2108        # Loop through each frame of the video and save it as image
2109        print()
2110        for i, frame in utils.progressbar(
2111            enumerate(reader), title=f"writing {format} frames", c="m", width=20
2112        ):
2113            output_file = os.path.join(output_dir, f"{prefix}{str(i).zfill(5)}.{format}")
2114            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:
1332def read(obj: Any, unpack=True, force=False) -> Any:
1333    """Read an object from file. Same as `load()`."""
1334    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 {:.8g} {:.8g} {:.8g}\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(".off"):
1259        with open(fileoutput, "w", encoding="UTF-8") as outF:
1260            outF.write("OFF\n")
1261            outF.write(str(objct.npoints) + " " + str(objct.ncells) + " 0\n\n")
1262            for p in objct.vertices:
1263                outF.write(" ".join([str(i) for i in p]) + "\n")
1264            for c in objct.cells:
1265                outF.write(str(len(c)) + " " + " ".join([str(i) for i in c]) + "\n")
1266        return objct
1267
1268    elif fr.endswith(".xml"):  # write tetrahedral dolfin xml
1269        vertices = objct.vertices.astype(str)
1270        faces = np.array(objct.cells).astype(str)
1271        ncoords = vertices.shape[0]
1272        with open(fileoutput, "w", encoding="UTF-8") as outF:
1273            outF.write('<?xml version="1.0" encoding="UTF-8"?>\n')
1274            outF.write('<dolfin xmlns:dolfin="http://www.fenicsproject.org">\n')
1275
1276            if len(faces[0]) == 4:  # write tetrahedral mesh
1277                ntets = faces.shape[0]
1278                outF.write('  <mesh celltype="tetrahedron" dim="3">\n')
1279                outF.write('    <vertices size="' + str(ncoords) + '">\n')
1280                for i in range(ncoords):
1281                    x, y, z = vertices[i]
1282                    outF.write('      <vertex index="'+str(i)+'" x="'+x+'" y="'+y+'" z="'+z+'"/>\n')
1283                outF.write('    </vertices>\n')
1284                outF.write('    <cells size="' + str(ntets) + '">\n')
1285                for i in range(ntets):
1286                    v0, v1, v2, v3 = faces[i]
1287                    outF.write('     <tetrahedron index="'+str(i)
1288                               + '" v0="'+v0+'" v1="'+v1+'" v2="'+v2+'" v3="'+v3+'"/>\n')
1289
1290            elif len(faces[0]) == 3:  # write triangle mesh
1291                ntri = faces.shape[0]
1292                outF.write('  <mesh celltype="triangle" dim="2">\n')
1293                outF.write('    <vertices size="' + str(ncoords) + '">\n')
1294                for i in range(ncoords):
1295                    x, y, _ = vertices[i]
1296                    outF.write('      <vertex index="'+str(i)+'" x="'+x+'" y="'+y+'"/>\n')
1297                outF.write('    </vertices>\n')
1298                outF.write('    <cells size="' + str(ntri) + '">\n')
1299                for i in range(ntri):
1300                    v0, v1, v2 = faces[i]
1301                    outF.write('     <triangle index="'+str(i)+'" v0="'+v0+'" v1="'+v1+'" v2="'+v2+'"/>\n')
1302
1303            outF.write("    </cells>\n")
1304            outF.write("  </mesh>\n")
1305            outF.write("</dolfin>\n")
1306        return objct
1307
1308    else:
1309        vedo.logger.error(f"Unknown format {fileoutput}, file not saved")
1310        return objct
1311
1312    try:
1313        if binary:
1314            writer.SetFileTypeToBinary()
1315        else:
1316            writer.SetFileTypeToASCII()
1317    except AttributeError:
1318        pass
1319
1320    try:
1321        writer.SetInputData(obj)
1322        writer.SetFileName(fileoutput)
1323        writer.Write()
1324    except:
1325        vedo.logger.error(f"could not save {fileoutput}")
1326    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:
1328def save(obj: Any, fileoutput="out.png", binary=True) -> Any:
1329    """Save an object to file. Same as `write()`."""
1330    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:
1337def export_window(fileoutput: str, binary=False, plt=None) -> "vedo.Plotter":
1338    """
1339    Exporter which writes out the rendered scene into an HTML, X3D or Numpy file.
1340
1341    Example:
1342        - [export_x3d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/export_x3d.py)
1343
1344        Check out the HTML generated webpage [here](https://vedo.embl.es/examples/embryo.html).
1345
1346        <img src='https://user-images.githubusercontent.com/32848391/57160341-c6ffbd80-6de8-11e9-95ff-7215ce642bc5.jpg' width="600"/>
1347
1348    .. note::
1349        the rendering window can also be exported to `numpy` file `scene.npz`
1350        by pressing `E` key at any moment during visualization.
1351    """
1352    if plt is None:
1353        plt = vedo.plotter_instance
1354
1355    fr = fileoutput.lower()
1356    ####################################################################
1357    if fr.endswith(".npy") or fr.endswith(".npz"):
1358        _export_npy(plt, fileoutput)
1359
1360    ####################################################################
1361    elif fr.endswith(".x3d"):
1362        # obj = plt.get_actors()
1363        # if plt.axes_instances:
1364        #     obj.append(plt.axes_instances[0])
1365
1366        # for a in obj:
1367        #     if isinstance(a, Assembly):
1368        #         plt.remove(a)
1369        #         plt.add(a.unpack())
1370
1371        plt.render()
1372
1373        exporter = vtki.new("X3DExporter")
1374        exporter.SetBinary(binary)
1375        exporter.FastestOff()
1376        exporter.SetInput(plt.window)
1377        exporter.SetFileName(fileoutput)
1378        # exporter.WriteToOutputStringOn()
1379        exporter.Update()
1380        exporter.Write()
1381
1382        wsize = plt.window.GetSize()
1383        x3d_html = _x3d_html_template.replace("~fileoutput", fileoutput)
1384        x3d_html = x3d_html.replace("~width",  str(wsize[0]))
1385        x3d_html = x3d_html.replace("~height", str(wsize[1]))
1386        with open(fileoutput.replace(".x3d", ".html"), "w", encoding="UTF-8") as outF:
1387            outF.write(x3d_html)
1388
1389    ####################################################################
1390    elif fr.endswith(".html"):
1391        savebk = vedo.notebook_backend
1392        vedo.notebook_backend = "k3d"
1393        vedo.settings.default_backend = "k3d"
1394        # acts = plt.get_actors()
1395        plt = vedo.backends.get_notebook_backend(plt.objects)
1396
1397        with open(fileoutput, "w", encoding="UTF-8") as fp:
1398            fp.write(plt.get_snapshot())
1399
1400        vedo.notebook_backend = savebk
1401        vedo.settings.default_backend = savebk
1402
1403    else:
1404        vedo.logger.error(f"export extension {fr.split('.')[-1]} is not supported")
1405
1406    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]:
1676def import_window(fileinput: str) -> Union["vedo.Plotter", None]:
1677    """
1678    Import a whole scene from a Numpy NPZ file.
1679
1680    Returns:
1681        `vedo.Plotter` instance
1682    """
1683    if fileinput.endswith(".npy") or fileinput.endswith(".npz"):
1684        return _import_npy(fileinput)
1685    
1686    # elif ".obj" in fileinput.lower():
1687    #     meshes = load_obj(fileinput, mtl_file, texture_path)
1688    #     plt = vedo.Plotter()
1689    #     plt.add(meshes)
1690    #     return plt
1691
1692    # elif fileinput.endswith(".h5") or fileinput.endswith(".hdf5"):
1693    #     return _import_hdf5(fileinput) # in store/file_io_HDF5.py
1694
1695    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]:
1698def load_obj(fileinput: str, mtl_file=None, texture_path=None) -> List[Mesh]:
1699    """
1700    Import a set of meshes from a OBJ wavefront file.
1701
1702    Arguments:
1703        mtl_file : (str)
1704            MTL file for OBJ wavefront files
1705        texture_path : (str)
1706            path of the texture files directory
1707
1708    Returns:
1709        `list(Mesh)`
1710    """
1711    window = vtki.vtkRenderWindow()
1712    window.SetOffScreenRendering(1)
1713    renderer = vtki.vtkRenderer()
1714    window.AddRenderer(renderer)
1715
1716    importer = vtki.new("OBJImporter")
1717    importer.SetFileName(fileinput)
1718    if mtl_file is None:
1719        mtl_file = fileinput.replace(".obj", ".mtl").replace(".OBJ", ".MTL")
1720    if os.path.isfile(mtl_file):
1721        importer.SetFileNameMTL(mtl_file)
1722    if texture_path is None:
1723        texture_path = fileinput.replace(".obj", ".txt").replace(".OBJ", ".TXT")
1724    # since the texture_path may be a directory which contains textures
1725    if os.path.exists(texture_path):
1726        importer.SetTexturePath(texture_path)
1727    importer.SetRenderWindow(window)
1728    importer.Update()
1729
1730    actors = renderer.GetActors()
1731    actors.InitTraversal()
1732    objs = []
1733    for _ in range(actors.GetNumberOfItems()):
1734        vactor = actors.GetNextActor()
1735        msh = Mesh(vactor)
1736        msh.name = "OBJMesh"
1737        tx = vactor.GetTexture()
1738        if tx:
1739            msh.texture(tx)
1740        objs.append(msh)
1741    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]:
1745def screenshot(filename="screenshot.png", scale=1, asarray=False) -> Union["vedo.Plotter", np.ndarray, None]:
1746    """
1747    Save a screenshot of the current rendering window.
1748
1749    Alternatively, press key `Shift-S` in the rendering window to save a screenshot.
1750    You can also use keyword `screenshot` in `show(..., screenshot="pic.png")`.
1751
1752    Arguments:
1753        scale : (int)
1754            Set image magnification as an integer multiplicative factor.
1755            E.g. setting a magnification of 2 produces an image twice as large,
1756            but 10x slower to generate.
1757        asarray : (bool)
1758            Return a numpy array of the image
1759    """
1760    # print("calling screenshot", filename, scale, asarray)
1761
1762    if not vedo.plotter_instance or not vedo.plotter_instance.window:
1763        # vedo.logger.error("in screenshot(), rendering window is not present, skip.")
1764        return vedo.plotter_instance  ##########
1765
1766    if asarray and scale == 1 and not vedo.plotter_instance.offscreen:
1767        nx, ny = vedo.plotter_instance.window.GetSize()
1768        arr = vtki.vtkUnsignedCharArray()
1769        vedo.plotter_instance.window.GetRGBACharPixelData(0, 0, nx-1, ny-1, 0, arr)
1770        narr = vedo.vtk2numpy(arr).T[:3].T.reshape([ny, nx, 3])
1771        narr = np.flip(narr, axis=0)
1772        return narr  ##########
1773
1774    filename = str(filename)
1775
1776    if filename.endswith(".pdf"):
1777        writer = vtki.new("GL2PSExporter")
1778        writer.SetRenderWindow(vedo.plotter_instance.window)
1779        writer.Write3DPropsAsRasterImageOff()
1780        writer.SilentOn()
1781        writer.SetSortToBSP()
1782        writer.SetFileFormatToPDF()
1783        writer.SetFilePrefix(filename.replace(".pdf", ""))
1784        writer.Write()
1785        return vedo.plotter_instance  ##########
1786
1787    elif filename.endswith(".svg"):
1788        writer = vtki.new("GL2PSExporter")
1789        writer.SetRenderWindow(vedo.plotter_instance.window)
1790        writer.Write3DPropsAsRasterImageOff()
1791        writer.SilentOn()
1792        writer.SetSortToBSP()
1793        writer.SetFileFormatToSVG()
1794        writer.SetFilePrefix(filename.replace(".svg", ""))
1795        writer.Write()
1796        return vedo.plotter_instance  ##########
1797
1798    elif filename.endswith(".eps"):
1799        writer = vtki.new("GL2PSExporter")
1800        writer.SetRenderWindow(vedo.plotter_instance.window)
1801        writer.Write3DPropsAsRasterImageOff()
1802        writer.SilentOn()
1803        writer.SetSortToBSP()
1804        writer.SetFileFormatToEPS()
1805        writer.SetFilePrefix(filename.replace(".eps", ""))
1806        writer.Write()
1807        return vedo.plotter_instance  ##########
1808
1809    if settings.screeshot_large_image:
1810        w2if = vtki.new("RenderLargeImage")
1811        w2if.SetInput(vedo.plotter_instance.renderer)
1812        w2if.SetMagnification(scale)
1813    else:
1814        w2if = vtki.new("WindowToImageFilter")
1815        w2if.SetInput(vedo.plotter_instance.window)
1816        if hasattr(w2if, "SetScale"):
1817            w2if.SetScale(int(scale), int(scale))
1818        if settings.screenshot_transparent_background:
1819            w2if.SetInputBufferTypeToRGBA()
1820        w2if.ReadFrontBufferOff()  # read from the back buffer
1821    w2if.Update()
1822
1823    if asarray:
1824        pd = w2if.GetOutput().GetPointData()
1825        npdata = utils.vtk2numpy(pd.GetArray("ImageScalars"))
1826        npdata = npdata[:, [0, 1, 2]]
1827        ydim, xdim, _ = w2if.GetOutput().GetDimensions()
1828        npdata = npdata.reshape([xdim, ydim, -1])
1829        npdata = np.flip(npdata, axis=0)
1830        return npdata ###########################
1831
1832    # elif settings.default_backend == "2d" and vedo.notebook_plotter:
1833    #     vedo.notebook_plotter.save(filename) # a PIL Image
1834    #     return vedo.notebook_plotter  ##########
1835
1836    if filename.lower().endswith(".png"):
1837        writer = vtki.new("PNGWriter")
1838        writer.SetFileName(filename)
1839        writer.SetInputData(w2if.GetOutput())
1840        writer.Write()
1841    elif filename.lower().endswith(".jpg") or filename.lower().endswith(".jpeg"):
1842        writer = vtki.new("JPEGWriter")
1843        writer.SetFileName(filename)
1844        writer.SetInputData(w2if.GetOutput())
1845        writer.Write()
1846    else:  # add .png
1847        writer = vtki.new("PNGWriter")
1848        writer.SetFileName(filename + ".png")
1849        writer.SetInputData(w2if.GetOutput())
1850        writer.Write()
1851    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:
1854def ask(*question, **kwarg) -> str:
1855    """
1856    Ask a question from command line. Return the answer as a string.
1857    See function `colors.printc()` for the description of the keyword options.
1858
1859    Arguments:
1860        options : (list)
1861            a python list of possible answers to choose from.
1862        default : (str)
1863            the default answer when just hitting return.
1864
1865    Example:
1866    ```python
1867    import vedo
1868    res = vedo.ask("Continue?", options=['Y','n'], default='Y', c='g')
1869    print(res)
1870    ```
1871    """
1872    kwarg.update({"end": " "})
1873    if "invert" not in kwarg:
1874        kwarg.update({"invert": True})
1875    if "box" in kwarg:
1876        kwarg.update({"box": ""})
1877
1878    options = kwarg.pop("options", [])
1879    default = kwarg.pop("default", "")
1880    if options:
1881        opt = "["
1882        for o in options:
1883            opt += o + "/"
1884        opt = opt[:-1] + "]"
1885        colors.printc(*question, opt, **kwarg)
1886    else:
1887        colors.printc(*question, **kwarg)
1888
1889    try:
1890        resp = input()
1891    except Exception:
1892        resp = ""
1893        return resp
1894
1895    if options:
1896        if resp not in options:
1897            if default and str(repr(resp)) == "''":
1898                return default
1899            colors.printc("Please choose one option in:", opt, italic=True, bold=False)
1900            kwarg["options"] = options
1901            return ask(*question, **kwarg)  # ask again
1902    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:
1906class Video:
1907    """
1908    Generate a video from a rendering window.
1909    """
1910
1911    def __init__(self, name="movie.mp4", duration=None, fps=24, backend="imageio"):
1912        """
1913        Class to generate a video from the specified rendering window.
1914        Program `ffmpeg` is used to create video from each generated frame.
1915
1916        Arguments:
1917            name : (str)
1918                name of the output file.
1919            fps : (int)
1920                set the number of frames per second.
1921            duration : (float)
1922                set the total `duration` of the video and recalculates `fps` accordingly.
1923            backend : (str)
1924                the backend engine to be used `['imageio', 'ffmpeg', 'cv']`
1925
1926        Examples:
1927            - [make_video.py](https://github.com/marcomusy/vedo/tree/master/examples/other/make_video.py)
1928
1929            ![](https://user-images.githubusercontent.com/32848391/50739007-2bfc2b80-11da-11e9-97e6-620a3541a6fa.jpg)
1930        """
1931        self.name = name
1932        self.duration = duration
1933        self.backend = backend
1934        self.fps = float(fps)
1935        self.command = "ffmpeg -loglevel panic -y -r"
1936        self.options = "-b:v 8000k"
1937
1938        self.frames = []
1939        self.tmp_dir = TemporaryDirectory()
1940        self.get_filename = lambda x: os.path.join(self.tmp_dir.name, x)
1941        colors.printc(":video:  Video file", self.name, "is open... ", c="m", end="")
1942
1943    def add_frame(self) -> "Video":
1944        """Add frame to current video."""
1945        fr = self.get_filename(str(len(self.frames)) + ".png")
1946        screenshot(fr)
1947        self.frames.append(fr)
1948        return self
1949
1950    def pause(self, pause=0) -> "Video":
1951        """Insert a `pause`, in seconds."""
1952        fr = self.frames[-1]
1953        n = int(self.fps * pause)
1954        for _ in range(n):
1955            fr2 = self.get_filename(str(len(self.frames)) + ".png")
1956            self.frames.append(fr2)
1957            os.system("cp -f %s %s" % (fr, fr2))
1958        return self
1959
1960    def action(self, elevation=(0, 80), azimuth=(0, 359), cameras=(), resetcam=False) -> "Video":
1961        """
1962        Automatic shooting of a static scene by specifying rotation and elevation ranges.
1963
1964        Arguments:
1965            elevation : list
1966                initial and final elevation angles
1967            azimuth_range : list
1968                initial and final azimuth angles
1969            cameras : list
1970                list of cameras to go through, each camera can be dictionary or a vtkCamera
1971        """
1972        if not self.duration:
1973            self.duration = 5
1974
1975        plt = vedo.plotter_instance
1976        if not plt:
1977            vedo.logger.error("No vedo plotter found, cannot make video.")
1978            return self
1979        n = int(self.fps * self.duration)
1980
1981        cams = []
1982        for cm in cameras:
1983            cams.append(utils.camera_from_dict(cm))
1984        nc = len(cams)
1985
1986        plt.show(resetcam=resetcam, interactive=False)
1987
1988        if nc:
1989            for i in range(n):
1990                plt.move_camera(cams, i / n)
1991                plt.show()
1992                self.add_frame()
1993
1994        else:  ########################################
1995
1996            for i in range(n):
1997                plt.camera.Elevation((elevation[1] - elevation[0]) / n)
1998                plt.camera.Azimuth((azimuth[1] - azimuth[0]) / n)
1999                plt.show()
2000                self.add_frame()
2001
2002        return self
2003
2004    def close(self) -> None:
2005        """
2006        Render the video and write it to file.
2007        """
2008        if self.duration:
2009            self.fps = int(len(self.frames) / float(self.duration) + 0.5)
2010            colors.printc("recalculated fps:", self.fps, c="m", end="")
2011        else:
2012            self.fps = int(self.fps)
2013
2014        ########################################
2015        if self.backend == "ffmpeg":
2016            out = os.system(
2017                self.command
2018                + " "
2019                + str(self.fps)
2020                + " -i "
2021                + f"'{self.tmp_dir.name}'"
2022                + os.sep
2023                + "%01d.png "
2024                + self.options
2025                + " "
2026                + f"'{self.name}'"
2027            )
2028            if out:
2029                vedo.logger.error(f":noentry: backend {self.backend} returning error: {out}")
2030            else:
2031                colors.printc(f":save: saved to {self.name}", c="m")
2032
2033        ########################################
2034        elif "cv" in self.backend:
2035            try:
2036                import cv2
2037            except ImportError:
2038                vedo.logger.error("opencv is not installed")
2039                return
2040
2041            cap = cv2.VideoCapture(os.path.join(self.tmp_dir.name, "%1d.png"))
2042            fourcc = cv2.VideoWriter_fourcc(*"mp4v")
2043            if vedo.plotter_instance:
2044                w, h = vedo.plotter_instance.window.GetSize()
2045                writer = cv2.VideoWriter(self.name, fourcc, self.fps, (w, h), True)
2046            else:
2047                vedo.logger.error("No vedo plotter found, cannot make video.")
2048                return
2049
2050            while True:
2051                ret, frame = cap.read()
2052                if not ret:
2053                    break
2054                writer.write(frame)
2055
2056            cap.release()
2057            writer.release()
2058
2059        ########################################
2060        elif "imageio" in self.backend:
2061            try:
2062                import imageio
2063            except ImportError:
2064                vedo.logger.error("Please install imageio with:\n pip install imageio[ffmpeg]")
2065                return
2066
2067            if self.name.endswith(".mp4"):
2068                writer = imageio.get_writer(self.name, fps=self.fps)
2069            elif self.name.endswith(".gif"):
2070                writer = imageio.get_writer(self.name, mode="I", duration=1 / self.fps)
2071            elif self.name.endswith(".webm"):
2072                writer = imageio.get_writer(self.name, format="webm", fps=self.fps)
2073            else:
2074                vedo.logger.error(f"Unknown format of {self.name}.")
2075                return
2076
2077            for f in utils.humansort(self.frames):
2078                image = imageio.v3.imread(f)
2079                try:
2080                    writer.append_data(image)
2081                except TypeError:
2082                    vedo.logger.error(f"Could not append data to video {self.name}")
2083                    vedo.logger.error("Please install imageio with: pip install imageio[ffmpeg]")
2084                    break
2085            try:
2086                writer.close()
2087                colors.printc(f"... saved as {self.name}", c="m")
2088            except:
2089                colors.printc(f":noentry: Could not save video {self.name}", c="r")
2090
2091        # finalize cleanup
2092        self.tmp_dir.cleanup()
2093
2094    def split_frames(self, output_dir="video_frames", prefix="frame_", format="png") -> None:
2095        """Split an existing video file into frames."""
2096        try:
2097            import imageio
2098        except ImportError:
2099            vedo.logger.error("\nPlease install imageio with:\n pip install imageio")
2100            return
2101
2102        # Create the output directory if it doesn't exist
2103        if not os.path.exists(output_dir):
2104            os.makedirs(output_dir)
2105
2106        # Create a reader object to read the video
2107        reader = imageio.get_reader(self.name)
2108
2109        # Loop through each frame of the video and save it as image
2110        print()
2111        for i, frame in utils.progressbar(
2112            enumerate(reader), title=f"writing {format} frames", c="m", width=20
2113        ):
2114            output_file = os.path.join(output_dir, f"{prefix}{str(i).zfill(5)}.{format}")
2115            imageio.imwrite(output_file, frame, format=format)

Generate a video from a rendering window.

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

Add frame to current video.

def pause(self, pause=0) -> Video:
1950    def pause(self, pause=0) -> "Video":
1951        """Insert a `pause`, in seconds."""
1952        fr = self.frames[-1]
1953        n = int(self.fps * pause)
1954        for _ in range(n):
1955            fr2 = self.get_filename(str(len(self.frames)) + ".png")
1956            self.frames.append(fr2)
1957            os.system("cp -f %s %s" % (fr, fr2))
1958        return self

Insert a pause, in seconds.

def action( self, elevation=(0, 80), azimuth=(0, 359), cameras=(), resetcam=False) -> Video:
1960    def action(self, elevation=(0, 80), azimuth=(0, 359), cameras=(), resetcam=False) -> "Video":
1961        """
1962        Automatic shooting of a static scene by specifying rotation and elevation ranges.
1963
1964        Arguments:
1965            elevation : list
1966                initial and final elevation angles
1967            azimuth_range : list
1968                initial and final azimuth angles
1969            cameras : list
1970                list of cameras to go through, each camera can be dictionary or a vtkCamera
1971        """
1972        if not self.duration:
1973            self.duration = 5
1974
1975        plt = vedo.plotter_instance
1976        if not plt:
1977            vedo.logger.error("No vedo plotter found, cannot make video.")
1978            return self
1979        n = int(self.fps * self.duration)
1980
1981        cams = []
1982        for cm in cameras:
1983            cams.append(utils.camera_from_dict(cm))
1984        nc = len(cams)
1985
1986        plt.show(resetcam=resetcam, interactive=False)
1987
1988        if nc:
1989            for i in range(n):
1990                plt.move_camera(cams, i / n)
1991                plt.show()
1992                self.add_frame()
1993
1994        else:  ########################################
1995
1996            for i in range(n):
1997                plt.camera.Elevation((elevation[1] - elevation[0]) / n)
1998                plt.camera.Azimuth((azimuth[1] - azimuth[0]) / n)
1999                plt.show()
2000                self.add_frame()
2001
2002        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:
2004    def close(self) -> None:
2005        """
2006        Render the video and write it to file.
2007        """
2008        if self.duration:
2009            self.fps = int(len(self.frames) / float(self.duration) + 0.5)
2010            colors.printc("recalculated fps:", self.fps, c="m", end="")
2011        else:
2012            self.fps = int(self.fps)
2013
2014        ########################################
2015        if self.backend == "ffmpeg":
2016            out = os.system(
2017                self.command
2018                + " "
2019                + str(self.fps)
2020                + " -i "
2021                + f"'{self.tmp_dir.name}'"
2022                + os.sep
2023                + "%01d.png "
2024                + self.options
2025                + " "
2026                + f"'{self.name}'"
2027            )
2028            if out:
2029                vedo.logger.error(f":noentry: backend {self.backend} returning error: {out}")
2030            else:
2031                colors.printc(f":save: saved to {self.name}", c="m")
2032
2033        ########################################
2034        elif "cv" in self.backend:
2035            try:
2036                import cv2
2037            except ImportError:
2038                vedo.logger.error("opencv is not installed")
2039                return
2040
2041            cap = cv2.VideoCapture(os.path.join(self.tmp_dir.name, "%1d.png"))
2042            fourcc = cv2.VideoWriter_fourcc(*"mp4v")
2043            if vedo.plotter_instance:
2044                w, h = vedo.plotter_instance.window.GetSize()
2045                writer = cv2.VideoWriter(self.name, fourcc, self.fps, (w, h), True)
2046            else:
2047                vedo.logger.error("No vedo plotter found, cannot make video.")
2048                return
2049
2050            while True:
2051                ret, frame = cap.read()
2052                if not ret:
2053                    break
2054                writer.write(frame)
2055
2056            cap.release()
2057            writer.release()
2058
2059        ########################################
2060        elif "imageio" in self.backend:
2061            try:
2062                import imageio
2063            except ImportError:
2064                vedo.logger.error("Please install imageio with:\n pip install imageio[ffmpeg]")
2065                return
2066
2067            if self.name.endswith(".mp4"):
2068                writer = imageio.get_writer(self.name, fps=self.fps)
2069            elif self.name.endswith(".gif"):
2070                writer = imageio.get_writer(self.name, mode="I", duration=1 / self.fps)
2071            elif self.name.endswith(".webm"):
2072                writer = imageio.get_writer(self.name, format="webm", fps=self.fps)
2073            else:
2074                vedo.logger.error(f"Unknown format of {self.name}.")
2075                return
2076
2077            for f in utils.humansort(self.frames):
2078                image = imageio.v3.imread(f)
2079                try:
2080                    writer.append_data(image)
2081                except TypeError:
2082                    vedo.logger.error(f"Could not append data to video {self.name}")
2083                    vedo.logger.error("Please install imageio with: pip install imageio[ffmpeg]")
2084                    break
2085            try:
2086                writer.close()
2087                colors.printc(f"... saved as {self.name}", c="m")
2088            except:
2089                colors.printc(f":noentry: Could not save video {self.name}", c="r")
2090
2091        # finalize cleanup
2092        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:
2094    def split_frames(self, output_dir="video_frames", prefix="frame_", format="png") -> None:
2095        """Split an existing video file into frames."""
2096        try:
2097            import imageio
2098        except ImportError:
2099            vedo.logger.error("\nPlease install imageio with:\n pip install imageio")
2100            return
2101
2102        # Create the output directory if it doesn't exist
2103        if not os.path.exists(output_dir):
2104            os.makedirs(output_dir)
2105
2106        # Create a reader object to read the video
2107        reader = imageio.get_reader(self.name)
2108
2109        # Loop through each frame of the video and save it as image
2110        print()
2111        for i, frame in utils.progressbar(
2112            enumerate(reader), title=f"writing {format} frames", c="m", width=20
2113        ):
2114            output_file = os.path.join(output_dir, f"{prefix}{str(i).zfill(5)}.{format}")
2115            imageio.imwrite(output_file, frame, format=format)

Split an existing video file into frames.