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