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