vedo.file_io

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

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

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

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

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

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

def loadStructuredPoints(filename, as_points=True):
610def loadStructuredPoints(filename, as_points=True):
611    """
612    Load and return a `vtkStructuredPoints` object from file.
613    
614    If `as_points` is True, return a `Points` object
615    instead of a `vtkStructuredPoints`.
616    """
617    reader = vtk.new("StructuredPointsReader")
618    reader.SetFileName(filename)
619    reader.Update()
620    if as_points:
621        v2p = vtk.new("ImageToPoints")
622        v2p.SetInputData(reader.GetOutput())
623        v2p.Update()
624        pts = Points(v2p.GetOutput())
625        return pts
626    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):
629def loadStructuredGrid(filename):
630    """Load and return a `vtkStructuredGrid` object from file."""
631    if filename.endswith(".vts"):
632        reader = vtk.new("XMLStructuredGridReader")
633    else:
634        reader = vtk.new("StructuredGridReader")
635    reader.SetFileName(filename)
636    reader.Update()
637    return reader.GetOutput()

Load and return a vtkStructuredGrid object from file.

def loadRectilinearGrid(filename):
639def loadRectilinearGrid(filename):
640    """Load and return a `vtkRectilinearGrid` object from file."""
641    if filename.endswith(".vtr"):
642        reader = vtk.new("XMLRectilinearGridReader")
643    else:
644        reader = vtk.new("RectilinearGridReader")
645    reader.SetFileName(filename)
646    reader.Update()
647    return reader.GetOutput()

Load and return a vtkRectilinearGrid object from file.

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

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

def export_window(fileoutput, binary=False, plt=None):
1324def export_window(fileoutput, binary=False, plt=None):
1325    """
1326    Exporter which writes out the rendered scene into an HTML, X3D or Numpy file.
1327
1328    Example:
1329        - [export_x3d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/export_x3d.py)
1330
1331        Check out the HTML generated webpage [here](https://vedo.embl.es/examples/embryo.html).
1332
1333        <img src='https://user-images.githubusercontent.com/32848391/57160341-c6ffbd80-6de8-11e9-95ff-7215ce642bc5.jpg' width="600"/>
1334
1335    .. note::
1336        the rendering window can also be exported to `numpy` file `scene.npz`
1337        by pressing `E` key at any moment during visualization.
1338    """
1339    if plt is None:
1340        plt = vedo.plotter_instance
1341
1342    fr = fileoutput.lower()
1343    ####################################################################
1344    if fr.endswith(".npy") or fr.endswith(".npz"):
1345        _export_npy(plt, fileoutput)
1346
1347    ####################################################################
1348    elif fr.endswith(".x3d"):
1349        # obj = plt.get_actors()
1350        # if plt.axes_instances:
1351        #     obj.append(plt.axes_instances[0])
1352
1353        # for a in obj:
1354        #     if isinstance(a, Assembly):
1355        #         plt.remove(a)
1356        #         plt.add(a.unpack())
1357
1358        plt.render()
1359
1360        exporter = vtk.new("X3DExporter")
1361        exporter.SetBinary(binary)
1362        exporter.FastestOff()
1363        exporter.SetInput(plt.window)
1364        exporter.SetFileName(fileoutput)
1365        # exporter.WriteToOutputStringOn()
1366        exporter.Update()
1367        exporter.Write()
1368
1369        wsize = plt.window.GetSize()
1370        x3d_html = _x3d_html_template.replace("~fileoutput", fileoutput)
1371        x3d_html = x3d_html.replace("~width",  str(wsize[0]))
1372        x3d_html = x3d_html.replace("~height", str(wsize[1]))
1373        with open(fileoutput.replace(".x3d", ".html"), "w", encoding="UTF-8") as outF:
1374            outF.write(x3d_html)
1375
1376    ####################################################################
1377    elif fr.endswith(".html"):
1378        savebk = vedo.notebook_backend
1379        vedo.notebook_backend = "k3d"
1380        vedo.settings.default_backend = "k3d"
1381        # acts = plt.get_actors()
1382        plt = vedo.backends.get_notebook_backend(plt.objects)
1383
1384        with open(fileoutput, "w", encoding="UTF-8") as fp:
1385            fp.write(plt.get_snapshot())
1386
1387        vedo.notebook_backend = savebk
1388        vedo.settings.default_backend = savebk
1389
1390    else:
1391        vedo.logger.error(f"export extension {fr.split('.')[-1]} is not supported")
1392
1393    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, mtl_file=None, texture_path=None):
1665def import_window(fileinput, mtl_file=None, texture_path=None):
1666    """Import a whole scene from a Numpy NPZ or OBJ wavefront file.
1667
1668    Arguments:
1669        mtl_file : (str)
1670            MTL file for OBJ wavefront files
1671        texture_path : (str)
1672            path of the texture files directory
1673
1674    Returns:
1675        `vedo.Plotter` instance
1676    """
1677    if fileinput.endswith(".npy") or fileinput.endswith(".npz"):
1678        return _import_npy(fileinput)
1679    
1680    # elif fileinput.endswith(".h5") or fileinput.endswith(".hdf5"):
1681    #     return _import_hdf5(fileinput) # in store/file_io_HDF5.py
1682
1683    elif ".obj" in fileinput.lower():
1684
1685        window = vtk.vtkRenderWindow()
1686        window.SetOffScreenRendering(1)
1687        renderer = vtk.vtkRenderer()
1688        window.AddRenderer(renderer)
1689
1690        importer = vtk.new("OBJImporter")
1691        importer.SetFileName(fileinput)
1692        if mtl_file is not False:
1693            if mtl_file is None:
1694                mtl_file = fileinput.replace(".obj", ".mtl").replace(".OBJ", ".MTL")
1695            importer.SetFileNameMTL(mtl_file)
1696        if texture_path is not False:
1697            if texture_path is None:
1698                texture_path = fileinput.replace(".obj", ".txt").replace(".OBJ", ".TXT")
1699            importer.SetTexturePath(texture_path)
1700        importer.SetRenderWindow(window)
1701        importer.Update()
1702
1703        plt = vedo.Plotter()
1704        actors = renderer.GetActors()
1705        actors.InitTraversal()
1706        for _ in range(actors.GetNumberOfItems()):
1707            vactor = actors.GetNextActor()
1708            act = Mesh(vactor)
1709            act_tu = vactor.GetTexture()
1710            if act_tu:
1711                act.texture(act_tu)
1712            plt.actors.append(act)
1713        return plt
1714
1715    return None

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

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

