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