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