vedo.Plotter instance

def screenshot(filename='screenshot.png', scale=1, asarray=False):
1719def screenshot(filename="screenshot.png", scale=1, asarray=False):
1720    """
1721    Save a screenshot of the current rendering window.
1722
1723    Alternatively, press key `Shift-S` in the rendering window to save a screenshot.
1724    You can also use keyword `screenshot` in `show(..., screenshot="pic.png")`.
1725
1726    Arguments:
1727        scale : (int)
1728            Set image magnification as an integer multiplicative factor.
1729            E.g. setting a magnification of 2 produces an image twice as large,
1730            but 10x slower to generate.
1731        asarray : (bool)
1732            Return a numpy array of the image
1733    """
1734    # print("calling screenshot", filename, scale, asarray)
1735
1736    if not vedo.plotter_instance or not vedo.plotter_instance.window:
1737        # vedo.logger.error("in screenshot(), rendering window is not present, skip.")
1738        return vedo.plotter_instance  ##########
1739
1740    if asarray and scale == 1 and not vedo.plotter_instance.offscreen:
1741        nx, ny = vedo.plotter_instance.window.GetSize()
1742        arr = vtk.vtkUnsignedCharArray()
1743        vedo.plotter_instance.window.GetRGBACharPixelData(0, 0, nx-1, ny-1, 0, arr)
1744        narr = vedo.vtk2numpy(arr).T[:3].T.reshape([ny, nx, 3])
1745        narr = np.flip(narr, axis=0)
1746        return narr  ##########
1747
1748    filename = str(filename)
1749
1750    if filename.endswith(".pdf"):
1751        writer = vtk.new("GL2PSExporter")
1752        writer.SetRenderWindow(vedo.plotter_instance.window)
1753        writer.Write3DPropsAsRasterImageOff()
1754        writer.SilentOn()
1755        writer.SetSortToBSP()
1756        writer.SetFileFormatToPDF()
1757        writer.SetFilePrefix(filename.replace(".pdf", ""))
1758        writer.Write()
1759        return vedo.plotter_instance  ##########
1760
1761    elif filename.endswith(".svg"):
1762        writer = vtk.new("GL2PSExporter")
1763        writer.SetRenderWindow(vedo.plotter_instance.window)
1764        writer.Write3DPropsAsRasterImageOff()
1765        writer.SilentOn()
1766        writer.SetSortToBSP()
1767        writer.SetFileFormatToSVG()
1768        writer.SetFilePrefix(filename.replace(".svg", ""))
1769        writer.Write()
1770        return vedo.plotter_instance  ##########
1771
1772    elif filename.endswith(".eps"):
1773        writer = vtk.new("GL2PSExporter")
1774        writer.SetRenderWindow(vedo.plotter_instance.window)
1775        writer.Write3DPropsAsRasterImageOff()
1776        writer.SilentOn()
1777        writer.SetSortToBSP()
1778        writer.SetFileFormatToEPS()
1779        writer.SetFilePrefix(filename.replace(".eps", ""))
1780        writer.Write()
1781        return vedo.plotter_instance  ##########
1782
1783    if settings.screeshot_large_image:
1784        w2if = vtk.new("RenderLargeImage")
1785        w2if.SetInput(vedo.plotter_instance.renderer)
1786        w2if.SetMagnification(scale)
1787    else:
1788        w2if = vtk.new("WindowToImageFilter")
1789        w2if.SetInput(vedo.plotter_instance.window)
1790        if hasattr(w2if, "SetScale"):
1791            w2if.SetScale(int(scale), int(scale))
1792        if settings.screenshot_transparent_background:
1793            w2if.SetInputBufferTypeToRGBA()
1794        w2if.ReadFrontBufferOff()  # read from the back buffer
1795    w2if.Update()
1796
1797    if asarray:
1798        pd = w2if.GetOutput().GetPointData()
1799        npdata = utils.vtk2numpy(pd.GetArray("ImageScalars"))
1800        npdata = npdata[:, [0, 1, 2]]
1801        ydim, xdim, _ = w2if.GetOutput().GetDimensions()
1802        npdata = npdata.reshape([xdim, ydim, -1])
1803        npdata = np.flip(npdata, axis=0)
1804        return npdata ###########################
1805
1806    # elif settings.default_backend == "2d" and vedo.notebook_plotter:
1807    #     vedo.notebook_plotter.save(filename) # a PIL Image
1808    #     return vedo.notebook_plotter  ##########
1809
1810    if filename.lower().endswith(".png"):
1811        writer = vtk.new("PNGWriter")
1812        writer.SetFileName(filename)
1813        writer.SetInputData(w2if.GetOutput())
1814        writer.Write()
1815    elif filename.lower().endswith(".jpg") or filename.lower().endswith(".jpeg"):
1816        writer = vtk.new("JPEGWriter")
1817        writer.SetFileName(filename)
1818        writer.SetInputData(w2if.GetOutput())
1819        writer.Write()
1820    else:  # add .png
1821        writer = vtk.new("PNGWriter")
1822        writer.SetFileName(filename + ".png")
1823        writer.SetInputData(w2if.GetOutput())
1824        writer.Write()
1825    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):
1828def ask(*question, **kwarg):
1829    """
1830    Ask a question from command line. Return the answer as a string.
1831    See function `colors.printc()` for the description of the keyword options.
1832
1833    Arguments:
1834        options : (list)
1835            a python list of possible answers to choose from.
1836        default : (str)
1837            the default answer when just hitting return.
1838
1839    Example:
1840    ```python
1841    import vedo
1842    res = vedo.ask("Continue?", options=['Y','n'], default='Y', c='g')
1843    print(res)
1844    ```
1845    """
1846    kwarg.update({"end": " "})
1847    if "invert" not in kwarg:
1848        kwarg.update({"invert": True})
1849    if "box" in kwarg:
1850        kwarg.update({"box": ""})
1851
1852    options = kwarg.pop("options", [])
1853    default = kwarg.pop("default", "")
1854    if options:
1855        opt = "["
1856        for o in options:
1857            opt += o + "/"
1858        opt = opt[:-1] + "]"
1859        colors.printc(*question, opt, **kwarg)
1860    else:
1861        colors.printc(*question, **kwarg)
1862
1863    try:
1864        resp = input()
1865    except Exception:
1866        resp = ""
1867        return resp
1868
1869    if options:
1870        if resp not in options:
1871            if default and str(repr(resp)) == "''":
1872                return default
1873            colors.printc("Please choose one option in:", opt, italic=True, bold=False)
1874            kwarg["options"] = options
1875            return ask(*question, **kwarg)  # ask again
1876    return resp

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

