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

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

def download(url: str, force=False, verbose=True) -> str:
452def download(url: str, force=False, verbose=True) -> str:
453    """
454    Retrieve a file from a URL, save it locally and return its path.
455    Use `force=True` to force a reload and discard cached copies.
456    """
457    if not url.startswith("https://"):
458        # assume it's a file so no need to download
459        return url
460    url = url.replace("www.dropbox", "dl.dropbox")
461
462    if "github.com" in url:
463        url = url.replace("/blob/", "/raw/")
464
465    basename = os.path.basename(url)
466
467    if "?" in basename:
468        basename = basename.split("?")[0]
469
470    home_directory = os.path.expanduser("~")
471    cachedir = os.path.join(home_directory, settings.cache_directory, "vedo")
472    fname = os.path.join(cachedir, basename)
473    # Create the directory if it does not exist
474    if not os.path.exists(cachedir):
475        os.makedirs(cachedir)
476
477    if not force and os.path.exists(fname):
478        if verbose:
479            colors.printc("reusing cached file:", fname)
480        return fname
481
482    try:
483        from urllib.request import urlopen, Request
484        req = Request(url, headers={"User-Agent": "Mozilla/5.0"})
485        if verbose:
486            colors.printc("reading", basename, "from", url.split("/")[2][:40], "...", end="")
487
488    except ImportError:
489        import urllib2
490        import contextlib
491        urlopen = lambda url_: contextlib.closing(urllib2.urlopen(url_))
492        req = url
493        if verbose:
494            colors.printc("reading", basename, "from", url.split("/")[2][:40], "...", end="")
495
496    with urlopen(req) as response, open(fname, "wb") as output:
497        output.write(response.read())
498
499    if verbose:
500        colors.printc(" done.")
501    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:
581def gunzip(filename: str) -> str:
582    """Unzip a `.gz` file to a temporary file and returns its path."""
583    if not filename.endswith(".gz"):
584        # colors.printc("gunzip() error: file must end with .gz", c='r')
585        return filename
586
587    import gzip
588
589    tmp_file = NamedTemporaryFile(delete=False)
590    tmp_file.name = os.path.join(
591        os.path.dirname(tmp_file.name), os.path.basename(filename).replace(".gz", "")
592    )
593    inF = gzip.open(filename, "rb")
594    with open(tmp_file.name, "wb") as outF:
595        outF.write(inF.read())
596    inF.close()
597    return tmp_file.name

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

def loadStructuredPoints(filename: Union[str, os.PathLike], as_points=True):
616def loadStructuredPoints(filename: Union[str, os.PathLike], as_points=True):
617    """
618    Load and return a `vtkStructuredPoints` object from file.
619    
620    If `as_points` is True, return a `Points` object
621    instead of a `vtkStructuredPoints`.
622    """
623    filename = str(filename)
624    reader = vtki.new("StructuredPointsReader")
625    reader.SetFileName(filename)
626    reader.Update()
627    if as_points:
628        v2p = vtki.new("ImageToPoints")
629        v2p.SetInputData(reader.GetOutput())
630        v2p.Update()
631        pts = Points(v2p.GetOutput())
632        return pts
633    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: Union[str, os.PathLike]):
636def loadStructuredGrid(filename: Union[str, os.PathLike]):
637    """Load and return a `vtkStructuredGrid` object from file."""
638    filename = str(filename)
639    if filename.endswith(".vts"):
640        reader = vtki.new("XMLStructuredGridReader")
641    else:
642        reader = vtki.new("StructuredGridReader")
643    reader.SetFileName(filename)
644    reader.Update()
645    return reader.GetOutput()

Load and return a vtkStructuredGrid object from file.

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

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

