vedo.applications
This module contains vedo applications which provide some ready-to-use funcionalities
1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3import time 4import os 5from typing import Callable 6 7import numpy as np 8 9import vedo 10from vedo.colors import color_map, get_color 11from vedo.utils import is_sequence, lin_interpolate, mag, precision 12from vedo.plotter import Event, Plotter 13from vedo.pointcloud import fit_plane, Points 14from vedo.shapes import Line, Ribbon, Spline, Text2D 15from vedo.pyplot import CornerHistogram 16from vedo.addons import SliderWidget 17 18 19__docformat__ = "google" 20 21__doc__ = """ 22This module contains vedo applications which provide some *ready-to-use* funcionalities 23 24<img src="https://vedo.embl.es/images/advanced/app_raycaster.gif" width="500"> 25""" 26 27__all__ = [ 28 "Browser", 29 "IsosurfaceBrowser", 30 "FreeHandCutPlotter", 31 "RayCastPlotter", 32 "Slicer3DPlotter", 33 "Slicer2DPlotter", 34 "SplinePlotter", 35 "AnimationPlayer", 36 "Clock", 37] 38 39 40################################# 41class Slicer3DPlotter(Plotter): 42 """ 43 Generate a rendering window with slicing planes for the input Volume. 44 """ 45 46 def __init__( 47 self, 48 volume, 49 alpha=1, 50 cmaps=("gist_ncar_r", "hot_r", "bone_r", "jet", "Spectral_r"), 51 map2cells=False, # buggy 52 clamp=True, 53 use_slider3d=False, 54 show_histo=True, 55 show_icon=True, 56 draggable=False, 57 pos=(0, 0), 58 size="auto", 59 screensize="auto", 60 title="", 61 bg="white", 62 bg2="lightblue", 63 axes=7, 64 resetcam=True, 65 interactive=True, 66 ): 67 """ 68 Generate a rendering window with slicing planes for the input Volume. 69 70 Arguments: 71 alpha : (float) 72 transparency of the slicing planes 73 cmaps : (list) 74 list of color maps names to cycle when clicking button 75 map2cells : (bool) 76 scalars are mapped to cells, not interpolated 77 clamp : (bool) 78 clamp scalar to reduce the effect of tails in color mapping 79 use_slider3d : (bool) 80 show sliders attached along the axes 81 show_histo : (bool) 82 show histogram on bottom left 83 show_icon : (bool) 84 show a small 3D rendering icon of the volume 85 draggable : (bool) 86 make the icon draggable 87 88 Examples: 89 - [slicer1.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/slicer1.py) 90 91 <img src="https://vedo.embl.es/images/volumetric/slicer1.jpg" width="500"> 92 """ 93 self._cmap_slicer = "gist_ncar_r" 94 95 if not title: 96 if volume.filename: 97 title = volume.filename 98 else: 99 title = "Volume Slicer" 100 101 ################################ 102 Plotter.__init__( 103 self, 104 pos=pos, 105 bg=bg, 106 bg2=bg2, 107 size=size, 108 screensize=screensize, 109 title=title, 110 interactive=interactive, 111 axes=axes, 112 ) 113 ################################ 114 box = volume.box().wireframe().alpha(0.1) 115 116 self.show(box, viewup="z", resetcam=resetcam, interactive=False) 117 if show_icon: 118 self.add_inset(volume, pos=(0.85, 0.85), size=0.15, c="w", draggable=draggable) 119 120 # inits 121 la, ld = 0.7, 0.3 # ambient, diffuse 122 dims = volume.dimensions() 123 data = volume.pointdata[0] 124 rmin, rmax = volume.imagedata().GetScalarRange() 125 if clamp: 126 hdata, edg = np.histogram(data, bins=50) 127 logdata = np.log(hdata + 1) 128 # mean of the logscale plot 129 meanlog = np.sum(np.multiply(edg[:-1], logdata)) / np.sum(logdata) 130 rmax = min(rmax, meanlog + (meanlog - rmin) * 0.9) 131 rmin = max(rmin, meanlog - (rmax - meanlog) * 0.9) 132 vedo.logger.debug( 133 "scalar range clamped to range: (" 134 + precision(rmin, 3) 135 + ", " 136 + precision(rmax, 3) 137 + ")" 138 ) 139 self._cmap_slicer = cmaps[0] 140 visibles = [None, None, None] 141 msh = volume.zslice(int(dims[2] / 2)) 142 msh.alpha(alpha).lighting("", la, ld, 0) 143 msh.cmap(self._cmap_slicer, vmin=rmin, vmax=rmax) 144 if map2cells: 145 msh.mapPointsToCells() 146 self.renderer.AddActor(msh) 147 visibles[2] = msh 148 msh.add_scalarbar(pos=(0.04, 0.0), horizontal=True, font_size=0) 149 150 def slider_function_x(widget, event): 151 i = int(widget.GetRepresentation().GetValue()) 152 msh = volume.xslice(i).alpha(alpha).lighting("", la, ld, 0) 153 msh.cmap(self._cmap_slicer, vmin=rmin, vmax=rmax) 154 if map2cells: 155 msh.mapPointsToCells() 156 self.renderer.RemoveActor(visibles[0]) 157 if i and i < dims[0]: 158 self.renderer.AddActor(msh) 159 visibles[0] = msh 160 161 def slider_function_y(widget, event): 162 i = int(widget.GetRepresentation().GetValue()) 163 msh = volume.yslice(i).alpha(alpha).lighting("", la, ld, 0) 164 msh.cmap(self._cmap_slicer, vmin=rmin, vmax=rmax) 165 if map2cells: 166 msh.mapPointsToCells() 167 self.renderer.RemoveActor(visibles[1]) 168 if i and i < dims[1]: 169 self.renderer.AddActor(msh) 170 visibles[1] = msh 171 172 def slider_function_z(widget, event): 173 i = int(widget.GetRepresentation().GetValue()) 174 msh = volume.zslice(i).alpha(alpha).lighting("", la, ld, 0) 175 msh.cmap(self._cmap_slicer, vmin=rmin, vmax=rmax) 176 if map2cells: 177 msh.mapPointsToCells() 178 self.renderer.RemoveActor(visibles[2]) 179 if i and i < dims[2]: 180 self.renderer.AddActor(msh) 181 visibles[2] = msh 182 183 cx, cy, cz, ch = "dr", "dg", "db", (0.3, 0.3, 0.3) 184 if np.sum(self.renderer.GetBackground()) < 1.5: 185 cx, cy, cz = "lr", "lg", "lb" 186 ch = (0.8, 0.8, 0.8) 187 188 if not use_slider3d: 189 self.add_slider( 190 slider_function_x, 191 0, 192 dims[0], 193 title="X", 194 title_size=0.5, 195 pos=[(0.8, 0.12), (0.95, 0.12)], 196 show_value=False, 197 c=cx, 198 ) 199 self.add_slider( 200 slider_function_y, 201 0, 202 dims[1], 203 title="Y", 204 title_size=0.5, 205 pos=[(0.8, 0.08), (0.95, 0.08)], 206 show_value=False, 207 c=cy, 208 ) 209 self.add_slider( 210 slider_function_z, 211 0, 212 dims[2], 213 title="Z", 214 title_size=0.6, 215 value=int(dims[2] / 2), 216 pos=[(0.8, 0.04), (0.95, 0.04)], 217 show_value=False, 218 c=cz, 219 ) 220 else: # 3d sliders attached to the axes bounds 221 bs = box.bounds() 222 self.add_slider3d( 223 slider_function_x, 224 pos1=(bs[0], bs[2], bs[4]), 225 pos2=(bs[1], bs[2], bs[4]), 226 xmin=0, 227 xmax=dims[0], 228 t=box.diagonal_size() / mag(box.xbounds()) * 0.6, 229 c=cx, 230 show_value=False, 231 ) 232 self.add_slider3d( 233 slider_function_y, 234 pos1=(bs[1], bs[2], bs[4]), 235 pos2=(bs[1], bs[3], bs[4]), 236 xmin=0, 237 xmax=dims[1], 238 t=box.diagonal_size() / mag(box.ybounds()) * 0.6, 239 c=cy, 240 show_value=False, 241 ) 242 self.add_slider3d( 243 slider_function_z, 244 pos1=(bs[0], bs[2], bs[4]), 245 pos2=(bs[0], bs[2], bs[5]), 246 xmin=0, 247 xmax=dims[2], 248 value=int(dims[2] / 2), 249 t=box.diagonal_size() / mag(box.zbounds()) * 0.6, 250 c=cz, 251 show_value=False, 252 ) 253 254 ################# 255 def buttonfunc(): 256 bu.switch() 257 self._cmap_slicer = bu.status() 258 for mesh in visibles: 259 if mesh: 260 mesh.cmap(self._cmap_slicer, vmin=rmin, vmax=rmax) 261 if map2cells: 262 mesh.mapPointsToCells() 263 self.renderer.RemoveActor(mesh.scalarbar) 264 mesh.add_scalarbar(pos=(0.04, 0.0), horizontal=True) 265 self.renderer.AddActor(mesh.scalarbar) 266 267 bu = self.add_button( 268 buttonfunc, 269 pos=(0.27, 0.005), 270 states=cmaps, 271 c=["db"] * len(cmaps), 272 bc=["lb"] * len(cmaps), # colors of states 273 size=14, 274 bold=True, 275 ) 276 277 ################# 278 hist = None 279 if show_histo: 280 hist = CornerHistogram( 281 data, s=0.2, bins=25, logscale=1, pos=(0.02, 0.02), c=ch, bg=ch, alpha=0.7 282 ) 283 284 self.add([msh, hist]) 285 if interactive: 286 self.interactive() 287 288 289######################################################################################## 290class Slicer2DPlotter(Plotter): 291 """ 292 A single slice of a Volume which always faces the camera, 293 but at the same time can be oriented arbitrarily in space. 294 """ 295 296 def __init__(self, volume, levels=(None, None), histo_color="red5", **kwargs): 297 """ 298 A single slice of a Volume which always faces the camera, 299 but at the same time can be oriented arbitrarily in space. 300 301 Arguments: 302 levels : (list) 303 window and color levels 304 histo_color : (color) 305 histogram color, use `None` to disable it 306 307 <img src="https://vedo.embl.es/images/volumetric/read_volume3.jpg" width="500"> 308 """ 309 if "shape" not in kwargs: 310 custom_shape = [ # define here the 2 rendering rectangle spaces 311 dict(bottomleft=(0.0, 0.0), topright=(1, 1), bg="k9"), # the full window 312 dict(bottomleft=(0.8, 0.8), topright=(1, 1), bg="k8", bg2="lb"), 313 ] 314 kwargs["shape"] = custom_shape 315 316 Plotter.__init__(self, **kwargs) 317 318 # reuse the same underlying data as in vol 319 vsl = vedo.volume.VolumeSlice(volume) 320 321 # no argument will grab the existing cmap in vol (or use build_lut()) 322 vsl.colorize() 323 324 if levels[0] and levels[1]: 325 vsl.lighting(window=levels[0], level=levels[1]) 326 327 usage = Text2D( 328 ( 329 "Left click & drag :rightarrow modify luminosity and contrast\n" 330 "SHIFT+Left click :rightarrow slice image obliquely\n" 331 "SHIFT+Middle click :rightarrow slice image perpendicularly\n" 332 "R :rightarrow Reset the Window/Color levels\n" 333 "X :rightarrow Reset to sagittal view\n" 334 "Y :rightarrow Reset to coronal view\n" 335 "Z :rightarrow Reset to axial view" 336 ), 337 font="Calco", 338 pos="top-left", 339 s=0.8, 340 bg="yellow", 341 alpha=0.25, 342 ) 343 344 hist = None 345 if histo_color is not None: 346 # hist = CornerHistogram( 347 # volume.pointdata[0], 348 # bins=25, 349 # logscale=1, 350 # pos=(0.02, 0.02), 351 # s=0.175, 352 # c="dg", 353 # bg="k", 354 # alpha=1, 355 # ) 356 hist = vedo.pyplot.histogram( 357 volume.pointdata[0], 358 bins=10, 359 logscale=True, 360 c=histo_color, 361 ytitle="log_10 (counts)", 362 axes=dict(text_scale=1.9), 363 ) 364 hist = hist.as2d(pos="bottom-left", scale=0.5) 365 366 axes = kwargs.pop("axes", 7) 367 interactive = kwargs.pop("interactive", True) 368 if axes == 7: 369 ax = vedo.addons.RulerAxes(vsl, xtitle="x - ", ytitle="y - ", ztitle="z - ") 370 371 box = vsl.box().alpha(0.2) 372 self.at(0).show(vsl, box, ax, usage, hist, mode="image") 373 self.at(1).show(volume, interactive=interactive) 374 375 376######################################################################## 377class RayCastPlotter(Plotter): 378 """ 379 Generate Volume rendering using ray casting. 380 """ 381 382 def __init__(self, volume, **kwargs): 383 """ 384 Generate a window for Volume rendering using ray casting. 385 386 Returns: 387 `vedo.Plotter` object. 388 389 Examples: 390 - [app_raycaster.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/app_raycaster.py) 391 392 ![](https://vedo.embl.es/images/advanced/app_raycaster.gif) 393 """ 394 395 Plotter.__init__(self, **kwargs) 396 397 self.alphaslider0 = 0.33 398 self.alphaslider1 = 0.66 399 self.alphaslider2 = 1 400 401 self.property = volume.GetProperty() 402 img = volume.imagedata() 403 404 if volume.dimensions()[2] < 3: 405 vedo.logger.error("RayCastPlotter: not enough z slices.") 406 raise RuntimeError 407 408 smin, smax = img.GetScalarRange() 409 x0alpha = smin + (smax - smin) * 0.25 410 x1alpha = smin + (smax - smin) * 0.5 411 x2alpha = smin + (smax - smin) * 1.0 412 413 ############################## color map slider 414 # Create transfer mapping scalar value to color 415 cmaps = [ 416 "jet", 417 "viridis", 418 "bone", 419 "hot", 420 "plasma", 421 "winter", 422 "cool", 423 "gist_earth", 424 "coolwarm", 425 "tab10", 426 ] 427 cols_cmaps = [] 428 for cm in cmaps: 429 cols = color_map(range(0, 21), cm, 0, 20) # sample 20 colors 430 cols_cmaps.append(cols) 431 Ncols = len(cmaps) 432 csl = (0.9, 0.9, 0.9) 433 if sum(get_color(self.renderer.GetBackground())) > 1.5: 434 csl = (0.1, 0.1, 0.1) 435 436 def sliderColorMap(widget, event): 437 sliderRep = widget.GetRepresentation() 438 k = int(sliderRep.GetValue()) 439 sliderRep.SetTitleText(cmaps[k]) 440 volume.color(cmaps[k]) 441 442 w1 = self.add_slider( 443 sliderColorMap, 444 0, 445 Ncols - 1, 446 value=0, 447 show_value=0, 448 title=cmaps[0], 449 c=csl, 450 pos=[(0.8, 0.05), (0.965, 0.05)], 451 ) 452 w1.GetRepresentation().SetTitleHeight(0.018) 453 454 ############################## alpha sliders 455 # Create transfer mapping scalar value to opacity 456 opacityTransferFunction = self.property.GetScalarOpacity() 457 458 def setOTF(): 459 opacityTransferFunction.RemoveAllPoints() 460 opacityTransferFunction.AddPoint(smin, 0.0) 461 opacityTransferFunction.AddPoint(smin + (smax - smin) * 0.1, 0.0) 462 opacityTransferFunction.AddPoint(x0alpha, self.alphaslider0) 463 opacityTransferFunction.AddPoint(x1alpha, self.alphaslider1) 464 opacityTransferFunction.AddPoint(x2alpha, self.alphaslider2) 465 466 setOTF() 467 468 def sliderA0(widget, event): 469 self.alphaslider0 = widget.GetRepresentation().GetValue() 470 setOTF() 471 472 self.add_slider( 473 sliderA0, 474 0, 475 1, 476 value=self.alphaslider0, 477 pos=[(0.84, 0.1), (0.84, 0.26)], 478 c=csl, 479 show_value=0, 480 ) 481 482 def sliderA1(widget, event): 483 self.alphaslider1 = widget.GetRepresentation().GetValue() 484 setOTF() 485 486 self.add_slider( 487 sliderA1, 488 0, 489 1, 490 value=self.alphaslider1, 491 pos=[(0.89, 0.1), (0.89, 0.26)], 492 c=csl, 493 show_value=0, 494 ) 495 496 def sliderA2(widget, event): 497 self.alphaslider2 = widget.GetRepresentation().GetValue() 498 setOTF() 499 500 w2 = self.add_slider( 501 sliderA2, 502 0, 503 1, 504 value=self.alphaslider2, 505 pos=[(0.96, 0.1), (0.96, 0.26)], 506 c=csl, 507 show_value=0, 508 title="Opacity levels", 509 ) 510 w2.GetRepresentation().SetTitleHeight(0.016) 511 512 # add a button 513 def button_func_mode(): 514 s = volume.mode() 515 snew = (s + 1) % 2 516 volume.mode(snew) 517 bum.switch() 518 519 bum = self.add_button( 520 button_func_mode, 521 pos=(0.7, 0.035), 522 states=["composite", "max proj."], 523 c=["bb", "gray"], 524 bc=["gray", "bb"], # colors of states 525 font="", 526 size=16, 527 bold=0, 528 italic=False, 529 ) 530 bum.status(volume.mode()) 531 532 # add histogram of scalar 533 plot = CornerHistogram( 534 volume, 535 bins=25, 536 logscale=1, 537 c=(0.7, 0.7, 0.7), 538 bg=(0.7, 0.7, 0.7), 539 pos=(0.78, 0.065), 540 lines=True, 541 dots=False, 542 nmax=3.1415e06, # subsample otherwise is too slow 543 ) 544 545 plot.GetPosition2Coordinate().SetValue(0.197, 0.20, 0) 546 plot.GetXAxisActor2D().SetFontFactor(0.7) 547 plot.GetProperty().SetOpacity(0.5) 548 self.add([plot, volume]) 549 550 551##################################################################################### 552class IsosurfaceBrowser(Plotter): 553 """ 554 Generate a Volume isosurfacing controlled by a slider. 555 """ 556 557 def __init__( 558 self, 559 volume, 560 isovalue=None, 561 c=None, 562 alpha=1, 563 lego=False, 564 res=50, 565 use_gpu=False, 566 precompute=False, 567 progress=False, 568 cmap="hot", 569 delayed=False, 570 sliderpos=4, 571 pos=(0, 0), 572 size="auto", 573 screensize="auto", 574 title="", 575 bg="white", 576 bg2=None, 577 axes=1, 578 interactive=True, 579 ): 580 """ 581 Generate a `vedo.Plotter` for Volume isosurfacing using a slider. 582 583 Set `delayed=True` to delay slider update on mouse release. 584 585 Set `res` to set the resolution, e.g. the number of desired isosurfaces to be 586 generated on the fly. 587 588 Set `precompute=True` to precompute the isosurfaces (so slider browsing will be smoother). 589 590 Examples: 591 - [app_isobrowser.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/app_isobrowser.py) 592 593 ![](https://vedo.embl.es/images/advanced/app_isobrowser.gif) 594 """ 595 596 Plotter.__init__( 597 self, 598 pos=pos, 599 bg=bg, 600 bg2=bg2, 601 size=size, 602 screensize=screensize, 603 title=title, 604 interactive=interactive, 605 axes=axes, 606 ) 607 608 ### GPU ################################ 609 if use_gpu and hasattr(volume.GetProperty(), "GetIsoSurfaceValues"): 610 611 scrange = volume.scalar_range() 612 delta = scrange[1] - scrange[0] 613 if not delta: 614 return 615 616 if isovalue is None: 617 isovalue = delta / 3.0 + scrange[0] 618 619 ### isovalue slider callback 620 def slider_isovalue(widget, event): 621 value = widget.GetRepresentation().GetValue() 622 isovals.SetValue(0, value) 623 624 isovals = volume.GetProperty().GetIsoSurfaceValues() 625 isovals.SetValue(0, isovalue) 626 self.renderer.AddActor(volume.mode(5).alpha(alpha).c(c)) 627 628 self.add_slider( 629 slider_isovalue, 630 scrange[0] + 0.02 * delta, 631 scrange[1] - 0.02 * delta, 632 value=isovalue, 633 pos=sliderpos, 634 title="scalar value", 635 show_value=True, 636 delayed=delayed, 637 ) 638 639 ### CPU ################################ 640 else: 641 642 self._prev_value = 1e30 643 644 scrange = volume.scalar_range() 645 delta = scrange[1] - scrange[0] 646 if not delta: 647 return 648 649 if lego: 650 res = int(res / 2) # because lego is much slower 651 slidertitle = "" 652 else: 653 slidertitle = "scalar value" 654 655 allowed_vals = np.linspace(scrange[0], scrange[1], num=res) 656 657 bacts = {} # cache the meshes so we dont need to recompute 658 if precompute: 659 delayed = False # no need to delay the slider in this case 660 if progress: 661 pb = vedo.ProgressBar(0, len(allowed_vals), delay=1) 662 663 for value in allowed_vals: 664 value_name = precision(value, 2) 665 if lego: 666 mesh = volume.legosurface(vmin=value) 667 if mesh.ncells: 668 mesh.cmap(cmap, vmin=scrange[0], vmax=scrange[1], on="cells") 669 else: 670 mesh = volume.isosurface(value).color(c).alpha(alpha) 671 bacts.update({value_name: mesh}) # store it 672 if progress: 673 pb.print("isosurfacing volume..") 674 675 ### isovalue slider callback 676 def slider_isovalue(widget, event): 677 678 prevact = self.actors[0] 679 if isinstance(widget, float): 680 value = widget 681 else: 682 value = widget.GetRepresentation().GetValue() 683 684 # snap to the closest 685 idx = (np.abs(allowed_vals - value)).argmin() 686 value = allowed_vals[idx] 687 688 if abs(value - self._prev_value) / delta < 0.001: 689 return 690 self._prev_value = value 691 692 value_name = precision(value, 2) 693 if value_name in bacts: # reusing the already existing mesh 694 # print('reusing') 695 mesh = bacts[value_name] 696 else: # else generate it 697 # print('generating', value) 698 if lego: 699 mesh = volume.legosurface(vmin=value) 700 if mesh.ncells: 701 mesh.cmap(cmap, vmin=scrange[0], vmax=scrange[1], on="cells") 702 else: 703 mesh = volume.isosurface(value).color(c).alpha(alpha) 704 bacts.update({value_name: mesh}) # store it 705 706 self.renderer.RemoveActor(prevact) 707 self.renderer.AddActor(mesh) 708 self.actors[0] = mesh 709 710 ################################################ 711 712 if isovalue is None: 713 isovalue = delta / 3.0 + scrange[0] 714 715 self.actors = [None] 716 slider_isovalue(isovalue, "") # init call 717 if lego: 718 self.actors[0].add_scalarbar(pos=(0.8, 0.12)) 719 720 self.add_slider( 721 slider_isovalue, 722 scrange[0] + 0.02 * delta, 723 scrange[1] - 0.02 * delta, 724 value=isovalue, 725 pos=sliderpos, 726 title=slidertitle, 727 show_value=True, 728 delayed=delayed, 729 ) 730 731 732############################################################################## 733class Browser(Plotter): 734 """ 735 Browse a series of vedo objects by using a simple slider. 736 """ 737 def __init__( 738 self, 739 objects=(), 740 sliderpos=((0.50, 0.07), (0.95, 0.07)), 741 c=None, # slider color 742 prefix="", 743 font="Calco", # slider font 744 axes=1, 745 resetcam=False, # resetcam while using the slider 746 **kwargs, 747 ): 748 """ 749 Browse a series of vedo objects by using a simple slider. 750 751 Examples: 752 ```python 753 from vedo import load, dataurl 754 from vedo.applications import Browser 755 meshes = load(dataurl+'timecourse1d.npy') # python list of Meshes 756 plt = Browser(meshes, bg='k') # vedo.Plotter 757 plt.show(interactive=False, zoom='tight') # show the meshes 758 plt.play(dt=50) # delay in milliseconds 759 plt.close() 760 ``` 761 762 - [morphomatics_tube.py](https://github.com/marcomusy/vedo/tree/master/examples/other/morphomatics_tube.py) 763 """ 764 Plotter.__init__(self, axes=axes, **kwargs) 765 766 if isinstance(objects, str): 767 objects = vedo.file_io.load(objects) 768 769 self += objects 770 771 self.slider = None 772 self.timer_callback_id = None 773 774 # define the slider func ########################## 775 def slider_function(widget=None, event=None): 776 777 must_render = False 778 if isinstance(widget, vedo.plotter.Event): 779 if self.slider.value < len(self.actors)-1: 780 self.slider.value = self.slider.value + 1 781 else: 782 self.slider.value = 0 783 must_render = True 784 785 k = int(self.slider.value) 786 ak = self.actors[k] 787 for a in self.actors: 788 if a == ak: 789 a.on() 790 else: 791 a.off() 792 if resetcam: 793 self.reset_camera() 794 tx = str(k) 795 if ak.filename: 796 tx = ak.filename.split("/")[-1] 797 tx = tx.split("\\")[-1] # windows os 798 elif ak.name: 799 tx = ak.name 800 801 self.slider.title = prefix + tx 802 803 if must_render: 804 self.render() 805 ################################################## 806 807 self.slider_function = slider_function 808 self.slider = self.add_slider( 809 slider_function, 810 0.5, 811 len(objects) - 0.5, 812 pos=sliderpos, 813 font=font, 814 c=c, 815 show_value=False, 816 ) 817 self.slider.GetRepresentation().SetTitleHeight(0.020) 818 slider_function(self.slider) # init call 819 820 def play(self, dt=100): 821 """Start playing the slides at a given speed.""" 822 self.timer_callback_id = self.add_callback("timer", self.slider_function) 823 self.timer_callback("start", dt=dt) 824 self.interactive() 825 826 827############################################################################################# 828class FreeHandCutPlotter(Plotter): 829 """A tool to edit meshes interactively.""" 830 831 # thanks to Jakub Kaminski for the original version of this script 832 def __init__( 833 self, 834 mesh, 835 splined=True, 836 font="Bongas", 837 alpha=0.9, 838 lw=4, 839 lc="red5", 840 pc="red4", 841 c="green3", 842 tc="k9", 843 tol=0.008, 844 **options, 845 ): 846 """ 847 A `vedo.Plotter` derived class which edits polygonal meshes interactively. 848 849 Can also be invoked from command line with: 850 851 ```bash 852 vedo --edit https://vedo.embl.es/examples/data/porsche.ply 853 ``` 854 855 Usage: 856 - Left-click and hold to rotate 857 - Right-click and move to draw line 858 - Second right-click to stop drawing 859 - Press "c" to clear points 860 - "z/Z" to cut mesh (Z inverts inside-out the selection area) 861 - "L" to keep only the largest connected surface 862 - "s" to save mesh to file (tag `_edited` is appended to filename) 863 - "u" to undo last action 864 - "h" for help, "i" for info 865 866 Arguments: 867 mesh : (Mesh, Points) 868 The input Mesh or pointcloud. 869 splined : (bool) 870 join points with a spline or a simple line. 871 font : (str) 872 Font name for the instructions. 873 alpha : (float) 874 transparency of the instruction message panel. 875 lw : (str) 876 selection line width. 877 lc : (str) 878 selection line color. 879 pc : (str) 880 selection points color. 881 c : (str) 882 background color of instructions. 883 tc : (str) 884 text color of instructions. 885 tol : (int) 886 tolerance of the point proximity. 887 888 Examples: 889 - [cut_freehand.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/cut_freehand.py) 890 891 ![](https://vedo.embl.es/images/basic/cutFreeHand.gif) 892 """ 893 894 if not isinstance(mesh, Points): 895 vedo.logger.error("FreeHandCutPlotter input must be Points or Mesh") 896 raise RuntimeError() 897 898 super().__init__(**options) 899 900 self.mesh = mesh 901 self.mesh_prev = mesh 902 self.splined = splined 903 self.linecolor = lc 904 self.linewidth = lw 905 self.pointcolor = pc 906 self.color = c 907 self.alpha = alpha 908 909 self.msg = "Right-click and move to draw line\n" 910 self.msg += "Second right-click to stop drawing\n" 911 self.msg += "Press L to extract largest surface\n" 912 self.msg += " z/Z to cut mesh (s to save)\n" 913 self.msg += " c to clear points, u to undo" 914 self.txt2d = Text2D(self.msg, pos="top-left", font=font, s=0.9) 915 self.txt2d.c(tc).background(c, alpha).frame() 916 917 self.idkeypress = self.add_callback("KeyPress", self._on_keypress) 918 self.idrightclck = self.add_callback("RightButton", self._on_right_click) 919 self.idmousemove = self.add_callback("MouseMove", self._on_mouse_move) 920 self.drawmode = False 921 self.tol = tol # tolerance of point distance 922 self.cpoints = [] 923 self.points = None 924 self.spline = None 925 self.jline = None 926 self.topline = None 927 self.top_pts = [] 928 929 def init(self, init_points): 930 """Set an initial number of points to define a region""" 931 if isinstance(init_points, Points): 932 self.cpoints = init_points.points() 933 else: 934 self.cpoints = np.array(init_points) 935 self.points = Points(self.cpoints, r=self.linewidth).c(self.pointcolor).pickable(0) 936 if self.splined: 937 self.spline = Spline(self.cpoints, res=len(self.cpoints) * 4) 938 else: 939 self.spline = Line(self.cpoints) 940 self.spline.lw(self.linewidth).c(self.linecolor).pickable(False) 941 self.jline = Line(self.cpoints[0], self.cpoints[-1], lw=1, c=self.linecolor).pickable(0) 942 self.add([self.points, self.spline, self.jline]).render() 943 return self 944 945 def _on_right_click(self, evt): 946 self.drawmode = not self.drawmode # toggle mode 947 if self.drawmode: 948 self.txt2d.background(self.linecolor, self.alpha) 949 else: 950 self.txt2d.background(self.color, self.alpha) 951 if len(self.cpoints) > 2: 952 self.remove([self.spline, self.jline]) 953 if self.splined: # show the spline closed 954 self.spline = Spline(self.cpoints, closed=True, res=len(self.cpoints) * 4) 955 else: 956 self.spline = Line(self.cpoints, closed=True) 957 self.spline.lw(self.linewidth).c(self.linecolor).pickable(False) 958 self.add(self.spline) 959 self.render() 960 961 def _on_mouse_move(self, evt): 962 if self.drawmode: 963 cpt = self.compute_world_coordinate(evt.picked2d) # make this 2d-screen point 3d 964 if self.cpoints and mag(cpt - self.cpoints[-1]) < self.mesh.diagonal_size() * self.tol: 965 return # new point is too close to the last one. skip 966 self.cpoints.append(cpt) 967 if len(self.cpoints) > 2: 968 self.remove([self.points, self.spline, self.jline, self.topline]) 969 self.points = Points(self.cpoints, r=self.linewidth).c(self.pointcolor).pickable(0) 970 if self.splined: 971 self.spline = Spline(self.cpoints, res=len(self.cpoints) * 4) # not closed here 972 else: 973 self.spline = Line(self.cpoints) 974 975 if evt.actor: 976 self.top_pts.append(evt.picked3d) 977 self.topline = Points(self.top_pts, r=self.linewidth) 978 self.topline.c(self.linecolor).pickable(False) 979 980 self.spline.lw(self.linewidth).c(self.linecolor).pickable(False) 981 self.txt2d.background(self.linecolor) 982 self.jline = Line(self.cpoints[0], self.cpoints[-1], lw=1, c=self.linecolor).pickable(0) 983 self.add([self.points, self.spline, self.jline, self.topline]).render() 984 985 def _on_keypress(self, evt): 986 if evt.keypress.lower() == "z" and self.spline: # Cut mesh with a ribbon-like surface 987 inv = False 988 if evt.keypress == "Z": 989 inv = True 990 self.txt2d.background("red8").text(" ... working ... ") 991 self.render() 992 self.mesh_prev = self.mesh.clone() 993 tol = self.mesh.diagonal_size() / 2 # size of ribbon (not shown) 994 pts = self.spline.points() 995 n = fit_plane(pts, signed=True).normal # compute normal vector to points 996 rb = Ribbon(pts - tol * n, pts + tol * n, closed=True) 997 self.mesh.cut_with_mesh(rb, invert=inv) # CUT 998 self.txt2d.text(self.msg) # put back original message 999 if self.drawmode: 1000 self._on_right_click(evt) # toggle mode to normal 1001 else: 1002 self.txt2d.background(self.color, self.alpha) 1003 self.remove([self.spline, self.points, self.jline, self.topline]).render() 1004 self.cpoints, self.points, self.spline = [], None, None 1005 self.top_pts, self.topline = [], None 1006 1007 elif evt.keypress == "L": 1008 self.txt2d.background("red8") 1009 self.txt2d.text(" ... removing smaller ... \n ... parts of the mesh ... ") 1010 self.render() 1011 self.remove(self.mesh) 1012 self.mesh_prev = self.mesh 1013 mcut = self.mesh.extract_largest_region() 1014 mcut.filename = self.mesh.filename # copy over various properties 1015 mcut.name = self.mesh.name 1016 mcut.scalarbar = self.mesh.scalarbar 1017 mcut.info = self.mesh.info 1018 self.mesh = mcut # discard old mesh by overwriting it 1019 self.txt2d.text(self.msg).background(self.color) # put back original message 1020 self.add(mcut).render() 1021 1022 elif evt.keypress == 'u': # Undo last action 1023 if self.drawmode: 1024 self._on_right_click(evt) # toggle mode to normal 1025 else: 1026 self.txt2d.background(self.color, self.alpha) 1027 self.remove([self.mesh, self.spline, self.jline, self.points, self.topline]) 1028 self.mesh = self.mesh_prev 1029 self.cpoints, self.points, self.spline = [], None, None 1030 self.top_pts, self.topline = [], None 1031 self.add(self.mesh).render() 1032 1033 elif evt.keypress in ("c", "Delete"): 1034 # clear all points 1035 self.remove([self.spline, self.points, self.jline, self.topline]).render() 1036 self.cpoints, self.points, self.spline = [], None, None 1037 self.top_pts, self.topline = [], None 1038 1039 elif evt.keypress == "r": # reset camera and axes 1040 try: 1041 self.remove(self.axes_instances[0]) 1042 self.axes_instances[0] = None 1043 self.add_global_axes(axtype=1, c=None, bounds=self.mesh.bounds()) 1044 self.renderer.ResetCamera() 1045 self.render() 1046 except: 1047 pass 1048 1049 elif evt.keypress == "s": 1050 if self.mesh.filename: 1051 fname = os.path.basename(self.mesh.filename) 1052 fname, extension = os.path.splitext(fname) 1053 fname = fname.replace("_edited", "") 1054 fname = f"{fname}_edited{extension}" 1055 else: 1056 fname = "mesh_edited.vtk" 1057 self.write(fname) 1058 1059 def write(self, filename="mesh_edited.vtk"): 1060 """Save the resulting mesh to file""" 1061 self.mesh.write(filename) 1062 vedo.logger.info(f"mesh saved to file {filename}") 1063 return self 1064 1065 def start(self, *args, **kwargs): 1066 """Start window interaction (with mouse and keyboard)""" 1067 acts = [self.txt2d, self.mesh, self.points, self.spline, self.jline] 1068 self.show(acts + list(args), **kwargs) 1069 return self 1070 1071 1072######################################################################## 1073class SplinePlotter(Plotter): 1074 """ 1075 Interactive drawing of splined curves on meshes. 1076 """ 1077 1078 def __init__(self, obj, init_points=(), closed=False, splined=True, **kwargs): 1079 """ 1080 Create an interactive application that allows the user to click points and 1081 retrieve the coordinates of such points and optionally a spline or line 1082 (open or closed). 1083 1084 Input object can be a image file name or a 3D mesh. 1085 """ 1086 super().__init__(**kwargs) 1087 1088 self.mode = "trackball" 1089 self.verbose = True 1090 self.splined = splined 1091 self.resolution = None # spline resolution (None = automatic) 1092 self.closed = closed 1093 self.lcolor = "yellow4" 1094 self.lwidth = 3 1095 self.pcolor = "purple5" 1096 self.psize = 10 1097 1098 self.cpoints = list(init_points) 1099 self.vpoints = None 1100 self.line = None 1101 1102 if isinstance(obj, str): 1103 self.object = vedo.file_io.load(obj) 1104 else: 1105 self.object = obj 1106 1107 if isinstance(self.object, vedo.Picture): 1108 self.mode = "image" 1109 self.parallel_projection(True) 1110 1111 t = ( 1112 "Click to add a point\n" 1113 "Right-click to remove it\n" 1114 "Drag mouse to change contrast\n" 1115 "Press c to clear points\n" 1116 "Press q to continue" 1117 ) 1118 self.instructions = Text2D(t, pos="bottom-left", c="white", bg="green", font="Calco") 1119 1120 self += [self.object, self.instructions] 1121 1122 self.callid1 = self.add_callback("KeyPress", self._key_press) 1123 self.callid2 = self.add_callback("LeftButtonPress", self._on_left_click) 1124 self.callid3 = self.add_callback("RightButtonPress", self._on_right_click) 1125 1126 def points(self, newpts=None): 1127 """Retrieve the 3D coordinates of the clicked points""" 1128 if newpts is not None: 1129 self.cpoints = newpts 1130 self._update() 1131 return self 1132 return np.array(self.cpoints) 1133 1134 def _on_left_click(self, evt): 1135 if not evt.actor: 1136 return 1137 if evt.actor.name == "points": 1138 # remove clicked point if clicked twice 1139 pid = self.vpoints.closest_point(evt.picked3d, return_point_id=True) 1140 self.cpoints.pop(pid) 1141 self._update() 1142 return 1143 p = evt.picked3d 1144 self.cpoints.append(p) 1145 self._update() 1146 if self.verbose: 1147 vedo.colors.printc("Added point:", precision(p, 4), c="g") 1148 1149 def _on_right_click(self, evt): 1150 if evt.actor and len(self.cpoints) > 0: 1151 self.cpoints.pop() # pop removes from the list the last pt 1152 self._update() 1153 if self.verbose: 1154 vedo.colors.printc("Deleted last point", c="r") 1155 1156 def _update(self): 1157 self.remove(self.line, self.vpoints) # remove old points and spline 1158 self.vpoints = Points(self.cpoints).ps(self.psize).c(self.pcolor) 1159 self.vpoints.name = "points" 1160 self.vpoints.pickable(True) # to allow toggle 1161 minnr = 1 1162 if self.splined: 1163 minnr = 2 1164 if self.lwidth and len(self.cpoints) > minnr: 1165 if self.splined: 1166 try: 1167 self.line = Spline(self.cpoints, closed=self.closed, res=self.resolution) 1168 except ValueError: 1169 # if clicking too close splining might fail 1170 self.cpoints.pop() 1171 return 1172 else: 1173 self.line = Line(self.cpoints, closed=self.closed) 1174 self.line.c(self.lcolor).lw(self.lwidth).pickable(False) 1175 self.add(self.vpoints, self.line) 1176 else: 1177 self.add(self.vpoints) 1178 1179 def _key_press(self, evt): 1180 if evt.keypress == "c": 1181 self.cpoints = [] 1182 self.remove(self.line, self.vpoints).render() 1183 if self.verbose: 1184 vedo.colors.printc("==== Cleared all points ====", c="r", invert=True) 1185 1186 def start(self): 1187 """Start the interaction""" 1188 self.show(self.object, self.instructions, mode=self.mode) 1189 return self 1190 1191 1192######################################################################## 1193class Animation(Plotter): 1194 """ 1195 A `Plotter` derived class that allows to animate simultaneously various objects 1196 by specifying event times and durations of different visual effects. 1197 1198 Arguments: 1199 total_duration : (float) 1200 expand or shrink the total duration of video to this value 1201 time_resolution : (float) 1202 in seconds, save a frame at this rate 1203 show_progressbar : (bool) 1204 whether to show a progress bar or not 1205 video_filename : (str) 1206 output file name of the video 1207 video_fps : (int) 1208 desired value of the nr of frames per second 1209 1210 .. warning:: this is still an experimental feature at the moment. 1211 """ 1212 1213 def __init__( 1214 self, 1215 total_duration=None, 1216 time_resolution=0.02, 1217 show_progressbar=True, 1218 video_filename="animation.mp4", 1219 video_fps=12, 1220 ): 1221 Plotter.__init__(self) 1222 self.resetcam = True 1223 1224 self.events = [] 1225 self.time_resolution = time_resolution 1226 self.total_duration = total_duration 1227 self.show_progressbar = show_progressbar 1228 self.video_filename = video_filename 1229 self.video_fps = video_fps 1230 self.bookingMode = True 1231 self._inputvalues = [] 1232 self._performers = [] 1233 self._lastT = None 1234 self._lastDuration = None 1235 self._lastActs = None 1236 self.eps = 0.00001 1237 1238 def _parse(self, objs, t, duration): 1239 if t is None: 1240 if self._lastT: 1241 t = self._lastT 1242 else: 1243 t = 0.0 1244 if duration is None: 1245 if self._lastDuration: 1246 duration = self._lastDuration 1247 else: 1248 duration = 0.0 1249 if objs is None: 1250 if self._lastActs: 1251 objs = self._lastActs 1252 else: 1253 vedo.logger.error("Need to specify actors!") 1254 raise RuntimeError 1255 1256 objs2 = objs 1257 1258 if is_sequence(objs): 1259 objs2 = objs 1260 else: 1261 objs2 = [objs] 1262 1263 # quantize time steps and duration 1264 t = int(t / self.time_resolution + 0.5) * self.time_resolution 1265 nsteps = int(duration / self.time_resolution + 0.5) 1266 duration = nsteps * self.time_resolution 1267 1268 rng = np.linspace(t, t + duration, nsteps + 1) 1269 1270 self._lastT = t 1271 self._lastDuration = duration 1272 self._lastActs = objs2 1273 1274 for a in objs2: 1275 if a not in self.actors: 1276 self.actors.append(a) 1277 1278 return objs2, t, duration, rng 1279 1280 def switch_on(self, acts=None, t=None): 1281 """Switch on the input list of meshes.""" 1282 return self.fade_in(acts, t, 0) 1283 1284 def switch_off(self, acts=None, t=None): 1285 """Switch off the input list of meshes.""" 1286 return self.fade_out(acts, t, 0) 1287 1288 def fade_in(self, acts=None, t=None, duration=None): 1289 """Gradually switch on the input list of meshes by increasing opacity.""" 1290 if self.bookingMode: 1291 acts, t, duration, rng = self._parse(acts, t, duration) 1292 for tt in rng: 1293 alpha = lin_interpolate(tt, [t, t + duration], [0, 1]) 1294 self.events.append((tt, self.fade_in, acts, alpha)) 1295 else: 1296 for a in self._performers: 1297 if hasattr(a, "alpha"): 1298 if a.alpha() >= self._inputvalues: 1299 continue 1300 a.alpha(self._inputvalues) 1301 return self 1302 1303 def fade_out(self, acts=None, t=None, duration=None): 1304 """Gradually switch off the input list of meshes by increasing transparency.""" 1305 if self.bookingMode: 1306 acts, t, duration, rng = self._parse(acts, t, duration) 1307 for tt in rng: 1308 alpha = lin_interpolate(tt, [t, t + duration], [1, 0]) 1309 self.events.append((tt, self.fade_out, acts, alpha)) 1310 else: 1311 for a in self._performers: 1312 if a.alpha() <= self._inputvalues: 1313 continue 1314 a.alpha(self._inputvalues) 1315 return self 1316 1317 def change_alpha_between(self, alpha1, alpha2, acts=None, t=None, duration=None): 1318 """Gradually change transparency for the input list of meshes.""" 1319 if self.bookingMode: 1320 acts, t, duration, rng = self._parse(acts, t, duration) 1321 for tt in rng: 1322 alpha = lin_interpolate(tt, [t, t + duration], [alpha1, alpha2]) 1323 self.events.append((tt, self.fade_out, acts, alpha)) 1324 else: 1325 for a in self._performers: 1326 a.alpha(self._inputvalues) 1327 return self 1328 1329 def change_color(self, c, acts=None, t=None, duration=None): 1330 """Gradually change color for the input list of meshes.""" 1331 if self.bookingMode: 1332 acts, t, duration, rng = self._parse(acts, t, duration) 1333 1334 col2 = get_color(c) 1335 for tt in rng: 1336 inputvalues = [] 1337 for a in acts: 1338 col1 = a.color() 1339 r = lin_interpolate(tt, [t, t + duration], [col1[0], col2[0]]) 1340 g = lin_interpolate(tt, [t, t + duration], [col1[1], col2[1]]) 1341 b = lin_interpolate(tt, [t, t + duration], [col1[2], col2[2]]) 1342 inputvalues.append((r, g, b)) 1343 self.events.append((tt, self.change_color, acts, inputvalues)) 1344 else: 1345 for i, a in enumerate(self._performers): 1346 a.color(self._inputvalues[i]) 1347 return self 1348 1349 def change_backcolor(self, c, acts=None, t=None, duration=None): 1350 """Gradually change backface color for the input list of meshes. 1351 An initial backface color should be set in advance.""" 1352 if self.bookingMode: 1353 acts, t, duration, rng = self._parse(acts, t, duration) 1354 1355 col2 = get_color(c) 1356 for tt in rng: 1357 inputvalues = [] 1358 for a in acts: 1359 if a.GetBackfaceProperty(): 1360 col1 = a.backColor() 1361 r = lin_interpolate(tt, [t, t + duration], [col1[0], col2[0]]) 1362 g = lin_interpolate(tt, [t, t + duration], [col1[1], col2[1]]) 1363 b = lin_interpolate(tt, [t, t + duration], [col1[2], col2[2]]) 1364 inputvalues.append((r, g, b)) 1365 else: 1366 inputvalues.append(None) 1367 self.events.append((tt, self.change_backcolor, acts, inputvalues)) 1368 else: 1369 for i, a in enumerate(self._performers): 1370 a.backColor(self._inputvalues[i]) 1371 return self 1372 1373 def change_to_wireframe(self, acts=None, t=None): 1374 """Switch representation to wireframe for the input list of meshes at time `t`.""" 1375 if self.bookingMode: 1376 acts, t, _, _ = self._parse(acts, t, None) 1377 self.events.append((t, self.change_to_wireframe, acts, True)) 1378 else: 1379 for a in self._performers: 1380 a.wireframe(self._inputvalues) 1381 return self 1382 1383 def change_to_surface(self, acts=None, t=None): 1384 """Switch representation to surface for the input list of meshes at time `t`.""" 1385 if self.bookingMode: 1386 acts, t, _, _ = self._parse(acts, t, None) 1387 self.events.append((t, self.change_to_surface, acts, False)) 1388 else: 1389 for a in self._performers: 1390 a.wireframe(self._inputvalues) 1391 return self 1392 1393 def change_line_width(self, lw, acts=None, t=None, duration=None): 1394 """Gradually change line width of the mesh edges for the input list of meshes.""" 1395 if self.bookingMode: 1396 acts, t, duration, rng = self._parse(acts, t, duration) 1397 for tt in rng: 1398 inputvalues = [] 1399 for a in acts: 1400 newlw = lin_interpolate(tt, [t, t + duration], [a.lw(), lw]) 1401 inputvalues.append(newlw) 1402 self.events.append((tt, self.change_line_width, acts, inputvalues)) 1403 else: 1404 for i, a in enumerate(self._performers): 1405 a.lw(self._inputvalues[i]) 1406 return self 1407 1408 def change_line_color(self, c, acts=None, t=None, duration=None): 1409 """Gradually change line color of the mesh edges for the input list of meshes.""" 1410 if self.bookingMode: 1411 acts, t, duration, rng = self._parse(acts, t, duration) 1412 col2 = get_color(c) 1413 for tt in rng: 1414 inputvalues = [] 1415 for a in acts: 1416 col1 = a.linecolor() 1417 r = lin_interpolate(tt, [t, t + duration], [col1[0], col2[0]]) 1418 g = lin_interpolate(tt, [t, t + duration], [col1[1], col2[1]]) 1419 b = lin_interpolate(tt, [t, t + duration], [col1[2], col2[2]]) 1420 inputvalues.append((r, g, b)) 1421 self.events.append((tt, self.change_line_color, acts, inputvalues)) 1422 else: 1423 for i, a in enumerate(self._performers): 1424 a.linecolor(self._inputvalues[i]) 1425 return self 1426 1427 def change_lighting(self, style, acts=None, t=None, duration=None): 1428 """Gradually change the lighting style for the input list of meshes. 1429 1430 Allowed styles are: [metallic, plastic, shiny, glossy, default]. 1431 """ 1432 if self.bookingMode: 1433 acts, t, duration, rng = self._parse(acts, t, duration) 1434 1435 c = (1,1,0.99) 1436 if style=='metallic': pars = [0.1, 0.3, 1.0, 10, c] 1437 elif style=='plastic' : pars = [0.3, 0.4, 0.3, 5, c] 1438 elif style=='shiny' : pars = [0.2, 0.6, 0.8, 50, c] 1439 elif style=='glossy' : pars = [0.1, 0.7, 0.9, 90, c] 1440 elif style=='default' : pars = [0.1, 1.0, 0.05, 5, c] 1441 else: 1442 vedo.logger.error(f"Unknown lighting style {style}") 1443 1444 for tt in rng: 1445 inputvalues = [] 1446 for a in acts: 1447 pr = a.GetProperty() 1448 aa = pr.GetAmbient() 1449 ad = pr.GetDiffuse() 1450 asp = pr.GetSpecular() 1451 aspp = pr.GetSpecularPower() 1452 naa = lin_interpolate(tt, [t,t+duration], [aa, pars[0]]) 1453 nad = lin_interpolate(tt, [t,t+duration], [ad, pars[1]]) 1454 nasp = lin_interpolate(tt, [t,t+duration], [asp, pars[2]]) 1455 naspp= lin_interpolate(tt, [t,t+duration], [aspp,pars[3]]) 1456 inputvalues.append((naa, nad, nasp, naspp)) 1457 self.events.append((tt, self.change_lighting, acts, inputvalues)) 1458 else: 1459 for i, a in enumerate(self._performers): 1460 pr = a.GetProperty() 1461 vals = self._inputvalues[i] 1462 pr.SetAmbient(vals[0]) 1463 pr.SetDiffuse(vals[1]) 1464 pr.SetSpecular(vals[2]) 1465 pr.SetSpecularPower(vals[3]) 1466 return self 1467 1468 def move(self, act=None, pt=(0, 0, 0), t=None, duration=None, style="linear"): 1469 """Smoothly change the position of a specific object to a new point in space.""" 1470 if self.bookingMode: 1471 acts, t, duration, rng = self._parse(act, t, duration) 1472 if len(acts) != 1: 1473 vedo.logger.error("in move(), can move only one object.") 1474 cpos = acts[0].pos() 1475 pt = np.array(pt) 1476 dv = (pt - cpos) / len(rng) 1477 for j, tt in enumerate(rng): 1478 i = j + 1 1479 if "quad" in style: 1480 x = i / len(rng) 1481 y = x * x 1482 self.events.append((tt, self.move, acts, cpos + dv * i * y)) 1483 else: 1484 self.events.append((tt, self.move, acts, cpos + dv * i)) 1485 else: 1486 self._performers[0].pos(self._inputvalues) 1487 return self 1488 1489 def rotate(self, act=None, axis=(1, 0, 0), angle=0, t=None, duration=None): 1490 """Smoothly rotate a specific object by a specified angle and axis.""" 1491 if self.bookingMode: 1492 acts, t, duration, rng = self._parse(act, t, duration) 1493 if len(acts) != 1: 1494 vedo.logger.error("in rotate(), can move only one object.") 1495 for tt in rng: 1496 ang = angle / len(rng) 1497 self.events.append((tt, self.rotate, acts, (axis, ang))) 1498 else: 1499 ax = self._inputvalues[0] 1500 if ax == "x": 1501 self._performers[0].rotate_x(self._inputvalues[1]) 1502 elif ax == "y": 1503 self._performers[0].rotate_y(self._inputvalues[1]) 1504 elif ax == "z": 1505 self._performers[0].rotate_z(self._inputvalues[1]) 1506 return self 1507 1508 def scale(self, acts=None, factor=1, t=None, duration=None): 1509 """Smoothly scale a specific object to a specified scale factor.""" 1510 if self.bookingMode: 1511 acts, t, duration, rng = self._parse(acts, t, duration) 1512 for tt in rng: 1513 fac = lin_interpolate(tt, [t, t + duration], [1, factor]) 1514 self.events.append((tt, self.scale, acts, fac)) 1515 else: 1516 for a in self._performers: 1517 a.scale(self._inputvalues) 1518 return self 1519 1520 def mesh_erode(self, act=None, corner=6, t=None, duration=None): 1521 """Erode a mesh by removing cells that are close to one of the 8 corners 1522 of the bounding box. 1523 """ 1524 if self.bookingMode: 1525 acts, t, duration, rng = self._parse(act, t, duration) 1526 if len(acts) != 1: 1527 vedo.logger.error("in meshErode(), can erode only one object.") 1528 diag = acts[0].diagonal_size() 1529 x0, x1, y0, y1, z0, z1 = acts[0].GetBounds() 1530 corners = [ 1531 (x0, y0, z0), 1532 (x1, y0, z0), 1533 (x1, y1, z0), 1534 (x0, y1, z0), 1535 (x0, y0, z1), 1536 (x1, y0, z1), 1537 (x1, y1, z1), 1538 (x0, y1, z1), 1539 ] 1540 pcl = acts[0].closest_point(corners[corner]) 1541 dmin = np.linalg.norm(pcl - corners[corner]) 1542 for tt in rng: 1543 d = lin_interpolate(tt, [t, t + duration], [dmin, diag * 1.01]) 1544 if d > 0: 1545 ids = acts[0].closest_point(corners[corner], radius=d, return_point_id=True) 1546 if len(ids) <= acts[0].npoints: 1547 self.events.append((tt, self.mesh_erode, acts, ids)) 1548 return self 1549 1550 def play(self): 1551 """Play the internal list of events and save a video.""" 1552 1553 self.events = sorted(self.events, key=lambda x: x[0]) 1554 self.bookingMode = False 1555 1556 if self.show_progressbar: 1557 pb = vedo.ProgressBar(0, len(self.events), c="g") 1558 1559 if self.total_duration is None: 1560 self.total_duration = self.events[-1][0] - self.events[0][0] 1561 1562 if self.video_filename: 1563 vd = vedo.Video(self.video_filename, fps=self.video_fps, duration=self.total_duration) 1564 1565 ttlast = 0 1566 for e in self.events: 1567 1568 tt, action, self._performers, self._inputvalues = e 1569 action(0, 0) 1570 1571 dt = tt - ttlast 1572 if dt > self.eps: 1573 self.show(interactive=False, resetcam=self.resetcam) 1574 if self.video_filename: 1575 vd.add_frame() 1576 1577 if dt > self.time_resolution + self.eps: 1578 if self.video_filename: 1579 vd.pause(dt) 1580 1581 ttlast = tt 1582 1583 if self.show_progressbar: 1584 pb.print("t=" + str(int(tt * 100) / 100) + "s, " + action.__name__) 1585 1586 self.show(interactive=False, resetcam=self.resetcam) 1587 if self.video_filename: 1588 vd.add_frame() 1589 vd.close() 1590 1591 self.show(interactive=True, resetcam=self.resetcam) 1592 self.bookingMode = True 1593 1594 1595######################################################################## 1596class AnimationPlayer(vedo.Plotter): 1597 """ 1598 A Plotter with play/pause, step forward/backward and slider functionalties. 1599 Useful for inspecting time series. 1600 1601 The user has the responsibility to update all actors in the callback function. 1602 Pay attention to that the idx can both increment and decrement, 1603 as well as make large jumps. 1604 1605 Arguments: 1606 func : (Callable) 1607 a function that passes an integer as input and updates the scene 1608 irange : (tuple) 1609 the range of the integer input representing the time series index 1610 dt : (float) 1611 the time interval between two calls to `func` in milliseconds 1612 loop : (bool) 1613 whether to loop the animation 1614 c : (list, str) 1615 the color of the play/pause button 1616 bc : (list) 1617 the background color of the play/pause button and the slider 1618 button_size : (int) 1619 the size of the play/pause buttons 1620 button_pos : (float, float) 1621 the position of the play/pause buttons as a fraction of the window size 1622 button_gap : (float) 1623 the gap between the buttons 1624 slider_length : (float) 1625 the length of the slider as a fraction of the window size 1626 slider_pos : (float, float) 1627 the position of the slider as a fraction of the window size 1628 kwargs: (dict) 1629 keyword arguments to be passed to `Plotter` 1630 1631 Examples: 1632 - [aspring2_player.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring2_player.py) 1633 """ 1634 # Original class contributed by @mikaeltulldahl (Mikael Tulldahl) 1635 1636 PLAY_SYMBOL = " \u23F5 " 1637 PAUSE_SYMBOL = " \u23F8 " 1638 ONE_BACK_SYMBOL = " \u29CF" 1639 ONE_FORWARD_SYMBOL = "\u29D0 " 1640 1641 def __init__( 1642 self, 1643 func: Callable[[int],None], 1644 irange: tuple, 1645 dt: float = 1.0, 1646 loop: bool = True, 1647 c=("white", "white"), 1648 bc=("green3","red4"), 1649 button_size=25, 1650 button_pos=(0.5,0.08), 1651 button_gap=0.055, 1652 slider_length=0.5, 1653 slider_pos=(0.5,0.055), 1654 **kwargs, 1655 ): 1656 super().__init__(**kwargs) 1657 1658 min_value, max_value = np.array(irange).astype(int) 1659 button_pos = np.array(button_pos) 1660 slider_pos = np.array(slider_pos) 1661 1662 self._func = func 1663 1664 self.value = min_value-1 1665 self.min_value = min_value 1666 self.max_value = max_value 1667 self.dt = max(dt, 1) 1668 self.is_playing = False 1669 self._loop = loop 1670 1671 self.timer_callback_id = self.add_callback("timer", self._handle_timer) 1672 self.timer_id = None 1673 1674 self.play_pause_button = self.add_button( 1675 self.toggle, 1676 pos=button_pos, # x,y fraction from bottom left corner 1677 states=[self.PLAY_SYMBOL, self.PAUSE_SYMBOL], 1678 font="Kanopus", 1679 size=button_size, 1680 bc=bc, 1681 ) 1682 self.button_oneback = self.add_button( 1683 self.onebackward, 1684 pos=(-button_gap, 0) + button_pos, 1685 states=[self.ONE_BACK_SYMBOL], 1686 font="Kanopus", 1687 size=button_size, 1688 c=c, 1689 bc=bc, 1690 ) 1691 self.button_oneforward = self.add_button( 1692 self.oneforward, 1693 pos=(button_gap, 0) + button_pos, 1694 states=[self.ONE_FORWARD_SYMBOL], 1695 font="Kanopus", 1696 size=button_size, 1697 bc=bc, 1698 ) 1699 d = (1-slider_length)/2 1700 self.slider: SliderWidget = self.add_slider( 1701 self._slider_callback, 1702 self.min_value, 1703 self.max_value - 1, 1704 value=self.min_value, 1705 pos=[(d-0.5, 0)+slider_pos, (0.5-d, 0)+slider_pos], 1706 show_value=False, 1707 c=bc[0], 1708 alpha=1, 1709 ) 1710 1711 def pause(self) -> None: 1712 """Pause the animation.""" 1713 self.is_playing = False 1714 if self.timer_id is not None: 1715 self.timer_callback("destroy", self.timer_id) 1716 self.timer_id = None 1717 self.play_pause_button.status(self.PLAY_SYMBOL) 1718 1719 def resume(self) -> None: 1720 """Resume the animation.""" 1721 if self.timer_id is not None: 1722 self.timer_callback("destroy", self.timer_id) 1723 self.timer_id = self.timer_callback("create", dt=int(self.dt)) 1724 self.is_playing = True 1725 self.play_pause_button.status(self.PAUSE_SYMBOL) 1726 1727 def toggle(self) -> None: 1728 """Toggle between play and pause.""" 1729 if not self.is_playing: 1730 self.resume() 1731 else: 1732 self.pause() 1733 1734 def oneforward(self) -> None: 1735 """Advance the animation by one frame.""" 1736 self.pause() 1737 self.set_frame(self.value + 1) 1738 1739 def onebackward(self) -> None: 1740 """Go back one frame in the animation.""" 1741 self.pause() 1742 self.set_frame(self.value - 1) 1743 1744 def set_frame(self, value: int) -> None: 1745 """Set the current value of the animation.""" 1746 if self._loop: 1747 if value < self.min_value: 1748 value = self.max_value - 1 1749 elif value >= self.max_value: 1750 value = self.min_value 1751 else: 1752 if value < self.min_value: 1753 self.pause() 1754 value = self.min_value 1755 elif value >= self.max_value - 1: 1756 value = self.max_value - 1 1757 self.pause() 1758 1759 if self.value != value: 1760 self.value = value 1761 self.slider.value = value 1762 self._func(value) 1763 1764 def _slider_callback(self, widget: SliderWidget, _: str) -> None: 1765 self.pause() 1766 self.set_frame(int(round(widget.value))) 1767 1768 def _handle_timer(self, _: Event = None) -> None: 1769 self.set_frame(self.value + 1) 1770 1771 def stop(self) -> "AnimationPlayer": 1772 """ 1773 Stop the animation timers, remove buttons and slider. 1774 Behave like a normal `Plotter` after this. 1775 """ 1776 # stop timer 1777 if self.timer_id is not None: 1778 self.timer_callback("destroy", self.timer_id) 1779 self.timer_id = None 1780 1781 # remove callbacks 1782 self.remove_callback(self.timer_callback_id) 1783 1784 # remove buttons 1785 self.slider.off() 1786 self.renderer.RemoveActor(self.play_pause_button.actor) 1787 self.renderer.RemoveActor(self.button_oneback.actor) 1788 self.renderer.RemoveActor(self.button_oneforward.actor) 1789 return self 1790 1791 1792######################################################################## 1793class Clock(vedo.Assembly): 1794 """Clock animation.""" 1795 1796 def __init__(self, h=None, m=None, s=None, font="Quikhand", title="", c="k"): 1797 """ 1798 Create a clock with current time or user provided time. 1799 1800 Arguments: 1801 h : (int) 1802 hours in range [0,23] 1803 m : (int) 1804 minutes in range [0,59] 1805 s : (int) 1806 seconds in range [0,59] 1807 font : (str) 1808 font type 1809 title : (str) 1810 some extra text to show on the clock 1811 c : (str) 1812 color of the numbers 1813 1814 Example: 1815 ```python 1816 import time 1817 from vedo import show 1818 from vedo.applications import Clock 1819 clock = Clock() 1820 plt = show(clock, interactive=False) 1821 for i in range(10): 1822 time.sleep(1) 1823 clock.update() 1824 plt.render() 1825 plt.close() 1826 ``` 1827 ![](https://vedo.embl.es/images/feats/clock.png) 1828 """ 1829 self.elapsed = 0 1830 self._start = time.time() 1831 1832 wd = "" 1833 if h is None and m is None: 1834 t = time.localtime() 1835 h = t.tm_hour 1836 m = t.tm_min 1837 s = t.tm_sec 1838 if not title: 1839 d = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] 1840 wd = f"{d[t.tm_wday]} {t.tm_mday}/{t.tm_mon}/{t.tm_year} " 1841 1842 h = int(h) % 24 1843 m = int(m) % 60 1844 t = (h * 60 + m) / 12 / 60 1845 1846 alpha = 2 * np.pi * t + np.pi / 2 1847 beta = 12 * 2 * np.pi * t + np.pi / 2 1848 1849 x1, y1 = np.cos(alpha), np.sin(alpha) 1850 x2, y2 = np.cos(beta), np.sin(beta) 1851 if s is not None: 1852 s = int(s) % 60 1853 gamma = s * 2 * np.pi / 60 + np.pi / 2 1854 x3, y3 = np.cos(gamma), np.sin(gamma) 1855 1856 ore = Line([0, 0], [x1, y1], lw=14, c="red4").scale(0.5).mirror() 1857 minu = Line([0, 0], [x2, y2], lw=7, c="blue3").scale(0.75).mirror() 1858 secs = None 1859 if s is not None: 1860 secs = Line([0, 0], [x3, y3], lw=1, c="k").scale(0.95).mirror() 1861 secs.z(0.003) 1862 back1 = vedo.shapes.Circle(res=180, c="k5") 1863 back2 = vedo.shapes.Circle(res=12).mirror().scale(0.84).rotate_z(-360 / 12) 1864 labels = back2.labels(range(1, 13), justify="center", font=font, c=c, scale=0.14) 1865 txt = vedo.shapes.Text3D(wd + title, font="VictorMono", justify="top-center", s=0.07, c=c) 1866 txt.pos(0, -0.25, 0.001) 1867 labels.z(0.001) 1868 minu.z(0.002) 1869 vedo.Assembly.__init__(self, [back1, labels, ore, minu, secs, txt]) 1870 self.name = "Clock" 1871 1872 def update(self, h=None, m=None, s=None): 1873 """Update clock with current or user time.""" 1874 parts = self.unpack() 1875 self.elapsed = time.time() - self._start 1876 1877 if h is None and m is None: 1878 t = time.localtime() 1879 h = t.tm_hour 1880 m = t.tm_min 1881 s = t.tm_sec 1882 1883 h = int(h) % 24 1884 m = int(m) % 60 1885 t = (h * 60 + m) / 12 / 60 1886 1887 alpha = 2 * np.pi * t + np.pi / 2 1888 beta = 12 * 2 * np.pi * t + np.pi / 2 1889 1890 x1, y1 = np.cos(alpha), np.sin(alpha) 1891 x2, y2 = np.cos(beta), np.sin(beta) 1892 if s is not None: 1893 s = int(s) % 60 1894 gamma = s * 2 * np.pi / 60 + np.pi / 2 1895 x3, y3 = np.cos(gamma), np.sin(gamma) 1896 1897 pts2 = parts[2].points() 1898 pts2[1] = [-x1 * 0.5, y1 * 0.5, 0.001] 1899 parts[2].points(pts2) 1900 1901 pts3 = parts[3].points() 1902 pts3[1] = [-x2 * 0.75, y2 * 0.75, 0.002] 1903 parts[3].points(pts3) 1904 1905 if s is not None: 1906 pts4 = parts[4].points() 1907 pts4[1] = [-x3 * 0.95, y3 * 0.95, 0.003] 1908 parts[4].points(pts4) 1909 1910 return self
734class Browser(Plotter): 735 """ 736 Browse a series of vedo objects by using a simple slider. 737 """ 738 def __init__( 739 self, 740 objects=(), 741 sliderpos=((0.50, 0.07), (0.95, 0.07)), 742 c=None, # slider color 743 prefix="", 744 font="Calco", # slider font 745 axes=1, 746 resetcam=False, # resetcam while using the slider 747 **kwargs, 748 ): 749 """ 750 Browse a series of vedo objects by using a simple slider. 751 752 Examples: 753 ```python 754 from vedo import load, dataurl 755 from vedo.applications import Browser 756 meshes = load(dataurl+'timecourse1d.npy') # python list of Meshes 757 plt = Browser(meshes, bg='k') # vedo.Plotter 758 plt.show(interactive=False, zoom='tight') # show the meshes 759 plt.play(dt=50) # delay in milliseconds 760 plt.close() 761 ``` 762 763 - [morphomatics_tube.py](https://github.com/marcomusy/vedo/tree/master/examples/other/morphomatics_tube.py) 764 """ 765 Plotter.__init__(self, axes=axes, **kwargs) 766 767 if isinstance(objects, str): 768 objects = vedo.file_io.load(objects) 769 770 self += objects 771 772 self.slider = None 773 self.timer_callback_id = None 774 775 # define the slider func ########################## 776 def slider_function(widget=None, event=None): 777 778 must_render = False 779 if isinstance(widget, vedo.plotter.Event): 780 if self.slider.value < len(self.actors)-1: 781 self.slider.value = self.slider.value + 1 782 else: 783 self.slider.value = 0 784 must_render = True 785 786 k = int(self.slider.value) 787 ak = self.actors[k] 788 for a in self.actors: 789 if a == ak: 790 a.on() 791 else: 792 a.off() 793 if resetcam: 794 self.reset_camera() 795 tx = str(k) 796 if ak.filename: 797 tx = ak.filename.split("/")[-1] 798 tx = tx.split("\\")[-1] # windows os 799 elif ak.name: 800 tx = ak.name 801 802 self.slider.title = prefix + tx 803 804 if must_render: 805 self.render() 806 ################################################## 807 808 self.slider_function = slider_function 809 self.slider = self.add_slider( 810 slider_function, 811 0.5, 812 len(objects) - 0.5, 813 pos=sliderpos, 814 font=font, 815 c=c, 816 show_value=False, 817 ) 818 self.slider.GetRepresentation().SetTitleHeight(0.020) 819 slider_function(self.slider) # init call 820 821 def play(self, dt=100): 822 """Start playing the slides at a given speed.""" 823 self.timer_callback_id = self.add_callback("timer", self.slider_function) 824 self.timer_callback("start", dt=dt) 825 self.interactive()
Browse a series of vedo objects by using a simple slider.
738 def __init__( 739 self, 740 objects=(), 741 sliderpos=((0.50, 0.07), (0.95, 0.07)), 742 c=None, # slider color 743 prefix="", 744 font="Calco", # slider font 745 axes=1, 746 resetcam=False, # resetcam while using the slider 747 **kwargs, 748 ): 749 """ 750 Browse a series of vedo objects by using a simple slider. 751 752 Examples: 753 ```python 754 from vedo import load, dataurl 755 from vedo.applications import Browser 756 meshes = load(dataurl+'timecourse1d.npy') # python list of Meshes 757 plt = Browser(meshes, bg='k') # vedo.Plotter 758 plt.show(interactive=False, zoom='tight') # show the meshes 759 plt.play(dt=50) # delay in milliseconds 760 plt.close() 761 ``` 762 763 - [morphomatics_tube.py](https://github.com/marcomusy/vedo/tree/master/examples/other/morphomatics_tube.py) 764 """ 765 Plotter.__init__(self, axes=axes, **kwargs) 766 767 if isinstance(objects, str): 768 objects = vedo.file_io.load(objects) 769 770 self += objects 771 772 self.slider = None 773 self.timer_callback_id = None 774 775 # define the slider func ########################## 776 def slider_function(widget=None, event=None): 777 778 must_render = False 779 if isinstance(widget, vedo.plotter.Event): 780 if self.slider.value < len(self.actors)-1: 781 self.slider.value = self.slider.value + 1 782 else: 783 self.slider.value = 0 784 must_render = True 785 786 k = int(self.slider.value) 787 ak = self.actors[k] 788 for a in self.actors: 789 if a == ak: 790 a.on() 791 else: 792 a.off() 793 if resetcam: 794 self.reset_camera() 795 tx = str(k) 796 if ak.filename: 797 tx = ak.filename.split("/")[-1] 798 tx = tx.split("\\")[-1] # windows os 799 elif ak.name: 800 tx = ak.name 801 802 self.slider.title = prefix + tx 803 804 if must_render: 805 self.render() 806 ################################################## 807 808 self.slider_function = slider_function 809 self.slider = self.add_slider( 810 slider_function, 811 0.5, 812 len(objects) - 0.5, 813 pos=sliderpos, 814 font=font, 815 c=c, 816 show_value=False, 817 ) 818 self.slider.GetRepresentation().SetTitleHeight(0.020) 819 slider_function(self.slider) # init call
Browse a series of vedo objects by using a simple slider.
Examples:
from vedo import load, dataurl from vedo.applications import Browser meshes = load(dataurl+'timecourse1d.npy') # python list of Meshes plt = Browser(meshes, bg='k') # vedo.Plotter plt.show(interactive=False, zoom='tight') # show the meshes plt.play(dt=50) # delay in milliseconds plt.close()
821 def play(self, dt=100): 822 """Start playing the slides at a given speed.""" 823 self.timer_callback_id = self.add_callback("timer", self.slider_function) 824 self.timer_callback("start", dt=dt) 825 self.interactive()
Start playing the slides at a given speed.
Inherited Members
- vedo.plotter.Plotter
- process_events
- at
- add
- remove
- remove_lights
- pop
- render
- interactive
- use_depth_peeling
- background
- get_meshes
- get_volumes
- reset_camera
- reset_viewup
- move_camera
- fly_to
- look_at
- record
- parallel_projection
- fov
- zoom
- azimuth
- elevation
- roll
- dolly
- add_slider
- add_slider3d
- add_spline_tool
- add_icon
- add_global_axes
- add_legend_box
- add_hint
- add_shadows
- add_ambient_occlusion
- add_depth_of_field
- add_renderer_frame
- add_hover_legend
- add_scale_indicator
- fill_event
- add_callback
- remove_callback
- timer_callback
- compute_world_coordinate
- compute_screen_coordinates
- pick_area
- show
- add_inset
- clear
- break_interaction
- user_mode
- close_window
- close
- screenshot
- topicture
- export
- color_picker
553class IsosurfaceBrowser(Plotter): 554 """ 555 Generate a Volume isosurfacing controlled by a slider. 556 """ 557 558 def __init__( 559 self, 560 volume, 561 isovalue=None, 562 c=None, 563 alpha=1, 564 lego=False, 565 res=50, 566 use_gpu=False, 567 precompute=False, 568 progress=False, 569 cmap="hot", 570 delayed=False, 571 sliderpos=4, 572 pos=(0, 0), 573 size="auto", 574 screensize="auto", 575 title="", 576 bg="white", 577 bg2=None, 578 axes=1, 579 interactive=True, 580 ): 581 """ 582 Generate a `vedo.Plotter` for Volume isosurfacing using a slider. 583 584 Set `delayed=True` to delay slider update on mouse release. 585 586 Set `res` to set the resolution, e.g. the number of desired isosurfaces to be 587 generated on the fly. 588 589 Set `precompute=True` to precompute the isosurfaces (so slider browsing will be smoother). 590 591 Examples: 592 - [app_isobrowser.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/app_isobrowser.py) 593 594 ![](https://vedo.embl.es/images/advanced/app_isobrowser.gif) 595 """ 596 597 Plotter.__init__( 598 self, 599 pos=pos, 600 bg=bg, 601 bg2=bg2, 602 size=size, 603 screensize=screensize, 604 title=title, 605 interactive=interactive, 606 axes=axes, 607 ) 608 609 ### GPU ################################ 610 if use_gpu and hasattr(volume.GetProperty(), "GetIsoSurfaceValues"): 611 612 scrange = volume.scalar_range() 613 delta = scrange[1] - scrange[0] 614 if not delta: 615 return 616 617 if isovalue is None: 618 isovalue = delta / 3.0 + scrange[0] 619 620 ### isovalue slider callback 621 def slider_isovalue(widget, event): 622 value = widget.GetRepresentation().GetValue() 623 isovals.SetValue(0, value) 624 625 isovals = volume.GetProperty().GetIsoSurfaceValues() 626 isovals.SetValue(0, isovalue) 627 self.renderer.AddActor(volume.mode(5).alpha(alpha).c(c)) 628 629 self.add_slider( 630 slider_isovalue, 631 scrange[0] + 0.02 * delta, 632 scrange[1] - 0.02 * delta, 633 value=isovalue, 634 pos=sliderpos, 635 title="scalar value", 636 show_value=True, 637 delayed=delayed, 638 ) 639 640 ### CPU ################################ 641 else: 642 643 self._prev_value = 1e30 644 645 scrange = volume.scalar_range() 646 delta = scrange[1] - scrange[0] 647 if not delta: 648 return 649 650 if lego: 651 res = int(res / 2) # because lego is much slower 652 slidertitle = "" 653 else: 654 slidertitle = "scalar value" 655 656 allowed_vals = np.linspace(scrange[0], scrange[1], num=res) 657 658 bacts = {} # cache the meshes so we dont need to recompute 659 if precompute: 660 delayed = False # no need to delay the slider in this case 661 if progress: 662 pb = vedo.ProgressBar(0, len(allowed_vals), delay=1) 663 664 for value in allowed_vals: 665 value_name = precision(value, 2) 666 if lego: 667 mesh = volume.legosurface(vmin=value) 668 if mesh.ncells: 669 mesh.cmap(cmap, vmin=scrange[0], vmax=scrange[1], on="cells") 670 else: 671 mesh = volume.isosurface(value).color(c).alpha(alpha) 672 bacts.update({value_name: mesh}) # store it 673 if progress: 674 pb.print("isosurfacing volume..") 675 676 ### isovalue slider callback 677 def slider_isovalue(widget, event): 678 679 prevact = self.actors[0] 680 if isinstance(widget, float): 681 value = widget 682 else: 683 value = widget.GetRepresentation().GetValue() 684 685 # snap to the closest 686 idx = (np.abs(allowed_vals - value)).argmin() 687 value = allowed_vals[idx] 688 689 if abs(value - self._prev_value) / delta < 0.001: 690 return 691 self._prev_value = value 692 693 value_name = precision(value, 2) 694 if value_name in bacts: # reusing the already existing mesh 695 # print('reusing') 696 mesh = bacts[value_name] 697 else: # else generate it 698 # print('generating', value) 699 if lego: 700 mesh = volume.legosurface(vmin=value) 701 if mesh.ncells: 702 mesh.cmap(cmap, vmin=scrange[0], vmax=scrange[1], on="cells") 703 else: 704 mesh = volume.isosurface(value).color(c).alpha(alpha) 705 bacts.update({value_name: mesh}) # store it 706 707 self.renderer.RemoveActor(prevact) 708 self.renderer.AddActor(mesh) 709 self.actors[0] = mesh 710 711 ################################################ 712 713 if isovalue is None: 714 isovalue = delta / 3.0 + scrange[0] 715 716 self.actors = [None] 717 slider_isovalue(isovalue, "") # init call 718 if lego: 719 self.actors[0].add_scalarbar(pos=(0.8, 0.12)) 720 721 self.add_slider( 722 slider_isovalue, 723 scrange[0] + 0.02 * delta, 724 scrange[1] - 0.02 * delta, 725 value=isovalue, 726 pos=sliderpos, 727 title=slidertitle, 728 show_value=True, 729 delayed=delayed, 730 )
Generate a Volume isosurfacing controlled by a slider.
558 def __init__( 559 self, 560 volume, 561 isovalue=None, 562 c=None, 563 alpha=1, 564 lego=False, 565 res=50, 566 use_gpu=False, 567 precompute=False, 568 progress=False, 569 cmap="hot", 570 delayed=False, 571 sliderpos=4, 572 pos=(0, 0), 573 size="auto", 574 screensize="auto", 575 title="", 576 bg="white", 577 bg2=None, 578 axes=1, 579 interactive=True, 580 ): 581 """ 582 Generate a `vedo.Plotter` for Volume isosurfacing using a slider. 583 584 Set `delayed=True` to delay slider update on mouse release. 585 586 Set `res` to set the resolution, e.g. the number of desired isosurfaces to be 587 generated on the fly. 588 589 Set `precompute=True` to precompute the isosurfaces (so slider browsing will be smoother). 590 591 Examples: 592 - [app_isobrowser.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/app_isobrowser.py) 593 594 ![](https://vedo.embl.es/images/advanced/app_isobrowser.gif) 595 """ 596 597 Plotter.__init__( 598 self, 599 pos=pos, 600 bg=bg, 601 bg2=bg2, 602 size=size, 603 screensize=screensize, 604 title=title, 605 interactive=interactive, 606 axes=axes, 607 ) 608 609 ### GPU ################################ 610 if use_gpu and hasattr(volume.GetProperty(), "GetIsoSurfaceValues"): 611 612 scrange = volume.scalar_range() 613 delta = scrange[1] - scrange[0] 614 if not delta: 615 return 616 617 if isovalue is None: 618 isovalue = delta / 3.0 + scrange[0] 619 620 ### isovalue slider callback 621 def slider_isovalue(widget, event): 622 value = widget.GetRepresentation().GetValue() 623 isovals.SetValue(0, value) 624 625 isovals = volume.GetProperty().GetIsoSurfaceValues() 626 isovals.SetValue(0, isovalue) 627 self.renderer.AddActor(volume.mode(5).alpha(alpha).c(c)) 628 629 self.add_slider( 630 slider_isovalue, 631 scrange[0] + 0.02 * delta, 632 scrange[1] - 0.02 * delta, 633 value=isovalue, 634 pos=sliderpos, 635 title="scalar value", 636 show_value=True, 637 delayed=delayed, 638 ) 639 640 ### CPU ################################ 641 else: 642 643 self._prev_value = 1e30 644 645 scrange = volume.scalar_range() 646 delta = scrange[1] - scrange[0] 647 if not delta: 648 return 649 650 if lego: 651 res = int(res / 2) # because lego is much slower 652 slidertitle = "" 653 else: 654 slidertitle = "scalar value" 655 656 allowed_vals = np.linspace(scrange[0], scrange[1], num=res) 657 658 bacts = {} # cache the meshes so we dont need to recompute 659 if precompute: 660 delayed = False # no need to delay the slider in this case 661 if progress: 662 pb = vedo.ProgressBar(0, len(allowed_vals), delay=1) 663 664 for value in allowed_vals: 665 value_name = precision(value, 2) 666 if lego: 667 mesh = volume.legosurface(vmin=value) 668 if mesh.ncells: 669 mesh.cmap(cmap, vmin=scrange[0], vmax=scrange[1], on="cells") 670 else: 671 mesh = volume.isosurface(value).color(c).alpha(alpha) 672 bacts.update({value_name: mesh}) # store it 673 if progress: 674 pb.print("isosurfacing volume..") 675 676 ### isovalue slider callback 677 def slider_isovalue(widget, event): 678 679 prevact = self.actors[0] 680 if isinstance(widget, float): 681 value = widget 682 else: 683 value = widget.GetRepresentation().GetValue() 684 685 # snap to the closest 686 idx = (np.abs(allowed_vals - value)).argmin() 687 value = allowed_vals[idx] 688 689 if abs(value - self._prev_value) / delta < 0.001: 690 return 691 self._prev_value = value 692 693 value_name = precision(value, 2) 694 if value_name in bacts: # reusing the already existing mesh 695 # print('reusing') 696 mesh = bacts[value_name] 697 else: # else generate it 698 # print('generating', value) 699 if lego: 700 mesh = volume.legosurface(vmin=value) 701 if mesh.ncells: 702 mesh.cmap(cmap, vmin=scrange[0], vmax=scrange[1], on="cells") 703 else: 704 mesh = volume.isosurface(value).color(c).alpha(alpha) 705 bacts.update({value_name: mesh}) # store it 706 707 self.renderer.RemoveActor(prevact) 708 self.renderer.AddActor(mesh) 709 self.actors[0] = mesh 710 711 ################################################ 712 713 if isovalue is None: 714 isovalue = delta / 3.0 + scrange[0] 715 716 self.actors = [None] 717 slider_isovalue(isovalue, "") # init call 718 if lego: 719 self.actors[0].add_scalarbar(pos=(0.8, 0.12)) 720 721 self.add_slider( 722 slider_isovalue, 723 scrange[0] + 0.02 * delta, 724 scrange[1] - 0.02 * delta, 725 value=isovalue, 726 pos=sliderpos, 727 title=slidertitle, 728 show_value=True, 729 delayed=delayed, 730 )
Generate a vedo.Plotter
for Volume isosurfacing using a slider.
Set delayed=True
to delay slider update on mouse release.
Set res
to set the resolution, e.g. the number of desired isosurfaces to be
generated on the fly.
Set precompute=True
to precompute the isosurfaces (so slider browsing will be smoother).
Examples:
Inherited Members
- vedo.plotter.Plotter
- process_events
- at
- add
- remove
- remove_lights
- pop
- render
- interactive
- use_depth_peeling
- background
- get_meshes
- get_volumes
- reset_camera
- reset_viewup
- move_camera
- fly_to
- look_at
- record
- play
- parallel_projection
- fov
- zoom
- azimuth
- elevation
- roll
- dolly
- add_slider
- add_slider3d
- add_spline_tool
- add_icon
- add_global_axes
- add_legend_box
- add_hint
- add_shadows
- add_ambient_occlusion
- add_depth_of_field
- add_renderer_frame
- add_hover_legend
- add_scale_indicator
- fill_event
- add_callback
- remove_callback
- timer_callback
- compute_world_coordinate
- compute_screen_coordinates
- pick_area
- show
- add_inset
- clear
- break_interaction
- user_mode
- close_window
- close
- screenshot
- topicture
- export
- color_picker
829class FreeHandCutPlotter(Plotter): 830 """A tool to edit meshes interactively.""" 831 832 # thanks to Jakub Kaminski for the original version of this script 833 def __init__( 834 self, 835 mesh, 836 splined=True, 837 font="Bongas", 838 alpha=0.9, 839 lw=4, 840 lc="red5", 841 pc="red4", 842 c="green3", 843 tc="k9", 844 tol=0.008, 845 **options, 846 ): 847 """ 848 A `vedo.Plotter` derived class which edits polygonal meshes interactively. 849 850 Can also be invoked from command line with: 851 852 ```bash 853 vedo --edit https://vedo.embl.es/examples/data/porsche.ply 854 ``` 855 856 Usage: 857 - Left-click and hold to rotate 858 - Right-click and move to draw line 859 - Second right-click to stop drawing 860 - Press "c" to clear points 861 - "z/Z" to cut mesh (Z inverts inside-out the selection area) 862 - "L" to keep only the largest connected surface 863 - "s" to save mesh to file (tag `_edited` is appended to filename) 864 - "u" to undo last action 865 - "h" for help, "i" for info 866 867 Arguments: 868 mesh : (Mesh, Points) 869 The input Mesh or pointcloud. 870 splined : (bool) 871 join points with a spline or a simple line. 872 font : (str) 873 Font name for the instructions. 874 alpha : (float) 875 transparency of the instruction message panel. 876 lw : (str) 877 selection line width. 878 lc : (str) 879 selection line color. 880 pc : (str) 881 selection points color. 882 c : (str) 883 background color of instructions. 884 tc : (str) 885 text color of instructions. 886 tol : (int) 887 tolerance of the point proximity. 888 889 Examples: 890 - [cut_freehand.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/cut_freehand.py) 891 892 ![](https://vedo.embl.es/images/basic/cutFreeHand.gif) 893 """ 894 895 if not isinstance(mesh, Points): 896 vedo.logger.error("FreeHandCutPlotter input must be Points or Mesh") 897 raise RuntimeError() 898 899 super().__init__(**options) 900 901 self.mesh = mesh 902 self.mesh_prev = mesh 903 self.splined = splined 904 self.linecolor = lc 905 self.linewidth = lw 906 self.pointcolor = pc 907 self.color = c 908 self.alpha = alpha 909 910 self.msg = "Right-click and move to draw line\n" 911 self.msg += "Second right-click to stop drawing\n" 912 self.msg += "Press L to extract largest surface\n" 913 self.msg += " z/Z to cut mesh (s to save)\n" 914 self.msg += " c to clear points, u to undo" 915 self.txt2d = Text2D(self.msg, pos="top-left", font=font, s=0.9) 916 self.txt2d.c(tc).background(c, alpha).frame() 917 918 self.idkeypress = self.add_callback("KeyPress", self._on_keypress) 919 self.idrightclck = self.add_callback("RightButton", self._on_right_click) 920 self.idmousemove = self.add_callback("MouseMove", self._on_mouse_move) 921 self.drawmode = False 922 self.tol = tol # tolerance of point distance 923 self.cpoints = [] 924 self.points = None 925 self.spline = None 926 self.jline = None 927 self.topline = None 928 self.top_pts = [] 929 930 def init(self, init_points): 931 """Set an initial number of points to define a region""" 932 if isinstance(init_points, Points): 933 self.cpoints = init_points.points() 934 else: 935 self.cpoints = np.array(init_points) 936 self.points = Points(self.cpoints, r=self.linewidth).c(self.pointcolor).pickable(0) 937 if self.splined: 938 self.spline = Spline(self.cpoints, res=len(self.cpoints) * 4) 939 else: 940 self.spline = Line(self.cpoints) 941 self.spline.lw(self.linewidth).c(self.linecolor).pickable(False) 942 self.jline = Line(self.cpoints[0], self.cpoints[-1], lw=1, c=self.linecolor).pickable(0) 943 self.add([self.points, self.spline, self.jline]).render() 944 return self 945 946 def _on_right_click(self, evt): 947 self.drawmode = not self.drawmode # toggle mode 948 if self.drawmode: 949 self.txt2d.background(self.linecolor, self.alpha) 950 else: 951 self.txt2d.background(self.color, self.alpha) 952 if len(self.cpoints) > 2: 953 self.remove([self.spline, self.jline]) 954 if self.splined: # show the spline closed 955 self.spline = Spline(self.cpoints, closed=True, res=len(self.cpoints) * 4) 956 else: 957 self.spline = Line(self.cpoints, closed=True) 958 self.spline.lw(self.linewidth).c(self.linecolor).pickable(False) 959 self.add(self.spline) 960 self.render() 961 962 def _on_mouse_move(self, evt): 963 if self.drawmode: 964 cpt = self.compute_world_coordinate(evt.picked2d) # make this 2d-screen point 3d 965 if self.cpoints and mag(cpt - self.cpoints[-1]) < self.mesh.diagonal_size() * self.tol: 966 return # new point is too close to the last one. skip 967 self.cpoints.append(cpt) 968 if len(self.cpoints) > 2: 969 self.remove([self.points, self.spline, self.jline, self.topline]) 970 self.points = Points(self.cpoints, r=self.linewidth).c(self.pointcolor).pickable(0) 971 if self.splined: 972 self.spline = Spline(self.cpoints, res=len(self.cpoints) * 4) # not closed here 973 else: 974 self.spline = Line(self.cpoints) 975 976 if evt.actor: 977 self.top_pts.append(evt.picked3d) 978 self.topline = Points(self.top_pts, r=self.linewidth) 979 self.topline.c(self.linecolor).pickable(False) 980 981 self.spline.lw(self.linewidth).c(self.linecolor).pickable(False) 982 self.txt2d.background(self.linecolor) 983 self.jline = Line(self.cpoints[0], self.cpoints[-1], lw=1, c=self.linecolor).pickable(0) 984 self.add([self.points, self.spline, self.jline, self.topline]).render() 985 986 def _on_keypress(self, evt): 987 if evt.keypress.lower() == "z" and self.spline: # Cut mesh with a ribbon-like surface 988 inv = False 989 if evt.keypress == "Z": 990 inv = True 991 self.txt2d.background("red8").text(" ... working ... ") 992 self.render() 993 self.mesh_prev = self.mesh.clone() 994 tol = self.mesh.diagonal_size() / 2 # size of ribbon (not shown) 995 pts = self.spline.points() 996 n = fit_plane(pts, signed=True).normal # compute normal vector to points 997 rb = Ribbon(pts - tol * n, pts + tol * n, closed=True) 998 self.mesh.cut_with_mesh(rb, invert=inv) # CUT 999 self.txt2d.text(self.msg) # put back original message 1000 if self.drawmode: 1001 self._on_right_click(evt) # toggle mode to normal 1002 else: 1003 self.txt2d.background(self.color, self.alpha) 1004 self.remove([self.spline, self.points, self.jline, self.topline]).render() 1005 self.cpoints, self.points, self.spline = [], None, None 1006 self.top_pts, self.topline = [], None 1007 1008 elif evt.keypress == "L": 1009 self.txt2d.background("red8") 1010 self.txt2d.text(" ... removing smaller ... \n ... parts of the mesh ... ") 1011 self.render() 1012 self.remove(self.mesh) 1013 self.mesh_prev = self.mesh 1014 mcut = self.mesh.extract_largest_region() 1015 mcut.filename = self.mesh.filename # copy over various properties 1016 mcut.name = self.mesh.name 1017 mcut.scalarbar = self.mesh.scalarbar 1018 mcut.info = self.mesh.info 1019 self.mesh = mcut # discard old mesh by overwriting it 1020 self.txt2d.text(self.msg).background(self.color) # put back original message 1021 self.add(mcut).render() 1022 1023 elif evt.keypress == 'u': # Undo last action 1024 if self.drawmode: 1025 self._on_right_click(evt) # toggle mode to normal 1026 else: 1027 self.txt2d.background(self.color, self.alpha) 1028 self.remove([self.mesh, self.spline, self.jline, self.points, self.topline]) 1029 self.mesh = self.mesh_prev 1030 self.cpoints, self.points, self.spline = [], None, None 1031 self.top_pts, self.topline = [], None 1032 self.add(self.mesh).render() 1033 1034 elif evt.keypress in ("c", "Delete"): 1035 # clear all points 1036 self.remove([self.spline, self.points, self.jline, self.topline]).render() 1037 self.cpoints, self.points, self.spline = [], None, None 1038 self.top_pts, self.topline = [], None 1039 1040 elif evt.keypress == "r": # reset camera and axes 1041 try: 1042 self.remove(self.axes_instances[0]) 1043 self.axes_instances[0] = None 1044 self.add_global_axes(axtype=1, c=None, bounds=self.mesh.bounds()) 1045 self.renderer.ResetCamera() 1046 self.render() 1047 except: 1048 pass 1049 1050 elif evt.keypress == "s": 1051 if self.mesh.filename: 1052 fname = os.path.basename(self.mesh.filename) 1053 fname, extension = os.path.splitext(fname) 1054 fname = fname.replace("_edited", "") 1055 fname = f"{fname}_edited{extension}" 1056 else: 1057 fname = "mesh_edited.vtk" 1058 self.write(fname) 1059 1060 def write(self, filename="mesh_edited.vtk"): 1061 """Save the resulting mesh to file""" 1062 self.mesh.write(filename) 1063 vedo.logger.info(f"mesh saved to file {filename}") 1064 return self 1065 1066 def start(self, *args, **kwargs): 1067 """Start window interaction (with mouse and keyboard)""" 1068 acts = [self.txt2d, self.mesh, self.points, self.spline, self.jline] 1069 self.show(acts + list(args), **kwargs) 1070 return self
A tool to edit meshes interactively.
833 def __init__( 834 self, 835 mesh, 836 splined=True, 837 font="Bongas", 838 alpha=0.9, 839 lw=4, 840 lc="red5", 841 pc="red4", 842 c="green3", 843 tc="k9", 844 tol=0.008, 845 **options, 846 ): 847 """ 848 A `vedo.Plotter` derived class which edits polygonal meshes interactively. 849 850 Can also be invoked from command line with: 851 852 ```bash 853 vedo --edit https://vedo.embl.es/examples/data/porsche.ply 854 ``` 855 856 Usage: 857 - Left-click and hold to rotate 858 - Right-click and move to draw line 859 - Second right-click to stop drawing 860 - Press "c" to clear points 861 - "z/Z" to cut mesh (Z inverts inside-out the selection area) 862 - "L" to keep only the largest connected surface 863 - "s" to save mesh to file (tag `_edited` is appended to filename) 864 - "u" to undo last action 865 - "h" for help, "i" for info 866 867 Arguments: 868 mesh : (Mesh, Points) 869 The input Mesh or pointcloud. 870 splined : (bool) 871 join points with a spline or a simple line. 872 font : (str) 873 Font name for the instructions. 874 alpha : (float) 875 transparency of the instruction message panel. 876 lw : (str) 877 selection line width. 878 lc : (str) 879 selection line color. 880 pc : (str) 881 selection points color. 882 c : (str) 883 background color of instructions. 884 tc : (str) 885 text color of instructions. 886 tol : (int) 887 tolerance of the point proximity. 888 889 Examples: 890 - [cut_freehand.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/cut_freehand.py) 891 892 ![](https://vedo.embl.es/images/basic/cutFreeHand.gif) 893 """ 894 895 if not isinstance(mesh, Points): 896 vedo.logger.error("FreeHandCutPlotter input must be Points or Mesh") 897 raise RuntimeError() 898 899 super().__init__(**options) 900 901 self.mesh = mesh 902 self.mesh_prev = mesh 903 self.splined = splined 904 self.linecolor = lc 905 self.linewidth = lw 906 self.pointcolor = pc 907 self.color = c 908 self.alpha = alpha 909 910 self.msg = "Right-click and move to draw line\n" 911 self.msg += "Second right-click to stop drawing\n" 912 self.msg += "Press L to extract largest surface\n" 913 self.msg += " z/Z to cut mesh (s to save)\n" 914 self.msg += " c to clear points, u to undo" 915 self.txt2d = Text2D(self.msg, pos="top-left", font=font, s=0.9) 916 self.txt2d.c(tc).background(c, alpha).frame() 917 918 self.idkeypress = self.add_callback("KeyPress", self._on_keypress) 919 self.idrightclck = self.add_callback("RightButton", self._on_right_click) 920 self.idmousemove = self.add_callback("MouseMove", self._on_mouse_move) 921 self.drawmode = False 922 self.tol = tol # tolerance of point distance 923 self.cpoints = [] 924 self.points = None 925 self.spline = None 926 self.jline = None 927 self.topline = None 928 self.top_pts = []
A vedo.Plotter
derived class which edits polygonal meshes interactively.
Can also be invoked from command line with:
vedo --edit https://vedo.embl.es/examples/data/porsche.ply
Usage:
- Left-click and hold to rotate
- Right-click and move to draw line
- Second right-click to stop drawing
- Press "c" to clear points
- "z/Z" to cut mesh (Z inverts inside-out the selection area)
- "L" to keep only the largest connected surface
- "s" to save mesh to file (tag
_edited
is appended to filename)- "u" to undo last action
- "h" for help, "i" for info
Arguments:
- mesh : (Mesh, Points) The input Mesh or pointcloud.
- splined : (bool) join points with a spline or a simple line.
- font : (str) Font name for the instructions.
- alpha : (float) transparency of the instruction message panel.
- lw : (str) selection line width.
- lc : (str) selection line color.
- pc : (str) selection points color.
- c : (str) background color of instructions.
- tc : (str) text color of instructions.
- tol : (int) tolerance of the point proximity.
Examples:
930 def init(self, init_points): 931 """Set an initial number of points to define a region""" 932 if isinstance(init_points, Points): 933 self.cpoints = init_points.points() 934 else: 935 self.cpoints = np.array(init_points) 936 self.points = Points(self.cpoints, r=self.linewidth).c(self.pointcolor).pickable(0) 937 if self.splined: 938 self.spline = Spline(self.cpoints, res=len(self.cpoints) * 4) 939 else: 940 self.spline = Line(self.cpoints) 941 self.spline.lw(self.linewidth).c(self.linecolor).pickable(False) 942 self.jline = Line(self.cpoints[0], self.cpoints[-1], lw=1, c=self.linecolor).pickable(0) 943 self.add([self.points, self.spline, self.jline]).render() 944 return self
Set an initial number of points to define a region
1060 def write(self, filename="mesh_edited.vtk"): 1061 """Save the resulting mesh to file""" 1062 self.mesh.write(filename) 1063 vedo.logger.info(f"mesh saved to file {filename}") 1064 return self
Save the resulting mesh to file
1066 def start(self, *args, **kwargs): 1067 """Start window interaction (with mouse and keyboard)""" 1068 acts = [self.txt2d, self.mesh, self.points, self.spline, self.jline] 1069 self.show(acts + list(args), **kwargs) 1070 return self
Start window interaction (with mouse and keyboard)
Inherited Members
- vedo.plotter.Plotter
- process_events
- at
- add
- remove
- remove_lights
- pop
- render
- interactive
- use_depth_peeling
- background
- get_meshes
- get_volumes
- reset_camera
- reset_viewup
- move_camera
- fly_to
- look_at
- record
- play
- parallel_projection
- fov
- zoom
- azimuth
- elevation
- roll
- dolly
- add_slider
- add_slider3d
- add_spline_tool
- add_icon
- add_global_axes
- add_legend_box
- add_hint
- add_shadows
- add_ambient_occlusion
- add_depth_of_field
- add_renderer_frame
- add_hover_legend
- add_scale_indicator
- fill_event
- add_callback
- remove_callback
- timer_callback
- compute_world_coordinate
- compute_screen_coordinates
- pick_area
- show
- add_inset
- clear
- break_interaction
- user_mode
- close_window
- close
- screenshot
- topicture
- export
- color_picker
378class RayCastPlotter(Plotter): 379 """ 380 Generate Volume rendering using ray casting. 381 """ 382 383 def __init__(self, volume, **kwargs): 384 """ 385 Generate a window for Volume rendering using ray casting. 386 387 Returns: 388 `vedo.Plotter` object. 389 390 Examples: 391 - [app_raycaster.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/app_raycaster.py) 392 393 ![](https://vedo.embl.es/images/advanced/app_raycaster.gif) 394 """ 395 396 Plotter.__init__(self, **kwargs) 397 398 self.alphaslider0 = 0.33 399 self.alphaslider1 = 0.66 400 self.alphaslider2 = 1 401 402 self.property = volume.GetProperty() 403 img = volume.imagedata() 404 405 if volume.dimensions()[2] < 3: 406 vedo.logger.error("RayCastPlotter: not enough z slices.") 407 raise RuntimeError 408 409 smin, smax = img.GetScalarRange() 410 x0alpha = smin + (smax - smin) * 0.25 411 x1alpha = smin + (smax - smin) * 0.5 412 x2alpha = smin + (smax - smin) * 1.0 413 414 ############################## color map slider 415 # Create transfer mapping scalar value to color 416 cmaps = [ 417 "jet", 418 "viridis", 419 "bone", 420 "hot", 421 "plasma", 422 "winter", 423 "cool", 424 "gist_earth", 425 "coolwarm", 426 "tab10", 427 ] 428 cols_cmaps = [] 429 for cm in cmaps: 430 cols = color_map(range(0, 21), cm, 0, 20) # sample 20 colors 431 cols_cmaps.append(cols) 432 Ncols = len(cmaps) 433 csl = (0.9, 0.9, 0.9) 434 if sum(get_color(self.renderer.GetBackground())) > 1.5: 435 csl = (0.1, 0.1, 0.1) 436 437 def sliderColorMap(widget, event): 438 sliderRep = widget.GetRepresentation() 439 k = int(sliderRep.GetValue()) 440 sliderRep.SetTitleText(cmaps[k]) 441 volume.color(cmaps[k]) 442 443 w1 = self.add_slider( 444 sliderColorMap, 445 0, 446 Ncols - 1, 447 value=0, 448 show_value=0, 449 title=cmaps[0], 450 c=csl, 451 pos=[(0.8, 0.05), (0.965, 0.05)], 452 ) 453 w1.GetRepresentation().SetTitleHeight(0.018) 454 455 ############################## alpha sliders 456 # Create transfer mapping scalar value to opacity 457 opacityTransferFunction = self.property.GetScalarOpacity() 458 459 def setOTF(): 460 opacityTransferFunction.RemoveAllPoints() 461 opacityTransferFunction.AddPoint(smin, 0.0) 462 opacityTransferFunction.AddPoint(smin + (smax - smin) * 0.1, 0.0) 463 opacityTransferFunction.AddPoint(x0alpha, self.alphaslider0) 464 opacityTransferFunction.AddPoint(x1alpha, self.alphaslider1) 465 opacityTransferFunction.AddPoint(x2alpha, self.alphaslider2) 466 467 setOTF() 468 469 def sliderA0(widget, event): 470 self.alphaslider0 = widget.GetRepresentation().GetValue() 471 setOTF() 472 473 self.add_slider( 474 sliderA0, 475 0, 476 1, 477 value=self.alphaslider0, 478 pos=[(0.84, 0.1), (0.84, 0.26)], 479 c=csl, 480 show_value=0, 481 ) 482 483 def sliderA1(widget, event): 484 self.alphaslider1 = widget.GetRepresentation().GetValue() 485 setOTF() 486 487 self.add_slider( 488 sliderA1, 489 0, 490 1, 491 value=self.alphaslider1, 492 pos=[(0.89, 0.1), (0.89, 0.26)], 493 c=csl, 494 show_value=0, 495 ) 496 497 def sliderA2(widget, event): 498 self.alphaslider2 = widget.GetRepresentation().GetValue() 499 setOTF() 500 501 w2 = self.add_slider( 502 sliderA2, 503 0, 504 1, 505 value=self.alphaslider2, 506 pos=[(0.96, 0.1), (0.96, 0.26)], 507 c=csl, 508 show_value=0, 509 title="Opacity levels", 510 ) 511 w2.GetRepresentation().SetTitleHeight(0.016) 512 513 # add a button 514 def button_func_mode(): 515 s = volume.mode() 516 snew = (s + 1) % 2 517 volume.mode(snew) 518 bum.switch() 519 520 bum = self.add_button( 521 button_func_mode, 522 pos=(0.7, 0.035), 523 states=["composite", "max proj."], 524 c=["bb", "gray"], 525 bc=["gray", "bb"], # colors of states 526 font="", 527 size=16, 528 bold=0, 529 italic=False, 530 ) 531 bum.status(volume.mode()) 532 533 # add histogram of scalar 534 plot = CornerHistogram( 535 volume, 536 bins=25, 537 logscale=1, 538 c=(0.7, 0.7, 0.7), 539 bg=(0.7, 0.7, 0.7), 540 pos=(0.78, 0.065), 541 lines=True, 542 dots=False, 543 nmax=3.1415e06, # subsample otherwise is too slow 544 ) 545 546 plot.GetPosition2Coordinate().SetValue(0.197, 0.20, 0) 547 plot.GetXAxisActor2D().SetFontFactor(0.7) 548 plot.GetProperty().SetOpacity(0.5) 549 self.add([plot, volume])
Generate Volume rendering using ray casting.
383 def __init__(self, volume, **kwargs): 384 """ 385 Generate a window for Volume rendering using ray casting. 386 387 Returns: 388 `vedo.Plotter` object. 389 390 Examples: 391 - [app_raycaster.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/app_raycaster.py) 392 393 ![](https://vedo.embl.es/images/advanced/app_raycaster.gif) 394 """ 395 396 Plotter.__init__(self, **kwargs) 397 398 self.alphaslider0 = 0.33 399 self.alphaslider1 = 0.66 400 self.alphaslider2 = 1 401 402 self.property = volume.GetProperty() 403 img = volume.imagedata() 404 405 if volume.dimensions()[2] < 3: 406 vedo.logger.error("RayCastPlotter: not enough z slices.") 407 raise RuntimeError 408 409 smin, smax = img.GetScalarRange() 410 x0alpha = smin + (smax - smin) * 0.25 411 x1alpha = smin + (smax - smin) * 0.5 412 x2alpha = smin + (smax - smin) * 1.0 413 414 ############################## color map slider 415 # Create transfer mapping scalar value to color 416 cmaps = [ 417 "jet", 418 "viridis", 419 "bone", 420 "hot", 421 "plasma", 422 "winter", 423 "cool", 424 "gist_earth", 425 "coolwarm", 426 "tab10", 427 ] 428 cols_cmaps = [] 429 for cm in cmaps: 430 cols = color_map(range(0, 21), cm, 0, 20) # sample 20 colors 431 cols_cmaps.append(cols) 432 Ncols = len(cmaps) 433 csl = (0.9, 0.9, 0.9) 434 if sum(get_color(self.renderer.GetBackground())) > 1.5: 435 csl = (0.1, 0.1, 0.1) 436 437 def sliderColorMap(widget, event): 438 sliderRep = widget.GetRepresentation() 439 k = int(sliderRep.GetValue()) 440 sliderRep.SetTitleText(cmaps[k]) 441 volume.color(cmaps[k]) 442 443 w1 = self.add_slider( 444 sliderColorMap, 445 0, 446 Ncols - 1, 447 value=0, 448 show_value=0, 449 title=cmaps[0], 450 c=csl, 451 pos=[(0.8, 0.05), (0.965, 0.05)], 452 ) 453 w1.GetRepresentation().SetTitleHeight(0.018) 454 455 ############################## alpha sliders 456 # Create transfer mapping scalar value to opacity 457 opacityTransferFunction = self.property.GetScalarOpacity() 458 459 def setOTF(): 460 opacityTransferFunction.RemoveAllPoints() 461 opacityTransferFunction.AddPoint(smin, 0.0) 462 opacityTransferFunction.AddPoint(smin + (smax - smin) * 0.1, 0.0) 463 opacityTransferFunction.AddPoint(x0alpha, self.alphaslider0) 464 opacityTransferFunction.AddPoint(x1alpha, self.alphaslider1) 465 opacityTransferFunction.AddPoint(x2alpha, self.alphaslider2) 466 467 setOTF() 468 469 def sliderA0(widget, event): 470 self.alphaslider0 = widget.GetRepresentation().GetValue() 471 setOTF() 472 473 self.add_slider( 474 sliderA0, 475 0, 476 1, 477 value=self.alphaslider0, 478 pos=[(0.84, 0.1), (0.84, 0.26)], 479 c=csl, 480 show_value=0, 481 ) 482 483 def sliderA1(widget, event): 484 self.alphaslider1 = widget.GetRepresentation().GetValue() 485 setOTF() 486 487 self.add_slider( 488 sliderA1, 489 0, 490 1, 491 value=self.alphaslider1, 492 pos=[(0.89, 0.1), (0.89, 0.26)], 493 c=csl, 494 show_value=0, 495 ) 496 497 def sliderA2(widget, event): 498 self.alphaslider2 = widget.GetRepresentation().GetValue() 499 setOTF() 500 501 w2 = self.add_slider( 502 sliderA2, 503 0, 504 1, 505 value=self.alphaslider2, 506 pos=[(0.96, 0.1), (0.96, 0.26)], 507 c=csl, 508 show_value=0, 509 title="Opacity levels", 510 ) 511 w2.GetRepresentation().SetTitleHeight(0.016) 512 513 # add a button 514 def button_func_mode(): 515 s = volume.mode() 516 snew = (s + 1) % 2 517 volume.mode(snew) 518 bum.switch() 519 520 bum = self.add_button( 521 button_func_mode, 522 pos=(0.7, 0.035), 523 states=["composite", "max proj."], 524 c=["bb", "gray"], 525 bc=["gray", "bb"], # colors of states 526 font="", 527 size=16, 528 bold=0, 529 italic=False, 530 ) 531 bum.status(volume.mode()) 532 533 # add histogram of scalar 534 plot = CornerHistogram( 535 volume, 536 bins=25, 537 logscale=1, 538 c=(0.7, 0.7, 0.7), 539 bg=(0.7, 0.7, 0.7), 540 pos=(0.78, 0.065), 541 lines=True, 542 dots=False, 543 nmax=3.1415e06, # subsample otherwise is too slow 544 ) 545 546 plot.GetPosition2Coordinate().SetValue(0.197, 0.20, 0) 547 plot.GetXAxisActor2D().SetFontFactor(0.7) 548 plot.GetProperty().SetOpacity(0.5) 549 self.add([plot, volume])
Generate a window for Volume rendering using ray casting.
Returns:
vedo.Plotter
object.
Examples:
Inherited Members
- vedo.plotter.Plotter
- process_events
- at
- add
- remove
- remove_lights
- pop
- render
- interactive
- use_depth_peeling
- background
- get_meshes
- get_volumes
- reset_camera
- reset_viewup
- move_camera
- fly_to
- look_at
- record
- play
- parallel_projection
- fov
- zoom
- azimuth
- elevation
- roll
- dolly
- add_slider
- add_slider3d
- add_spline_tool
- add_icon
- add_global_axes
- add_legend_box
- add_hint
- add_shadows
- add_ambient_occlusion
- add_depth_of_field
- add_renderer_frame
- add_hover_legend
- add_scale_indicator
- fill_event
- add_callback
- remove_callback
- timer_callback
- compute_world_coordinate
- compute_screen_coordinates
- pick_area
- show
- add_inset
- clear
- break_interaction
- user_mode
- close_window
- close
- screenshot
- topicture
- export
- color_picker
42class Slicer3DPlotter(Plotter): 43 """ 44 Generate a rendering window with slicing planes for the input Volume. 45 """ 46 47 def __init__( 48 self, 49 volume, 50 alpha=1, 51 cmaps=("gist_ncar_r", "hot_r", "bone_r", "jet", "Spectral_r"), 52 map2cells=False, # buggy 53 clamp=True, 54 use_slider3d=False, 55 show_histo=True, 56 show_icon=True, 57 draggable=False, 58 pos=(0, 0), 59 size="auto", 60 screensize="auto", 61 title="", 62 bg="white", 63 bg2="lightblue", 64 axes=7, 65 resetcam=True, 66 interactive=True, 67 ): 68 """ 69 Generate a rendering window with slicing planes for the input Volume. 70 71 Arguments: 72 alpha : (float) 73 transparency of the slicing planes 74 cmaps : (list) 75 list of color maps names to cycle when clicking button 76 map2cells : (bool) 77 scalars are mapped to cells, not interpolated 78 clamp : (bool) 79 clamp scalar to reduce the effect of tails in color mapping 80 use_slider3d : (bool) 81 show sliders attached along the axes 82 show_histo : (bool) 83 show histogram on bottom left 84 show_icon : (bool) 85 show a small 3D rendering icon of the volume 86 draggable : (bool) 87 make the icon draggable 88 89 Examples: 90 - [slicer1.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/slicer1.py) 91 92 <img src="https://vedo.embl.es/images/volumetric/slicer1.jpg" width="500"> 93 """ 94 self._cmap_slicer = "gist_ncar_r" 95 96 if not title: 97 if volume.filename: 98 title = volume.filename 99 else: 100 title = "Volume Slicer" 101 102 ################################ 103 Plotter.__init__( 104 self, 105 pos=pos, 106 bg=bg, 107 bg2=bg2, 108 size=size, 109 screensize=screensize, 110 title=title, 111 interactive=interactive, 112 axes=axes, 113 ) 114 ################################ 115 box = volume.box().wireframe().alpha(0.1) 116 117 self.show(box, viewup="z", resetcam=resetcam, interactive=False) 118 if show_icon: 119 self.add_inset(volume, pos=(0.85, 0.85), size=0.15, c="w", draggable=draggable) 120 121 # inits 122 la, ld = 0.7, 0.3 # ambient, diffuse 123 dims = volume.dimensions() 124 data = volume.pointdata[0] 125 rmin, rmax = volume.imagedata().GetScalarRange() 126 if clamp: 127 hdata, edg = np.histogram(data, bins=50) 128 logdata = np.log(hdata + 1) 129 # mean of the logscale plot 130 meanlog = np.sum(np.multiply(edg[:-1], logdata)) / np.sum(logdata) 131 rmax = min(rmax, meanlog + (meanlog - rmin) * 0.9) 132 rmin = max(rmin, meanlog - (rmax - meanlog) * 0.9) 133 vedo.logger.debug( 134 "scalar range clamped to range: (" 135 + precision(rmin, 3) 136 + ", " 137 + precision(rmax, 3) 138 + ")" 139 ) 140 self._cmap_slicer = cmaps[0] 141 visibles = [None, None, None] 142 msh = volume.zslice(int(dims[2] / 2)) 143 msh.alpha(alpha).lighting("", la, ld, 0) 144 msh.cmap(self._cmap_slicer, vmin=rmin, vmax=rmax) 145 if map2cells: 146 msh.mapPointsToCells() 147 self.renderer.AddActor(msh) 148 visibles[2] = msh 149 msh.add_scalarbar(pos=(0.04, 0.0), horizontal=True, font_size=0) 150 151 def slider_function_x(widget, event): 152 i = int(widget.GetRepresentation().GetValue()) 153 msh = volume.xslice(i).alpha(alpha).lighting("", la, ld, 0) 154 msh.cmap(self._cmap_slicer, vmin=rmin, vmax=rmax) 155 if map2cells: 156 msh.mapPointsToCells() 157 self.renderer.RemoveActor(visibles[0]) 158 if i and i < dims[0]: 159 self.renderer.AddActor(msh) 160 visibles[0] = msh 161 162 def slider_function_y(widget, event): 163 i = int(widget.GetRepresentation().GetValue()) 164 msh = volume.yslice(i).alpha(alpha).lighting("", la, ld, 0) 165 msh.cmap(self._cmap_slicer, vmin=rmin, vmax=rmax) 166 if map2cells: 167 msh.mapPointsToCells() 168 self.renderer.RemoveActor(visibles[1]) 169 if i and i < dims[1]: 170 self.renderer.AddActor(msh) 171 visibles[1] = msh 172 173 def slider_function_z(widget, event): 174 i = int(widget.GetRepresentation().GetValue()) 175 msh = volume.zslice(i).alpha(alpha).lighting("", la, ld, 0) 176 msh.cmap(self._cmap_slicer, vmin=rmin, vmax=rmax) 177 if map2cells: 178 msh.mapPointsToCells() 179 self.renderer.RemoveActor(visibles[2]) 180 if i and i < dims[2]: 181 self.renderer.AddActor(msh) 182 visibles[2] = msh 183 184 cx, cy, cz, ch = "dr", "dg", "db", (0.3, 0.3, 0.3) 185 if np.sum(self.renderer.GetBackground()) < 1.5: 186 cx, cy, cz = "lr", "lg", "lb" 187 ch = (0.8, 0.8, 0.8) 188 189 if not use_slider3d: 190 self.add_slider( 191 slider_function_x, 192 0, 193 dims[0], 194 title="X", 195 title_size=0.5, 196 pos=[(0.8, 0.12), (0.95, 0.12)], 197 show_value=False, 198 c=cx, 199 ) 200 self.add_slider( 201 slider_function_y, 202 0, 203 dims[1], 204 title="Y", 205 title_size=0.5, 206 pos=[(0.8, 0.08), (0.95, 0.08)], 207 show_value=False, 208 c=cy, 209 ) 210 self.add_slider( 211 slider_function_z, 212 0, 213 dims[2], 214 title="Z", 215 title_size=0.6, 216 value=int(dims[2] / 2), 217 pos=[(0.8, 0.04), (0.95, 0.04)], 218 show_value=False, 219 c=cz, 220 ) 221 else: # 3d sliders attached to the axes bounds 222 bs = box.bounds() 223 self.add_slider3d( 224 slider_function_x, 225 pos1=(bs[0], bs[2], bs[4]), 226 pos2=(bs[1], bs[2], bs[4]), 227 xmin=0, 228 xmax=dims[0], 229 t=box.diagonal_size() / mag(box.xbounds()) * 0.6, 230 c=cx, 231 show_value=False, 232 ) 233 self.add_slider3d( 234 slider_function_y, 235 pos1=(bs[1], bs[2], bs[4]), 236 pos2=(bs[1], bs[3], bs[4]), 237 xmin=0, 238 xmax=dims[1], 239 t=box.diagonal_size() / mag(box.ybounds()) * 0.6, 240 c=cy, 241 show_value=False, 242 ) 243 self.add_slider3d( 244 slider_function_z, 245 pos1=(bs[0], bs[2], bs[4]), 246 pos2=(bs[0], bs[2], bs[5]), 247 xmin=0, 248 xmax=dims[2], 249 value=int(dims[2] / 2), 250 t=box.diagonal_size() / mag(box.zbounds()) * 0.6, 251 c=cz, 252 show_value=False, 253 ) 254 255 ################# 256 def buttonfunc(): 257 bu.switch() 258 self._cmap_slicer = bu.status() 259 for mesh in visibles: 260 if mesh: 261 mesh.cmap(self._cmap_slicer, vmin=rmin, vmax=rmax) 262 if map2cells: 263 mesh.mapPointsToCells() 264 self.renderer.RemoveActor(mesh.scalarbar) 265 mesh.add_scalarbar(pos=(0.04, 0.0), horizontal=True) 266 self.renderer.AddActor(mesh.scalarbar) 267 268 bu = self.add_button( 269 buttonfunc, 270 pos=(0.27, 0.005), 271 states=cmaps, 272 c=["db"] * len(cmaps), 273 bc=["lb"] * len(cmaps), # colors of states 274 size=14, 275 bold=True, 276 ) 277 278 ################# 279 hist = None 280 if show_histo: 281 hist = CornerHistogram( 282 data, s=0.2, bins=25, logscale=1, pos=(0.02, 0.02), c=ch, bg=ch, alpha=0.7 283 ) 284 285 self.add([msh, hist]) 286 if interactive: 287 self.interactive()
Generate a rendering window with slicing planes for the input Volume.
47 def __init__( 48 self, 49 volume, 50 alpha=1, 51 cmaps=("gist_ncar_r", "hot_r", "bone_r", "jet", "Spectral_r"), 52 map2cells=False, # buggy 53 clamp=True, 54 use_slider3d=False, 55 show_histo=True, 56 show_icon=True, 57 draggable=False, 58 pos=(0, 0), 59 size="auto", 60 screensize="auto", 61 title="", 62 bg="white", 63 bg2="lightblue", 64 axes=7, 65 resetcam=True, 66 interactive=True, 67 ): 68 """ 69 Generate a rendering window with slicing planes for the input Volume. 70 71 Arguments: 72 alpha : (float) 73 transparency of the slicing planes 74 cmaps : (list) 75 list of color maps names to cycle when clicking button 76 map2cells : (bool) 77 scalars are mapped to cells, not interpolated 78 clamp : (bool) 79 clamp scalar to reduce the effect of tails in color mapping 80 use_slider3d : (bool) 81 show sliders attached along the axes 82 show_histo : (bool) 83 show histogram on bottom left 84 show_icon : (bool) 85 show a small 3D rendering icon of the volume 86 draggable : (bool) 87 make the icon draggable 88 89 Examples: 90 - [slicer1.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/slicer1.py) 91 92 <img src="https://vedo.embl.es/images/volumetric/slicer1.jpg" width="500"> 93 """ 94 self._cmap_slicer = "gist_ncar_r" 95 96 if not title: 97 if volume.filename: 98 title = volume.filename 99 else: 100 title = "Volume Slicer" 101 102 ################################ 103 Plotter.__init__( 104 self, 105 pos=pos, 106 bg=bg, 107 bg2=bg2, 108 size=size, 109 screensize=screensize, 110 title=title, 111 interactive=interactive, 112 axes=axes, 113 ) 114 ################################ 115 box = volume.box().wireframe().alpha(0.1) 116 117 self.show(box, viewup="z", resetcam=resetcam, interactive=False) 118 if show_icon: 119 self.add_inset(volume, pos=(0.85, 0.85), size=0.15, c="w", draggable=draggable) 120 121 # inits 122 la, ld = 0.7, 0.3 # ambient, diffuse 123 dims = volume.dimensions() 124 data = volume.pointdata[0] 125 rmin, rmax = volume.imagedata().GetScalarRange() 126 if clamp: 127 hdata, edg = np.histogram(data, bins=50) 128 logdata = np.log(hdata + 1) 129 # mean of the logscale plot 130 meanlog = np.sum(np.multiply(edg[:-1], logdata)) / np.sum(logdata) 131 rmax = min(rmax, meanlog + (meanlog - rmin) * 0.9) 132 rmin = max(rmin, meanlog - (rmax - meanlog) * 0.9) 133 vedo.logger.debug( 134 "scalar range clamped to range: (" 135 + precision(rmin, 3) 136 + ", " 137 + precision(rmax, 3) 138 + ")" 139 ) 140 self._cmap_slicer = cmaps[0] 141 visibles = [None, None, None] 142 msh = volume.zslice(int(dims[2] / 2)) 143 msh.alpha(alpha).lighting("", la, ld, 0) 144 msh.cmap(self._cmap_slicer, vmin=rmin, vmax=rmax) 145 if map2cells: 146 msh.mapPointsToCells() 147 self.renderer.AddActor(msh) 148 visibles[2] = msh 149 msh.add_scalarbar(pos=(0.04, 0.0), horizontal=True, font_size=0) 150 151 def slider_function_x(widget, event): 152 i = int(widget.GetRepresentation().GetValue()) 153 msh = volume.xslice(i).alpha(alpha).lighting("", la, ld, 0) 154 msh.cmap(self._cmap_slicer, vmin=rmin, vmax=rmax) 155 if map2cells: 156 msh.mapPointsToCells() 157 self.renderer.RemoveActor(visibles[0]) 158 if i and i < dims[0]: 159 self.renderer.AddActor(msh) 160 visibles[0] = msh 161 162 def slider_function_y(widget, event): 163 i = int(widget.GetRepresentation().GetValue()) 164 msh = volume.yslice(i).alpha(alpha).lighting("", la, ld, 0) 165 msh.cmap(self._cmap_slicer, vmin=rmin, vmax=rmax) 166 if map2cells: 167 msh.mapPointsToCells() 168 self.renderer.RemoveActor(visibles[1]) 169 if i and i < dims[1]: 170 self.renderer.AddActor(msh) 171 visibles[1] = msh 172 173 def slider_function_z(widget, event): 174 i = int(widget.GetRepresentation().GetValue()) 175 msh = volume.zslice(i).alpha(alpha).lighting("", la, ld, 0) 176 msh.cmap(self._cmap_slicer, vmin=rmin, vmax=rmax) 177 if map2cells: 178 msh.mapPointsToCells() 179 self.renderer.RemoveActor(visibles[2]) 180 if i and i < dims[2]: 181 self.renderer.AddActor(msh) 182 visibles[2] = msh 183 184 cx, cy, cz, ch = "dr", "dg", "db", (0.3, 0.3, 0.3) 185 if np.sum(self.renderer.GetBackground()) < 1.5: 186 cx, cy, cz = "lr", "lg", "lb" 187 ch = (0.8, 0.8, 0.8) 188 189 if not use_slider3d: 190 self.add_slider( 191 slider_function_x, 192 0, 193 dims[0], 194 title="X", 195 title_size=0.5, 196 pos=[(0.8, 0.12), (0.95, 0.12)], 197 show_value=False, 198 c=cx, 199 ) 200 self.add_slider( 201 slider_function_y, 202 0, 203 dims[1], 204 title="Y", 205 title_size=0.5, 206 pos=[(0.8, 0.08), (0.95, 0.08)], 207 show_value=False, 208 c=cy, 209 ) 210 self.add_slider( 211 slider_function_z, 212 0, 213 dims[2], 214 title="Z", 215 title_size=0.6, 216 value=int(dims[2] / 2), 217 pos=[(0.8, 0.04), (0.95, 0.04)], 218 show_value=False, 219 c=cz, 220 ) 221 else: # 3d sliders attached to the axes bounds 222 bs = box.bounds() 223 self.add_slider3d( 224 slider_function_x, 225 pos1=(bs[0], bs[2], bs[4]), 226 pos2=(bs[1], bs[2], bs[4]), 227 xmin=0, 228 xmax=dims[0], 229 t=box.diagonal_size() / mag(box.xbounds()) * 0.6, 230 c=cx, 231 show_value=False, 232 ) 233 self.add_slider3d( 234 slider_function_y, 235 pos1=(bs[1], bs[2], bs[4]), 236 pos2=(bs[1], bs[3], bs[4]), 237 xmin=0, 238 xmax=dims[1], 239 t=box.diagonal_size() / mag(box.ybounds()) * 0.6, 240 c=cy, 241 show_value=False, 242 ) 243 self.add_slider3d( 244 slider_function_z, 245 pos1=(bs[0], bs[2], bs[4]), 246 pos2=(bs[0], bs[2], bs[5]), 247 xmin=0, 248 xmax=dims[2], 249 value=int(dims[2] / 2), 250 t=box.diagonal_size() / mag(box.zbounds()) * 0.6, 251 c=cz, 252 show_value=False, 253 ) 254 255 ################# 256 def buttonfunc(): 257 bu.switch() 258 self._cmap_slicer = bu.status() 259 for mesh in visibles: 260 if mesh: 261 mesh.cmap(self._cmap_slicer, vmin=rmin, vmax=rmax) 262 if map2cells: 263 mesh.mapPointsToCells() 264 self.renderer.RemoveActor(mesh.scalarbar) 265 mesh.add_scalarbar(pos=(0.04, 0.0), horizontal=True) 266 self.renderer.AddActor(mesh.scalarbar) 267 268 bu = self.add_button( 269 buttonfunc, 270 pos=(0.27, 0.005), 271 states=cmaps, 272 c=["db"] * len(cmaps), 273 bc=["lb"] * len(cmaps), # colors of states 274 size=14, 275 bold=True, 276 ) 277 278 ################# 279 hist = None 280 if show_histo: 281 hist = CornerHistogram( 282 data, s=0.2, bins=25, logscale=1, pos=(0.02, 0.02), c=ch, bg=ch, alpha=0.7 283 ) 284 285 self.add([msh, hist]) 286 if interactive: 287 self.interactive()
Generate a rendering window with slicing planes for the input Volume.
Arguments:
- alpha : (float) transparency of the slicing planes
- cmaps : (list) list of color maps names to cycle when clicking button
- map2cells : (bool) scalars are mapped to cells, not interpolated
- clamp : (bool) clamp scalar to reduce the effect of tails in color mapping
- use_slider3d : (bool) show sliders attached along the axes
- show_histo : (bool) show histogram on bottom left
- show_icon : (bool) show a small 3D rendering icon of the volume
- draggable : (bool) make the icon draggable
Examples:
Inherited Members
- vedo.plotter.Plotter
- process_events
- at
- add
- remove
- remove_lights
- pop
- render
- interactive
- use_depth_peeling
- background
- get_meshes
- get_volumes
- reset_camera
- reset_viewup
- move_camera
- fly_to
- look_at
- record
- play
- parallel_projection
- fov
- zoom
- azimuth
- elevation
- roll
- dolly
- add_slider
- add_slider3d
- add_spline_tool
- add_icon
- add_global_axes
- add_legend_box
- add_hint
- add_shadows
- add_ambient_occlusion
- add_depth_of_field
- add_renderer_frame
- add_hover_legend
- add_scale_indicator
- fill_event
- add_callback
- remove_callback
- timer_callback
- compute_world_coordinate
- compute_screen_coordinates
- pick_area
- show
- add_inset
- clear
- break_interaction
- user_mode
- close_window
- close
- screenshot
- topicture
- export
- color_picker
291class Slicer2DPlotter(Plotter): 292 """ 293 A single slice of a Volume which always faces the camera, 294 but at the same time can be oriented arbitrarily in space. 295 """ 296 297 def __init__(self, volume, levels=(None, None), histo_color="red5", **kwargs): 298 """ 299 A single slice of a Volume which always faces the camera, 300 but at the same time can be oriented arbitrarily in space. 301 302 Arguments: 303 levels : (list) 304 window and color levels 305 histo_color : (color) 306 histogram color, use `None` to disable it 307 308 <img src="https://vedo.embl.es/images/volumetric/read_volume3.jpg" width="500"> 309 """ 310 if "shape" not in kwargs: 311 custom_shape = [ # define here the 2 rendering rectangle spaces 312 dict(bottomleft=(0.0, 0.0), topright=(1, 1), bg="k9"), # the full window 313 dict(bottomleft=(0.8, 0.8), topright=(1, 1), bg="k8", bg2="lb"), 314 ] 315 kwargs["shape"] = custom_shape 316 317 Plotter.__init__(self, **kwargs) 318 319 # reuse the same underlying data as in vol 320 vsl = vedo.volume.VolumeSlice(volume) 321 322 # no argument will grab the existing cmap in vol (or use build_lut()) 323 vsl.colorize() 324 325 if levels[0] and levels[1]: 326 vsl.lighting(window=levels[0], level=levels[1]) 327 328 usage = Text2D( 329 ( 330 "Left click & drag :rightarrow modify luminosity and contrast\n" 331 "SHIFT+Left click :rightarrow slice image obliquely\n" 332 "SHIFT+Middle click :rightarrow slice image perpendicularly\n" 333 "R :rightarrow Reset the Window/Color levels\n" 334 "X :rightarrow Reset to sagittal view\n" 335 "Y :rightarrow Reset to coronal view\n" 336 "Z :rightarrow Reset to axial view" 337 ), 338 font="Calco", 339 pos="top-left", 340 s=0.8, 341 bg="yellow", 342 alpha=0.25, 343 ) 344 345 hist = None 346 if histo_color is not None: 347 # hist = CornerHistogram( 348 # volume.pointdata[0], 349 # bins=25, 350 # logscale=1, 351 # pos=(0.02, 0.02), 352 # s=0.175, 353 # c="dg", 354 # bg="k", 355 # alpha=1, 356 # ) 357 hist = vedo.pyplot.histogram( 358 volume.pointdata[0], 359 bins=10, 360 logscale=True, 361 c=histo_color, 362 ytitle="log_10 (counts)", 363 axes=dict(text_scale=1.9), 364 ) 365 hist = hist.as2d(pos="bottom-left", scale=0.5) 366 367 axes = kwargs.pop("axes", 7) 368 interactive = kwargs.pop("interactive", True) 369 if axes == 7: 370 ax = vedo.addons.RulerAxes(vsl, xtitle="x - ", ytitle="y - ", ztitle="z - ") 371 372 box = vsl.box().alpha(0.2) 373 self.at(0).show(vsl, box, ax, usage, hist, mode="image") 374 self.at(1).show(volume, interactive=interactive)
A single slice of a Volume which always faces the camera, but at the same time can be oriented arbitrarily in space.
297 def __init__(self, volume, levels=(None, None), histo_color="red5", **kwargs): 298 """ 299 A single slice of a Volume which always faces the camera, 300 but at the same time can be oriented arbitrarily in space. 301 302 Arguments: 303 levels : (list) 304 window and color levels 305 histo_color : (color) 306 histogram color, use `None` to disable it 307 308 <img src="https://vedo.embl.es/images/volumetric/read_volume3.jpg" width="500"> 309 """ 310 if "shape" not in kwargs: 311 custom_shape = [ # define here the 2 rendering rectangle spaces 312 dict(bottomleft=(0.0, 0.0), topright=(1, 1), bg="k9"), # the full window 313 dict(bottomleft=(0.8, 0.8), topright=(1, 1), bg="k8", bg2="lb"), 314 ] 315 kwargs["shape"] = custom_shape 316 317 Plotter.__init__(self, **kwargs) 318 319 # reuse the same underlying data as in vol 320 vsl = vedo.volume.VolumeSlice(volume) 321 322 # no argument will grab the existing cmap in vol (or use build_lut()) 323 vsl.colorize() 324 325 if levels[0] and levels[1]: 326 vsl.lighting(window=levels[0], level=levels[1]) 327 328 usage = Text2D( 329 ( 330 "Left click & drag :rightarrow modify luminosity and contrast\n" 331 "SHIFT+Left click :rightarrow slice image obliquely\n" 332 "SHIFT+Middle click :rightarrow slice image perpendicularly\n" 333 "R :rightarrow Reset the Window/Color levels\n" 334 "X :rightarrow Reset to sagittal view\n" 335 "Y :rightarrow Reset to coronal view\n" 336 "Z :rightarrow Reset to axial view" 337 ), 338 font="Calco", 339 pos="top-left", 340 s=0.8, 341 bg="yellow", 342 alpha=0.25, 343 ) 344 345 hist = None 346 if histo_color is not None: 347 # hist = CornerHistogram( 348 # volume.pointdata[0], 349 # bins=25, 350 # logscale=1, 351 # pos=(0.02, 0.02), 352 # s=0.175, 353 # c="dg", 354 # bg="k", 355 # alpha=1, 356 # ) 357 hist = vedo.pyplot.histogram( 358 volume.pointdata[0], 359 bins=10, 360 logscale=True, 361 c=histo_color, 362 ytitle="log_10 (counts)", 363 axes=dict(text_scale=1.9), 364 ) 365 hist = hist.as2d(pos="bottom-left", scale=0.5) 366 367 axes = kwargs.pop("axes", 7) 368 interactive = kwargs.pop("interactive", True) 369 if axes == 7: 370 ax = vedo.addons.RulerAxes(vsl, xtitle="x - ", ytitle="y - ", ztitle="z - ") 371 372 box = vsl.box().alpha(0.2) 373 self.at(0).show(vsl, box, ax, usage, hist, mode="image") 374 self.at(1).show(volume, interactive=interactive)
A single slice of a Volume which always faces the camera, but at the same time can be oriented arbitrarily in space.
Arguments:
- levels : (list) window and color levels
- histo_color : (color)
histogram color, use
None
to disable it
Inherited Members
- vedo.plotter.Plotter
- process_events
- at
- add
- remove
- remove_lights
- pop
- render
- interactive
- use_depth_peeling
- background
- get_meshes
- get_volumes
- reset_camera
- reset_viewup
- move_camera
- fly_to
- look_at
- record
- play
- parallel_projection
- fov
- zoom
- azimuth
- elevation
- roll
- dolly
- add_slider
- add_slider3d
- add_spline_tool
- add_icon
- add_global_axes
- add_legend_box
- add_hint
- add_shadows
- add_ambient_occlusion
- add_depth_of_field
- add_renderer_frame
- add_hover_legend
- add_scale_indicator
- fill_event
- add_callback
- remove_callback
- timer_callback
- compute_world_coordinate
- compute_screen_coordinates
- pick_area
- show
- add_inset
- clear
- break_interaction
- user_mode
- close_window
- close
- screenshot
- topicture
- export
- color_picker
1074class SplinePlotter(Plotter): 1075 """ 1076 Interactive drawing of splined curves on meshes. 1077 """ 1078 1079 def __init__(self, obj, init_points=(), closed=False, splined=True, **kwargs): 1080 """ 1081 Create an interactive application that allows the user to click points and 1082 retrieve the coordinates of such points and optionally a spline or line 1083 (open or closed). 1084 1085 Input object can be a image file name or a 3D mesh. 1086 """ 1087 super().__init__(**kwargs) 1088 1089 self.mode = "trackball" 1090 self.verbose = True 1091 self.splined = splined 1092 self.resolution = None # spline resolution (None = automatic) 1093 self.closed = closed 1094 self.lcolor = "yellow4" 1095 self.lwidth = 3 1096 self.pcolor = "purple5" 1097 self.psize = 10 1098 1099 self.cpoints = list(init_points) 1100 self.vpoints = None 1101 self.line = None 1102 1103 if isinstance(obj, str): 1104 self.object = vedo.file_io.load(obj) 1105 else: 1106 self.object = obj 1107 1108 if isinstance(self.object, vedo.Picture): 1109 self.mode = "image" 1110 self.parallel_projection(True) 1111 1112 t = ( 1113 "Click to add a point\n" 1114 "Right-click to remove it\n" 1115 "Drag mouse to change contrast\n" 1116 "Press c to clear points\n" 1117 "Press q to continue" 1118 ) 1119 self.instructions = Text2D(t, pos="bottom-left", c="white", bg="green", font="Calco") 1120 1121 self += [self.object, self.instructions] 1122 1123 self.callid1 = self.add_callback("KeyPress", self._key_press) 1124 self.callid2 = self.add_callback("LeftButtonPress", self._on_left_click) 1125 self.callid3 = self.add_callback("RightButtonPress", self._on_right_click) 1126 1127 def points(self, newpts=None): 1128 """Retrieve the 3D coordinates of the clicked points""" 1129 if newpts is not None: 1130 self.cpoints = newpts 1131 self._update() 1132 return self 1133 return np.array(self.cpoints) 1134 1135 def _on_left_click(self, evt): 1136 if not evt.actor: 1137 return 1138 if evt.actor.name == "points": 1139 # remove clicked point if clicked twice 1140 pid = self.vpoints.closest_point(evt.picked3d, return_point_id=True) 1141 self.cpoints.pop(pid) 1142 self._update() 1143 return 1144 p = evt.picked3d 1145 self.cpoints.append(p) 1146 self._update() 1147 if self.verbose: 1148 vedo.colors.printc("Added point:", precision(p, 4), c="g") 1149 1150 def _on_right_click(self, evt): 1151 if evt.actor and len(self.cpoints) > 0: 1152 self.cpoints.pop() # pop removes from the list the last pt 1153 self._update() 1154 if self.verbose: 1155 vedo.colors.printc("Deleted last point", c="r") 1156 1157 def _update(self): 1158 self.remove(self.line, self.vpoints) # remove old points and spline 1159 self.vpoints = Points(self.cpoints).ps(self.psize).c(self.pcolor) 1160 self.vpoints.name = "points" 1161 self.vpoints.pickable(True) # to allow toggle 1162 minnr = 1 1163 if self.splined: 1164 minnr = 2 1165 if self.lwidth and len(self.cpoints) > minnr: 1166 if self.splined: 1167 try: 1168 self.line = Spline(self.cpoints, closed=self.closed, res=self.resolution) 1169 except ValueError: 1170 # if clicking too close splining might fail 1171 self.cpoints.pop() 1172 return 1173 else: 1174 self.line = Line(self.cpoints, closed=self.closed) 1175 self.line.c(self.lcolor).lw(self.lwidth).pickable(False) 1176 self.add(self.vpoints, self.line) 1177 else: 1178 self.add(self.vpoints) 1179 1180 def _key_press(self, evt): 1181 if evt.keypress == "c": 1182 self.cpoints = [] 1183 self.remove(self.line, self.vpoints).render() 1184 if self.verbose: 1185 vedo.colors.printc("==== Cleared all points ====", c="r", invert=True) 1186 1187 def start(self): 1188 """Start the interaction""" 1189 self.show(self.object, self.instructions, mode=self.mode) 1190 return self
Interactive drawing of splined curves on meshes.
1079 def __init__(self, obj, init_points=(), closed=False, splined=True, **kwargs): 1080 """ 1081 Create an interactive application that allows the user to click points and 1082 retrieve the coordinates of such points and optionally a spline or line 1083 (open or closed). 1084 1085 Input object can be a image file name or a 3D mesh. 1086 """ 1087 super().__init__(**kwargs) 1088 1089 self.mode = "trackball" 1090 self.verbose = True 1091 self.splined = splined 1092 self.resolution = None # spline resolution (None = automatic) 1093 self.closed = closed 1094 self.lcolor = "yellow4" 1095 self.lwidth = 3 1096 self.pcolor = "purple5" 1097 self.psize = 10 1098 1099 self.cpoints = list(init_points) 1100 self.vpoints = None 1101 self.line = None 1102 1103 if isinstance(obj, str): 1104 self.object = vedo.file_io.load(obj) 1105 else: 1106 self.object = obj 1107 1108 if isinstance(self.object, vedo.Picture): 1109 self.mode = "image" 1110 self.parallel_projection(True) 1111 1112 t = ( 1113 "Click to add a point\n" 1114 "Right-click to remove it\n" 1115 "Drag mouse to change contrast\n" 1116 "Press c to clear points\n" 1117 "Press q to continue" 1118 ) 1119 self.instructions = Text2D(t, pos="bottom-left", c="white", bg="green", font="Calco") 1120 1121 self += [self.object, self.instructions] 1122 1123 self.callid1 = self.add_callback("KeyPress", self._key_press) 1124 self.callid2 = self.add_callback("LeftButtonPress", self._on_left_click) 1125 self.callid3 = self.add_callback("RightButtonPress", self._on_right_click)
Create an interactive application that allows the user to click points and retrieve the coordinates of such points and optionally a spline or line (open or closed).
Input object can be a image file name or a 3D mesh.
1127 def points(self, newpts=None): 1128 """Retrieve the 3D coordinates of the clicked points""" 1129 if newpts is not None: 1130 self.cpoints = newpts 1131 self._update() 1132 return self 1133 return np.array(self.cpoints)
Retrieve the 3D coordinates of the clicked points
1187 def start(self): 1188 """Start the interaction""" 1189 self.show(self.object, self.instructions, mode=self.mode) 1190 return self
Start the interaction
Inherited Members
- vedo.plotter.Plotter
- process_events
- at
- add
- remove
- remove_lights
- pop
- render
- interactive
- use_depth_peeling
- background
- get_meshes
- get_volumes
- reset_camera
- reset_viewup
- move_camera
- fly_to
- look_at
- record
- play
- parallel_projection
- fov
- zoom
- azimuth
- elevation
- roll
- dolly
- add_slider
- add_slider3d
- add_spline_tool
- add_icon
- add_global_axes
- add_legend_box
- add_hint
- add_shadows
- add_ambient_occlusion
- add_depth_of_field
- add_renderer_frame
- add_hover_legend
- add_scale_indicator
- fill_event
- add_callback
- remove_callback
- timer_callback
- compute_world_coordinate
- compute_screen_coordinates
- pick_area
- show
- add_inset
- clear
- break_interaction
- user_mode
- close_window
- close
- screenshot
- topicture
- export
- color_picker
1597class AnimationPlayer(vedo.Plotter): 1598 """ 1599 A Plotter with play/pause, step forward/backward and slider functionalties. 1600 Useful for inspecting time series. 1601 1602 The user has the responsibility to update all actors in the callback function. 1603 Pay attention to that the idx can both increment and decrement, 1604 as well as make large jumps. 1605 1606 Arguments: 1607 func : (Callable) 1608 a function that passes an integer as input and updates the scene 1609 irange : (tuple) 1610 the range of the integer input representing the time series index 1611 dt : (float) 1612 the time interval between two calls to `func` in milliseconds 1613 loop : (bool) 1614 whether to loop the animation 1615 c : (list, str) 1616 the color of the play/pause button 1617 bc : (list) 1618 the background color of the play/pause button and the slider 1619 button_size : (int) 1620 the size of the play/pause buttons 1621 button_pos : (float, float) 1622 the position of the play/pause buttons as a fraction of the window size 1623 button_gap : (float) 1624 the gap between the buttons 1625 slider_length : (float) 1626 the length of the slider as a fraction of the window size 1627 slider_pos : (float, float) 1628 the position of the slider as a fraction of the window size 1629 kwargs: (dict) 1630 keyword arguments to be passed to `Plotter` 1631 1632 Examples: 1633 - [aspring2_player.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/aspring2_player.py) 1634 """ 1635 # Original class contributed by @mikaeltulldahl (Mikael Tulldahl) 1636 1637 PLAY_SYMBOL = " \u23F5 " 1638 PAUSE_SYMBOL = " \u23F8 " 1639 ONE_BACK_SYMBOL = " \u29CF" 1640 ONE_FORWARD_SYMBOL = "\u29D0 " 1641 1642 def __init__( 1643 self, 1644 func: Callable[[int],None], 1645 irange: tuple, 1646 dt: float = 1.0, 1647 loop: bool = True, 1648 c=("white", "white"), 1649 bc=("green3","red4"), 1650 button_size=25, 1651 button_pos=(0.5,0.08), 1652 button_gap=0.055, 1653 slider_length=0.5, 1654 slider_pos=(0.5,0.055), 1655 **kwargs, 1656 ): 1657 super().__init__(**kwargs) 1658 1659 min_value, max_value = np.array(irange).astype(int) 1660 button_pos = np.array(button_pos) 1661 slider_pos = np.array(slider_pos) 1662 1663 self._func = func 1664 1665 self.value = min_value-1 1666 self.min_value = min_value 1667 self.max_value = max_value 1668 self.dt = max(dt, 1) 1669 self.is_playing = False 1670 self._loop = loop 1671 1672 self.timer_callback_id = self.add_callback("timer", self._handle_timer) 1673 self.timer_id = None 1674 1675 self.play_pause_button = self.add_button( 1676 self.toggle, 1677 pos=button_pos, # x,y fraction from bottom left corner 1678 states=[self.PLAY_SYMBOL, self.PAUSE_SYMBOL], 1679 font="Kanopus", 1680 size=button_size, 1681 bc=bc, 1682 ) 1683 self.button_oneback = self.add_button( 1684 self.onebackward, 1685 pos=(-button_gap, 0) + button_pos, 1686 states=[self.ONE_BACK_SYMBOL], 1687 font="Kanopus", 1688 size=button_size, 1689 c=c, 1690 bc=bc, 1691 ) 1692 self.button_oneforward = self.add_button( 1693 self.oneforward, 1694 pos=(button_gap, 0) + button_pos, 1695 states=[self.ONE_FORWARD_SYMBOL], 1696 font="Kanopus", 1697 size=button_size, 1698 bc=bc, 1699 ) 1700 d = (1-slider_length)/2 1701 self.slider: SliderWidget = self.add_slider( 1702 self._slider_callback, 1703 self.min_value, 1704 self.max_value - 1, 1705 value=self.min_value, 1706 pos=[(d-0.5, 0)+slider_pos, (0.5-d, 0)+slider_pos], 1707 show_value=False, 1708 c=bc[0], 1709 alpha=1, 1710 ) 1711 1712 def pause(self) -> None: 1713 """Pause the animation.""" 1714 self.is_playing = False 1715 if self.timer_id is not None: 1716 self.timer_callback("destroy", self.timer_id) 1717 self.timer_id = None 1718 self.play_pause_button.status(self.PLAY_SYMBOL) 1719 1720 def resume(self) -> None: 1721 """Resume the animation.""" 1722 if self.timer_id is not None: 1723 self.timer_callback("destroy", self.timer_id) 1724 self.timer_id = self.timer_callback("create", dt=int(self.dt)) 1725 self.is_playing = True 1726 self.play_pause_button.status(self.PAUSE_SYMBOL) 1727 1728 def toggle(self) -> None: 1729 """Toggle between play and pause.""" 1730 if not self.is_playing: 1731 self.resume() 1732 else: 1733 self.pause() 1734 1735 def oneforward(self) -> None: 1736 """Advance the animation by one frame.""" 1737 self.pause() 1738 self.set_frame(self.value + 1) 1739 1740 def onebackward(self) -> None: 1741 """Go back one frame in the animation.""" 1742 self.pause() 1743 self.set_frame(self.value - 1) 1744 1745 def set_frame(self, value: int) -> None: 1746 """Set the current value of the animation.""" 1747 if self._loop: 1748 if value < self.min_value: 1749 value = self.max_value - 1 1750 elif value >= self.max_value: 1751 value = self.min_value 1752 else: 1753 if value < self.min_value: 1754 self.pause() 1755 value = self.min_value 1756 elif value >= self.max_value - 1: 1757 value = self.max_value - 1 1758 self.pause() 1759 1760 if self.value != value: 1761 self.value = value 1762 self.slider.value = value 1763 self._func(value) 1764 1765 def _slider_callback(self, widget: SliderWidget, _: str) -> None: 1766 self.pause() 1767 self.set_frame(int(round(widget.value))) 1768 1769 def _handle_timer(self, _: Event = None) -> None: 1770 self.set_frame(self.value + 1) 1771 1772 def stop(self) -> "AnimationPlayer": 1773 """ 1774 Stop the animation timers, remove buttons and slider. 1775 Behave like a normal `Plotter` after this. 1776 """ 1777 # stop timer 1778 if self.timer_id is not None: 1779 self.timer_callback("destroy", self.timer_id) 1780 self.timer_id = None 1781 1782 # remove callbacks 1783 self.remove_callback(self.timer_callback_id) 1784 1785 # remove buttons 1786 self.slider.off() 1787 self.renderer.RemoveActor(self.play_pause_button.actor) 1788 self.renderer.RemoveActor(self.button_oneback.actor) 1789 self.renderer.RemoveActor(self.button_oneforward.actor) 1790 return self
A Plotter with play/pause, step forward/backward and slider functionalties. Useful for inspecting time series.
The user has the responsibility to update all actors in the callback function. Pay attention to that the idx can both increment and decrement, as well as make large jumps.
Arguments:
- func : (Callable) a function that passes an integer as input and updates the scene
- irange : (tuple) the range of the integer input representing the time series index
- dt : (float)
the time interval between two calls to
func
in milliseconds - loop : (bool) whether to loop the animation
- c : (list, str) the color of the play/pause button
- bc : (list) the background color of the play/pause button and the slider
- button_size : (int) the size of the play/pause buttons
- button_pos : (float, float) the position of the play/pause buttons as a fraction of the window size
- button_gap : (float) the gap between the buttons
- slider_length : (float) the length of the slider as a fraction of the window size
- slider_pos : (float, float) the position of the slider as a fraction of the window size
- kwargs: (dict)
keyword arguments to be passed to
Plotter
Examples:
1642 def __init__( 1643 self, 1644 func: Callable[[int],None], 1645 irange: tuple, 1646 dt: float = 1.0, 1647 loop: bool = True, 1648 c=("white", "white"), 1649 bc=("green3","red4"), 1650 button_size=25, 1651 button_pos=(0.5,0.08), 1652 button_gap=0.055, 1653 slider_length=0.5, 1654 slider_pos=(0.5,0.055), 1655 **kwargs, 1656 ): 1657 super().__init__(**kwargs) 1658 1659 min_value, max_value = np.array(irange).astype(int) 1660 button_pos = np.array(button_pos) 1661 slider_pos = np.array(slider_pos) 1662 1663 self._func = func 1664 1665 self.value = min_value-1 1666 self.min_value = min_value 1667 self.max_value = max_value 1668 self.dt = max(dt, 1) 1669 self.is_playing = False 1670 self._loop = loop 1671 1672 self.timer_callback_id = self.add_callback("timer", self._handle_timer) 1673 self.timer_id = None 1674 1675 self.play_pause_button = self.add_button( 1676 self.toggle, 1677 pos=button_pos, # x,y fraction from bottom left corner 1678 states=[self.PLAY_SYMBOL, self.PAUSE_SYMBOL], 1679 font="Kanopus", 1680 size=button_size, 1681 bc=bc, 1682 ) 1683 self.button_oneback = self.add_button( 1684 self.onebackward, 1685 pos=(-button_gap, 0) + button_pos, 1686 states=[self.ONE_BACK_SYMBOL], 1687 font="Kanopus", 1688 size=button_size, 1689 c=c, 1690 bc=bc, 1691 ) 1692 self.button_oneforward = self.add_button( 1693 self.oneforward, 1694 pos=(button_gap, 0) + button_pos, 1695 states=[self.ONE_FORWARD_SYMBOL], 1696 font="Kanopus", 1697 size=button_size, 1698 bc=bc, 1699 ) 1700 d = (1-slider_length)/2 1701 self.slider: SliderWidget = self.add_slider( 1702 self._slider_callback, 1703 self.min_value, 1704 self.max_value - 1, 1705 value=self.min_value, 1706 pos=[(d-0.5, 0)+slider_pos, (0.5-d, 0)+slider_pos], 1707 show_value=False, 1708 c=bc[0], 1709 alpha=1, 1710 )
Arguments:
- shape : (str, list) shape of the grid of renderers in format (rows, columns). Ignored if N is specified.
- N : (int) number of desired renderers arranged in a grid automatically.
- pos : (list) (x,y) position in pixels of top-left corner of the rendering window on the screen
- size : (str, list) size of the rendering window. If 'auto', guess it based on screensize.
- screensize : (list) physical size of the monitor screen in pixels
- bg : (color, str) background color or specify jpg image file name with path
- bg2 : (color) background color of a gradient towards the top
axes : (int)
Note that Axes type-1 can be fully customized by passing a dictionary
axes=dict()
. Check outvedo.addons.Axes()
for the available options.- 0, no axes
- 1, draw three gray grid walls
- 2, show cartesian axes from (0,0,0)
- 3, show positive range of cartesian axes from (0,0,0)
- 4, show a triad at bottom left
- 5, show a cube at bottom left
- 6, mark the corners of the bounding box
- 7, draw a 3D ruler at each side of the cartesian axes
- 8, show the VTK CubeAxesActor object
- 9, show the bounding box outLine,
- 10, show three circles representing the maximum bounding box,
- 11, show a large grid on the x-y plane (use with zoom=8)
- 12, show polar axes.
- 13, draw a simple ruler at the bottom of the window
- sharecam : (bool) if False each renderer will have an independent vtkCamera
- interactive : (bool) if True will stop after show() to allow interaction w/ window
- offscreen : (bool) if True will not show the rendering window
- qt_widget : (QVTKRenderWindowInteractor)
render in a Qt-Widget using an QVTKRenderWindowInteractor.
Overrides offscreen to True.
Overrides interactive to False.
See examples
qt_windows1.py
andqt_windows2.py
1712 def pause(self) -> None: 1713 """Pause the animation.""" 1714 self.is_playing = False 1715 if self.timer_id is not None: 1716 self.timer_callback("destroy", self.timer_id) 1717 self.timer_id = None 1718 self.play_pause_button.status(self.PLAY_SYMBOL)
Pause the animation.
1720 def resume(self) -> None: 1721 """Resume the animation.""" 1722 if self.timer_id is not None: 1723 self.timer_callback("destroy", self.timer_id) 1724 self.timer_id = self.timer_callback("create", dt=int(self.dt)) 1725 self.is_playing = True 1726 self.play_pause_button.status(self.PAUSE_SYMBOL)
Resume the animation.
1728 def toggle(self) -> None: 1729 """Toggle between play and pause.""" 1730 if not self.is_playing: 1731 self.resume() 1732 else: 1733 self.pause()
Toggle between play and pause.
1735 def oneforward(self) -> None: 1736 """Advance the animation by one frame.""" 1737 self.pause() 1738 self.set_frame(self.value + 1)
Advance the animation by one frame.
1740 def onebackward(self) -> None: 1741 """Go back one frame in the animation.""" 1742 self.pause() 1743 self.set_frame(self.value - 1)
Go back one frame in the animation.
1745 def set_frame(self, value: int) -> None: 1746 """Set the current value of the animation.""" 1747 if self._loop: 1748 if value < self.min_value: 1749 value = self.max_value - 1 1750 elif value >= self.max_value: 1751 value = self.min_value 1752 else: 1753 if value < self.min_value: 1754 self.pause() 1755 value = self.min_value 1756 elif value >= self.max_value - 1: 1757 value = self.max_value - 1 1758 self.pause() 1759 1760 if self.value != value: 1761 self.value = value 1762 self.slider.value = value 1763 self._func(value)
Set the current value of the animation.
1772 def stop(self) -> "AnimationPlayer": 1773 """ 1774 Stop the animation timers, remove buttons and slider. 1775 Behave like a normal `Plotter` after this. 1776 """ 1777 # stop timer 1778 if self.timer_id is not None: 1779 self.timer_callback("destroy", self.timer_id) 1780 self.timer_id = None 1781 1782 # remove callbacks 1783 self.remove_callback(self.timer_callback_id) 1784 1785 # remove buttons 1786 self.slider.off() 1787 self.renderer.RemoveActor(self.play_pause_button.actor) 1788 self.renderer.RemoveActor(self.button_oneback.actor) 1789 self.renderer.RemoveActor(self.button_oneforward.actor) 1790 return self
Stop the animation timers, remove buttons and slider.
Behave like a normal Plotter
after this.
Inherited Members
- vedo.plotter.Plotter
- process_events
- at
- add
- remove
- remove_lights
- pop
- render
- interactive
- use_depth_peeling
- background
- get_meshes
- get_volumes
- reset_camera
- reset_viewup
- move_camera
- fly_to
- look_at
- record
- play
- parallel_projection
- fov
- zoom
- azimuth
- elevation
- roll
- dolly
- add_slider
- add_slider3d
- add_spline_tool
- add_icon
- add_global_axes
- add_legend_box
- add_hint
- add_shadows
- add_ambient_occlusion
- add_depth_of_field
- add_renderer_frame
- add_hover_legend
- add_scale_indicator
- fill_event
- add_callback
- remove_callback
- timer_callback
- compute_world_coordinate
- compute_screen_coordinates
- pick_area
- show
- add_inset
- clear
- break_interaction
- user_mode
- close_window
- close
- screenshot
- topicture
- export
- color_picker
1794class Clock(vedo.Assembly): 1795 """Clock animation.""" 1796 1797 def __init__(self, h=None, m=None, s=None, font="Quikhand", title="", c="k"): 1798 """ 1799 Create a clock with current time or user provided time. 1800 1801 Arguments: 1802 h : (int) 1803 hours in range [0,23] 1804 m : (int) 1805 minutes in range [0,59] 1806 s : (int) 1807 seconds in range [0,59] 1808 font : (str) 1809 font type 1810 title : (str) 1811 some extra text to show on the clock 1812 c : (str) 1813 color of the numbers 1814 1815 Example: 1816 ```python 1817 import time 1818 from vedo import show 1819 from vedo.applications import Clock 1820 clock = Clock() 1821 plt = show(clock, interactive=False) 1822 for i in range(10): 1823 time.sleep(1) 1824 clock.update() 1825 plt.render() 1826 plt.close() 1827 ``` 1828 ![](https://vedo.embl.es/images/feats/clock.png) 1829 """ 1830 self.elapsed = 0 1831 self._start = time.time() 1832 1833 wd = "" 1834 if h is None and m is None: 1835 t = time.localtime() 1836 h = t.tm_hour 1837 m = t.tm_min 1838 s = t.tm_sec 1839 if not title: 1840 d = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] 1841 wd = f"{d[t.tm_wday]} {t.tm_mday}/{t.tm_mon}/{t.tm_year} " 1842 1843 h = int(h) % 24 1844 m = int(m) % 60 1845 t = (h * 60 + m) / 12 / 60 1846 1847 alpha = 2 * np.pi * t + np.pi / 2 1848 beta = 12 * 2 * np.pi * t + np.pi / 2 1849 1850 x1, y1 = np.cos(alpha), np.sin(alpha) 1851 x2, y2 = np.cos(beta), np.sin(beta) 1852 if s is not None: 1853 s = int(s) % 60 1854 gamma = s * 2 * np.pi / 60 + np.pi / 2 1855 x3, y3 = np.cos(gamma), np.sin(gamma) 1856 1857 ore = Line([0, 0], [x1, y1], lw=14, c="red4").scale(0.5).mirror() 1858 minu = Line([0, 0], [x2, y2], lw=7, c="blue3").scale(0.75).mirror() 1859 secs = None 1860 if s is not None: 1861 secs = Line([0, 0], [x3, y3], lw=1, c="k").scale(0.95).mirror() 1862 secs.z(0.003) 1863 back1 = vedo.shapes.Circle(res=180, c="k5") 1864 back2 = vedo.shapes.Circle(res=12).mirror().scale(0.84).rotate_z(-360 / 12) 1865 labels = back2.labels(range(1, 13), justify="center", font=font, c=c, scale=0.14) 1866 txt = vedo.shapes.Text3D(wd + title, font="VictorMono", justify="top-center", s=0.07, c=c) 1867 txt.pos(0, -0.25, 0.001) 1868 labels.z(0.001) 1869 minu.z(0.002) 1870 vedo.Assembly.__init__(self, [back1, labels, ore, minu, secs, txt]) 1871 self.name = "Clock" 1872 1873 def update(self, h=None, m=None, s=None): 1874 """Update clock with current or user time.""" 1875 parts = self.unpack() 1876 self.elapsed = time.time() - self._start 1877 1878 if h is None and m is None: 1879 t = time.localtime() 1880 h = t.tm_hour 1881 m = t.tm_min 1882 s = t.tm_sec 1883 1884 h = int(h) % 24 1885 m = int(m) % 60 1886 t = (h * 60 + m) / 12 / 60 1887 1888 alpha = 2 * np.pi * t + np.pi / 2 1889 beta = 12 * 2 * np.pi * t + np.pi / 2 1890 1891 x1, y1 = np.cos(alpha), np.sin(alpha) 1892 x2, y2 = np.cos(beta), np.sin(beta) 1893 if s is not None: 1894 s = int(s) % 60 1895 gamma = s * 2 * np.pi / 60 + np.pi / 2 1896 x3, y3 = np.cos(gamma), np.sin(gamma) 1897 1898 pts2 = parts[2].points() 1899 pts2[1] = [-x1 * 0.5, y1 * 0.5, 0.001] 1900 parts[2].points(pts2) 1901 1902 pts3 = parts[3].points() 1903 pts3[1] = [-x2 * 0.75, y2 * 0.75, 0.002] 1904 parts[3].points(pts3) 1905 1906 if s is not None: 1907 pts4 = parts[4].points() 1908 pts4[1] = [-x3 * 0.95, y3 * 0.95, 0.003] 1909 parts[4].points(pts4) 1910 1911 return self
Clock animation.
1797 def __init__(self, h=None, m=None, s=None, font="Quikhand", title="", c="k"): 1798 """ 1799 Create a clock with current time or user provided time. 1800 1801 Arguments: 1802 h : (int) 1803 hours in range [0,23] 1804 m : (int) 1805 minutes in range [0,59] 1806 s : (int) 1807 seconds in range [0,59] 1808 font : (str) 1809 font type 1810 title : (str) 1811 some extra text to show on the clock 1812 c : (str) 1813 color of the numbers 1814 1815 Example: 1816 ```python 1817 import time 1818 from vedo import show 1819 from vedo.applications import Clock 1820 clock = Clock() 1821 plt = show(clock, interactive=False) 1822 for i in range(10): 1823 time.sleep(1) 1824 clock.update() 1825 plt.render() 1826 plt.close() 1827 ``` 1828 ![](https://vedo.embl.es/images/feats/clock.png) 1829 """ 1830 self.elapsed = 0 1831 self._start = time.time() 1832 1833 wd = "" 1834 if h is None and m is None: 1835 t = time.localtime() 1836 h = t.tm_hour 1837 m = t.tm_min 1838 s = t.tm_sec 1839 if not title: 1840 d = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] 1841 wd = f"{d[t.tm_wday]} {t.tm_mday}/{t.tm_mon}/{t.tm_year} " 1842 1843 h = int(h) % 24 1844 m = int(m) % 60 1845 t = (h * 60 + m) / 12 / 60 1846 1847 alpha = 2 * np.pi * t + np.pi / 2 1848 beta = 12 * 2 * np.pi * t + np.pi / 2 1849 1850 x1, y1 = np.cos(alpha), np.sin(alpha) 1851 x2, y2 = np.cos(beta), np.sin(beta) 1852 if s is not None: 1853 s = int(s) % 60 1854 gamma = s * 2 * np.pi / 60 + np.pi / 2 1855 x3, y3 = np.cos(gamma), np.sin(gamma) 1856 1857 ore = Line([0, 0], [x1, y1], lw=14, c="red4").scale(0.5).mirror() 1858 minu = Line([0, 0], [x2, y2], lw=7, c="blue3").scale(0.75).mirror() 1859 secs = None 1860 if s is not None: 1861 secs = Line([0, 0], [x3, y3], lw=1, c="k").scale(0.95).mirror() 1862 secs.z(0.003) 1863 back1 = vedo.shapes.Circle(res=180, c="k5") 1864 back2 = vedo.shapes.Circle(res=12).mirror().scale(0.84).rotate_z(-360 / 12) 1865 labels = back2.labels(range(1, 13), justify="center", font=font, c=c, scale=0.14) 1866 txt = vedo.shapes.Text3D(wd + title, font="VictorMono", justify="top-center", s=0.07, c=c) 1867 txt.pos(0, -0.25, 0.001) 1868 labels.z(0.001) 1869 minu.z(0.002) 1870 vedo.Assembly.__init__(self, [back1, labels, ore, minu, secs, txt]) 1871 self.name = "Clock"
Create a clock with current time or user provided time.
Arguments:
- h : (int) hours in range [0,23]
- m : (int) minutes in range [0,59]
- s : (int) seconds in range [0,59]
- font : (str) font type
- title : (str) some extra text to show on the clock
- c : (str) color of the numbers
Example:
import time from vedo import show from vedo.applications import Clock clock = Clock() plt = show(clock, interactive=False) for i in range(10): time.sleep(1) clock.update() plt.render() plt.close()
1873 def update(self, h=None, m=None, s=None): 1874 """Update clock with current or user time.""" 1875 parts = self.unpack() 1876 self.elapsed = time.time() - self._start 1877 1878 if h is None and m is None: 1879 t = time.localtime() 1880 h = t.tm_hour 1881 m = t.tm_min 1882 s = t.tm_sec 1883 1884 h = int(h) % 24 1885 m = int(m) % 60 1886 t = (h * 60 + m) / 12 / 60 1887 1888 alpha = 2 * np.pi * t + np.pi / 2 1889 beta = 12 * 2 * np.pi * t + np.pi / 2 1890 1891 x1, y1 = np.cos(alpha), np.sin(alpha) 1892 x2, y2 = np.cos(beta), np.sin(beta) 1893 if s is not None: 1894 s = int(s) % 60 1895 gamma = s * 2 * np.pi / 60 + np.pi / 2 1896 x3, y3 = np.cos(gamma), np.sin(gamma) 1897 1898 pts2 = parts[2].points() 1899 pts2[1] = [-x1 * 0.5, y1 * 0.5, 0.001] 1900 parts[2].points(pts2) 1901 1902 pts3 = parts[3].points() 1903 pts3[1] = [-x2 * 0.75, y2 * 0.75, 0.002] 1904 parts[3].points(pts3) 1905 1906 if s is not None: 1907 pts4 = parts[4].points() 1908 pts4[1] = [-x3 * 0.95, y3 * 0.95, 0.003] 1909 parts[4].points(pts4) 1910 1911 return self
Update clock with current or user time.