Arguments:
  • options : (list) a python list of possible answers to choose from.
  • default : (str) the default answer when just hitting return.

Example:

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

Generate a video from a rendering window.

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

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

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

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

Add frame to current video.

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

Insert a pause, in seconds.

def action( self, elevation=(0, 80), azimuth=(0, 359), cameras=(), resetcam=False):
1934    def action(self, elevation=(0, 80), azimuth=(0, 359), cameras=(), resetcam=False):
1935        """
1936        Automatic shooting of a static scene by specifying rotation and elevation ranges.
1937
1938        Arguments:
1939            elevation : list
1940                initial and final elevation angles
1941            azimuth_range : list
1942                initial and final azimuth angles
1943            cameras : list
1944                list of cameras to go through, each camera can be dictionary or a vtkCamera
1945        """
1946        if not self.duration:
1947            self.duration = 5
1948
1949        plt = vedo.plotter_instance
1950        n = int(self.fps * self.duration)
1951
1952        cams = []
1953        for cm in cameras:
1954            cams.append(utils.camera_from_dict(cm))
1955        nc = len(cams)
1956
1957        plt.show(resetcam=resetcam, interactive=False)
1958
1959        if nc:
1960            for i in range(n):
1961                plt.move_camera(cams, i / n)
1962                plt.show()
1963                self.add_frame()
1964
1965        else:  ########################################
1966
1967            for i in range(n):
1968                plt.camera.Elevation((elevation[1] - elevation[0]) / n)
1969                plt.camera.Azimuth((azimuth[1] - azimuth[0]) / n)
1970                plt.show()
1971                self.add_frame()
1972
1973        return self