def export_window( fileoutput: Union[str, os.PathLike], binary=False, plt=None) -> vedo.plotter.Plotter:
1355def export_window(fileoutput: Union[str, os.PathLike], binary=False, plt=None) -> "vedo.Plotter":
1356    """
1357    Exporter which writes out the rendered scene into an HTML, X3D or Numpy file.
1358
1359    Example:
1360        - [export_x3d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/export_x3d.py)
1361
1362        Check out the HTML generated webpage [here](https://vedo.embl.es/examples/embryo.html).
1363
1364        <img src='https://user-images.githubusercontent.com/32848391/57160341-c6ffbd80-6de8-11e9-95ff-7215ce642bc5.jpg' width="600"/>
1365
1366    .. note::
1367        the rendering window can also be exported to `numpy` file `scene.npz`
1368        by pressing `E` key at any moment during visualization.
1369    """
1370    fileoutput = str(fileoutput)
1371    if plt is None:
1372        plt = vedo.plotter_instance
1373
1374    fr = fileoutput.lower()
1375    ####################################################################
1376    if fr.endswith(".npy") or fr.endswith(".npz"):
1377        _export_npy(plt, fileoutput)
1378
1379    ####################################################################
1380    elif fr.endswith(".x3d"):
1381        # obj = plt.get_actors()
1382        # if plt.axes_instances:
1383        #     obj.append(plt.axes_instances[0])
1384
1385        # for a in obj:
1386        #     if isinstance(a, Assembly):
1387        #         plt.remove(a)
1388        #         plt.add(a.unpack())
1389
1390        plt.render()
1391
1392        exporter = vtki.new("X3DExporter")
1393        exporter.SetBinary(binary)
1394        exporter.FastestOff()
1395        exporter.SetInput(plt.window)
1396        exporter.SetFileName(fileoutput)
1397        # exporter.WriteToOutputStringOn()
1398        exporter.Update()
1399        exporter.Write()
1400
1401        wsize = plt.window.GetSize()
1402        x3d_html = _x3d_html_template.replace("~fileoutput", fileoutput)
1403        x3d_html = x3d_html.replace("~width",  str(wsize[0]))
1404        x3d_html = x3d_html.replace("~height", str(wsize[1]))
1405        with open(fileoutput.replace(".x3d", ".html"), "w", encoding="UTF-8") as outF:
1406            outF.write(x3d_html)
1407
1408    ####################################################################
1409    elif fr.endswith(".html"):
1410        savebk = vedo.notebook_backend
1411        vedo.notebook_backend = "k3d"
1412        vedo.settings.default_backend = "k3d"
1413        # acts = plt.get_actors()
1414        plt = vedo.backends.get_notebook_backend(plt.objects)
1415
1416        with open(fileoutput, "w", encoding="UTF-8") as fp:
1417            fp.write(plt.get_snapshot())
1418
1419        vedo.notebook_backend = savebk
1420        vedo.settings.default_backend = savebk
1421
1422    else:
1423        vedo.logger.error(f"export extension {fr.split('.')[-1]} is not supported")
1424
1425    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: Union[str, os.PathLike]) -> Optional[vedo.plotter.Plotter]:
1697def import_window(fileinput: Union[str, os.PathLike]) -> Union["vedo.Plotter", None]:
1698    """
1699    Import a whole scene from a Numpy NPZ file.
1700
1701    Returns:
1702        `vedo.Plotter` instance
1703    """
1704    fileinput = str(fileinput)
1705
1706    if fileinput.endswith(".npy") or fileinput.endswith(".npz"):
1707        return _import_npy(fileinput)
1708    
1709    # elif ".obj" in fileinput.lower():
1710    #     meshes = load_obj(fileinput, mtl_file, texture_path)
1711    #     plt = vedo.Plotter()
1712    #     plt.add(meshes)
1713    #     return plt
1714
1715    # elif fileinput.endswith(".h5") or fileinput.endswith(".hdf5"):
1716    #     return _import_hdf5(fileinput) # in store/file_io_HDF5.py
1717
1718    return None

Import a whole scene from a Numpy NPZ file.

