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