Automatic shooting of a static scene by specifying rotation and elevation ranges.

Arguments:
  • elevation : list initial and final elevation angles
  • azimuth_range : list initial and final azimuth angles
  • cameras : list list of cameras to go through, each camera can be dictionary or a vtkCamera
def close(self):
1975    def close(self):
1976        """
1977        Render the video and write it to file.
1978        """
1979        if self.duration:
1980            self.fps = int(len(self.frames) / float(self.duration) + 0.5)
1981            colors.printc("recalculated fps:", self.fps, c="m", end="")
1982        else:
1983            self.fps = int(self.fps)
1984
1985        ########################################
1986        if self.backend == "ffmpeg":
1987            out = os.system(
1988                self.command
1989                + " "
1990                + str(self.fps)
1991                + " -i "
1992                + f"'{self.tmp_dir.name}'"
1993                + os.sep
1994                + "%01d.png "
1995                + self.options
1996                + " "
1997                + f"'{self.name}'"
1998            )
1999            if out:
2000                vedo.logger.error(f":noentry: backend {self.backend} returning error: {out}")
2001            else:
2002                colors.printc(f":save: saved to {self.name}", c="m")
2003
2004        ########################################
2005        elif "cv" in self.backend:
2006            try:
2007                import cv2
2008            except ImportError:
2009                vedo.logger.error("opencv is not installed")
2010                return
2011
2012            cap = cv2.VideoCapture(os.path.join(self.tmp_dir.name, "%1d.png"))
2013            fourcc = cv2.VideoWriter_fourcc(*"mp4v")
2014            w, h = vedo.plotter_instance.window.GetSize()
2015            writer = cv2.VideoWriter(self.name, fourcc, self.fps, (w, h), True)
2016
2017            while True:
2018                ret, frame = cap.read()
2019                if not ret:
2020                    break
2021                writer.write(frame)
2022
2023            cap.release()
2024            writer.release()
2025
2026        ########################################
2027        elif "imageio" in self.backend:
2028            try:
2029                import imageio
2030            except ImportError:
2031                vedo.logger.error("Please install imageio with:\n pip install imageio[ffmpeg]")
2032                return
2033
2034            if self.name.endswith(".mp4"):
2035                writer = imageio.get_writer(self.name, fps=self.fps)
2036            elif self.name.endswith(".gif"):
2037                writer = imageio.get_writer(self.name, mode="I", duration=1 / self.fps)
2038            elif self.name.endswith(".webm"):
2039                writer = imageio.get_writer(self.name, format="webm", fps=self.fps)
2040            else:
2041                vedo.logger.error(f"Unknown format of {self.name}.")
2042                return
2043
2044            for f in utils.humansort(self.frames):
2045                image = imageio.v3.imread(f)
2046                writer.append_data(image)
2047            try:
2048                writer.close()
2049                colors.printc(f"... saved as {self.name}", c="m")
2050            except:
2051                colors.printc(f":noentry: Could not save video {self.name}", c="r")
2052
2053        # finalize cleanup
2054        self.tmp_dir.cleanup()

Render the video and write it to file.

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

Split an existing video file into frames.