Returns:

vedo.Plotter instance

def load_obj( fileinput: Union[str, os.PathLike], mtl_file=None, texture_path=None) -> List[vedo.mesh.Mesh]:
1721def load_obj(fileinput: Union[str, os.PathLike], mtl_file=None, texture_path=None) -> List[Mesh]:
1722    """
1723    Import a set of meshes from a OBJ wavefront file.
1724
1725    Arguments:
1726        mtl_file : (str)
1727            MTL file for OBJ wavefront files
1728        texture_path : (str)
1729            path of the texture files directory
1730
1731    Returns:
1732        `list(Mesh)`
1733    """
1734    fileinput = str(fileinput)
1735
1736    window = vtki.vtkRenderWindow()
1737    window.SetOffScreenRendering(1)
1738    renderer = vtki.vtkRenderer()
1739    window.AddRenderer(renderer)
1740
1741    importer = vtki.new("OBJImporter")
1742    importer.SetFileName(fileinput)
1743    if mtl_file is None:
1744        mtl_file = fileinput.replace(".obj", ".mtl").replace(".OBJ", ".MTL")
1745    if os.path.isfile(mtl_file):
1746        importer.SetFileNameMTL(mtl_file)
1747    if texture_path is None:
1748        texture_path = fileinput.replace(".obj", ".txt").replace(".OBJ", ".TXT")
1749    # since the texture_path may be a directory which contains textures
1750    if os.path.exists(texture_path):
1751        importer.SetTexturePath(texture_path)
1752    importer.SetRenderWindow(window)
1753    importer.Update()
1754
1755    actors = renderer.GetActors()
1756    actors.InitTraversal()
1757    objs = []
1758    for _ in range(actors.GetNumberOfItems()):
1759        vactor = actors.GetNextActor()
1760        msh = Mesh(vactor)
1761        msh.name = "OBJMesh"
1762        tx = vactor.GetTexture()
1763        if tx:
1764            msh.texture(tx)
1765        objs.append(msh)
1766    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]:
1770def screenshot(filename="screenshot.png", scale=1, asarray=False) -> Union["vedo.Plotter", np.ndarray, None]:
1771    """
1772    Save a screenshot of the current rendering window.
1773
1774    Alternatively, press key `Shift-S` in the rendering window to save a screenshot.
1775    You can also use keyword `screenshot` in `show(..., screenshot="pic.png")`.
1776
1777    Arguments:
1778        scale : (int)
1779            Set image magnification as an integer multiplicative factor.
1780            E.g. setting a magnification of 2 produces an image twice as large,
1781            but 10x slower to generate.
1782        asarray : (bool)
1783            Return a numpy array of the image
1784    """
1785    filename = str(filename)
1786    # print("calling screenshot", filename, scale, asarray)
1787
1788    if not vedo.plotter_instance or not vedo.plotter_instance.window:
1789        # vedo.logger.error("in screenshot(), rendering window is not present, skip.")
1790        return vedo.plotter_instance  ##########
1791    
1792    if vedo.plotter_instance.renderer:
1793        vedo.plotter_instance.renderer.ResetCameraClippingRange()
1794
1795    if asarray and scale == 1 and not vedo.plotter_instance.offscreen:
1796        nx, ny = vedo.plotter_instance.window.GetSize()
1797        arr = vtki.vtkUnsignedCharArray()
1798        vedo.plotter_instance.window.GetRGBACharPixelData(0, 0, nx-1, ny-1, 0, arr)
1799        narr = vedo.vtk2numpy(arr).T[:3].T.reshape([ny, nx, 3])
1800        narr = np.flip(narr, axis=0)
1801        return narr  ##########
1802
1803    ###########################
1804    if filename.endswith(".pdf"):
1805        writer = vtki.new("GL2PSExporter")
1806        writer.SetRenderWindow(vedo.plotter_instance.window)
1807        writer.Write3DPropsAsRasterImageOff()
1808        writer.SilentOn()
1809        writer.SetSortToBSP()
1810        writer.SetFileFormatToPDF()
1811        writer.SetFilePrefix(filename.replace(".pdf", ""))
1812        writer.Write()
1813        return vedo.plotter_instance  ##########
1814
1815    elif filename.endswith(".svg"):
1816        writer = vtki.new("GL2PSExporter")
1817        writer.SetRenderWindow(vedo.plotter_instance.window)
1818        writer.Write3DPropsAsRasterImageOff()
1819        writer.SilentOn()
1820        writer.SetSortToBSP()
1821        writer.SetFileFormatToSVG()
1822        writer.SetFilePrefix(filename.replace(".svg", ""))
1823        writer.Write()
1824        return vedo.plotter_instance  ##########
1825
1826    elif filename.endswith(".eps"):
1827        writer = vtki.new("GL2PSExporter")
1828        writer.SetRenderWindow(vedo.plotter_instance.window)
1829        writer.Write3DPropsAsRasterImageOff()
1830        writer.SilentOn()
1831        writer.SetSortToBSP()
1832        writer.SetFileFormatToEPS()
1833        writer.SetFilePrefix(filename.replace(".eps", ""))
1834        writer.Write()
1835        return vedo.plotter_instance  ##########
1836
1837    if settings.screeshot_large_image:
1838        w2if = vtki.new("RenderLargeImage")
1839        w2if.SetInput(vedo.plotter_instance.renderer)
1840        w2if.SetMagnification(scale)
1841    else:
1842        w2if = vtki.new("WindowToImageFilter")
1843        w2if.SetInput(vedo.plotter_instance.window)
1844        if hasattr(w2if, "SetScale"):
1845            w2if.SetScale(int(scale), int(scale))
1846        if settings.screenshot_transparent_background:
1847            w2if.SetInputBufferTypeToRGBA()
1848        w2if.ReadFrontBufferOff()  # read from the back buffer
1849    w2if.Update()
1850
1851    if asarray:
1852        pd = w2if.GetOutput().GetPointData()
1853        npdata = utils.vtk2numpy(pd.GetArray("ImageScalars"))
1854        npdata = npdata[:, [0, 1, 2]]
1855        ydim, xdim, _ = w2if.GetOutput().GetDimensions()
1856        npdata = npdata.reshape([xdim, ydim, -1])
1857        npdata = np.flip(npdata, axis=0)
1858        return npdata ###########################
1859
1860    # elif settings.default_backend == "2d" and vedo.notebook_plotter:
1861    #     vedo.notebook_plotter.save(filename) # a PIL Image
1862    #     return vedo.notebook_plotter  ##########
1863
1864    if filename.lower().endswith(".png"):
1865        writer = vtki.new("PNGWriter")
1866        writer.SetFileName(filename)
1867        writer.SetInputData(w2if.GetOutput())
1868        writer.Write()
1869    elif filename.lower().endswith(".jpg") or filename.lower().endswith(".jpeg"):
1870        writer = vtki.new("JPEGWriter")
1871        writer.SetFileName(filename)
1872        writer.SetInputData(w2if.GetOutput())
1873        writer.Write()
1874    else:  # add .png
1875        writer = vtki.new("PNGWriter")
1876        writer.SetFileName(filename + ".png")
1877        writer.SetInputData(w2if.GetOutput())
1878        writer.Write()
1879    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:
1882def ask(*question, **kwarg) -> str:
1883    """
1884    Ask a question from command line. Return the answer as a string.
1885    See function `colors.printc()` for the description of the keyword options.
1886
1887    Arguments:
1888        options : (list)
1889            a python list of possible answers to choose from.
1890        default : (str)
1891            the default answer when just hitting return.
1892
1893    Example:
1894    ```python
1895    import vedo
1896    res = vedo.ask("Continue?", options=['Y','n'], default='Y', c='g')
1897    print(res)
1898    ```
1899    """
1900    kwarg.update({"end": " "})
1901    if "invert" not in kwarg:
1902        kwarg.update({"invert": True})
1903    if "box" in kwarg:
1904        kwarg.update({"box": ""})
1905
1906    options = kwarg.pop("options", [])
1907    default = kwarg.pop("default", "")
1908    if options:
1909        opt = "["
1910        for o in options:
1911            opt += o + "/"
1912        opt = opt[:-1] + "]"
1913        colors.printc(*question, opt, **kwarg)
1914    else:
1915        colors.printc(*question, **kwarg)
1916
1917    try:
1918        resp = input()
1919    except Exception:
1920        resp = ""
1921        return resp
1922
1923    if options:
1924        if resp not in options:
1925            if default and str(repr(resp)) == "''":
1926                return default
1927            colors.printc("Please choose one option in:", opt, italic=True, bold=False)
1928            kwarg["options"] = options
1929            return ask(*question, **kwarg)  # ask again
1930    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 = ask("Continue?", options=['Y','n'], default='Y', c='g')
print(res)
class Video:
1934class Video:
1935    """
1936    Generate a video from a rendering window.
1937    """
1938
1939    def __init__(self, name="movie.mp4", duration=None, fps=24, scale=1, backend="imageio"):
1940        """
1941        Class to generate a video from the specified rendering window.
1942        Program `ffmpeg` is used to create video from each generated frame.
1943
1944        Arguments:
1945            name : (Union[str, os.PathLike])
1946                name of the output file.
1947            duration : (float)
1948                set the total `duration` of the video and recalculates `fps` accordingly.
1949            fps : (int)
1950                set the number of frames per second.
1951            scale : (int)
1952                set the image magnification as an integer multiplicative factor.
1953            backend : (str)
1954                the backend engine to be used `['imageio', 'ffmpeg', 'cv']`
1955
1956        Examples:
1957            - [make_video.py](https://github.com/marcomusy/vedo/tree/master/examples/other/make_video.py)
1958
1959            ![](https://user-images.githubusercontent.com/32848391/50739007-2bfc2b80-11da-11e9-97e6-620a3541a6fa.jpg)
1960        """
1961        self.name = str(name)
1962        self.duration = duration
1963        self.backend = backend
1964        self.fps = float(fps)
1965        self.command = "ffmpeg -loglevel panic -y -r"
1966        self.options = "-b:v 8000k"
1967        self.scale = scale
1968
1969        self.frames = []
1970        self.tmp_dir = TemporaryDirectory()
1971        self.get_filename = lambda x: os.path.join(self.tmp_dir.name, x)
1972        colors.printc(":video:  Video file", self.name, "is open... ", c="m", end="")
1973
1974    def add_frame(self) -> "Video":
1975        """Add frame to current video."""
1976        fr = self.get_filename(str(len(self.frames)) + ".png")
1977        screenshot(fr, scale=self.scale)
1978        self.frames.append(fr)
1979        return self
1980
1981    def pause(self, pause=0) -> "Video":
1982        """Insert a `pause`, in seconds."""
1983        fr = self.frames[-1]
1984        n = int(self.fps * pause)
1985        for _ in range(n):
1986            fr2 = self.get_filename(str(len(self.frames)) + ".png")
1987            self.frames.append(fr2)
1988            os.system("cp -f %s %s" % (fr, fr2))
1989        return self
1990
1991    def action(self, elevation=(0, 80), azimuth=(0, 359), cameras=(), resetcam=False) -> "Video":
1992        """
1993        Automatic shooting of a static scene by specifying rotation and elevation ranges.
1994
1995        Arguments:
1996            elevation : list
1997                initial and final elevation angles
1998            azimuth_range : list
1999                initial and final azimuth angles
2000            cameras : list
2001                list of cameras to go through, each camera can be dictionary or a vtkCamera
2002        """
2003        if not self.duration:
2004            self.duration = 5
2005
2006        plt = vedo.plotter_instance
2007        if not plt:
2008            vedo.logger.error("No vedo plotter found, cannot make video.")
2009            return self
2010        n = int(self.fps * self.duration)
2011
2012        cams = []
2013        for cm in cameras:
2014            cams.append(utils.camera_from_dict(cm))
2015        nc = len(cams)
2016
2017        plt.show(resetcam=resetcam, interactive=False)
2018
2019        if nc:
2020            for i in range(n):
2021                plt.move_camera(cams, i / (n-1))
2022                plt.render()
2023                self.add_frame()
2024
2025        else:  ########################################
2026
2027            for i in range(n):
2028                plt.camera.Elevation((elevation[1] - elevation[0]) / n)
2029                plt.camera.Azimuth((azimuth[1] - azimuth[0]) / n)
2030                plt.render()
2031                self.add_frame()
2032
2033        return self
2034
2035    def close(self) -> None:
2036        """
2037        Render the video and write it to file.
2038        """
2039        if self.duration:
2040            self.fps = int(len(self.frames) / float(self.duration) + 0.5)
2041            colors.printc("recalculated fps:", self.fps, c="m", end="")
2042        else:
2043            self.fps = int(self.fps)
2044
2045        ########################################
2046        if self.backend == "ffmpeg":
2047            out = os.system(
2048                self.command
2049                + " "
2050                + str(self.fps)
2051                + " -i "
2052                + f"'{self.tmp_dir.name}'"
2053                + os.sep
2054                + "%01d.png "
2055                + self.options
2056                + " "
2057                + f"'{self.name}'"
2058            )
2059            if out:
2060                vedo.logger.error(f":noentry: backend {self.backend} returning error: {out}")
2061            else:
2062                colors.printc(f":save: saved to {self.name}", c="m")
2063
2064        ########################################
2065        elif "cv" in self.backend:
2066            try:
2067                import cv2
2068            except ImportError:
2069                vedo.logger.error("opencv is not installed")
2070                return
2071
2072            cap = cv2.VideoCapture(os.path.join(self.tmp_dir.name, "%1d.png"))
2073            fourcc = cv2.VideoWriter_fourcc(*"mp4v")
2074            if vedo.plotter_instance:
2075                w, h = vedo.plotter_instance.window.GetSize()
2076                writer = cv2.VideoWriter(self.name, fourcc, self.fps, (w, h), True)
2077            else:
2078                vedo.logger.error("No vedo plotter found, cannot make video.")
2079                return
2080
2081            while True:
2082                ret, frame = cap.read()
2083                if not ret:
2084                    break
2085                writer.write(frame)
2086
2087            cap.release()
2088            writer.release()
2089
2090        ########################################
2091        elif "imageio" in self.backend:
2092            try:
2093                import imageio
2094            except ImportError:
2095                vedo.logger.error("Please install imageio with:\n pip install imageio[ffmpeg]")
2096                return
2097
2098            if self.name.endswith(".mp4"):
2099                writer = imageio.get_writer(self.name, fps=self.fps)
2100            elif self.name.endswith(".gif"):
2101                writer = imageio.get_writer(self.name, mode="I", duration=1 / self.fps)
2102            elif self.name.endswith(".webm"):
2103                writer = imageio.get_writer(self.name, format="webm", fps=self.fps)
2104            else:
2105                vedo.logger.error(f"Unknown format of {self.name}.")
2106                return
2107
2108            for f in utils.humansort(self.frames):
2109                image = imageio.v3.imread(f)
2110                try:
2111                    writer.append_data(image)
2112                except TypeError:
2113                    vedo.logger.error(f"Could not append data to video {self.name}")
2114                    vedo.logger.error("Please install imageio with: pip install imageio[ffmpeg]")
2115                    break
2116            try:
2117                writer.close()
2118                colors.printc(f"... saved as {self.name}", c="m")
2119            except:
2120                colors.printc(f":noentry: Could not save video {self.name}", c="r")
2121
2122        # finalize cleanup
2123        self.tmp_dir.cleanup()
2124
2125    def split_frames(self, output_dir="video_frames", prefix="frame_", format="png") -> None:
2126        """Split an existing video file into frames."""
2127        try:
2128            import imageio
2129        except ImportError:
2130            vedo.logger.error("\nPlease install imageio with:\n pip install imageio")
2131            return
2132
2133        # Create the output directory if it doesn't exist
2134        if not os.path.exists(output_dir):
2135            os.makedirs(output_dir)
2136
2137        # Create a reader object to read the video
2138        reader = imageio.get_reader(self.name)
2139
2140        # Loop through each frame of the video and save it as image
2141        print()
2142        for i, frame in utils.progressbar(
2143            enumerate(reader), title=f"writing {format} frames", c="m", width=20
2144        ):
2145            output_file = os.path.join(output_dir, f"{prefix}{str(i).zfill(5)}.{format}")
2146            imageio.imwrite(output_file, frame, format=format)

Generate a video from a rendering window.

Video(name='movie.mp4', duration=None, fps=24, scale=1, backend='imageio')
1939    def __init__(self, name="movie.mp4", duration=None, fps=24, scale=1, backend="imageio"):
1940        """
1941        Class to generate a video from the specified rendering window.
1942        Program `ffmpeg` is used to create video from each generated frame.
1943
1944        Arguments:
1945            name : (Union[str, os.PathLike])
1946                name of the output file.
1947            duration : (float)
1948                set the total `duration` of the video and recalculates `fps` accordingly.
1949            fps : (int)
1950                set the number of frames per second.
1951            scale : (int)
1952                set the image magnification as an integer multiplicative factor.
1953            backend : (str)
1954                the backend engine to be used `['imageio', 'ffmpeg', 'cv']`
1955
1956        Examples:
1957            - [make_video.py](https://github.com/marcomusy/vedo/tree/master/examples/other/make_video.py)
1958
1959            ![](https://user-images.githubusercontent.com/32848391/50739007-2bfc2b80-11da-11e9-97e6-620a3541a6fa.jpg)
1960        """
1961        self.name = str(name)
1962        self.duration = duration
1963        self.backend = backend
1964        self.fps = float(fps)
1965        self.command = "ffmpeg -loglevel panic -y -r"
1966        self.options = "-b:v 8000k"
1967        self.scale = scale
1968
1969        self.frames = []
1970        self.tmp_dir = TemporaryDirectory()
1971        self.get_filename = lambda x: os.path.join(self.tmp_dir.name, x)
1972        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 : (Union[str, os.PathLike]) name of the output file.
  • duration : (float) set the total duration of the video and recalculates fps accordingly.
  • fps : (int) set the number of frames per second.
  • scale : (int) set the image magnification as an integer multiplicative factor.
  • backend : (str) the backend engine to be used ['imageio', 'ffmpeg', 'cv']
Examples:

def add_frame(self) -> Video:
1974    def add_frame(self) -> "Video":
1975        """Add frame to current video."""
1976        fr = self.get_filename(str(len(self.frames)) + ".png")
1977        screenshot(fr, scale=self.scale)
1978        self.frames.append(fr)
1979        return self

Add frame to current video.

def pause(self, pause=0) -> Video:
1981    def pause(self, pause=0) -> "Video":
1982        """Insert a `pause`, in seconds."""
1983        fr = self.frames[-1]
1984        n = int(self.fps * pause)
1985        for _ in range(n):
1986            fr2 = self.get_filename(str(len(self.frames)) + ".png")
1987            self.frames.append(fr2)
1988            os.system("cp -f %s %s" % (fr, fr2))
1989        return self

Insert a pause, in seconds.

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

Split an existing video file into frames.