vedo.pointcloud
Submodule to work with point clouds
1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3import numpy as np 4 5try: 6 import vedo.vtkclasses as vtk 7except ImportError: 8 import vtkmodules.all as vtk 9 10import vedo 11from vedo import colors 12from vedo import utils 13from vedo.base import BaseActor 14 15__docformat__ = "google" 16 17__doc__ = """ 18Submodule to work with point clouds <br> 19 20![](https://vedo.embl.es/images/basic/pca.png) 21""" 22 23__all__ = [ 24 "Points", 25 "Point", 26 "merge", 27 "visible_points", 28 "delaunay2d", 29 "voronoi", 30 "fit_line", 31 "fit_circle", 32 "fit_plane", 33 "fit_sphere", 34 "pca_ellipse", 35 "pca_ellipsoid", 36] 37 38 39#################################################### 40def merge(*meshs, flag=False): 41 """ 42 Build a new Mesh (or Points) formed by the fusion of the inputs. 43 44 Similar to Assembly, but in this case the input objects become a single entity. 45 46 To keep track of the original identities of the inputs you can use `flag`. 47 In this case a point array of IDs is added to the output. 48 49 Examples: 50 - [warp1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/warp1.py) 51 52 ![](https://vedo.embl.es/images/advanced/warp1.png) 53 54 - [value_iteration.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/value_iteration.py) 55 56 """ 57 acts = [a for a in utils.flatten(meshs) if a] 58 59 if not acts: 60 return None 61 62 idarr = [] 63 polyapp = vtk.vtkAppendPolyData() 64 for i, a in enumerate(acts): 65 try: 66 poly = a.polydata() 67 except AttributeError: 68 # so a vtkPolydata can also be passed 69 poly = a 70 polyapp.AddInputData(poly) 71 if flag: 72 idarr += [i] * poly.GetNumberOfPoints() 73 polyapp.Update() 74 mpoly = polyapp.GetOutput() 75 76 if flag: 77 varr = utils.numpy2vtk(idarr, dtype=np.uint16, name="OriginalMeshID") 78 mpoly.GetPointData().AddArray(varr) 79 80 if isinstance(acts[0], vedo.Mesh): 81 msh = vedo.Mesh(mpoly) 82 else: 83 msh = Points(mpoly) 84 85 if isinstance(acts[0], vtk.vtkActor): 86 cprp = vtk.vtkProperty() 87 cprp.DeepCopy(acts[0].GetProperty()) 88 msh.SetProperty(cprp) 89 msh.property = cprp 90 91 msh.pipeline = utils.OperationNode( 92 "merge", parents=acts, 93 comment=f"#pts {msh.inputdata().GetNumberOfPoints()}", 94 ) 95 return msh 96 97 98#################################################### 99def visible_points(mesh, area=(), tol=None, invert=False): 100 """ 101 Extract points based on whether they are visible or not. 102 Visibility is determined by accessing the z-buffer of a rendering window. 103 The position of each input point is converted into display coordinates, 104 and then the z-value at that point is obtained. 105 If within the user-specified tolerance, the point is considered visible. 106 Associated data attributes are passed to the output as well. 107 108 This filter also allows you to specify a rectangular window in display (pixel) 109 coordinates in which the visible points must lie. 110 111 Arguments: 112 area : (list) 113 specify a rectangular region as (xmin,xmax,ymin,ymax) 114 tol : (float) 115 a tolerance in normalized display coordinate system 116 invert : (bool) 117 select invisible points instead. 118 119 Example: 120 ```python 121 from vedo import Ellipsoid, show, visible_points 122 s = Ellipsoid().rotate_y(30) 123 124 #Camera options: pos, focal_point, viewup, distance, 125 camopts = dict(pos=(0,0,25), focal_point=(0,0,0)) 126 show(s, camera=camopts, offscreen=True) 127 128 m = visible_points(s) 129 #print('visible pts:', m.points()) # numpy array 130 show(m, new=True, axes=1) # optionally draw result on a new window 131 ``` 132 ![](https://vedo.embl.es/images/feats/visible_points.png) 133 """ 134 # specify a rectangular region 135 svp = vtk.vtkSelectVisiblePoints() 136 svp.SetInputData(mesh.polydata()) 137 svp.SetRenderer(vedo.plotter_instance.renderer) 138 139 if len(area) == 4: 140 svp.SetSelection(area[0], area[1], area[2], area[3]) 141 if tol is not None: 142 svp.SetTolerance(tol) 143 if invert: 144 svp.SelectInvisibleOn() 145 svp.Update() 146 147 m = Points(svp.GetOutput()).point_size(5) 148 m.name = "VisiblePoints" 149 return m 150 151 152def delaunay2d(plist, mode="scipy", boundaries=(), tol=None, alpha=0.0, offset=0.0, transform=None): 153 """ 154 Create a mesh from points in the XY plane. 155 If `mode='fit'` then the filter computes a best fitting 156 plane and projects the points onto it. 157 158 Arguments: 159 tol : (float) 160 specify a tolerance to control discarding of closely spaced points. 161 This tolerance is specified as a fraction of the diagonal length of the bounding box of the points. 162 alpha : (float) 163 for a non-zero alpha value, only edges or triangles contained 164 within a sphere centered at mesh vertices will be output. 165 Otherwise, only triangles will be output. 166 offset : (float) 167 multiplier to control the size of the initial, bounding Delaunay triangulation. 168 transform: vtkTransform 169 a VTK transformation (eg. a thinplate spline) 170 which is applied to points to generate a 2D problem. 171 This maps a 3D dataset into a 2D dataset where triangulation can be done on the XY plane. 172 The points are transformed and triangulated. 173 The topology of triangulated points is used as the output topology. 174 175 Examples: 176 - [delaunay2d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/delaunay2d.py) 177 178 ![](https://vedo.embl.es/images/basic/delaunay2d.png) 179 """ 180 if isinstance(plist, Points): 181 parents = [plist] 182 plist = plist.points() 183 else: 184 parents = [] 185 plist = np.ascontiguousarray(plist) 186 plist = utils.make3d(plist) 187 188 ############################################# 189 if mode == "scipy": 190 from scipy.spatial import Delaunay as scipy_delaunay 191 192 tri = scipy_delaunay(plist[:, 0:2]) 193 return vedo.mesh.Mesh([plist, tri.simplices]) 194 ############################################# 195 196 pd = vtk.vtkPolyData() 197 vpts = vtk.vtkPoints() 198 vpts.SetData(utils.numpy2vtk(plist, dtype=np.float32)) 199 pd.SetPoints(vpts) 200 201 delny = vtk.vtkDelaunay2D() 202 delny.SetInputData(pd) 203 if tol: 204 delny.SetTolerance(tol) 205 delny.SetAlpha(alpha) 206 delny.SetOffset(offset) 207 if transform: 208 if hasattr(transform, "transform"): 209 transform = transform.transform 210 delny.SetTransform(transform) 211 212 if mode == "xy" and boundaries: 213 boundary = vtk.vtkPolyData() 214 boundary.SetPoints(vpts) 215 cell_array = vtk.vtkCellArray() 216 for b in boundaries: 217 cpolygon = vtk.vtkPolygon() 218 for idd in b: 219 cpolygon.GetPointIds().InsertNextId(idd) 220 cell_array.InsertNextCell(cpolygon) 221 boundary.SetPolys(cell_array) 222 delny.SetSourceData(boundary) 223 224 if mode == "fit": 225 delny.SetProjectionPlaneMode(vtk.VTK_BEST_FITTING_PLANE) 226 delny.Update() 227 msh = vedo.mesh.Mesh(delny.GetOutput()).clean().lighting("off") 228 229 msh.pipeline = utils.OperationNode( 230 "delaunay2d", parents=parents, 231 comment=f"#cells {msh.inputdata().GetNumberOfCells()}" 232 ) 233 return msh 234 235 236def voronoi(pts, padding=0.0, fit=False, method="vtk"): 237 """ 238 Generate the 2D Voronoi convex tiling of the input points (z is ignored). 239 The points are assumed to lie in a plane. The output is a Mesh. Each output cell is a convex polygon. 240 241 The 2D Voronoi tessellation is a tiling of space, where each Voronoi tile represents the region nearest 242 to one of the input points. Voronoi tessellations are important in computational geometry 243 (and many other fields), and are the dual of Delaunay triangulations. 244 245 Thus the triangulation is constructed in the x-y plane, and the z coordinate is ignored 246 (although carried through to the output). 247 If you desire to triangulate in a different plane, you can use fit=True. 248 249 A brief summary is as follows. Each (generating) input point is associated with 250 an initial Voronoi tile, which is simply the bounding box of the point set. 251 A locator is then used to identify nearby points: each neighbor in turn generates a 252 clipping line positioned halfway between the generating point and the neighboring point, 253 and orthogonal to the line connecting them. Clips are readily performed by evaluationg the 254 vertices of the convex Voronoi tile as being on either side (inside,outside) of the clip line. 255 If two intersections of the Voronoi tile are found, the portion of the tile "outside" the clip 256 line is discarded, resulting in a new convex, Voronoi tile. As each clip occurs, 257 the Voronoi "Flower" error metric (the union of error spheres) is compared to the extent of the region 258 containing the neighboring clip points. The clip region (along with the points contained in it) is grown 259 by careful expansion (e.g., outward spiraling iterator over all candidate clip points). 260 When the Voronoi Flower is contained within the clip region, the algorithm terminates and the Voronoi 261 tile is output. Once complete, it is possible to construct the Delaunay triangulation from the Voronoi 262 tessellation. Note that topological and geometric information is used to generate a valid triangulation 263 (e.g., merging points and validating topology). 264 265 Arguments: 266 pts : (list) 267 list of input points. 268 padding : (float) 269 padding distance. The default is 0. 270 fit : (bool) 271 detect automatically the best fitting plane. The default is False. 272 273 Examples: 274 - [voronoi1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/voronoi1.py) 275 276 ![](https://vedo.embl.es/images/basic/voronoi1.png) 277 278 - [voronoi2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/voronoi2.py) 279 280 ![](https://vedo.embl.es/images/advanced/voronoi2.png) 281 """ 282 if method == "scipy": 283 from scipy.spatial import Voronoi as scipy_voronoi 284 285 pts = np.asarray(pts)[:, (0, 1)] 286 vor = scipy_voronoi(pts) 287 regs = [] # filter out invalid indices 288 for r in vor.regions: 289 flag = True 290 for x in r: 291 if x < 0: 292 flag = False 293 break 294 if flag and len(r) > 0: 295 regs.append(r) 296 297 m = vedo.Mesh([vor.vertices, regs], c="orange5") 298 m.celldata["VoronoiID"] = np.array(list(range(len(regs)))).astype(int) 299 m.locator = None 300 301 elif method == "vtk": 302 vor = vtk.vtkVoronoi2D() 303 if isinstance(pts, Points): 304 vor.SetInputData(pts.polydata()) 305 else: 306 pts = np.asarray(pts) 307 if pts.shape[1] == 2: 308 pts = np.c_[pts, np.zeros(len(pts))] 309 pd = vtk.vtkPolyData() 310 vpts = vtk.vtkPoints() 311 vpts.SetData(utils.numpy2vtk(pts, dtype=np.float32)) 312 pd.SetPoints(vpts) 313 vor.SetInputData(pd) 314 vor.SetPadding(padding) 315 vor.SetGenerateScalarsToPointIds() 316 if fit: 317 vor.SetProjectionPlaneModeToBestFittingPlane() 318 else: 319 vor.SetProjectionPlaneModeToXYPlane() 320 vor.Update() 321 poly = vor.GetOutput() 322 arr = poly.GetCellData().GetArray(0) 323 if arr: 324 arr.SetName("VoronoiID") 325 m = vedo.Mesh(poly, c="orange5") 326 m.locator = vor.GetLocator() 327 328 else: 329 vedo.logger.error(f"Unknown method {method} in voronoi()") 330 raise RuntimeError 331 332 m.lw(2).lighting("off").wireframe() 333 m.name = "Voronoi" 334 return m 335 336 337def _rotate_points(points, n0=None, n1=(0, 0, 1)): 338 # Rotate a set of 3D points from direction n0 to direction n1. 339 # Return the rotated points and the normal to the fitting plane (if n0 is None). 340 # The pointing direction of the normal in this case is arbitrary. 341 points = np.asarray(points) 342 343 if points.ndim == 1: 344 points = points[np.newaxis, :] 345 346 if len(points[0]) == 2: 347 return points, (0, 0, 1) 348 349 if n0 is None: # fit plane 350 datamean = points.mean(axis=0) 351 vv = np.linalg.svd(points - datamean)[2] 352 n0 = np.cross(vv[0], vv[1]) 353 354 n0 = n0 / np.linalg.norm(n0) 355 n1 = n1 / np.linalg.norm(n1) 356 k = np.cross(n0, n1) 357 l = np.linalg.norm(k) 358 if not l: 359 k = n0 360 k /= np.linalg.norm(k) 361 362 ct = np.dot(n0, n1) 363 theta = np.arccos(ct) 364 st = np.sin(theta) 365 v = k * (1 - ct) 366 367 rpoints = [] 368 for p in points: 369 a = p * ct 370 b = np.cross(k, p) * st 371 c = v * np.dot(k, p) 372 rpoints.append(a + b + c) 373 374 return np.array(rpoints), n0 375 376 377def fit_line(points): 378 """ 379 Fits a line through points. 380 381 Extra info is stored in `Line.slope`, `Line.center`, `Line.variances`. 382 383 Examples: 384 - [fitline.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitline.py) 385 386 ![](https://vedo.embl.es/images/advanced/fitline.png) 387 """ 388 if isinstance(points, Points): 389 points = points.points() 390 data = np.array(points) 391 datamean = data.mean(axis=0) 392 _, dd, vv = np.linalg.svd(data - datamean) 393 vv = vv[0] / np.linalg.norm(vv[0]) 394 # vv contains the first principal component, i.e. the direction 395 # vector of the best fit line in the least squares sense. 396 xyz_min = points.min(axis=0) 397 xyz_max = points.max(axis=0) 398 a = np.linalg.norm(xyz_min - datamean) 399 b = np.linalg.norm(xyz_max - datamean) 400 p1 = datamean - a * vv 401 p2 = datamean + b * vv 402 line = vedo.shapes.Line(p1, p2, lw=1) 403 line.slope = vv 404 line.center = datamean 405 line.variances = dd 406 return line 407 408 409def fit_circle(points): 410 """ 411 Fits a circle through a set of 3D points, with a very fast non-iterative method. 412 413 Returns the tuple `(center, radius, normal_to_circle)`. 414 415 .. warning:: 416 trying to fit s-shaped points will inevitably lead to instabilities and 417 circles of small radius. 418 419 References: 420 *J.F. Crawford, Nucl. Instr. Meth. 211, 1983, 223-225.* 421 """ 422 data = np.asarray(points) 423 424 offs = data.mean(axis=0) 425 data, n0 = _rotate_points(data - offs) 426 427 xi = data[:, 0] 428 yi = data[:, 1] 429 430 x = sum(xi) 431 xi2 = xi * xi 432 xx = sum(xi2) 433 xxx = sum(xi2 * xi) 434 435 y = sum(yi) 436 yi2 = yi * yi 437 yy = sum(yi2) 438 yyy = sum(yi2 * yi) 439 440 xiyi = xi * yi 441 xy = sum(xiyi) 442 xyy = sum(xiyi * yi) 443 xxy = sum(xi * xiyi) 444 445 N = len(xi) 446 k = (xx + yy) / N 447 448 a1 = xx - x * x / N 449 b1 = xy - x * y / N 450 c1 = 0.5 * (xxx + xyy - x * k) 451 452 a2 = xy - x * y / N 453 b2 = yy - y * y / N 454 c2 = 0.5 * (xxy + yyy - y * k) 455 456 d = a2 * b1 - a1 * b2 457 if not d: 458 return offs, 0, n0 459 x0 = (b1 * c2 - b2 * c1) / d 460 y0 = (c1 - a1 * x0) / b1 461 462 R = np.sqrt(x0 * x0 + y0 * y0 - 1 / N * (2 * x0 * x + 2 * y0 * y - xx - yy)) 463 464 c, _ = _rotate_points([x0, y0, 0], (0, 0, 1), n0) 465 466 return c[0] + offs, R, n0 467 468 469def fit_plane(points, signed=False): 470 """ 471 Fits a plane to a set of points. 472 473 Extra info is stored in `Plane.normal`, `Plane.center`, `Plane.variance`. 474 475 Arguments: 476 signed : (bool) 477 if True flip sign of the normal based on the ordering of the points 478 479 Examples: 480 - [fitline.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitline.py) 481 482 ![](https://vedo.embl.es/images/advanced/fitline.png) 483 """ 484 if isinstance(points, Points): 485 points = points.points() 486 data = np.asarray(points) 487 datamean = data.mean(axis=0) 488 pts = data - datamean 489 res = np.linalg.svd(pts) 490 dd, vv = res[1], res[2] 491 n = np.cross(vv[0], vv[1]) 492 if signed: 493 v = np.zeros_like(pts) 494 for i in range(len(pts) - 1): 495 vi = np.cross(pts[i], pts[i + 1]) 496 v[i] = vi / np.linalg.norm(vi) 497 ns = np.mean(v, axis=0) # normal to the points plane 498 if np.dot(n, ns) < 0: 499 n = -n 500 xyz_min = data.min(axis=0) 501 xyz_max = data.max(axis=0) 502 s = np.linalg.norm(xyz_max - xyz_min) 503 pla = vedo.shapes.Plane(datamean, n, s=[s, s]) 504 pla.normal = n 505 pla.center = datamean 506 pla.variance = dd[2] 507 pla.name = "FitPlane" 508 pla.top = n 509 return pla 510 511 512def fit_sphere(coords): 513 """ 514 Fits a sphere to a set of points. 515 516 Extra info is stored in `Sphere.radius`, `Sphere.center`, `Sphere.residue`. 517 518 Examples: 519 - [fitspheres1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/fitspheres1.py) 520 521 ![](https://vedo.embl.es/images/advanced/fitspheres1.jpg) 522 """ 523 if isinstance(coords, Points): 524 coords = coords.points() 525 coords = np.array(coords) 526 n = len(coords) 527 A = np.zeros((n, 4)) 528 A[:, :-1] = coords * 2 529 A[:, 3] = 1 530 f = np.zeros((n, 1)) 531 x = coords[:, 0] 532 y = coords[:, 1] 533 z = coords[:, 2] 534 f[:, 0] = x * x + y * y + z * z 535 try: 536 C, residue, rank, _ = np.linalg.lstsq(A, f, rcond=-1) # solve AC=f 537 except: 538 C, residue, rank, _ = np.linalg.lstsq(A, f) # solve AC=f 539 if rank < 4: 540 return None 541 t = (C[0] * C[0]) + (C[1] * C[1]) + (C[2] * C[2]) + C[3] 542 radius = np.sqrt(t)[0] 543 center = np.array([C[0][0], C[1][0], C[2][0]]) 544 if len(residue) > 0: 545 residue = np.sqrt(residue[0]) / n 546 else: 547 residue = 0 548 sph = vedo.shapes.Sphere(center, radius, c=(1, 0, 0)).wireframe(1) 549 sph.radius = radius 550 sph.center = center 551 sph.residue = residue 552 sph.name = "FitSphere" 553 return sph 554 555 556def pca_ellipse(points, pvalue=0.673, res=60): 557 """ 558 Show the oriented PCA 2D ellipse that contains the fraction `pvalue` of points. 559 560 Parameter `pvalue` sets the specified fraction of points inside the ellipse. 561 Normalized directions are stored in `ellipse.axis1`, `ellipse.axis12` 562 axes sizes are stored in `ellipse.va`, `ellipse.vb` 563 564 Arguments: 565 pvalue : (float) 566 ellipse will include this fraction of points 567 res : (int) 568 resolution of the ellipse 569 570 Examples: 571 - [histo_pca.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_pca.py) 572 573 ![](https://vedo.embl.es/images/pyplot/histo_pca.png) 574 """ 575 from scipy.stats import f 576 577 if isinstance(points, Points): 578 coords = points.points() 579 else: 580 coords = points 581 if len(coords) < 4: 582 vedo.logger.warning("in pca_ellipse(), there are not enough points!") 583 return None 584 585 P = np.array(coords, dtype=float)[:,(0,1)] 586 cov = np.cov(P, rowvar=0) # covariance matrix 587 _, s, R = np.linalg.svd(cov) # singular value decomposition 588 p, n = s.size, P.shape[0] 589 fppf = f.ppf(pvalue, p, n-p) # f % point function 590 ua, ub = np.sqrt(s*fppf/2)*2 # semi-axes (largest first) 591 center = np.mean(P, axis=0) # centroid of the ellipse 592 593 matri = vtk.vtkMatrix4x4() 594 matri.DeepCopy(( 595 R[0][0] * ua, R[1][0] * ub, 0, center[0], 596 R[0][1] * ua, R[1][1] * ub, 0, center[1], 597 0, 0, 1, 0, 598 0, 0, 0, 1) 599 ) 600 vtra = vtk.vtkTransform() 601 vtra.SetMatrix(matri) 602 603 elli = vedo.shapes.Circle(alpha=0.75, res=res) 604 605 # assign the transformation 606 elli.SetScale(vtra.GetScale()) 607 elli.SetOrientation(vtra.GetOrientation()) 608 elli.SetPosition(vtra.GetPosition()) 609 610 elli.center = np.array(vtra.GetPosition()) 611 elli.nr_of_points = n 612 elli.va = ua 613 elli.vb = ub 614 elli.axis1 = np.array(vtra.TransformPoint([1, 0, 0])) - elli.center 615 elli.axis2 = np.array(vtra.TransformPoint([0, 1, 0])) - elli.center 616 elli.axis1 /= np.linalg.norm(elli.axis1) 617 elli.axis2 /= np.linalg.norm(elli.axis2) 618 elli.transformation = vtra 619 elli.name = "PCAEllipse" 620 return elli 621 622 623def pca_ellipsoid(points, pvalue=0.673): 624 """ 625 Show the oriented PCA ellipsoid that contains fraction `pvalue` of points. 626 627 Parameter `pvalue` sets the specified fraction of points inside the ellipsoid. 628 629 Extra can be calculated with `mesh.asphericity()`, `mesh.asphericity_error()` 630 (asphericity is equal to 0 for a perfect sphere). 631 632 Axes sizes can be accessed in `ellips.va`, `ellips.vb`, `ellips.vc`, 633 normalized directions are stored in `ellips.axis1`, `ellips.axis12` 634 and `ellips.axis3`. 635 636 .. warning:: the meaning of `ellips.axis1`, has changed wrt `vedo==2022.1.0` 637 in that it's now the direction wrt the origin (e.i. the center is subtracted) 638 639 Examples: 640 - [pca_ellipsoid.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/pca_ellipsoid.py) 641 642 ![](https://vedo.embl.es/images/basic/pca.png) 643 """ 644 from scipy.stats import f 645 646 if isinstance(points, Points): 647 coords = points.points() 648 else: 649 coords = points 650 if len(coords) < 4: 651 vedo.logger.warning("in pcaEllipsoid(), there are not enough points!") 652 return None 653 654 P = np.array(coords, ndmin=2, dtype=float) 655 cov = np.cov(P, rowvar=0) # covariance matrix 656 _, s, R = np.linalg.svd(cov) # singular value decomposition 657 p, n = s.size, P.shape[0] 658 fppf = f.ppf(pvalue, p, n-p)*(n-1)*p*(n+1)/n/(n-p) # f % point function 659 cfac = 1 + 6/(n-1) # correction factor for low statistics 660 ua, ub, uc = np.sqrt(s*fppf)/cfac # semi-axes (largest first) 661 center = np.mean(P, axis=0) # centroid of the hyperellipsoid 662 663 elli = vedo.shapes.Ellipsoid((0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1), alpha=0.25) 664 665 matri = vtk.vtkMatrix4x4() 666 matri.DeepCopy(( 667 R[0][0] * ua*2, R[1][0] * ub*2, R[2][0] * uc*2, center[0], 668 R[0][1] * ua*2, R[1][1] * ub*2, R[2][1] * uc*2, center[1], 669 R[0][2] * ua*2, R[1][2] * ub*2, R[2][2] * uc*2, center[2], 670 0, 0, 0, 1) 671 ) 672 vtra = vtk.vtkTransform() 673 vtra.SetMatrix(matri) 674 675 # assign the transformation 676 elli.SetScale(vtra.GetScale()) 677 elli.SetOrientation(vtra.GetOrientation()) 678 elli.SetPosition(vtra.GetPosition()) 679 680 elli.center = np.array(vtra.GetPosition()) 681 elli.nr_of_points = n 682 elli.va = ua 683 elli.vb = ub 684 elli.vc = uc 685 elli.axis1 = np.array(vtra.TransformPoint([1, 0, 0])) - elli.center 686 elli.axis2 = np.array(vtra.TransformPoint([0, 1, 0])) - elli.center 687 elli.axis3 = np.array(vtra.TransformPoint([0, 0, 1])) - elli.center 688 elli.axis1 /= np.linalg.norm(elli.axis1) 689 elli.axis2 /= np.linalg.norm(elli.axis2) 690 elli.axis3 /= np.linalg.norm(elli.axis3) 691 elli.transformation = vtra 692 elli.name = "PCAEllipsoid" 693 return elli 694 695 696################################################### 697def Point(pos=(0, 0, 0), r=12, c="red", alpha=1.0): 698 """ 699 Create a simple point in space. 700 701 .. note:: if you are creating many points you should definitely use class `Points` instead! 702 """ 703 if isinstance(pos, vtk.vtkActor): 704 pos = pos.GetPosition() 705 pd = utils.buildPolyData([[0, 0, 0]]) 706 if len(pos) == 2: 707 pos = (pos[0], pos[1], 0.0) 708 pt = Points(pd, r, c, alpha) 709 pt.SetPosition(pos) 710 pt.name = "Point" 711 return pt 712 713 714################################################### 715class Points(BaseActor, vtk.vtkActor): 716 """Work with pointclouds.""" 717 718 def __init__(self, inputobj=None, r=4, c=(0.2, 0.2, 0.2), alpha=1, blur=False, emissive=True): 719 """ 720 Build an object made of only vertex points for a list of 2D/3D points. 721 Both shapes (N, 3) or (3, N) are accepted as input, if N>3. 722 For very large point clouds a list of colors and alpha can be assigned to each 723 point in the form c=[(R,G,B,A), ... ] where 0<=R<256, ... 0<=A<256. 724 725 Arguments: 726 inputobj : (list, tuple) 727 r : (int) 728 Point radius in units of pixels. 729 c : (str, list) 730 Color name or rgb tuple. 731 alpha : (float) 732 Transparency in range [0,1]. 733 blur : (bool) 734 Apply a gaussian convolution filter to the points. 735 In this case the radius `r` is in absolute units of the mesh coordinates. 736 emissive : (bool) 737 Halo of point becomes emissive. 738 739 Example: 740 ```python 741 from vedo import * 742 743 def fibonacci_sphere(n): 744 s = np.linspace(0, n, num=n, endpoint=False) 745 theta = s * 2.399963229728653 746 y = 1 - s * (2/(n-1)) 747 r = np.sqrt(1 - y * y) 748 x = np.cos(theta) * r 749 z = np.sin(theta) * r 750 return [x,y,z] 751 752 Points(fibonacci_sphere(1000)).show(axes=1).close() 753 ``` 754 ![](https://vedo.embl.es/images/feats/fibonacci.png) 755 """ 756 757 vtk.vtkActor.__init__(self) 758 BaseActor.__init__(self) 759 760 self._data = None 761 762 if blur: 763 self._mapper = vtk.vtkPointGaussianMapper() 764 if emissive: 765 self._mapper.SetEmissive(bool(emissive)) 766 self._mapper.SetScaleFactor(r * 1.4142) 767 768 # https://kitware.github.io/vtk-examples/site/Python/Meshes/PointInterpolator/ 769 if alpha < 1: 770 self._mapper.SetSplatShaderCode( 771 "//VTK::Color::Impl\n" 772 "float dist = dot(offsetVCVSOutput.xy,offsetVCVSOutput.xy);\n" 773 "if (dist > 1.0) {\n" 774 " discard;\n" 775 "} else {\n" 776 f" float scale = ({alpha} - dist);\n" 777 " ambientColor *= scale;\n" 778 " diffuseColor *= scale;\n" 779 "}\n" 780 ) 781 alpha = 1 782 783 else: 784 self._mapper = vtk.vtkPolyDataMapper() 785 self.SetMapper(self._mapper) 786 787 self._bfprop = None # backface property holder 788 789 self._scals_idx = 0 # index of the active scalar changed from CLI 790 self._ligthingnr = 0 # index of the lighting mode changed from CLI 791 self._cmap_name = "" # remember the name for self._keypress 792 # self.name = "Points" # better not to give it a name here 793 794 self.property = self.GetProperty() 795 796 try: 797 if not blur: 798 self.property.RenderPointsAsSpheresOn() 799 except AttributeError: 800 pass 801 802 if inputobj is None: #################### 803 self._data = vtk.vtkPolyData() 804 return 805 ######################################## 806 807 self.property.SetRepresentationToPoints() 808 self.property.SetPointSize(r) 809 self.property.LightingOff() 810 811 if isinstance(inputobj, vedo.BaseActor): 812 inputobj = inputobj.points() # numpy 813 814 ###### 815 if isinstance(inputobj, vtk.vtkActor): 816 poly_copy = vtk.vtkPolyData() 817 pr = vtk.vtkProperty() 818 pr.DeepCopy(inputobj.GetProperty()) 819 poly_copy.DeepCopy(inputobj.GetMapper().GetInput()) 820 pr.SetRepresentationToPoints() 821 pr.SetPointSize(r) 822 self._data = poly_copy 823 self._mapper.SetInputData(poly_copy) 824 self._mapper.SetScalarVisibility(inputobj.GetMapper().GetScalarVisibility()) 825 self.SetProperty(pr) 826 self.property = pr 827 828 elif isinstance(inputobj, vtk.vtkPolyData): 829 if inputobj.GetNumberOfCells() == 0: 830 carr = vtk.vtkCellArray() 831 for i in range(inputobj.GetNumberOfPoints()): 832 carr.InsertNextCell(1) 833 carr.InsertCellPoint(i) 834 inputobj.SetVerts(carr) 835 self._data = inputobj # cache vtkPolyData and mapper for speed 836 837 elif utils.is_sequence(inputobj): # passing point coords 838 plist = inputobj 839 n = len(plist) 840 841 if n == 3: # assume plist is in the format [all_x, all_y, all_z] 842 if utils.is_sequence(plist[0]) and len(plist[0]) > 3: 843 plist = np.stack((plist[0], plist[1], plist[2]), axis=1) 844 elif n == 2: # assume plist is in the format [all_x, all_y, 0] 845 if utils.is_sequence(plist[0]) and len(plist[0]) > 3: 846 plist = np.stack((plist[0], plist[1], np.zeros(len(plist[0]))), axis=1) 847 848 # if n and len(plist[0]) == 2: # make it 3d 849 # plist = np.c_[np.array(plist), np.zeros(len(plist))] 850 plist = utils.make3d(plist) 851 852 if ( 853 utils.is_sequence(c) 854 and (len(c) > 3 or (utils.is_sequence(c[0]) and len(c[0]) == 4)) 855 ) or utils.is_sequence(alpha): 856 857 cols = c 858 859 n = len(plist) 860 if n != len(cols): 861 vedo.logger.error(f"mismatch in Points() colors array lengths {n} and {len(cols)}") 862 raise RuntimeError() 863 864 src = vtk.vtkPointSource() 865 src.SetNumberOfPoints(n) 866 src.Update() 867 868 vgf = vtk.vtkVertexGlyphFilter() 869 vgf.SetInputData(src.GetOutput()) 870 vgf.Update() 871 pd = vgf.GetOutput() 872 873 pd.GetPoints().SetData(utils.numpy2vtk(plist, dtype=np.float32)) 874 875 ucols = vtk.vtkUnsignedCharArray() 876 ucols.SetNumberOfComponents(4) 877 ucols.SetName("Points_RGBA") 878 if utils.is_sequence(alpha): 879 if len(alpha) != n: 880 vedo.logger.error(f"mismatch in Points() alpha array lengths {n} and {len(cols)}") 881 raise RuntimeError() 882 alphas = alpha 883 alpha = 1 884 else: 885 alphas = (alpha,) * n 886 887 if utils.is_sequence(cols): 888 c = None 889 if len(cols[0]) == 4: 890 for i in range(n): # FAST 891 rc, gc, bc, ac = cols[i] 892 ucols.InsertNextTuple4(rc, gc, bc, ac) 893 else: 894 for i in range(n): # SLOW 895 rc, gc, bc = colors.get_color(cols[i]) 896 ucols.InsertNextTuple4(rc * 255, gc * 255, bc * 255, alphas[i] * 255) 897 else: 898 c = cols 899 900 pd.GetPointData().AddArray(ucols) 901 pd.GetPointData().SetActiveScalars("Points_RGBA") 902 self._mapper.SetInputData(pd) 903 self._mapper.ScalarVisibilityOn() 904 self._data = pd 905 906 else: 907 908 pd = utils.buildPolyData(plist) 909 self._mapper.SetInputData(pd) 910 c = colors.get_color(c) 911 self.property.SetColor(c) 912 self.property.SetOpacity(alpha) 913 self._data = pd 914 915 ########## 916 self.pipeline = utils.OperationNode( 917 self, parents=[], comment=f"#pts {self._data.GetNumberOfPoints()}" 918 ) 919 return 920 ########## 921 922 elif isinstance(inputobj, str): 923 verts = vedo.file_io.load(inputobj) 924 self.filename = inputobj 925 self._data = verts.polydata() 926 927 else: 928 929 # try to extract the points from the VTK input data object 930 try: 931 vvpts = inputobj.GetPoints() 932 pd = vtk.vtkPolyData() 933 pd.SetPoints(vvpts) 934 for i in range(inputobj.GetPointData().GetNumberOfArrays()): 935 arr = inputobj.GetPointData().GetArray(i) 936 pd.GetPointData().AddArray(arr) 937 938 self._mapper.SetInputData(pd) 939 c = colors.get_color(c) 940 self.property.SetColor(c) 941 self.property.SetOpacity(alpha) 942 self._data = pd 943 except: 944 vedo.logger.error(f"cannot build Points from type {type(inputobj)}") 945 raise RuntimeError() 946 947 c = colors.get_color(c) 948 self.property.SetColor(c) 949 self.property.SetOpacity(alpha) 950 951 self._mapper.SetInputData(self._data) 952 953 self.pipeline = utils.OperationNode( 954 self, parents=[], comment=f"#pts {self._data.GetNumberOfPoints()}" 955 ) 956 return 957 958 def _repr_html_(self): 959 """ 960 HTML representation of the Point cloud object for Jupyter Notebooks. 961 962 Returns: 963 HTML text with the image and some properties. 964 """ 965 import io 966 import base64 967 from PIL import Image 968 969 library_name = "vedo.pointcloud.Points" 970 help_url = "https://vedo.embl.es/docs/vedo/pointcloud.html" 971 972 arr = self.thumbnail() 973 im = Image.fromarray(arr) 974 buffered = io.BytesIO() 975 im.save(buffered, format="PNG", quality=100) 976 encoded = base64.b64encode(buffered.getvalue()).decode("utf-8") 977 url = "data:image/png;base64," + encoded 978 image = f"<img src='{url}'></img>" 979 980 bounds = "<br/>".join( 981 [ 982 utils.precision(min_x, 4) + " ... " + utils.precision(max_x, 4) 983 for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2]) 984 ] 985 ) 986 average_size = "{size:.3f}".format(size=self.average_size()) 987 988 help_text = "" 989 if self.name: 990 help_text += f"<b> {self.name}:   </b>" 991 help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>" 992 if self.filename: 993 dots = "" 994 if len(self.filename) > 30: 995 dots = "..." 996 help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>" 997 998 pdata = "" 999 if self._data.GetPointData().GetScalars(): 1000 if self._data.GetPointData().GetScalars().GetName(): 1001 name = self._data.GetPointData().GetScalars().GetName() 1002 pdata = "<tr><td><b> point data array </b></td><td>" + name + "</td></tr>" 1003 1004 cdata = "" 1005 if self._data.GetCellData().GetScalars(): 1006 if self._data.GetCellData().GetScalars().GetName(): 1007 name = self._data.GetCellData().GetScalars().GetName() 1008 cdata = "<tr><td><b> cell data array </b></td><td>" + name + "</td></tr>" 1009 1010 allt = [ 1011 "<table>", 1012 "<tr>", 1013 "<td>", 1014 image, 1015 "</td>", 1016 "<td style='text-align: center; vertical-align: center;'><br/>", 1017 help_text, 1018 "<table>", 1019 "<tr><td><b> bounds </b> <br/> (x/y/z) </td><td>" + str(bounds) + "</td></tr>", 1020 "<tr><td><b> center of mass </b></td><td>" 1021 + utils.precision(self.center_of_mass(), 3) 1022 + "</td></tr>", 1023 "<tr><td><b> average size </b></td><td>" + str(average_size) + "</td></tr>", 1024 "<tr><td><b> nr. points </b></td><td>" + str(self.npoints) + "</td></tr>", 1025 pdata, 1026 cdata, 1027 "</table>", 1028 "</table>", 1029 ] 1030 return "\n".join(allt) 1031 1032 1033 ################################################################################## 1034 def _update(self, polydata): 1035 # Overwrite the polygonal mesh with a new vtkPolyData 1036 self._data = polydata 1037 self.mapper().SetInputData(polydata) 1038 self.mapper().Modified() 1039 return self 1040 1041 def __add__(self, meshs): 1042 if isinstance(meshs, list): 1043 alist = [self] 1044 for l in meshs: 1045 if isinstance(l, vedo.Assembly): 1046 alist += l.unpack() 1047 else: 1048 alist += l 1049 return vedo.assembly.Assembly(alist) 1050 1051 if isinstance(meshs, vedo.Assembly): 1052 return meshs + self # use Assembly.__add__ 1053 1054 return vedo.assembly.Assembly([self, meshs]) 1055 1056 def polydata(self, transformed=True): 1057 """ 1058 Returns the `vtkPolyData` object associated to a `Mesh`. 1059 1060 .. note:: 1061 If `transformed=True` return a copy of polydata that corresponds 1062 to the current mesh position in space. 1063 """ 1064 if not self._data: 1065 self._data = self.mapper().GetInput() 1066 return self._data 1067 1068 if transformed: 1069 # if self.GetIsIdentity() or self._data.GetNumberOfPoints()==0: # commmentd out on 15th feb 2020 1070 if self._data.GetNumberOfPoints() == 0: 1071 # no need to do much 1072 return self._data 1073 1074 # otherwise make a copy that corresponds to 1075 # the actual position in space of the mesh 1076 M = self.GetMatrix() 1077 transform = vtk.vtkTransform() 1078 transform.SetMatrix(M) 1079 tp = vtk.vtkTransformPolyDataFilter() 1080 tp.SetTransform(transform) 1081 tp.SetInputData(self._data) 1082 tp.Update() 1083 return tp.GetOutput() 1084 1085 return self._data 1086 1087 1088 def clone(self, deep=True, transformed=False): 1089 """ 1090 Clone a `PointCloud` or `Mesh` object to make an exact copy of it. 1091 1092 Arguments: 1093 deep : (bool) 1094 if False only build a shallow copy of the object (faster copy). 1095 1096 transformed : (bool) 1097 if True reset the current transformation of the copy to unit. 1098 1099 Examples: 1100 - [mirror.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mirror.py) 1101 1102 ![](https://vedo.embl.es/images/basic/mirror.png) 1103 """ 1104 poly = self.polydata(transformed) 1105 poly_copy = vtk.vtkPolyData() 1106 if deep: 1107 poly_copy.DeepCopy(poly) 1108 else: 1109 poly_copy.ShallowCopy(poly) 1110 1111 if isinstance(self, vedo.Mesh): 1112 cloned = vedo.Mesh(poly_copy) 1113 else: 1114 cloned = Points(poly_copy) 1115 1116 pr = vtk.vtkProperty() 1117 pr.DeepCopy(self.GetProperty()) 1118 cloned.SetProperty(pr) 1119 cloned.property = pr 1120 1121 if self.GetBackfaceProperty(): 1122 bfpr = vtk.vtkProperty() 1123 bfpr.DeepCopy(self.GetBackfaceProperty()) 1124 cloned.SetBackfaceProperty(bfpr) 1125 1126 if not transformed: 1127 if self.transform: 1128 # already has a transform which can be non linear, so use that 1129 cloned.SetUserTransform(self.transform) 1130 else: 1131 # assign the same transformation to the copy 1132 cloned.SetOrigin(self.GetOrigin()) 1133 cloned.SetScale(self.GetScale()) 1134 cloned.SetOrientation(self.GetOrientation()) 1135 cloned.SetPosition(self.GetPosition()) 1136 1137 mp = cloned.mapper() 1138 sm = self.mapper() 1139 mp.SetScalarVisibility(sm.GetScalarVisibility()) 1140 mp.SetScalarRange(sm.GetScalarRange()) 1141 mp.SetColorMode(sm.GetColorMode()) 1142 lsr = sm.GetUseLookupTableScalarRange() 1143 mp.SetUseLookupTableScalarRange(lsr) 1144 mp.SetScalarMode(sm.GetScalarMode()) 1145 lut = sm.GetLookupTable() 1146 if lut: 1147 mp.SetLookupTable(lut) 1148 1149 if self.GetTexture(): 1150 cloned.texture(self.GetTexture()) 1151 1152 cloned.SetPickable(self.GetPickable()) 1153 1154 cloned.base = np.array(self.base) 1155 cloned.top = np.array(self.top) 1156 cloned.name = str(self.name) 1157 cloned.filename = str(self.filename) 1158 cloned.info = dict(self.info) 1159 1160 # better not to share the same locators with original obj 1161 cloned.point_locator = None 1162 cloned.cell_locator = None 1163 1164 cloned.pipeline = utils.OperationNode("clone", parents=[self], shape="diamond", c="#edede9") 1165 return cloned 1166 1167 def clone2d( 1168 self, 1169 pos=(0, 0), 1170 coordsys=4, 1171 scale=None, 1172 c=None, 1173 alpha=None, 1174 ps=2, 1175 lw=1, 1176 sendback=False, 1177 layer=0, 1178 ): 1179 """ 1180 Copy a 3D Mesh into a static 2D image. Returns a `vtkActor2D`. 1181 1182 Arguments: 1183 coordsys : (int) 1184 the coordinate system, options are 1185 - 0 = Displays 1186 - 1 = Normalized Display 1187 - 2 = Viewport (origin is the bottom-left corner of the window) 1188 - 3 = Normalized Viewport 1189 - 4 = View (origin is the center of the window) 1190 - 5 = World (anchor the 2d image to mesh) 1191 1192 ps : (int) 1193 point size in pixel units 1194 1195 lw : (int) 1196 line width in pixel units 1197 1198 sendback : (bool) 1199 put it behind any other 3D object 1200 1201 Examples: 1202 - [clone2d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/clone2d.py) 1203 1204 ![](https://vedo.embl.es/images/other/clone2d.png) 1205 """ 1206 if scale is None: 1207 msiz = self.diagonal_size() 1208 if vedo.plotter_instance and vedo.plotter_instance.window: 1209 sz = vedo.plotter_instance.window.GetSize() 1210 dsiz = utils.mag(sz) 1211 scale = dsiz / msiz / 10 1212 else: 1213 scale = 350 / msiz 1214 1215 cmsh = self.clone() 1216 poly = cmsh.pos(0, 0, 0).scale(scale).polydata() 1217 1218 mapper3d = self.mapper() 1219 cm = mapper3d.GetColorMode() 1220 lut = mapper3d.GetLookupTable() 1221 sv = mapper3d.GetScalarVisibility() 1222 use_lut = mapper3d.GetUseLookupTableScalarRange() 1223 vrange = mapper3d.GetScalarRange() 1224 sm = mapper3d.GetScalarMode() 1225 1226 mapper2d = vtk.vtkPolyDataMapper2D() 1227 mapper2d.ShallowCopy(mapper3d) 1228 mapper2d.SetInputData(poly) 1229 mapper2d.SetColorMode(cm) 1230 mapper2d.SetLookupTable(lut) 1231 mapper2d.SetScalarVisibility(sv) 1232 mapper2d.SetUseLookupTableScalarRange(use_lut) 1233 mapper2d.SetScalarRange(vrange) 1234 mapper2d.SetScalarMode(sm) 1235 1236 act2d = vtk.vtkActor2D() 1237 act2d.SetMapper(mapper2d) 1238 act2d.SetLayerNumber(layer) 1239 csys = act2d.GetPositionCoordinate() 1240 csys.SetCoordinateSystem(coordsys) 1241 act2d.SetPosition(pos) 1242 if c is not None: 1243 c = colors.get_color(c) 1244 act2d.GetProperty().SetColor(c) 1245 mapper2d.SetScalarVisibility(False) 1246 else: 1247 act2d.GetProperty().SetColor(cmsh.color()) 1248 if alpha is not None: 1249 act2d.GetProperty().SetOpacity(alpha) 1250 else: 1251 act2d.GetProperty().SetOpacity(cmsh.alpha()) 1252 act2d.GetProperty().SetPointSize(ps) 1253 act2d.GetProperty().SetLineWidth(lw) 1254 act2d.GetProperty().SetDisplayLocationToForeground() 1255 if sendback: 1256 act2d.GetProperty().SetDisplayLocationToBackground() 1257 1258 # print(csys.GetCoordinateSystemAsString()) 1259 # print(act2d.GetHeight(), act2d.GetWidth(), act2d.GetLayerNumber()) 1260 return act2d 1261 1262 def add_trail(self, offset=(0, 0, 0), n=50, c=None, alpha=1.0, lw=2): 1263 """ 1264 Add a trailing line to mesh. 1265 This new mesh is accessible through `mesh.trail`. 1266 1267 Arguments: 1268 offset : (float) 1269 set an offset vector from the object center. 1270 n : (int) 1271 number of segments 1272 lw : (float) 1273 line width of the trail 1274 1275 Examples: 1276 - [trail.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/trail.py) 1277 1278 ![](https://vedo.embl.es/images/simulations/trail.gif) 1279 1280 - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py) 1281 - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py) 1282 """ 1283 if self.trail is None: 1284 pos = self.GetPosition() 1285 self.trail_offset = np.asarray(offset) 1286 self.trail_points = [pos] * n 1287 1288 if c is None: 1289 col = self.GetProperty().GetColor() 1290 else: 1291 col = colors.get_color(c) 1292 1293 tline = vedo.shapes.Line(pos, pos, res=n, c=col, alpha=alpha, lw=lw) 1294 self.trail = tline # holds the Line 1295 return self 1296 1297 def update_trail(self): 1298 """ 1299 Update the trailing line of a moving object. 1300 """ 1301 if isinstance(self, vedo.shapes.Arrow): 1302 currentpos = self.tipPoint() # the tip of Arrow 1303 else: 1304 currentpos = np.array(self.GetPosition()) 1305 1306 self.trail_points.append(currentpos) # cycle 1307 self.trail_points.pop(0) 1308 1309 data = np.array(self.trail_points) - currentpos + self.trail_offset 1310 tpoly = self.trail.polydata(False) 1311 tpoly.GetPoints().SetData(utils.numpy2vtk(data, dtype=np.float32)) 1312 self.trail.SetPosition(currentpos) 1313 return self 1314 1315 1316 def _compute_shadow(self, plane, point, direction): 1317 shad = self.clone() 1318 shad._data.GetPointData().SetTCoords(None) # remove any texture coords 1319 shad.name = "Shadow" 1320 1321 pts = shad.points() 1322 if plane == 'x': 1323 # shad = shad.project_on_plane('x') 1324 # instead do it manually so in case of alpha<1 1325 # we dont see glitches due to coplanar points 1326 # we leave a small tolerance of 0.1% in thickness 1327 x0, x1 = self.xbounds() 1328 pts[:, 0] = (pts[:, 0] - (x0 + x1) / 2) / 1000 + self.GetOrigin()[0] 1329 shad.points(pts) 1330 shad.x(point) 1331 elif plane == 'y': 1332 x0, x1 = self.ybounds() 1333 pts[:, 1] = (pts[:, 1] - (x0 + x1) / 2) / 1000 + self.GetOrigin()[1] 1334 shad.points(pts) 1335 shad.y(point) 1336 elif plane == "z": 1337 x0, x1 = self.zbounds() 1338 pts[:, 2] = (pts[:, 2] - (x0 + x1) / 2) / 1000 + self.GetOrigin()[2] 1339 shad.points(pts) 1340 shad.z(point) 1341 else: 1342 shad = shad.project_on_plane(plane, point, direction) 1343 return shad 1344 1345 def add_shadow(self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0): 1346 """ 1347 Generate a shadow out of an `Mesh` on one of the three Cartesian planes. 1348 The output is a new `Mesh` representing the shadow. 1349 This new mesh is accessible through `mesh.shadow`. 1350 By default the shadow mesh is placed on the bottom wall of the bounding box. 1351 1352 See also `pointcloud.project_on_plane()`. 1353 1354 Arguments: 1355 plane : (str, Plane) 1356 if plane is `str`, plane can be one of `['x', 'y', 'z']`, 1357 represents x-plane, y-plane and z-plane, respectively. 1358 Otherwise, plane should be an instance of `vedo.shapes.Plane` 1359 point : (float, array) 1360 if plane is `str`, point should be a float represents the intercept. 1361 Otherwise, point is the camera point of perspective projection 1362 direction : (list) 1363 direction of oblique projection 1364 culling : (int) 1365 choose between front [1] or backface [-1] culling or None. 1366 1367 Examples: 1368 - [shadow1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shadow1.py) 1369 - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py) 1370 - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py) 1371 1372 ![](https://vedo.embl.es/images/simulations/57341963-b8910900-713c-11e9-898a-84b6d3712bce.gif) 1373 """ 1374 shad = self._compute_shadow(plane, point, direction) 1375 shad.c(c).alpha(alpha) 1376 1377 try: 1378 # Points dont have these methods 1379 shad.flat() 1380 if culling in (1, True): 1381 shad.frontface_culling() 1382 elif culling == -1: 1383 shad.backface_culling() 1384 except AttributeError: 1385 pass 1386 1387 shad.GetProperty().LightingOff() 1388 shad.SetPickable(False) 1389 shad.SetUseBounds(True) 1390 1391 if shad not in self.shadows: 1392 self.shadows.append(shad) 1393 shad.info = dict(plane=plane, point=point, direction=direction) 1394 return self 1395 1396 def update_shadows(self): 1397 """ 1398 Update the shadows of a moving object. 1399 """ 1400 for sha in self.shadows: 1401 plane = sha.info['plane'] 1402 point = sha.info['point'] 1403 direction = sha.info['direction'] 1404 new_sha = self._compute_shadow(plane, point, direction) 1405 sha._update(new_sha._data) 1406 return self 1407 1408 1409 def delete_cells_by_point_index(self, indices): 1410 """ 1411 Delete a list of vertices identified by any of their vertex index. 1412 1413 See also `delete_cells()`. 1414 1415 Examples: 1416 - [elete_mesh_pts.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/elete_mesh_pts.py) 1417 1418 ![](https://vedo.embl.es/images/basic/deleteMeshPoints.png) 1419 """ 1420 cell_ids = vtk.vtkIdList() 1421 data = self.inputdata() 1422 data.BuildLinks() 1423 n = 0 1424 for i in np.unique(indices): 1425 data.GetPointCells(i, cell_ids) 1426 for j in range(cell_ids.GetNumberOfIds()): 1427 data.DeleteCell(cell_ids.GetId(j)) # flag cell 1428 n += 1 1429 1430 data.RemoveDeletedCells() 1431 self.mapper().Modified() 1432 self.pipeline = utils.OperationNode(f"delete {n} cells\nby point index", parents=[self]) 1433 return self 1434 1435 def compute_normals_with_pca(self, n=20, orientation_point=None, invert=False): 1436 """ 1437 Generate point normals using PCA (principal component analysis). 1438 Basically this estimates a local tangent plane around each sample point p 1439 by considering a small neighborhood of points around p, and fitting a plane 1440 to the neighborhood (via PCA). 1441 1442 Arguments: 1443 n : (int) 1444 neighborhood size to calculate the normal 1445 orientation_point : (list) 1446 adjust the +/- sign of the normals so that 1447 the normals all point towards a specified point. If None, perform a traversal 1448 of the point cloud and flip neighboring normals so that they are mutually consistent. 1449 invert : (bool) 1450 flip all normals 1451 """ 1452 poly = self.polydata() 1453 pcan = vtk.vtkPCANormalEstimation() 1454 pcan.SetInputData(poly) 1455 pcan.SetSampleSize(n) 1456 1457 if orientation_point is not None: 1458 pcan.SetNormalOrientationToPoint() 1459 pcan.SetOrientationPoint(orientation_point) 1460 else: 1461 pcan.SetNormalOrientationToGraphTraversal() 1462 1463 if invert: 1464 pcan.FlipNormalsOn() 1465 pcan.Update() 1466 1467 varr = pcan.GetOutput().GetPointData().GetNormals() 1468 varr.SetName("Normals") 1469 self.inputdata().GetPointData().SetNormals(varr) 1470 self.inputdata().GetPointData().Modified() 1471 return self 1472 1473 def compute_acoplanarity(self, n=25, radius=None, on="points"): 1474 """ 1475 Compute acoplanarity which is a measure of how much a local region of the mesh 1476 differs from a plane. 1477 The information is stored in a `pointdata` or `celldata` array with name 'Acoplanarity'. 1478 Either `n` (number of neighbour points) or `radius` (radius of local search) can be specified. 1479 If a radius value is given and not enough points fall inside it, then a -1 is stored. 1480 1481 Example: 1482 ```python 1483 from vedo import * 1484 msh = ParametricShape('RandomHills') 1485 msh.compute_acoplanarity(radius=0.1, on='cells') 1486 msh.cmap("coolwarm", on='cells').add_scalarbar() 1487 msh.show(axes=1).close() 1488 ``` 1489 ![](https://vedo.embl.es/images/feats/acoplanarity.jpg) 1490 """ 1491 acoplanarities = [] 1492 if "point" in on: 1493 pts = self.points() 1494 elif "cell" in on: 1495 pts = self.cell_centers() 1496 else: 1497 raise ValueError(f"In compute_acoplanarity() set on to either 'cells' or 'points', not {on}") 1498 1499 for p in utils.progressbar(pts, delay=5, width=15, title=f"{on} acoplanarity"): 1500 if n: 1501 data = self.closest_point(p, n=n) 1502 npts = n 1503 elif radius: 1504 data = self.closest_point(p, radius=radius) 1505 npts = len(data) 1506 1507 try: 1508 center = data.mean(axis=0) 1509 res = np.linalg.svd(data - center) 1510 acoplanarities.append(res[1][2] / npts) 1511 except: 1512 acoplanarities.append(-1.0) 1513 1514 if "point" in on: 1515 self.pointdata["Acoplanarity"] = np.array(acoplanarities, dtype=float) 1516 else: 1517 self.celldata["Acoplanarity"] = np.array(acoplanarities, dtype=float) 1518 return self 1519 1520 def distance_to(self, pcloud, signed=False, invert=False, name="Distance"): 1521 """ 1522 Computes the distance from one point cloud or mesh to another point cloud or mesh. 1523 This new `pointdata` array is saved with default name "Distance". 1524 1525 Keywords `signed` and `invert` are used to compute signed distance, 1526 but the mesh in that case must have polygonal faces (not a simple point cloud), 1527 and normals must also be computed. 1528 1529 Examples: 1530 - [distance2mesh.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/distance2mesh.py) 1531 1532 ![](https://vedo.embl.es/images/basic/distance2mesh.png) 1533 """ 1534 if pcloud.inputdata().GetNumberOfPolys(): 1535 1536 poly1 = self.polydata() 1537 poly2 = pcloud.polydata() 1538 df = vtk.vtkDistancePolyDataFilter() 1539 df.ComputeSecondDistanceOff() 1540 df.SetInputData(0, poly1) 1541 df.SetInputData(1, poly2) 1542 df.SetSignedDistance(signed) 1543 df.SetNegateDistance(invert) 1544 df.Update() 1545 scals = df.GetOutput().GetPointData().GetScalars() 1546 dists = utils.vtk2numpy(scals) 1547 1548 else: # has no polygons and vtkDistancePolyDataFilter wants them (dont know why) 1549 1550 if signed: 1551 vedo.logger.warning("distanceTo() called with signed=True but input object has no polygons") 1552 1553 if not pcloud.point_locator: 1554 pcloud.point_locator = vtk.vtkPointLocator() 1555 pcloud.point_locator.SetDataSet(pcloud.polydata()) 1556 pcloud.point_locator.BuildLocator() 1557 1558 ids = [] 1559 ps1 = self.points() 1560 ps2 = pcloud.points() 1561 for p in ps1: 1562 pid = pcloud.point_locator.FindClosestPoint(p) 1563 ids.append(pid) 1564 1565 deltas = ps2[ids] - ps1 1566 dists = np.linalg.norm(deltas, axis=1).astype(np.float32) 1567 scals = utils.numpy2vtk(dists) 1568 1569 scals.SetName(name) 1570 self.inputdata().GetPointData().AddArray(scals) # must be self.inputdata() ! 1571 self.inputdata().GetPointData().SetActiveScalars(scals.GetName()) 1572 rng = scals.GetRange() 1573 self.mapper().SetScalarRange(rng[0], rng[1]) 1574 self.mapper().ScalarVisibilityOn() 1575 1576 self.pipeline = utils.OperationNode( 1577 "distance_to", 1578 parents=[self, pcloud], 1579 shape="cylinder", 1580 comment=f"#pts {self._data.GetNumberOfPoints()}", 1581 ) 1582 return dists 1583 1584 def alpha(self, opacity=None): 1585 """Set/get mesh's transparency. Same as `mesh.opacity()`.""" 1586 if opacity is None: 1587 return self.GetProperty().GetOpacity() 1588 1589 self.GetProperty().SetOpacity(opacity) 1590 bfp = self.GetBackfaceProperty() 1591 if bfp: 1592 if opacity < 1: 1593 self._bfprop = bfp 1594 self.SetBackfaceProperty(None) 1595 else: 1596 self.SetBackfaceProperty(self._bfprop) 1597 return self 1598 1599 def opacity(self, alpha=None): 1600 """Set/get mesh's transparency. Same as `mesh.alpha()`.""" 1601 return self.alpha(alpha) 1602 1603 def force_opaque(self, value=True): 1604 """ Force the Mesh, Line or point cloud to be treated as opaque""" 1605 ## force the opaque pass, fixes picking in vtk9 1606 # but causes other bad troubles with lines.. 1607 self.SetForceOpaque(value) 1608 return self 1609 1610 def force_translucent(self, value=True): 1611 """ Force the Mesh, Line or point cloud to be treated as translucent""" 1612 self.SetForceTranslucent(value) 1613 return self 1614 1615 def point_size(self, value=None): 1616 """Set/get mesh's point size of vertices. Same as `mesh.ps()`""" 1617 if value is None: 1618 return self.GetProperty().GetPointSize() 1619 #self.GetProperty().SetRepresentationToSurface() 1620 else: 1621 self.GetProperty().SetRepresentationToPoints() 1622 self.GetProperty().SetPointSize(value) 1623 return self 1624 1625 def ps(self, pointsize=None): 1626 """Set/get mesh's point size of vertices. Same as `mesh.point_size()`""" 1627 return self.point_size(pointsize) 1628 1629 def render_points_as_spheres(self, value=True): 1630 """Make points look spheric or make them look as squares.""" 1631 self.GetProperty().SetRenderPointsAsSpheres(value) 1632 return self 1633 1634 def color(self, c=False, alpha=None): 1635 """ 1636 Set/get mesh's color. 1637 If None is passed as input, will use colors from active scalars. 1638 Same as `mesh.c()`. 1639 """ 1640 # overrides base.color() 1641 if c is False: 1642 return np.array(self.GetProperty().GetColor()) 1643 if c is None: 1644 self.mapper().ScalarVisibilityOn() 1645 return self 1646 self.mapper().ScalarVisibilityOff() 1647 cc = colors.get_color(c) 1648 self.GetProperty().SetColor(cc) 1649 if self.trail: 1650 self.trail.GetProperty().SetColor(cc) 1651 if alpha is not None: 1652 self.alpha(alpha) 1653 return self 1654 1655 def clean(self): 1656 """ 1657 Clean pointcloud or mesh by removing coincident points. 1658 """ 1659 cpd = vtk.vtkCleanPolyData() 1660 cpd.PointMergingOn() 1661 cpd.ConvertLinesToPointsOn() 1662 cpd.ConvertPolysToLinesOn() 1663 cpd.ConvertStripsToPolysOn() 1664 cpd.SetInputData(self.inputdata()) 1665 cpd.Update() 1666 out = self._update(cpd.GetOutput()) 1667 1668 out.pipeline = utils.OperationNode( 1669 "clean", parents=[self], 1670 comment=f"#pts {out.inputdata().GetNumberOfPoints()}" 1671 ) 1672 return out 1673 1674 def subsample(self, fraction, absolute=False): 1675 """ 1676 Subsample a point cloud by requiring that the points 1677 or vertices are far apart at least by the specified fraction of the object size. 1678 If a Mesh is passed the polygonal faces are not removed 1679 but holes can appear as vertices are removed. 1680 1681 Examples: 1682 - [moving_least_squares1D.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/moving_least_squares1D.py) 1683 1684 ![](https://vedo.embl.es/images/advanced/moving_least_squares1D.png) 1685 1686 - [recosurface.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/recosurface.py) 1687 1688 ![](https://vedo.embl.es/images/advanced/recosurface.png) 1689 """ 1690 if not absolute: 1691 if fraction > 1: 1692 vedo.logger.warning( 1693 f"subsample(fraction=...), fraction must be < 1, but is {fraction}" 1694 ) 1695 if fraction <= 0: 1696 return self 1697 1698 cpd = vtk.vtkCleanPolyData() 1699 cpd.PointMergingOn() 1700 cpd.ConvertLinesToPointsOn() 1701 cpd.ConvertPolysToLinesOn() 1702 cpd.ConvertStripsToPolysOn() 1703 cpd.SetInputData(self.inputdata()) 1704 if absolute: 1705 cpd.SetTolerance(fraction / self.diagonal_size()) 1706 # cpd.SetToleranceIsAbsolute(absolute) 1707 else: 1708 cpd.SetTolerance(fraction) 1709 cpd.Update() 1710 1711 ps = 2 1712 if self.GetProperty().GetRepresentation() == 0: 1713 ps = self.GetProperty().GetPointSize() 1714 1715 out = self._update(cpd.GetOutput()).ps(ps) 1716 1717 out.pipeline = utils.OperationNode( 1718 "subsample", parents=[self], comment=f"#pts {out.inputdata().GetNumberOfPoints()}" 1719 ) 1720 return out 1721 1722 def threshold(self, scalars, above=None, below=None, on="points"): 1723 """ 1724 Extracts cells where scalar value satisfies threshold criterion. 1725 1726 Arguments: 1727 scalars : (str) 1728 name of the scalars array. 1729 above : (float) 1730 minimum value of the scalar 1731 below : (float) 1732 maximum value of the scalar 1733 on : (str) 1734 if 'cells' assume array of scalars refers to cell data. 1735 1736 Examples: 1737 - [mesh_threshold.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_threshold.py) 1738 """ 1739 thres = vtk.vtkThreshold() 1740 thres.SetInputData(self.inputdata()) 1741 1742 if on.startswith("c"): 1743 asso = vtk.vtkDataObject.FIELD_ASSOCIATION_CELLS 1744 else: 1745 asso = vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS 1746 1747 thres.SetInputArrayToProcess(0, 0, 0, asso, scalars) 1748 1749 if above is None and below is not None: 1750 thres.ThresholdByLower(below) 1751 elif below is None and above is not None: 1752 thres.ThresholdByUpper(above) 1753 else: 1754 thres.ThresholdBetween(above, below) 1755 thres.Update() 1756 1757 gf = vtk.vtkGeometryFilter() 1758 gf.SetInputData(thres.GetOutput()) 1759 gf.Update() 1760 return self._update(gf.GetOutput()) 1761 1762 def quantize(self, value): 1763 """ 1764 The user should input a value and all {x,y,z} coordinates 1765 will be quantized to that absolute grain size. 1766 """ 1767 poly = self.inputdata() 1768 qp = vtk.vtkQuantizePolyDataPoints() 1769 qp.SetInputData(poly) 1770 qp.SetQFactor(value) 1771 qp.Update() 1772 out = self._update(qp.GetOutput()).flat() 1773 out.pipeline = utils.OperationNode("quantize", parents=[self]) 1774 return out 1775 1776 def average_size(self): 1777 """ 1778 Calculate the average size of a mesh. 1779 This is the mean of the vertex distances from the center of mass. 1780 """ 1781 coords = self.points() 1782 cm = np.mean(coords, axis=0) 1783 if coords.shape[0] == 0: 1784 return 0.0 1785 cc = coords - cm 1786 return np.mean(np.linalg.norm(cc, axis=1)) 1787 1788 def center_of_mass(self): 1789 """Get the center of mass of mesh.""" 1790 cmf = vtk.vtkCenterOfMass() 1791 cmf.SetInputData(self.polydata()) 1792 cmf.Update() 1793 c = cmf.GetCenter() 1794 return np.array(c) 1795 1796 def normal_at(self, i): 1797 """Return the normal vector at vertex point `i`.""" 1798 normals = self.polydata().GetPointData().GetNormals() 1799 return np.array(normals.GetTuple(i)) 1800 1801 def normals(self, cells=False, recompute=True): 1802 """Retrieve vertex normals as a numpy array. 1803 1804 Arguments: 1805 cells : (bool) 1806 if `True` return cell normals. 1807 1808 recompute : (bool) 1809 if `True` normals are recalculated if not already present. 1810 Note that this might modify the number of mesh points. 1811 """ 1812 if cells: 1813 vtknormals = self.polydata().GetCellData().GetNormals() 1814 else: 1815 vtknormals = self.polydata().GetPointData().GetNormals() 1816 if not vtknormals and recompute: 1817 try: 1818 self.compute_normals(cells=cells) 1819 if cells: 1820 vtknormals = self.polydata().GetCellData().GetNormals() 1821 else: 1822 vtknormals = self.polydata().GetPointData().GetNormals() 1823 except AttributeError: 1824 # can be that 'Points' object has no attribute 'compute_normals' 1825 pass 1826 1827 if not vtknormals: 1828 return np.array([]) 1829 return utils.vtk2numpy(vtknormals) 1830 1831 def labels( 1832 self, 1833 content=None, 1834 on="points", 1835 scale=None, 1836 xrot=0.0, 1837 yrot=0.0, 1838 zrot=0.0, 1839 ratio=1, 1840 precision=None, 1841 italic=False, 1842 font="", 1843 justify="bottom-left", 1844 c="black", 1845 alpha=1.0, 1846 cells=None, 1847 ): 1848 """ 1849 Generate value or ID labels for mesh cells or points. 1850 For large nr. of labels use `font="VTK"` which is much faster. 1851 1852 See also: 1853 `labels2d()`, `flagpole()`, `caption()` and `legend()`. 1854 1855 Arguments: 1856 content : (list,int,str) 1857 either 'id', 'cellid', array name or array number. 1858 A array can also be passed (must match the nr. of points or cells). 1859 on : (str) 1860 generate labels for "cells" instead of "points" 1861 scale : (float) 1862 absolute size of labels, if left as None it is automatic 1863 zrot : (float) 1864 local rotation angle of label in degrees 1865 ratio : (int) 1866 skipping ratio, to reduce nr of labels for large meshes 1867 precision : (int) 1868 numeric precision of labels 1869 1870 ```python 1871 from vedo import * 1872 s = Sphere(res=10).linewidth(1).c("orange").compute_normals() 1873 point_ids = s.labels('id', on="points").c('green') 1874 cell_ids = s.labels('id', on="cells" ).c('black') 1875 show(s, point_ids, cell_ids) 1876 ``` 1877 ![](https://vedo.embl.es/images/feats/labels.png) 1878 1879 Examples: 1880 - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py) 1881 1882 ![](https://vedo.embl.es/images/basic/boundaries.png) 1883 """ 1884 if cells is not None: # deprecation message 1885 vedo.logger.warning("In labels(cells=...) please use labels(on='cells') instead") 1886 1887 if "cell" in on or "face" in on: 1888 cells = True 1889 1890 if isinstance(content, str): 1891 if content in ("cellid", "cellsid"): 1892 cells = True 1893 content = "id" 1894 1895 if cells: 1896 elems = self.cell_centers() 1897 norms = self.normals(cells=True, recompute=False) 1898 ns = np.sqrt(self.ncells) 1899 else: 1900 elems = self.points() 1901 norms = self.normals(cells=False, recompute=False) 1902 ns = np.sqrt(self.npoints) 1903 1904 hasnorms = False 1905 if len(norms) > 0: 1906 hasnorms = True 1907 1908 if scale is None: 1909 if not ns: 1910 ns = 100 1911 scale = self.diagonal_size() / ns / 10 1912 1913 arr = None 1914 mode = 0 1915 if content is None: 1916 mode = 0 1917 if cells: 1918 if self.inputdata().GetCellData().GetScalars(): 1919 name = self.inputdata().GetCellData().GetScalars().GetName() 1920 arr = self.celldata[name] 1921 else: 1922 if self.inputdata().GetPointData().GetScalars(): 1923 name = self.inputdata().GetPointData().GetScalars().GetName() 1924 arr = self.pointdata[name] 1925 elif isinstance(content, (str, int)): 1926 if content == "id": 1927 mode = 1 1928 elif cells: 1929 mode = 0 1930 arr = self.celldata[content] 1931 else: 1932 mode = 0 1933 arr = self.pointdata[content] 1934 elif utils.is_sequence(content): 1935 mode = 0 1936 arr = content 1937 # print('WEIRD labels() test', content) 1938 # exit() 1939 1940 if arr is None and mode == 0: 1941 vedo.logger.error("in labels(), array not found for points or cells") 1942 return None 1943 1944 tapp = vtk.vtkAppendPolyData() 1945 ninputs = 0 1946 1947 for i, e in enumerate(elems): 1948 if i % ratio: 1949 continue 1950 1951 if mode == 1: 1952 txt_lab = str(i) 1953 else: 1954 if precision: 1955 txt_lab = utils.precision(arr[i], precision) 1956 else: 1957 txt_lab = str(arr[i]) 1958 1959 if not txt_lab: 1960 continue 1961 1962 if font == "VTK": 1963 tx = vtk.vtkVectorText() 1964 tx.SetText(txt_lab) 1965 tx.Update() 1966 tx_poly = tx.GetOutput() 1967 else: 1968 tx_poly = vedo.shapes.Text3D(txt_lab, font=font, justify=justify) 1969 tx_poly = tx_poly.inputdata() 1970 1971 if tx_poly.GetNumberOfPoints() == 0: 1972 continue ####################### 1973 ninputs += 1 1974 1975 T = vtk.vtkTransform() 1976 T.PostMultiply() 1977 if italic: 1978 T.Concatenate([1,0.2,0,0, 1979 0,1,0,0, 1980 0,0,1,0, 1981 0,0,0,1]) 1982 if hasnorms: 1983 ni = norms[i] 1984 if cells: # center-justify 1985 bb = tx_poly.GetBounds() 1986 dx, dy = (bb[1] - bb[0]) / 2, (bb[3] - bb[2]) / 2 1987 T.Translate(-dx, -dy, 0) 1988 if xrot: 1989 T.RotateX(xrot) 1990 if yrot: 1991 T.RotateY(yrot) 1992 if zrot: 1993 T.RotateZ(zrot) 1994 crossvec = np.cross([0, 0, 1], ni) 1995 angle = np.arccos(np.dot([0, 0, 1], ni)) * 57.3 1996 T.RotateWXYZ(angle, crossvec) 1997 if cells: # small offset along normal only for cells 1998 T.Translate(ni * scale / 2) 1999 else: 2000 if xrot: 2001 T.RotateX(xrot) 2002 if yrot: 2003 T.RotateY(yrot) 2004 if zrot: 2005 T.RotateZ(zrot) 2006 T.Scale(scale, scale, scale) 2007 T.Translate(e) 2008 tf = vtk.vtkTransformPolyDataFilter() 2009 tf.SetInputData(tx_poly) 2010 tf.SetTransform(T) 2011 tf.Update() 2012 tapp.AddInputData(tf.GetOutput()) 2013 2014 if ninputs: 2015 tapp.Update() 2016 lpoly = tapp.GetOutput() 2017 else: # return an empty obj 2018 lpoly = vtk.vtkPolyData() 2019 2020 ids = vedo.mesh.Mesh(lpoly, c=c, alpha=alpha) 2021 ids.GetProperty().LightingOff() 2022 ids.PickableOff() 2023 ids.SetUseBounds(False) 2024 return ids 2025 2026 def labels2d( 2027 self, 2028 content="id", 2029 on="points", 2030 scale=1.0, 2031 precision=4, 2032 font="Calco", 2033 justify="bottom-left", 2034 angle=0.0, 2035 frame=False, 2036 c="black", 2037 bc=None, 2038 alpha=1.0, 2039 ): 2040 """ 2041 Generate value or ID bi-dimensional labels for mesh cells or points. 2042 2043 See also: `labels()`, `flagpole()`, `caption()` and `legend()`. 2044 2045 Arguments: 2046 content : (str) 2047 either 'id', 'cellid', or array name 2048 on : (str) 2049 generate labels for "cells" instead of "points" (the default) 2050 scale : (float) 2051 size scaling of labels 2052 precision : (int) 2053 precision of numeric labels 2054 angle : (float) 2055 local rotation angle of label in degrees 2056 frame : (bool) 2057 draw a frame around the label 2058 bc : (str) 2059 background color of the label 2060 2061 ```python 2062 from vedo import Sphere, show 2063 sph = Sphere(quads=True, res=4).compute_normals().wireframe() 2064 sph.celldata["zvals"] = sph.cell_centers()[:,2] 2065 l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9') 2066 show(sph, l2d, axes=1).close() 2067 ``` 2068 ![](https://vedo.embl.es/images/feats/labels2d.png) 2069 """ 2070 cells = False 2071 if isinstance(content, str): 2072 if content in ("cellid", "cellsid"): 2073 cells = True 2074 content = "id" 2075 2076 if "cell" in on: 2077 cells = True 2078 elif "point" in on: 2079 cells = False 2080 2081 if cells: 2082 if content != "id" and content not in self.celldata.keys(): 2083 vedo.logger.error(f"In labels2d: cell array {content} does not exist.") 2084 return None 2085 cellcloud = Points(self.cell_centers()) 2086 arr = self.inputdata().GetCellData().GetScalars() 2087 poly = cellcloud.polydata(False) 2088 poly.GetPointData().SetScalars(arr) 2089 else: 2090 poly = self.polydata() 2091 if content != "id" and content not in self.pointdata.keys(): 2092 vedo.logger.error(f"In labels2d: point array {content} does not exist.") 2093 return None 2094 self.pointdata.select(content) 2095 2096 mp = vtk.vtkLabeledDataMapper() 2097 2098 if content == "id": 2099 mp.SetLabelModeToLabelIds() 2100 else: 2101 mp.SetLabelModeToLabelScalars() 2102 if precision is not None: 2103 mp.SetLabelFormat(f"%-#.{precision}g") 2104 2105 pr = mp.GetLabelTextProperty() 2106 c = colors.get_color(c) 2107 pr.SetColor(c) 2108 pr.SetOpacity(alpha) 2109 pr.SetFrame(frame) 2110 pr.SetFrameColor(c) 2111 pr.SetItalic(False) 2112 pr.BoldOff() 2113 pr.ShadowOff() 2114 pr.UseTightBoundingBoxOn() 2115 pr.SetOrientation(angle) 2116 pr.SetFontFamily(vtk.VTK_FONT_FILE) 2117 fl = utils.get_font_path(font) 2118 pr.SetFontFile(fl) 2119 pr.SetFontSize(int(20 * scale)) 2120 2121 if "cent" in justify or "mid" in justify: 2122 pr.SetJustificationToCentered() 2123 elif "rig" in justify: 2124 pr.SetJustificationToRight() 2125 elif "left" in justify: 2126 pr.SetJustificationToLeft() 2127 # ------ 2128 if "top" in justify: 2129 pr.SetVerticalJustificationToTop() 2130 else: 2131 pr.SetVerticalJustificationToBottom() 2132 2133 if bc is not None: 2134 bc = colors.get_color(bc) 2135 pr.SetBackgroundColor(bc) 2136 pr.SetBackgroundOpacity(alpha) 2137 2138 mp.SetInputData(poly) 2139 a2d = vtk.vtkActor2D() 2140 a2d.PickableOff() 2141 a2d.SetMapper(mp) 2142 return a2d 2143 2144 def legend(self, txt): 2145 """Book a legend text.""" 2146 self.info["legend"] = txt 2147 return self 2148 2149 def flagpole( 2150 self, 2151 txt=None, 2152 point=None, 2153 offset=None, 2154 s=None, 2155 font="", 2156 rounded=True, 2157 c=None, 2158 alpha=1.0, 2159 lw=2, 2160 italic=0.0, 2161 padding=0.1, 2162 ): 2163 """ 2164 Generate a flag pole style element to describe an object. 2165 Returns a `Mesh` object. 2166 2167 Use flagpole.follow_camera() to make it face the camera in the scene. 2168 2169 See also `flagpost()`. 2170 2171 Arguments: 2172 txt : (str) 2173 Text to display. The default is the filename or the object name. 2174 point : (list) 2175 position of the flagpole pointer. 2176 offset : (list) 2177 text offset wrt the application point. 2178 s : (float) 2179 size of the flagpole. 2180 font : (str) 2181 font face. Check [available fonts here](https://vedo.embl.es/fonts). 2182 rounded : (bool) 2183 draw a rounded or squared box around the text. 2184 c : (list) 2185 text and box color. 2186 alpha : (float) 2187 opacity of text and box. 2188 lw : (float) 2189 line with of box frame. 2190 italic : (float) 2191 italicness of text. 2192 2193 Examples: 2194 - [intersect2d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/intersect2d.py) 2195 2196 ![](https://vedo.embl.es/images/pyplot/intersect2d.png) 2197 2198 - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py) 2199 - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py) 2200 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py) 2201 """ 2202 acts = [] 2203 2204 if txt is None: 2205 if self.filename: 2206 txt = self.filename.split("/")[-1] 2207 elif self.name: 2208 txt = self.name 2209 else: 2210 return None 2211 2212 x0, x1, y0, y1, z0, z1 = self.bounds() 2213 d = self.diagonal_size() 2214 if point is None: 2215 if d: 2216 point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1]) 2217 else: # it's a Point 2218 point = self.GetPosition() 2219 2220 pt = utils.make3d(point) 2221 2222 if offset is None: 2223 offset = [(x1 - x0) / 2, (y1 - y0) / 6, 0] 2224 offset = utils.make3d(offset) 2225 2226 if s is None: 2227 s = d / 20 2228 2229 sph = None 2230 if d and (z1 - z0) / d > 0.1: 2231 sph = vedo.shapes.Sphere(pt, r=s * 0.4, res=6) 2232 2233 if c is None: 2234 c = np.array(self.color()) / 1.4 2235 2236 lb = vedo.shapes.Text3D( 2237 txt, pos=pt + offset, s=s, font=font, italic=italic, justify="center-left" 2238 ) 2239 acts.append(lb) 2240 2241 if d and not sph: 2242 sph = vedo.shapes.Circle(pt, r=s / 3, res=15) 2243 acts.append(sph) 2244 2245 x0, x1, y0, y1, z0, z1 = lb.GetBounds() 2246 if rounded: 2247 box = vedo.shapes.KSpline( 2248 [(x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0)], closed=True 2249 ) 2250 else: 2251 box = vedo.shapes.Line( 2252 [(x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0), (x0, y0, z0)] 2253 ) 2254 2255 cnt = [(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2] 2256 2257 box.SetOrigin(cnt) 2258 box.scale([1 + padding, 1 + 2 * padding, 1]) 2259 acts.append(box) 2260 2261 # pts = box.points() 2262 # bfaces = [] 2263 # for i, pt in enumerate(pts): 2264 # if i: 2265 # face = [i-1, i, 0] 2266 # bfaces.append(face) 2267 # bpts = [cnt] + pts.tolist() 2268 # box2 = vedo.Mesh([bpts, bfaces]).z(-cnt[0]/10)#.c('w').alpha(0.1) 2269 # #should be made assembly otherwise later merge() nullifies it 2270 # box2.SetOrigin(cnt) 2271 # acts.append(box2) 2272 2273 x0, x1, y0, y1, z0, z1 = box.bounds() 2274 if x0 < pt[0] < x1: 2275 c0 = box.closest_point(pt) 2276 c1 = [c0[0], c0[1] + (pt[1] - y0) / 4, pt[2]] 2277 elif (pt[0] - x0) < (x1 - pt[0]): 2278 c0 = [x0, (y0 + y1) / 2, pt[2]] 2279 c1 = [x0 + (pt[0] - x0) / 4, (y0 + y1) / 2, pt[2]] 2280 else: 2281 c0 = [x1, (y0 + y1) / 2, pt[2]] 2282 c1 = [x1 + (pt[0] - x1) / 4, (y0 + y1) / 2, pt[2]] 2283 2284 con = vedo.shapes.Line([c0, c1, pt]) 2285 acts.append(con) 2286 2287 macts = vedo.merge(acts).c(c).alpha(alpha) 2288 macts.SetOrigin(pt) 2289 macts.bc("tomato").pickable(False) 2290 macts.GetProperty().LightingOff() 2291 macts.GetProperty().SetLineWidth(lw) 2292 macts.UseBoundsOff() 2293 macts.name = "FlagPole" 2294 return macts 2295 2296 def flagpost( 2297 self, 2298 txt=None, 2299 point=None, 2300 offset=None, 2301 s=1.0, 2302 c="k9", 2303 bc="k1", 2304 alpha=1, 2305 lw=0, 2306 font="Calco", 2307 justify="center-left", 2308 vspacing=1.0, 2309 ): 2310 """ 2311 Generate a flag post style element to describe an object. 2312 2313 Arguments: 2314 txt : (str) 2315 Text to display. The default is the filename or the object name. 2316 point : (list) 2317 position of the flag anchor point. The default is None. 2318 offset : (list) 2319 a 3D displacement or offset. The default is None. 2320 s : (float) 2321 size of the text to be shown 2322 c : (list) 2323 color of text and line 2324 bc : (list) 2325 color of the flag background 2326 alpha : (float) 2327 opacity of text and box. 2328 lw : (int) 2329 line with of box frame. The default is 0. 2330 font : (str) 2331 font name. Use a monospace font for better rendering. The default is "Calco". 2332 Type `vedo -r fonts` for a font demo. 2333 Check [available fonts here](https://vedo.embl.es/fonts). 2334 justify : (str) 2335 internal text justification. The default is "center-left". 2336 vspacing : (float) 2337 vertical spacing between lines. 2338 2339 Examples: 2340 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py) 2341 2342 ![](https://vedo.embl.es/images/other/flag_labels2.png) 2343 """ 2344 if txt is None: 2345 if self.filename: 2346 txt = self.filename.split("/")[-1] 2347 elif self.name: 2348 txt = self.name 2349 else: 2350 return None 2351 2352 x0, x1, y0, y1, z0, z1 = self.bounds() 2353 d = self.diagonal_size() 2354 if point is None: 2355 if d: 2356 point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1]) 2357 else: # it's a Point 2358 point = self.GetPosition() 2359 2360 point = utils.make3d(point) 2361 2362 if offset is None: 2363 offset = [0, 0, (z1 - z0) / 2] 2364 offset = utils.make3d(offset) 2365 2366 fpost = vedo.addons.Flagpost( 2367 txt, point, point + offset, s, c, bc, alpha, lw, font, justify, vspacing 2368 ) 2369 self._caption = fpost 2370 return fpost 2371 2372 def caption( 2373 self, 2374 txt=None, 2375 point=None, 2376 size=(0.30, 0.15), 2377 padding=5, 2378 font="Calco", 2379 justify="center-right", 2380 vspacing=1.0, 2381 c=None, 2382 alpha=1.0, 2383 lw=1, 2384 ontop=True, 2385 ): 2386 """ 2387 Add a 2D caption to an object which follows the camera movements. 2388 Latex is not supported. Returns the same input object for concatenation. 2389 2390 See also `flagpole()`, `flagpost()`, `labels()` and `legend()` 2391 with similar functionality. 2392 2393 Arguments: 2394 txt : (str) 2395 text to be rendered. The default is the file name. 2396 point : (list) 2397 anchoring point. The default is None. 2398 size : (list) 2399 (width, height) of the caption box. The default is (0.30, 0.15). 2400 padding : (float) 2401 padding space of the caption box in pixels. The default is 5. 2402 font : (str) 2403 font name. Use a monospace font for better rendering. The default is "VictorMono". 2404 Type `vedo -r fonts` for a font demo. 2405 Check [available fonts here](https://vedo.embl.es/fonts). 2406 justify : (str) 2407 internal text justification. The default is "center-right". 2408 vspacing : (float) 2409 vertical spacing between lines. The default is 1. 2410 c : (str) 2411 text and box color. The default is 'lb'. 2412 alpha : (float) 2413 text and box transparency. The default is 1. 2414 lw : (int) 2415 line width in pixels. The default is 1. 2416 ontop : (bool) 2417 keep the 2d caption always on top. The default is True. 2418 2419 Examples: 2420 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 2421 2422 ![](https://vedo.embl.es/images/pyplot/caption.png) 2423 2424 - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py) 2425 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py) 2426 """ 2427 if txt is None: 2428 if self.filename: 2429 txt = self.filename.split("/")[-1] 2430 elif self.name: 2431 txt = self.name 2432 2433 if not txt: # disable it 2434 self._caption = None 2435 return self 2436 2437 for r in vedo.shapes._reps: 2438 txt = txt.replace(r[0], r[1]) 2439 2440 if c is None: 2441 c = np.array(self.GetProperty().GetColor()) / 2 2442 else: 2443 c = colors.get_color(c) 2444 2445 if point is None: 2446 x0, x1, y0, y1, _, z1 = self.GetBounds() 2447 pt = [(x0 + x1) / 2, (y0 + y1) / 2, z1] 2448 point = self.closest_point(pt) 2449 2450 capt = vtk.vtkCaptionActor2D() 2451 capt.SetAttachmentPoint(point) 2452 capt.SetBorder(True) 2453 capt.SetLeader(True) 2454 sph = vtk.vtkSphereSource() 2455 sph.Update() 2456 capt.SetLeaderGlyphData(sph.GetOutput()) 2457 capt.SetMaximumLeaderGlyphSize(5) 2458 capt.SetPadding(int(padding)) 2459 capt.SetCaption(txt) 2460 capt.SetWidth(size[0]) 2461 capt.SetHeight(size[1]) 2462 capt.SetThreeDimensionalLeader(not ontop) 2463 2464 pra = capt.GetProperty() 2465 pra.SetColor(c) 2466 pra.SetOpacity(alpha) 2467 pra.SetLineWidth(lw) 2468 2469 pr = capt.GetCaptionTextProperty() 2470 pr.SetFontFamily(vtk.VTK_FONT_FILE) 2471 fl = utils.get_font_path(font) 2472 pr.SetFontFile(fl) 2473 pr.ShadowOff() 2474 pr.BoldOff() 2475 pr.FrameOff() 2476 pr.SetColor(c) 2477 pr.SetOpacity(alpha) 2478 pr.SetJustificationToLeft() 2479 if "top" in justify: 2480 pr.SetVerticalJustificationToTop() 2481 if "bottom" in justify: 2482 pr.SetVerticalJustificationToBottom() 2483 if "cent" in justify: 2484 pr.SetVerticalJustificationToCentered() 2485 pr.SetJustificationToCentered() 2486 if "left" in justify: 2487 pr.SetJustificationToLeft() 2488 if "right" in justify: 2489 pr.SetJustificationToRight() 2490 pr.SetLineSpacing(vspacing) 2491 self._caption = capt 2492 return self 2493 2494 2495 def align_to(self, target, iters=100, rigid=False, invert=False, use_centroids=False): 2496 """ 2497 Aligned to target mesh through the `Iterative Closest Point` algorithm. 2498 2499 The core of the algorithm is to match each vertex in one surface with 2500 the closest surface point on the other, then apply the transformation 2501 that modify one surface to best match the other (in the least-square sense). 2502 2503 Arguments: 2504 rigid : (bool) 2505 if True do not allow scaling 2506 invert : (bool) 2507 if True start by aligning the target to the source but 2508 invert the transformation finally. Useful when the target is smaller 2509 than the source. 2510 use_centroids : (bool) 2511 start by matching the centroids of the two objects. 2512 2513 Examples: 2514 - [align1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align1.py) 2515 2516 ![](https://vedo.embl.es/images/basic/align1.png) 2517 2518 - [align2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align2.py) 2519 2520 ![](https://vedo.embl.es/images/basic/align2.png) 2521 """ 2522 icp = vtk.vtkIterativeClosestPointTransform() 2523 icp.SetSource(self.polydata()) 2524 icp.SetTarget(target.polydata()) 2525 if invert: 2526 icp.Inverse() 2527 icp.SetMaximumNumberOfIterations(iters) 2528 if rigid: 2529 icp.GetLandmarkTransform().SetModeToRigidBody() 2530 icp.SetStartByMatchingCentroids(use_centroids) 2531 icp.Update() 2532 2533 M = icp.GetMatrix() 2534 if invert: 2535 M.Invert() # icp.GetInverse() doesnt work! 2536 # self.apply_transform(M) 2537 self.SetUserMatrix(M) 2538 2539 self.transform = self.GetUserTransform() 2540 self.point_locator = None 2541 self.cell_locator = None 2542 2543 self.pipeline = utils.OperationNode( 2544 "align_to", parents=[self, target], comment=f"rigid = {rigid}" 2545 ) 2546 return self 2547 2548 def transform_with_landmarks( 2549 self, source_landmarks, target_landmarks, rigid=False, affine=False, least_squares=False 2550 ): 2551 """ 2552 Transform mesh orientation and position based on a set of landmarks points. 2553 The algorithm finds the best matching of source points to target points 2554 in the mean least square sense, in one single step. 2555 2556 If affine is True the x, y and z axes can scale independently but stay collinear. 2557 With least_squares they can vary orientation. 2558 2559 Examples: 2560 - [align5.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align5.py) 2561 2562 ![](https://vedo.embl.es/images/basic/align5.png) 2563 """ 2564 2565 if utils.is_sequence(source_landmarks): 2566 ss = vtk.vtkPoints() 2567 for p in source_landmarks: 2568 ss.InsertNextPoint(p) 2569 else: 2570 ss = source_landmarks.polydata().GetPoints() 2571 if least_squares: 2572 source_landmarks = source_landmarks.points() 2573 2574 if utils.is_sequence(target_landmarks): 2575 st = vtk.vtkPoints() 2576 for p in target_landmarks: 2577 st.InsertNextPoint(p) 2578 else: 2579 st = target_landmarks.polydata().GetPoints() 2580 if least_squares: 2581 target_landmarks = target_landmarks.points() 2582 2583 if ss.GetNumberOfPoints() != st.GetNumberOfPoints(): 2584 n1 = ss.GetNumberOfPoints() 2585 n2 = st.GetNumberOfPoints() 2586 vedo.logger.error(f"source and target have different nr of points {n1} vs {n2}") 2587 raise RuntimeError() 2588 2589 lmt = vtk.vtkLandmarkTransform() 2590 lmt.SetSourceLandmarks(ss) 2591 lmt.SetTargetLandmarks(st) 2592 lmt.SetModeToSimilarity() 2593 if rigid: 2594 lmt.SetModeToRigidBody() 2595 lmt.Update() 2596 self.SetUserTransform(lmt) 2597 2598 elif affine: 2599 lmt.SetModeToAffine() 2600 lmt.Update() 2601 self.SetUserTransform(lmt) 2602 2603 elif least_squares: 2604 cms = source_landmarks.mean(axis=0) 2605 cmt = target_landmarks.mean(axis=0) 2606 m = np.linalg.lstsq(source_landmarks - cms, target_landmarks - cmt, rcond=None)[0] 2607 M = vtk.vtkMatrix4x4() 2608 for i in range(3): 2609 for j in range(3): 2610 M.SetElement(j, i, m[i][j]) 2611 lmt = vtk.vtkTransform() 2612 lmt.Translate(cmt) 2613 lmt.Concatenate(M) 2614 lmt.Translate(-cms) 2615 self.apply_transform(lmt, concatenate=True) 2616 else: 2617 self.SetUserTransform(lmt) 2618 2619 self.transform = lmt 2620 self.point_locator = None 2621 self.cell_locator = None 2622 self.pipeline = utils.OperationNode("transform_with_landmarks", parents=[self]) 2623 return self 2624 2625 2626 def apply_transform(self, T, reset=False, concatenate=False): 2627 """ 2628 Apply a linear or non-linear transformation to the mesh polygonal data. 2629 2630 Arguments: 2631 T : (matrix) 2632 `vtkTransform`, `vtkMatrix4x4` or a 4x4 or 3x3 python or numpy matrix. 2633 reset : (bool) 2634 if True reset the current transformation matrix 2635 to identity after having moved the object, otherwise the internal 2636 matrix will stay the same (to only affect visualization). 2637 It the input transformation has no internal defined matrix (ie. non linear) 2638 then reset will be assumed as True. 2639 concatenate : (bool) 2640 concatenate the transformation with the current existing one 2641 2642 Example: 2643 ```python 2644 from vedo import Cube, show 2645 c1 = Cube().rotate_z(5).x(2).y(1) 2646 print("cube1 position", c1.pos()) 2647 T = c1.get_transform() # rotate by 5 degrees, sum 2 to x and 1 to y 2648 c2 = Cube().c('r4') 2649 c2.apply_transform(T) # ignore previous movements 2650 c2.apply_transform(T, concatenate=True) 2651 c2.apply_transform(T, concatenate=True) 2652 print("cube2 position", c2.pos()) 2653 show(c1, c2, axes=1).close() 2654 ``` 2655 ![](https://vedo.embl.es/images/feats/apply_transform.png) 2656 """ 2657 self.point_locator = None 2658 self.cell_locator = None 2659 2660 if isinstance(T, vtk.vtkMatrix4x4): 2661 tr = vtk.vtkTransform() 2662 tr.SetMatrix(T) 2663 T = tr 2664 2665 elif utils.is_sequence(T): 2666 M = vtk.vtkMatrix4x4() 2667 n = len(T[0]) 2668 for i in range(n): 2669 for j in range(n): 2670 M.SetElement(i, j, T[i][j]) 2671 tr = vtk.vtkTransform() 2672 tr.SetMatrix(M) 2673 T = tr 2674 2675 if reset or not hasattr(T, "GetScale"): # might be non-linear 2676 2677 tf = vtk.vtkTransformPolyDataFilter() 2678 tf.SetTransform(T) 2679 tf.SetInputData(self.polydata()) 2680 tf.Update() 2681 2682 I = vtk.vtkMatrix4x4() 2683 self.PokeMatrix(I) # reset to identity 2684 self.SetUserTransform(None) 2685 2686 self._update(tf.GetOutput()) ### UPDATE 2687 self.transform = T 2688 2689 else: 2690 2691 if concatenate: 2692 2693 M = vtk.vtkTransform() 2694 M.PostMultiply() 2695 M.SetMatrix(self.GetMatrix()) 2696 2697 M.Concatenate(T) 2698 2699 self.SetScale(M.GetScale()) 2700 self.SetOrientation(M.GetOrientation()) 2701 self.SetPosition(M.GetPosition()) 2702 self.transform = M 2703 self.SetUserTransform(None) 2704 2705 else: 2706 2707 self.SetScale(T.GetScale()) 2708 self.SetOrientation(T.GetOrientation()) 2709 self.SetPosition(T.GetPosition()) 2710 self.SetUserTransform(None) 2711 2712 self.transform = T 2713 2714 return self 2715 2716 def normalize(self): 2717 """Scale Mesh average size to unit.""" 2718 coords = self.points() 2719 if not coords.shape[0]: 2720 return self 2721 cm = np.mean(coords, axis=0) 2722 pts = coords - cm 2723 xyz2 = np.sum(pts * pts, axis=0) 2724 scale = 1 / np.sqrt(np.sum(xyz2) / len(pts)) 2725 t = vtk.vtkTransform() 2726 t.PostMultiply() 2727 # t.Translate(-cm) 2728 t.Scale(scale, scale, scale) 2729 # t.Translate(cm) 2730 tf = vtk.vtkTransformPolyDataFilter() 2731 tf.SetInputData(self.inputdata()) 2732 tf.SetTransform(t) 2733 tf.Update() 2734 self.point_locator = None 2735 self.cell_locator = None 2736 return self._update(tf.GetOutput()) 2737 2738 def mirror(self, axis="x", origin=(0, 0, 0), reset=False): 2739 """ 2740 Mirror the mesh along one of the cartesian axes 2741 2742 Arguments: 2743 axis : (str) 2744 axis to use for mirroring, must be set to x, y, z or n. 2745 Or any combination of those. Adding 'n' reverses mesh faces (hence normals). 2746 origin : (list) 2747 use this point as the origin of the mirroring transformation. 2748 reset : (bool) 2749 if True keep into account the current position of the object, 2750 and then reset its internal transformation matrix to Identity. 2751 2752 Examples: 2753 - [mirror.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mirror.py) 2754 2755 ![](https://vedo.embl.es/images/basic/mirror.png) 2756 """ 2757 sx, sy, sz = 1, 1, 1 2758 if "x" in axis.lower(): sx = -1 2759 if "y" in axis.lower(): sy = -1 2760 if "z" in axis.lower(): sz = -1 2761 origin = np.array(origin) 2762 tr = vtk.vtkTransform() 2763 tr.PostMultiply() 2764 tr.Translate(-origin) 2765 tr.Scale(sx, sy, sz) 2766 tr.Translate(origin) 2767 tf = vtk.vtkTransformPolyDataFilter() 2768 tf.SetInputData(self.polydata(reset)) 2769 tf.SetTransform(tr) 2770 tf.Update() 2771 outpoly = tf.GetOutput() 2772 if reset: 2773 self.PokeMatrix(vtk.vtkMatrix4x4()) # reset to identity 2774 if sx * sy * sz < 0 or "n" in axis: 2775 rs = vtk.vtkReverseSense() 2776 rs.SetInputData(outpoly) 2777 rs.ReverseNormalsOff() 2778 rs.Update() 2779 outpoly = rs.GetOutput() 2780 2781 self.point_locator = None 2782 self.cell_locator = None 2783 2784 out = self._update(outpoly) 2785 2786 out.pipeline = utils.OperationNode(f"mirror\naxis = {axis}", parents=[self]) 2787 return out 2788 2789 def shear(self, x=0, y=0, z=0): 2790 """Apply a shear deformation along one of the main axes""" 2791 t = vtk.vtkTransform() 2792 sx, sy, sz = self.GetScale() 2793 t.SetMatrix([sx, x, 0, 0, 2794 y,sy, z, 0, 2795 0, 0,sz, 0, 2796 0, 0, 0, 1]) 2797 self.apply_transform(t, reset=True) 2798 return self 2799 2800 def flip_normals(self): 2801 """Flip all mesh normals. Same as `mesh.mirror('n')`.""" 2802 rs = vtk.vtkReverseSense() 2803 rs.SetInputData(self.inputdata()) 2804 rs.ReverseCellsOff() 2805 rs.ReverseNormalsOn() 2806 rs.Update() 2807 out = self._update(rs.GetOutput()) 2808 self.pipeline = utils.OperationNode("flip_normals", parents=[self]) 2809 return out 2810 2811 ##################################################################################### 2812 def cmap( 2813 self, 2814 input_cmap, 2815 input_array=None, 2816 on="points", 2817 name="Scalars", 2818 vmin=None, 2819 vmax=None, 2820 n_colors=256, 2821 alpha=1.0, 2822 logscale=False, 2823 ): 2824 """ 2825 Set individual point/cell colors by providing a list of scalar values and a color map. 2826 2827 Arguments: 2828 input_cmap : (str, list, vtkLookupTable, matplotlib.colors.LinearSegmentedColormap) 2829 color map scheme to transform a real number into a color. 2830 input_array : (str, list, vtkArray) 2831 can be the string name of an existing array, a numpy array or a `vtkArray`. 2832 on : (str) 2833 either 'points' or 'cells'. 2834 Apply the color map to data which is defined on either points or cells. 2835 name : (str) 2836 give a name to the provided numpy array (if input_array is a numpy array) 2837 vmin : (float) 2838 clip scalars to this minimum value 2839 vmax : (float) 2840 clip scalars to this maximum value 2841 n_colors : (int) 2842 number of distinct colors to be used in colormap table. 2843 alpha : (float, list) 2844 Mesh transparency. Can be a `list` of values one for each vertex. 2845 logscale : (bool) 2846 Use logscale 2847 2848 Examples: 2849 - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py) 2850 - [mesh_alphas.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_alphas.py) 2851 - [mesh_custom.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_custom.py) 2852 (and many others) 2853 2854 ![](https://vedo.embl.es/images/basic/mesh_custom.png) 2855 """ 2856 self._cmap_name = input_cmap 2857 poly = self.inputdata() 2858 2859 if input_array is None: 2860 if not self.pointdata.keys() and self.celldata.keys(): 2861 on = "cells" 2862 if not poly.GetCellData().GetScalars(): 2863 input_array = 0 # pick the first at hand 2864 2865 if on.startswith("point"): 2866 data = poly.GetPointData() 2867 n = poly.GetNumberOfPoints() 2868 elif on.startswith("cell"): 2869 data = poly.GetCellData() 2870 n = poly.GetNumberOfCells() 2871 else: 2872 vedo.logger.error("Must specify in cmap(on=...) to either 'cells' or 'points'") 2873 raise RuntimeError() 2874 2875 if input_array is None: # if None try to fetch the active scalars 2876 arr = data.GetScalars() 2877 if not arr: 2878 vedo.logger.error(f"in cmap(), cannot find any {on} active array ...skip coloring.") 2879 return self 2880 2881 if not arr.GetName(): # sometimes arrays dont have a name.. 2882 arr.SetName(name) 2883 2884 elif isinstance(input_array, str): # if a string is passed 2885 arr = data.GetArray(input_array) 2886 if not arr: 2887 vedo.logger.error(f"in cmap(), cannot find {on} array {input_array} ...skip coloring.") 2888 return self 2889 2890 elif isinstance(input_array, int): # if an int is passed 2891 if input_array < data.GetNumberOfArrays(): 2892 arr = data.GetArray(input_array) 2893 else: 2894 vedo.logger.error(f"in cmap(), cannot find {on} array at {input_array} ...skip coloring.") 2895 return self 2896 2897 elif utils.is_sequence(input_array): # if a numpy array is passed 2898 npts = len(input_array) 2899 if npts != n: 2900 vedo.logger.error(f"in cmap(), nr. of input {on} scalars {npts} != {n} ...skip coloring.") 2901 return self 2902 arr = utils.numpy2vtk(input_array, name=name, dtype=float) 2903 data.AddArray(arr) 2904 data.Modified() 2905 2906 elif isinstance(input_array, vtk.vtkArray): # if a vtkArray is passed 2907 arr = input_array 2908 data.AddArray(arr) 2909 data.Modified() 2910 2911 else: 2912 vedo.logger.error(f"in cmap(), cannot understand input type {type(input_array)}") 2913 raise RuntimeError() 2914 2915 # Now we have array "arr" 2916 array_name = arr.GetName() 2917 2918 if arr.GetNumberOfComponents() == 1: 2919 if vmin is None: 2920 vmin = arr.GetRange()[0] 2921 if vmax is None: 2922 vmax = arr.GetRange()[1] 2923 else: 2924 if vmin is None or vmax is None: 2925 vn = utils.mag(utils.vtk2numpy(arr)) 2926 if vmin is None: 2927 vmin = vn.min() 2928 if vmax is None: 2929 vmax = vn.max() 2930 2931 # interpolate alphas if they are not constant 2932 if not utils.is_sequence(alpha): 2933 alpha = [alpha] * n_colors 2934 else: 2935 v = np.linspace(0, 1, n_colors, endpoint=True) 2936 xp = np.linspace(0, 1, len(alpha), endpoint=True) 2937 alpha = np.interp(v, xp, alpha) 2938 2939 ########################### build the look-up table 2940 if isinstance(input_cmap, vtk.vtkLookupTable): # vtkLookupTable 2941 lut = input_cmap 2942 2943 elif utils.is_sequence(input_cmap): # manual sequence of colors 2944 lut = vtk.vtkLookupTable() 2945 if logscale: 2946 lut.SetScaleToLog10() 2947 lut.SetRange(vmin, vmax) 2948 ncols = len(input_cmap) 2949 lut.SetNumberOfTableValues(ncols) 2950 2951 for i, c in enumerate(input_cmap): 2952 r, g, b = colors.get_color(c) 2953 lut.SetTableValue(i, r, g, b, alpha[i]) 2954 lut.Build() 2955 2956 else: # assume string cmap name OR matplotlib.colors.LinearSegmentedColormap 2957 lut = vtk.vtkLookupTable() 2958 if logscale: 2959 lut.SetScaleToLog10() 2960 lut.SetVectorModeToMagnitude() 2961 lut.SetRange(vmin, vmax) 2962 lut.SetNumberOfTableValues(n_colors) 2963 mycols = colors.color_map(range(n_colors), input_cmap, 0, n_colors) 2964 for i, c in enumerate(mycols): 2965 r, g, b = c 2966 lut.SetTableValue(i, r, g, b, alpha[i]) 2967 lut.Build() 2968 2969 arr.SetLookupTable(lut) 2970 2971 data.SetActiveScalars(array_name) 2972 # data.SetScalars(arr) # wrong! it deletes array in position 0, never use SetScalars 2973 # data.SetActiveAttribute(array_name, 0) # boh! 2974 2975 if data.GetScalars(): 2976 data.GetScalars().SetLookupTable(lut) 2977 data.GetScalars().Modified() 2978 2979 self._mapper.SetLookupTable(lut) 2980 self._mapper.SetColorModeToMapScalars() # so we dont need to convert uint8 scalars 2981 2982 self._mapper.ScalarVisibilityOn() 2983 self._mapper.SetScalarRange(lut.GetRange()) 2984 if on.startswith("point"): 2985 self._mapper.SetScalarModeToUsePointData() 2986 else: 2987 self._mapper.SetScalarModeToUseCellData() 2988 if hasattr(self._mapper, "SetArrayName"): 2989 self._mapper.SetArrayName(array_name) 2990 2991 return self 2992 2993 def cell_individual_colors(self, colorlist): 2994 # DEPRECATED 2995 self.cellcolors = colorlist 2996 print("Please use property mesh.cellcolors=... instead of mesh.cell_individual_colors()") 2997 return self 2998 2999 @property 3000 def cellcolors(self): 3001 """ 3002 Colorize each cell (face) of a mesh by passing 3003 a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A]. 3004 Colors levels and opacities must be in the range [0,255]. 3005 3006 A single constant color can also be passed as string or RGBA. 3007 3008 A cell array named "CellsRGBA" is automatically created. 3009 3010 Examples: 3011 - [color_mesh_cells1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells1.py) 3012 - [color_mesh_cells2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells2.py) 3013 3014 ![](https://vedo.embl.es/images/basic/colorMeshCells.png) 3015 """ 3016 if "CellsRGBA" not in self.celldata.keys(): 3017 lut = self.mapper().GetLookupTable() 3018 vscalars = self._data.GetCellData().GetScalars() 3019 if vscalars is None or lut is None: 3020 arr = np.zeros([self.ncells, 4], dtype=np.uint8) 3021 col = np.array(self.property.GetColor()) 3022 col = np.round(col * 255).astype(np.uint8) 3023 alf = self.property.GetOpacity() 3024 alf = np.round(alf * 255).astype(np.uint8) 3025 arr[:, (0, 1, 2)] = col 3026 arr[:, 3] = alf 3027 else: 3028 cols = lut.MapScalars(vscalars, 0, 0) 3029 arr = utils.vtk2numpy(cols) 3030 self.celldata["CellsRGBA"] = arr 3031 self.celldata.select("CellsRGBA") 3032 return self.celldata["CellsRGBA"] 3033 3034 @cellcolors.setter 3035 def cellcolors(self, value): 3036 if isinstance(value, str): 3037 c = colors.get_color(value) 3038 value = np.array([*c, 1]) * 255 3039 value = np.round(value) 3040 3041 value = np.asarray(value) 3042 n = self.ncells 3043 3044 if value.ndim == 1: 3045 value = np.repeat([value], n, axis=0) 3046 3047 if value.shape[1] == 3: 3048 z = np.zeros((n, 1), dtype=np.uint8) 3049 value = np.append(value, z + 255, axis=1) 3050 3051 assert n == value.shape[0] 3052 3053 self.celldata["CellsRGBA"] = value.astype(np.uint8) 3054 self.celldata.select("CellsRGBA") 3055 3056 3057 @property 3058 def pointcolors(self): 3059 """ 3060 Colorize each point (or vertex of a mesh) by passing 3061 a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A]. 3062 Colors levels and opacities must be in the range [0,255]. 3063 3064 A single constant color can also be passed as string or RGBA. 3065 3066 A point array named "PointsRGBA" is automatically created. 3067 """ 3068 if "PointsRGBA" not in self.pointdata.keys(): 3069 lut = self.mapper().GetLookupTable() 3070 vscalars = self._data.GetPointData().GetScalars() 3071 if vscalars is None or lut is None: 3072 arr = np.zeros([self.npoints, 4], dtype=np.uint8) 3073 col = np.array(self.property.GetColor()) 3074 col = np.round(col * 255).astype(np.uint8) 3075 alf = self.property.GetOpacity() 3076 alf = np.round(alf * 255).astype(np.uint8) 3077 arr[:, (0, 1, 2)] = col 3078 arr[:, 3] = alf 3079 else: 3080 cols = lut.MapScalars(vscalars, 0, 0) 3081 arr = utils.vtk2numpy(cols) 3082 self.pointdata["PointsRGBA"] = arr 3083 self.pointdata.select("PointsRGBA") 3084 return self.pointdata["PointsRGBA"] 3085 3086 @pointcolors.setter 3087 def pointcolors(self, value): 3088 if isinstance(value, str): 3089 c = colors.get_color(value) 3090 value = np.array([*c, 1]) * 255 3091 value = np.round(value) 3092 3093 value = np.asarray(value) 3094 n = self.npoints 3095 3096 if value.ndim == 1: 3097 value = np.repeat([value], n, axis=0) 3098 3099 if value.shape[1] == 3: 3100 z = np.zeros((n, 1), dtype=np.uint8) 3101 value = np.append(value, z + 255, axis=1) 3102 3103 assert n == value.shape[0] 3104 3105 self.pointdata["PointsRGBA"] = value.astype(np.uint8) 3106 self.pointdata.select("PointsRGBA") 3107 3108 3109 def interpolate_data_from( 3110 self, 3111 source, 3112 radius=None, 3113 n=None, 3114 kernel="shepard", 3115 exclude=("Normals",), 3116 on="points", 3117 null_strategy=1, 3118 null_value=0, 3119 ): 3120 """ 3121 Interpolate over source to port its data onto the current object using various kernels. 3122 3123 If n (number of closest points to use) is set then radius value is ignored. 3124 3125 Arguments: 3126 kernel : (str) 3127 available kernels are [shepard, gaussian, linear] 3128 null_strategy : (int) 3129 specify a strategy to use when encountering a "null" point 3130 during the interpolation process. Null points occur when the local neighborhood 3131 (of nearby points to interpolate from) is empty. 3132 3133 - Case 0: an output array is created that marks points 3134 as being valid (=1) or null (invalid =0), and the null_value is set as well 3135 - Case 1: the output data value(s) are set to the provided null_value 3136 - Case 2: simply use the closest point to perform the interpolation. 3137 null_value : (float) 3138 see above. 3139 3140 Examples: 3141 - [interpolateMeshArray.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolateMeshArray.py) 3142 3143 ![](https://vedo.embl.es/images/advanced/interpolateMeshArray.png) 3144 """ 3145 if radius is None and not n: 3146 vedo.logger.error("in interpolate_data_from(): please set either radius or n") 3147 raise RuntimeError 3148 3149 if on == "points": 3150 points = source.polydata() 3151 elif on == "cells": 3152 poly2 = vtk.vtkPolyData() 3153 poly2.ShallowCopy(source.polydata()) 3154 c2p = vtk.vtkCellDataToPointData() 3155 c2p.SetInputData(poly2) 3156 c2p.Update() 3157 points = c2p.GetOutput() 3158 else: 3159 vedo.logger.error("in interpolate_data_from(), on must be on points or cells") 3160 raise RuntimeError() 3161 3162 locator = vtk.vtkPointLocator() 3163 locator.SetDataSet(points) 3164 locator.BuildLocator() 3165 3166 if kernel.lower() == "shepard": 3167 kern = vtk.vtkShepardKernel() 3168 kern.SetPowerParameter(2) 3169 elif kernel.lower() == "gaussian": 3170 kern = vtk.vtkGaussianKernel() 3171 kern.SetSharpness(2) 3172 elif kernel.lower() == "linear": 3173 kern = vtk.vtkLinearKernel() 3174 else: 3175 vedo.logger.error("available kernels are: [shepard, gaussian, linear]") 3176 raise RuntimeError() 3177 3178 if n: 3179 kern.SetNumberOfPoints(n) 3180 kern.SetKernelFootprintToNClosest() 3181 else: 3182 kern.SetRadius(radius) 3183 3184 interpolator = vtk.vtkPointInterpolator() 3185 interpolator.SetInputData(self.polydata()) 3186 interpolator.SetSourceData(points) 3187 interpolator.SetKernel(kern) 3188 interpolator.SetLocator(locator) 3189 interpolator.PassFieldArraysOff() 3190 interpolator.SetNullPointsStrategy(null_strategy) 3191 interpolator.SetNullValue(null_value) 3192 interpolator.SetValidPointsMaskArrayName("ValidPointMask") 3193 for ex in exclude: 3194 interpolator.AddExcludedArray(ex) 3195 interpolator.Update() 3196 3197 if on == "cells": 3198 p2c = vtk.vtkPointDataToCellData() 3199 p2c.SetInputData(interpolator.GetOutput()) 3200 p2c.Update() 3201 cpoly = p2c.GetOutput() 3202 else: 3203 cpoly = interpolator.GetOutput() 3204 3205 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 3206 self._update(cpoly) 3207 else: 3208 # bring the underlying polydata to where _data is 3209 M = vtk.vtkMatrix4x4() 3210 M.DeepCopy(self.GetMatrix()) 3211 M.Invert() 3212 tr = vtk.vtkTransform() 3213 tr.SetMatrix(M) 3214 tf = vtk.vtkTransformPolyDataFilter() 3215 tf.SetTransform(tr) 3216 tf.SetInputData(cpoly) 3217 tf.Update() 3218 self._update(tf.GetOutput()) 3219 3220 self.pipeline = utils.OperationNode("interpolate_data_from", parents=[self, source]) 3221 return self 3222 3223 def add_gaussian_noise(self, sigma=1.0): 3224 """ 3225 Add gaussian noise to point positions. 3226 An extra array is added named "GaussianNoise" with the shifts. 3227 3228 Arguments: 3229 sigma : (float) 3230 nr. of standard deviations, expressed in percent of the diagonal size of mesh. 3231 Can also be a list [sigma_x, sigma_y, sigma_z]. 3232 3233 Examples: 3234 ```python 3235 from vedo import Sphere 3236 Sphere().add_gaussian_noise(1.0).point_size(8).show().close() 3237 ``` 3238 """ 3239 sz = self.diagonal_size() 3240 pts = self.points() 3241 n = len(pts) 3242 ns = (np.random.randn(n, 3) * sigma) * (sz / 100) 3243 vpts = vtk.vtkPoints() 3244 vpts.SetNumberOfPoints(n) 3245 vpts.SetData(utils.numpy2vtk(pts + ns, dtype=np.float32)) 3246 self.inputdata().SetPoints(vpts) 3247 self.inputdata().GetPoints().Modified() 3248 self.pointdata["GaussianNoise"] = -ns 3249 self.pipeline = utils.OperationNode( 3250 "gaussian_noise", parents=[self], shape="egg", comment=f"sigma = {sigma}" 3251 ) 3252 return self 3253 3254 3255 def closest_point(self, pt, n=1, radius=None, return_point_id=False, return_cell_id=False): 3256 """ 3257 Find the closest point(s) on a mesh given from the input point `pt`. 3258 3259 Arguments: 3260 n : (int) 3261 if greater than 1, return a list of n ordered closest points 3262 radius : (float) 3263 if given, get all points within that radius. Then n is ignored. 3264 return_point_id : (bool) 3265 return point ID instead of coordinates 3266 return_cell_id : (bool) 3267 return cell ID in which the closest point sits 3268 3269 Examples: 3270 - [align1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align1.py) 3271 - [fitplanes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/fitplanes.py) 3272 - [quadratic_morphing.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/quadratic_morphing.py) 3273 3274 .. note:: 3275 The appropriate tree search locator is built on the fly and cached for speed. 3276 3277 If you want to reset it use `mymesh.point_locator=None` 3278 """ 3279 # NB: every time the mesh moves or is warped the locators are set to None 3280 if (n > 1 or radius) or (n == 1 and return_point_id): 3281 3282 poly = None 3283 if not self.point_locator: 3284 poly = self.polydata() 3285 self.point_locator = vtk.vtkStaticPointLocator() 3286 self.point_locator.SetDataSet(poly) 3287 self.point_locator.BuildLocator() 3288 3289 ########## 3290 if radius: 3291 vtklist = vtk.vtkIdList() 3292 self.point_locator.FindPointsWithinRadius(radius, pt, vtklist) 3293 elif n > 1: 3294 vtklist = vtk.vtkIdList() 3295 self.point_locator.FindClosestNPoints(n, pt, vtklist) 3296 else: # n==1 hence return_point_id==True 3297 ######## 3298 return self.point_locator.FindClosestPoint(pt) 3299 ######## 3300 3301 if return_point_id: 3302 ######## 3303 return utils.vtk2numpy(vtklist) 3304 ######## 3305 3306 if not poly: 3307 poly = self.polydata() 3308 trgp = [] 3309 for i in range(vtklist.GetNumberOfIds()): 3310 trgp_ = [0, 0, 0] 3311 vi = vtklist.GetId(i) 3312 poly.GetPoints().GetPoint(vi, trgp_) 3313 trgp.append(trgp_) 3314 ######## 3315 return np.array(trgp) 3316 ######## 3317 3318 else: 3319 3320 if not self.cell_locator: 3321 poly = self.polydata() 3322 3323 # As per Miquel example with limbs the vtkStaticCellLocator doesnt work !! 3324 # https://discourse.vtk.org/t/vtkstaticcelllocator-problem-vtk9-0-3/7854/4 3325 if vedo.vtk_version[0] >= 9 and vedo.vtk_version[0] > 0: 3326 self.cell_locator = vtk.vtkStaticCellLocator() 3327 else: 3328 self.cell_locator = vtk.vtkCellLocator() 3329 3330 self.cell_locator.SetDataSet(poly) 3331 self.cell_locator.BuildLocator() 3332 3333 trgp = [0, 0, 0] 3334 cid = vtk.mutable(0) 3335 dist2 = vtk.mutable(0) 3336 subid = vtk.mutable(0) 3337 self.cell_locator.FindClosestPoint(pt, trgp, cid, subid, dist2) 3338 3339 if return_cell_id: 3340 return int(cid) 3341 3342 return np.array(trgp) 3343 3344 3345 def hausdorff_distance(self, points): 3346 """ 3347 Compute the Hausdorff distance to the input point set. 3348 Returns a single `float`. 3349 3350 Example: 3351 ```python 3352 from vedo import * 3353 t = np.linspace(0, 2*np.pi, 100) 3354 x = 4/3 * sin(t)**3 3355 y = cos(t) - cos(2*t)/3 - cos(3*t)/6 - cos(4*t)/12 3356 pol1 = Line(np.c_[x,y], closed=True).triangulate() 3357 pol2 = Polygon(nsides=5).pos(2,2) 3358 d12 = pol1.distance_to(pol2) 3359 d21 = pol2.distance_to(pol1) 3360 pol1.lw(0).cmap("viridis") 3361 pol2.lw(0).cmap("viridis") 3362 print("distance d12, d21 :", min(d12), min(d21)) 3363 print("hausdorff distance:", pol1.hausdorff_distance(pol2)) 3364 print("chamfer distance :", pol1.chamfer_distance(pol2)) 3365 show(pol1, pol2, axes=1) 3366 ``` 3367 ![](https://vedo.embl.es/images/feats/heart.png) 3368 """ 3369 hp = vtk.vtkHausdorffDistancePointSetFilter() 3370 hp.SetInputData(0, self.polydata()) 3371 hp.SetInputData(1, points.polydata()) 3372 hp.SetTargetDistanceMethodToPointToCell() 3373 hp.Update() 3374 return hp.GetHausdorffDistance() 3375 3376 def chamfer_distance(self, pcloud): 3377 """ 3378 Compute the Chamfer distance to the input point set. 3379 Returns a single `float`. 3380 """ 3381 if not pcloud.point_locator: 3382 pcloud.point_locator = vtk.vtkPointLocator() 3383 pcloud.point_locator.SetDataSet(pcloud.polydata()) 3384 pcloud.point_locator.BuildLocator() 3385 if not self.point_locator: 3386 self.point_locator = vtk.vtkPointLocator() 3387 self.point_locator.SetDataSet(self.polydata()) 3388 self.point_locator.BuildLocator() 3389 3390 ps1 = self.points() 3391 ps2 = pcloud.points() 3392 3393 ids12 = [] 3394 for p in ps1: 3395 pid12 = pcloud.point_locator.FindClosestPoint(p) 3396 ids12.append(pid12) 3397 deltav = ps2[ids12] - ps1 3398 da = np.mean(np.linalg.norm(deltav, axis=1)) 3399 3400 ids21 = [] 3401 for p in ps2: 3402 pid21 = self.point_locator.FindClosestPoint(p) 3403 ids21.append(pid21) 3404 deltav = ps1[ids21] - ps2 3405 db = np.mean(np.linalg.norm(deltav, axis=1)) 3406 return (da + db) / 2 3407 3408 def remove_outliers(self, radius, neighbors=5): 3409 """ 3410 Remove outliers from a cloud of points within the specified `radius` search. 3411 3412 Arguments: 3413 radius : (float) 3414 Specify the local search radius. 3415 neighbors : (int) 3416 Specify the number of neighbors that a point must have, 3417 within the specified radius, for the point to not be considered isolated. 3418 3419 Examples: 3420 - [clustering.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/clustering.py) 3421 3422 ![](https://vedo.embl.es/images/basic/clustering.png) 3423 """ 3424 removal = vtk.vtkRadiusOutlierRemoval() 3425 removal.SetInputData(self.polydata()) 3426 removal.SetRadius(radius) 3427 removal.SetNumberOfNeighbors(neighbors) 3428 removal.GenerateOutliersOff() 3429 removal.Update() 3430 inputobj = removal.GetOutput() 3431 if inputobj.GetNumberOfCells() == 0: 3432 carr = vtk.vtkCellArray() 3433 for i in range(inputobj.GetNumberOfPoints()): 3434 carr.InsertNextCell(1) 3435 carr.InsertCellPoint(i) 3436 inputobj.SetVerts(carr) 3437 self._update(inputobj) 3438 self.mapper().ScalarVisibilityOff() 3439 self.pipeline = utils.OperationNode("remove_outliers", parents=[self]) 3440 return self 3441 3442 def smooth_mls_1d(self, f=0.2, radius=None): 3443 """ 3444 Smooth mesh or points with a `Moving Least Squares` variant. 3445 The point data array "Variances" will contain the residue calculated for each point. 3446 Input mesh's polydata is modified. 3447 3448 Arguments: 3449 f : (float) 3450 smoothing factor - typical range is [0,2]. 3451 radius : (float) 3452 radius search in absolute units. If set then `f` is ignored. 3453 3454 Examples: 3455 - [moving_least_squares1D.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/moving_least_squares1D.py) 3456 - [skeletonize.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/skeletonize.py) 3457 3458 ![](https://vedo.embl.es/images/advanced/moving_least_squares1D.png) 3459 """ 3460 coords = self.points() 3461 ncoords = len(coords) 3462 3463 if radius: 3464 Ncp = 0 3465 else: 3466 Ncp = int(ncoords * f / 10) 3467 if Ncp < 5: 3468 vedo.logger.warning(f"Please choose a fraction higher than {f}") 3469 Ncp = 5 3470 3471 variances, newline = [], [] 3472 for p in coords: 3473 points = self.closest_point(p, n=Ncp, radius=radius) 3474 if len(points) < 4: 3475 continue 3476 3477 points = np.array(points) 3478 pointsmean = points.mean(axis=0) # plane center 3479 _, dd, vv = np.linalg.svd(points - pointsmean) 3480 newp = np.dot(p - pointsmean, vv[0]) * vv[0] + pointsmean 3481 variances.append(dd[1] + dd[2]) 3482 newline.append(newp) 3483 3484 vdata = utils.numpy2vtk(np.array(variances)) 3485 vdata.SetName("Variances") 3486 self.inputdata().GetPointData().AddArray(vdata) 3487 self.inputdata().GetPointData().Modified() 3488 self.points(newline) 3489 self.pipeline = utils.OperationNode("smooth_mls_1d", parents=[self]) 3490 return self 3491 3492 def smooth_mls_2d(self, f=0.2, radius=None): 3493 """ 3494 Smooth mesh or points with a `Moving Least Squares` algorithm variant. 3495 The list `mesh.info['variances']` contains the residue calculated for each point. 3496 When a radius is specified points that are isolated will not be moved and will get 3497 a False entry in array `mesh.info['is_valid']`. 3498 3499 Arguments: 3500 f : (float) 3501 smoothing factor - typical range is [0,2]. 3502 radius : (float) 3503 radius search in absolute units. If set then `f` is ignored. 3504 3505 Examples: 3506 - [moving_least_squares2D.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/moving_least_squares2D.py) 3507 - [recosurface.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/recosurface.py) 3508 3509 ![](https://vedo.embl.es/images/advanced/recosurface.png) 3510 """ 3511 coords = self.points() 3512 ncoords = len(coords) 3513 3514 if radius: 3515 Ncp = 1 3516 else: 3517 Ncp = int(ncoords * f / 100) 3518 if Ncp < 4: 3519 vedo.logger.error(f"MLS2D: Please choose a fraction higher than {f}") 3520 Ncp = 4 3521 3522 variances, newpts, valid = [], [], [] 3523 pb = None 3524 if ncoords > 10000: 3525 pb = utils.ProgressBar(0, ncoords) 3526 for p in coords: 3527 if pb: 3528 pb.print("smoothMLS2D working ...") 3529 pts = self.closest_point(p, n=Ncp, radius=radius) 3530 if len(pts) > 3: 3531 ptsmean = pts.mean(axis=0) # plane center 3532 _, dd, vv = np.linalg.svd(pts - ptsmean) 3533 cv = np.cross(vv[0], vv[1]) 3534 t = (np.dot(cv, ptsmean) - np.dot(cv, p)) / np.dot(cv, cv) 3535 newp = p + cv * t 3536 newpts.append(newp) 3537 variances.append(dd[2]) 3538 if radius: 3539 valid.append(True) 3540 else: 3541 newpts.append(p) 3542 variances.append(0) 3543 if radius: 3544 valid.append(False) 3545 3546 self.info["variances"] = np.array(variances) 3547 self.info["is_valid"] = np.array(valid) 3548 self.points(newpts) 3549 3550 self.pipeline = utils.OperationNode("smooth_mls_2d", parents=[self]) 3551 return self 3552 3553 def smooth_lloyd_2d(self, iterations=2, bounds=None, options="Qbb Qc Qx"): 3554 """Lloyd relaxation of a 2D pointcloud.""" 3555 # Credits: https://hatarilabs.com/ih-en/ 3556 # tutorial-to-create-a-geospatial-voronoi-sh-mesh-with-python-scipy-and-geopandas 3557 from scipy.spatial import Voronoi as scipy_voronoi 3558 3559 def _constrain_points(points): 3560 # Update any points that have drifted beyond the boundaries of this space 3561 if bounds is not None: 3562 for point in points: 3563 if point[0] < bounds[0]: point[0] = bounds[0] 3564 if point[0] > bounds[1]: point[0] = bounds[1] 3565 if point[1] < bounds[2]: point[1] = bounds[2] 3566 if point[1] > bounds[3]: point[1] = bounds[3] 3567 return points 3568 3569 def _find_centroid(vertices): 3570 # The equation for the method used here to find the centroid of a 3571 # 2D polygon is given here: https://en.wikipedia.org/wiki/Centroid#Of_a_polygon 3572 area = 0 3573 centroid_x = 0 3574 centroid_y = 0 3575 for i in range(len(vertices) - 1): 3576 step = (vertices[i, 0] * vertices[i + 1, 1]) - (vertices[i + 1, 0] * vertices[i, 1]) 3577 centroid_x += (vertices[i, 0] + vertices[i + 1, 0]) * step 3578 centroid_y += (vertices[i, 1] + vertices[i + 1, 1]) * step 3579 area += step 3580 if area: 3581 centroid_x = (1.0 / (3.0 * area)) * centroid_x 3582 centroid_y = (1.0 / (3.0 * area)) * centroid_y 3583 # prevent centroids from escaping bounding box 3584 return _constrain_points([[centroid_x, centroid_y]])[0] 3585 3586 def _relax(voron): 3587 # Moves each point to the centroid of its cell in the voronoi 3588 # map to "relax" the points (i.e. jitter the points so as 3589 # to spread them out within the space). 3590 centroids = [] 3591 for idx in voron.point_region: 3592 # the region is a series of indices into voronoi.vertices 3593 # remove point at infinity, designated by index -1 3594 region = [i for i in voron.regions[idx] if i != -1] 3595 # enclose the polygon 3596 region = region + [region[0]] 3597 verts = voron.vertices[region] 3598 # find the centroid of those vertices 3599 centroids.append(_find_centroid(verts)) 3600 return _constrain_points(centroids) 3601 3602 if bounds is None: 3603 bounds = self.bounds() 3604 3605 pts = self.points()[:, (0, 1)] 3606 for i in range(iterations): 3607 vor = scipy_voronoi(pts, qhull_options=options) 3608 _constrain_points(vor.vertices) 3609 pts = _relax(vor) 3610 # m = vedo.Mesh([pts, self.faces()]) # not yet working properly 3611 out = Points(pts, c="k") 3612 out.pipeline = utils.OperationNode("smooth_lloyd", parents=[self]) 3613 return out 3614 3615 def project_on_plane(self, plane="z", point=None, direction=None): 3616 """ 3617 Project the mesh on one of the Cartesian planes. 3618 3619 Arguments: 3620 plane : (str, Plane) 3621 if plane is `str`, plane can be one of ['x', 'y', 'z'], 3622 represents x-plane, y-plane and z-plane, respectively. 3623 Otherwise, plane should be an instance of `vedo.shapes.Plane`. 3624 point : (float, array) 3625 if plane is `str`, point should be a float represents the intercept. 3626 Otherwise, point is the camera point of perspective projection 3627 direction : (array) 3628 direction of oblique projection 3629 3630 Note: 3631 Parameters `point` and `direction` are only used if the given plane 3632 is an instance of `vedo.shapes.Plane`. And one of these two params 3633 should be left as `None` to specify the projection type. 3634 3635 Example: 3636 ```python 3637 s.project_on_plane(plane='z') # project to z-plane 3638 plane = Plane(pos=(4, 8, -4), normal=(-1, 0, 1), s=(5,5)) 3639 s.project_on_plane(plane=plane) # orthogonal projection 3640 s.project_on_plane(plane=plane, point=(6, 6, 6)) # perspective projection 3641 s.project_on_plane(plane=plane, direction=(1, 2, -1)) # oblique projection 3642 ``` 3643 3644 Examples: 3645 - [silhouette2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/silhouette2.py) 3646 3647 ![](https://vedo.embl.es/images/basic/silhouette2.png) 3648 """ 3649 coords = self.points() 3650 3651 if plane == "x": 3652 coords[:, 0] = self.GetOrigin()[0] 3653 intercept = self.xbounds()[0] if point is None else point 3654 self.x(intercept) 3655 elif plane == "y": 3656 coords[:, 1] = self.GetOrigin()[1] 3657 intercept = self.ybounds()[0] if point is None else point 3658 self.y(intercept) 3659 elif plane == "z": 3660 coords[:, 2] = self.GetOrigin()[2] 3661 intercept = self.zbounds()[0] if point is None else point 3662 self.z(intercept) 3663 3664 elif isinstance(plane, vedo.shapes.Plane): 3665 normal = plane.normal / np.linalg.norm(plane.normal) 3666 pl = np.hstack((normal, -np.dot(plane.pos(), normal))).reshape(4, 1) 3667 if direction is None and point is None: 3668 # orthogonal projection 3669 pt = np.hstack((normal, [0])).reshape(4, 1) 3670 # proj_mat = pt.T @ pl * np.eye(4) - pt @ pl.T # python3 only 3671 proj_mat = np.matmul(pt.T, pl) * np.eye(4) - np.matmul(pt, pl.T) 3672 3673 elif direction is None: 3674 # perspective projection 3675 pt = np.hstack((np.array(point), [1])).reshape(4, 1) 3676 # proj_mat = pt.T @ pl * np.eye(4) - pt @ pl.T 3677 proj_mat = np.matmul(pt.T, pl) * np.eye(4) - np.matmul(pt, pl.T) 3678 3679 elif point is None: 3680 # oblique projection 3681 pt = np.hstack((np.array(direction), [0])).reshape(4, 1) 3682 # proj_mat = pt.T @ pl * np.eye(4) - pt @ pl.T 3683 proj_mat = np.matmul(pt.T, pl) * np.eye(4) - np.matmul(pt, pl.T) 3684 3685 coords = np.concatenate([coords, np.ones((coords.shape[:-1] + (1,)))], axis=-1) 3686 # coords = coords @ proj_mat.T 3687 coords = np.matmul(coords, proj_mat.T) 3688 coords = coords[:, :3] / coords[:, 3:] 3689 3690 else: 3691 vedo.logger.error(f"unknown plane {plane}") 3692 raise RuntimeError() 3693 3694 self.alpha(0.1) 3695 self.points(coords) 3696 return self 3697 3698 def warp(self, source, target, sigma=1.0, mode="3d"): 3699 """ 3700 `Thin Plate Spline` transformations describe a nonlinear warp transform defined by a set 3701 of source and target landmarks. Any point on the mesh close to a source landmark will 3702 be moved to a place close to the corresponding target landmark. 3703 The points in between are interpolated smoothly using 3704 Bookstein's Thin Plate Spline algorithm. 3705 3706 Transformation object can be accessed with `mesh.transform`. 3707 3708 Arguments: 3709 sigma : (float) 3710 specify the 'stiffness' of the spline. 3711 mode : (str) 3712 set the basis function to either abs(R) (for 3d) or R2LogR (for 2d meshes) 3713 3714 Examples: 3715 - [interpolate_field.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolate_field.py) 3716 - [warp1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/warp1.py) 3717 - [warp2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/warp2.py) 3718 3719 ![](https://vedo.embl.es/images/advanced/warp2.png) 3720 """ 3721 parents = [self] 3722 if isinstance(source, Points): 3723 parents.append(source) 3724 source = source.points() 3725 else: 3726 source = utils.make3d(source) 3727 3728 if isinstance(target, Points): 3729 parents.append(target) 3730 target = target.points() 3731 else: 3732 target = utils.make3d(target) 3733 3734 ns = len(source) 3735 ptsou = vtk.vtkPoints() 3736 ptsou.SetNumberOfPoints(ns) 3737 for i in range(ns): 3738 ptsou.SetPoint(i, source[i]) 3739 3740 nt = len(target) 3741 if ns != nt: 3742 vedo.logger.error(f"#source {ns} != {nt} #target points") 3743 raise RuntimeError() 3744 3745 pttar = vtk.vtkPoints() 3746 pttar.SetNumberOfPoints(nt) 3747 for i in range(ns): 3748 pttar.SetPoint(i, target[i]) 3749 3750 T = vtk.vtkThinPlateSplineTransform() 3751 if mode.lower() == "3d": 3752 T.SetBasisToR() 3753 elif mode.lower() == "2d": 3754 T.SetBasisToR2LogR() 3755 else: 3756 vedo.logger.error(f"unknown mode {mode}") 3757 raise RuntimeError() 3758 3759 T.SetSigma(sigma) 3760 T.SetSourceLandmarks(ptsou) 3761 T.SetTargetLandmarks(pttar) 3762 self.transform = T 3763 self.apply_transform(T, reset=True) 3764 3765 self.pipeline = utils.OperationNode("warp", parents=parents) 3766 return self 3767 3768 def cut_with_plane(self, origin=(0, 0, 0), normal=(1, 0, 0), invert=False): 3769 """ 3770 Cut the mesh with the plane defined by a point and a normal. 3771 3772 Arguments: 3773 origin : (array) 3774 the cutting plane goes through this point 3775 normal : (array) 3776 normal of the cutting plane 3777 3778 Example: 3779 ```python 3780 from vedo import Cube 3781 cube = Cube().cut_with_plane(normal=(1,1,1)) 3782 cube.back_color('pink').show() 3783 ``` 3784 ![](https://vedo.embl.es/images/feats/cut_with_plane_cube.png) 3785 3786 Examples: 3787 - [trail.py](https://github.com/marcomusy/vedo/blob/master/examples/simulations/trail.py) 3788 3789 ![](https://vedo.embl.es/images/simulations/trail.gif) 3790 3791 Check out also: 3792 `cut_with_box()`, `cut_with_cylinder()`, `cut_with_sphere()`. 3793 """ 3794 s = str(normal) 3795 if "x" in s: 3796 normal = (1, 0, 0) 3797 if "-" in s: 3798 normal = -np.array(normal) 3799 elif "y" in s: 3800 normal = (0, 1, 0) 3801 if "-" in s: 3802 normal = -np.array(normal) 3803 elif "z" in s: 3804 normal = (0, 0, 1) 3805 if "-" in s: 3806 normal = -np.array(normal) 3807 plane = vtk.vtkPlane() 3808 plane.SetOrigin(origin) 3809 plane.SetNormal(normal) 3810 3811 clipper = vtk.vtkClipPolyData() 3812 clipper.SetInputData(self.polydata(True)) # must be True 3813 clipper.SetClipFunction(plane) 3814 clipper.GenerateClippedOutputOff() 3815 clipper.GenerateClipScalarsOff() 3816 clipper.SetInsideOut(invert) 3817 clipper.SetValue(0) 3818 clipper.Update() 3819 3820 cpoly = clipper.GetOutput() 3821 3822 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 3823 self._update(cpoly) 3824 else: 3825 # bring the underlying polydata to where _data is 3826 M = vtk.vtkMatrix4x4() 3827 M.DeepCopy(self.GetMatrix()) 3828 M.Invert() 3829 tr = vtk.vtkTransform() 3830 tr.SetMatrix(M) 3831 tf = vtk.vtkTransformPolyDataFilter() 3832 tf.SetTransform(tr) 3833 tf.SetInputData(cpoly) 3834 tf.Update() 3835 self._update(tf.GetOutput()) 3836 3837 self.pipeline = utils.OperationNode("cut_with_plane", parents=[self]) 3838 return self 3839 3840 def cut_with_planes(self, origins, normals, invert=False): 3841 """ 3842 Cut the mesh with a convex set of planes defined by points and normals. 3843 3844 Arguments: 3845 origins : (array) 3846 each cutting plane goes through this point 3847 normals : (array) 3848 normal of each of the cutting planes 3849 3850 Check out also: 3851 `cut_with_box()`, `cut_with_cylinder()`, `cut_with_sphere()` 3852 """ 3853 3854 vpoints = vtk.vtkPoints() 3855 for p in utils.make3d(origins): 3856 vpoints.InsertNextPoint(p) 3857 normals = utils.make3d(normals) 3858 3859 planes = vtk.vtkPlanes() 3860 planes.SetPoints(vpoints) 3861 planes.SetNormals(utils.numpy2vtk(normals, dtype=float)) 3862 3863 clipper = vtk.vtkClipPolyData() 3864 clipper.SetInputData(self.polydata(True)) # must be True 3865 clipper.SetInsideOut(invert) 3866 clipper.SetClipFunction(planes) 3867 clipper.GenerateClippedOutputOff() 3868 clipper.GenerateClipScalarsOff() 3869 clipper.SetValue(0) 3870 clipper.Update() 3871 3872 cpoly = clipper.GetOutput() 3873 3874 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 3875 self._update(cpoly) 3876 else: 3877 # bring the underlying polydata to where _data is 3878 M = vtk.vtkMatrix4x4() 3879 M.DeepCopy(self.GetMatrix()) 3880 M.Invert() 3881 tr = vtk.vtkTransform() 3882 tr.SetMatrix(M) 3883 tf = vtk.vtkTransformPolyDataFilter() 3884 tf.SetTransform(tr) 3885 tf.SetInputData(cpoly) 3886 tf.Update() 3887 self._update(tf.GetOutput()) 3888 3889 self.pipeline = utils.OperationNode("cut_with_planes", parents=[self]) 3890 return self 3891 3892 def cut_with_box(self, bounds, invert=False): 3893 """ 3894 Cut the current mesh with a box or a set of boxes. 3895 This is much faster than `cut_with_mesh()`. 3896 3897 Input `bounds` can be either: 3898 - a Mesh or Points object 3899 - a list of 6 number representing a bounding box `[xmin,xmax, ymin,ymax, zmin,zmax]` 3900 - a list of bounding boxes like the above: `[[xmin1,...], [xmin2,...], ...]` 3901 3902 Example: 3903 ```python 3904 from vedo import Sphere, Cube, show 3905 mesh = Sphere(r=1, res=50) 3906 box = Cube(side=1.5).wireframe() 3907 mesh.cut_with_box(box) 3908 show(mesh, box, axes=1) 3909 ``` 3910 ![](https://vedo.embl.es/images/feats/cut_with_box_cube.png) 3911 3912 Check out also: 3913 `cut_with_line()`, `cut_with_plane()`, `cut_with_cylinder()` 3914 """ 3915 if isinstance(bounds, Points): 3916 bounds = bounds.bounds() 3917 3918 box = vtk.vtkBox() 3919 if utils.is_sequence(bounds[0]): 3920 for bs in bounds: 3921 box.AddBounds(bs) 3922 else: 3923 box.SetBounds(bounds) 3924 3925 clipper = vtk.vtkClipPolyData() 3926 clipper.SetInputData(self.polydata(True)) # must be True 3927 clipper.SetClipFunction(box) 3928 clipper.SetInsideOut(not invert) 3929 clipper.GenerateClippedOutputOff() 3930 clipper.GenerateClipScalarsOff() 3931 clipper.SetValue(0) 3932 clipper.Update() 3933 cpoly = clipper.GetOutput() 3934 3935 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 3936 self._update(cpoly) 3937 else: 3938 # bring the underlying polydata to where _data is 3939 M = vtk.vtkMatrix4x4() 3940 M.DeepCopy(self.GetMatrix()) 3941 M.Invert() 3942 tr = vtk.vtkTransform() 3943 tr.SetMatrix(M) 3944 tf = vtk.vtkTransformPolyDataFilter() 3945 tf.SetTransform(tr) 3946 tf.SetInputData(cpoly) 3947 tf.Update() 3948 self._update(tf.GetOutput()) 3949 3950 self.pipeline = utils.OperationNode("cut_with_box", parents=[self]) 3951 return self 3952 3953 def cut_with_line(self, points, invert=False, closed=True): 3954 """ 3955 Cut the current mesh with a line vertically in the z-axis direction like a cookie cutter. 3956 The polyline is defined by a set of points (z-coordinates are ignored). 3957 This is much faster than `cut_with_mesh()`. 3958 3959 Check out also: 3960 `cut_with_box()`, `cut_with_plane()`, `cut_with_sphere()` 3961 """ 3962 pplane = vtk.vtkPolyPlane() 3963 if isinstance(points, Points): 3964 points = points.points().tolist() 3965 3966 if closed: 3967 if isinstance(points, np.ndarray): 3968 points = points.tolist() 3969 points.append(points[0]) 3970 3971 vpoints = vtk.vtkPoints() 3972 for p in points: 3973 if len(p) == 2: 3974 p = [p[0], p[1], 0.0] 3975 vpoints.InsertNextPoint(p) 3976 3977 n = len(points) 3978 polyline = vtk.vtkPolyLine() 3979 polyline.Initialize(n, vpoints) 3980 polyline.GetPointIds().SetNumberOfIds(n) 3981 for i in range(n): 3982 polyline.GetPointIds().SetId(i, i) 3983 pplane.SetPolyLine(polyline) 3984 3985 clipper = vtk.vtkClipPolyData() 3986 clipper.SetInputData(self.polydata(True)) # must be True 3987 clipper.SetClipFunction(pplane) 3988 clipper.SetInsideOut(invert) 3989 clipper.GenerateClippedOutputOff() 3990 clipper.GenerateClipScalarsOff() 3991 clipper.SetValue(0) 3992 clipper.Update() 3993 cpoly = clipper.GetOutput() 3994 3995 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 3996 self._update(cpoly) 3997 else: 3998 # bring the underlying polydata to where _data is 3999 M = vtk.vtkMatrix4x4() 4000 M.DeepCopy(self.GetMatrix()) 4001 M.Invert() 4002 tr = vtk.vtkTransform() 4003 tr.SetMatrix(M) 4004 tf = vtk.vtkTransformPolyDataFilter() 4005 tf.SetTransform(tr) 4006 tf.SetInputData(cpoly) 4007 tf.Update() 4008 self._update(tf.GetOutput()) 4009 4010 self.pipeline = utils.OperationNode("cut_with_line", parents=[self]) 4011 return self 4012 4013 def cut_with_cookiecutter(self, lines): 4014 """ 4015 Cut the current mesh with a single line or a set of lines. 4016 4017 Input `lines` can be either: 4018 - a `Mesh` or `Points` object 4019 - a list of 3D points: `[(x1,y1,z1), (x2,y2,z2), ...]` 4020 - a list of 2D points: `[(x1,y1), (x2,y2), ...]` 4021 4022 Example: 4023 ```python 4024 from vedo import * 4025 grid = Mesh(dataurl + "dolfin_fine.vtk") 4026 grid.compute_quality().cmap("Greens") 4027 pols = merge( 4028 Polygon(nsides=10, r=0.3).pos(0.7, 0.3), 4029 Polygon(nsides=10, r=0.2).pos(0.3, 0.7), 4030 ) 4031 lines = pols.boundaries() 4032 grid.cut_with_cookiecutter(lines) 4033 show(grid, lines, axes=8, bg='blackboard').close() 4034 ``` 4035 ![](https://vedo.embl.es/images/feats/cookiecutter.png) 4036 4037 Check out also: 4038 `cut_with_line()` and `cut_with_point_loop()` 4039 4040 Note: 4041 In case of a warning message like: 4042 "Mesh and trim loop point data attributes are different" 4043 consider interpolating the mesh point data to the loop points, 4044 Eg. (in the above example): 4045 ```python 4046 lines = pols.boundaries().interpolate_data_from(grid, n=2) 4047 ``` 4048 4049 Note: 4050 trying to invert the selection by reversing the loop order 4051 will have no effect in this method, hence it does not have 4052 the `invert` option. 4053 """ 4054 if utils.is_sequence(lines): 4055 lines = utils.make3d(lines) 4056 iline = list(range(len(lines))) + [0] 4057 poly = utils.buildPolyData(lines, lines=[iline]) 4058 else: 4059 poly = lines.polydata() 4060 4061 # if invert: # not working 4062 # rev = vtk.vtkReverseSense() 4063 # rev.ReverseCellsOn() 4064 # rev.SetInputData(poly) 4065 # rev.Update() 4066 # poly = rev.GetOutput() 4067 4068 # Build loops from the polyline 4069 build_loops = vtk.vtkContourLoopExtraction() 4070 build_loops.SetInputData(poly) 4071 build_loops.Update() 4072 boundaryPoly = build_loops.GetOutput() 4073 4074 ccut = vtk.vtkCookieCutter() 4075 ccut.SetInputData(self.polydata()) 4076 ccut.SetLoopsData(boundaryPoly) 4077 ccut.SetPointInterpolationToMeshEdges() 4078 # ccut.SetPointInterpolationToLoopEdges() 4079 ccut.PassCellDataOn() 4080 # ccut.PassPointDataOn() 4081 ccut.Update() 4082 cpoly = ccut.GetOutput() 4083 4084 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 4085 self._update(cpoly) 4086 else: 4087 # bring the underlying polydata to where _data is 4088 M = vtk.vtkMatrix4x4() 4089 M.DeepCopy(self.GetMatrix()) 4090 M.Invert() 4091 tr = vtk.vtkTransform() 4092 tr.SetMatrix(M) 4093 tf = vtk.vtkTransformPolyDataFilter() 4094 tf.SetTransform(tr) 4095 tf.SetInputData(cpoly) 4096 tf.Update() 4097 self._update(tf.GetOutput()) 4098 4099 self.pipeline = utils.OperationNode("cut_with_cookiecutter", parents=[self]) 4100 return self 4101 4102 def cut_with_cylinder(self, center=(0, 0, 0), axis=(0, 0, 1), r=1, invert=False): 4103 """ 4104 Cut the current mesh with an infinite cylinder. 4105 This is much faster than `cut_with_mesh()`. 4106 4107 Arguments: 4108 center : (array) 4109 the center of the cylinder 4110 normal : (array) 4111 direction of the cylinder axis 4112 r : (float) 4113 radius of the cylinder 4114 4115 Example: 4116 ```python 4117 from vedo import Disc, show 4118 disc = Disc(r1=1, r2=1.2) 4119 mesh = disc.extrude(3, res=50).linewidth(1) 4120 mesh.cut_with_cylinder([0,0,2], r=0.4, axis='y', invert=True) 4121 show(mesh, axes=1) 4122 ``` 4123 ![](https://vedo.embl.es/images/feats/cut_with_cylinder.png) 4124 4125 Examples: 4126 - [optics_main1.py](https://github.com/marcomusy/vedo/blob/master/examples/simulations/optics_main1.py) 4127 4128 Check out also: 4129 `cut_with_box()`, `cut_with_plane()`, `cut_with_sphere()` 4130 """ 4131 s = str(axis) 4132 if "x" in s: 4133 axis = (1, 0, 0) 4134 elif "y" in s: 4135 axis = (0, 1, 0) 4136 elif "z" in s: 4137 axis = (0, 0, 1) 4138 cyl = vtk.vtkCylinder() 4139 cyl.SetCenter(center) 4140 cyl.SetAxis(axis[0], axis[1], axis[2]) 4141 cyl.SetRadius(r) 4142 4143 clipper = vtk.vtkClipPolyData() 4144 clipper.SetInputData(self.polydata(True)) # must be True 4145 clipper.SetClipFunction(cyl) 4146 clipper.SetInsideOut(not invert) 4147 clipper.GenerateClippedOutputOff() 4148 clipper.GenerateClipScalarsOff() 4149 clipper.SetValue(0) 4150 clipper.Update() 4151 cpoly = clipper.GetOutput() 4152 4153 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 4154 self._update(cpoly) 4155 else: 4156 # bring the underlying polydata to where _data is 4157 M = vtk.vtkMatrix4x4() 4158 M.DeepCopy(self.GetMatrix()) 4159 M.Invert() 4160 tr = vtk.vtkTransform() 4161 tr.SetMatrix(M) 4162 tf = vtk.vtkTransformPolyDataFilter() 4163 tf.SetTransform(tr) 4164 tf.SetInputData(cpoly) 4165 tf.Update() 4166 self._update(tf.GetOutput()) 4167 4168 self.pipeline = utils.OperationNode("cut_with_cylinder", parents=[self]) 4169 return self 4170 4171 def cut_with_sphere(self, center=(0, 0, 0), r=1.0, invert=False): 4172 """ 4173 Cut the current mesh with an sphere. 4174 This is much faster than `cut_with_mesh()`. 4175 4176 Arguments: 4177 center : (array) 4178 the center of the sphere 4179 r : (float) 4180 radius of the sphere 4181 4182 Example: 4183 ```python 4184 from vedo import Disc, show 4185 disc = Disc(r1=1, r2=1.2) 4186 mesh = disc.extrude(3, res=50).linewidth(1) 4187 mesh.cut_with_sphere([1,-0.7,2], r=1.5, invert=True) 4188 show(mesh, axes=1) 4189 ``` 4190 ![](https://vedo.embl.es/images/feats/cut_with_sphere.png) 4191 4192 Check out also: 4193 `cut_with_box()`, `cut_with_plane()`, `cut_with_cylinder()` 4194 """ 4195 sph = vtk.vtkSphere() 4196 sph.SetCenter(center) 4197 sph.SetRadius(r) 4198 4199 clipper = vtk.vtkClipPolyData() 4200 clipper.SetInputData(self.polydata(True)) # must be True 4201 clipper.SetClipFunction(sph) 4202 clipper.SetInsideOut(not invert) 4203 clipper.GenerateClippedOutputOff() 4204 clipper.GenerateClipScalarsOff() 4205 clipper.SetValue(0) 4206 clipper.Update() 4207 cpoly = clipper.GetOutput() 4208 4209 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 4210 self._update(cpoly) 4211 else: 4212 # bring the underlying polydata to where _data is 4213 M = vtk.vtkMatrix4x4() 4214 M.DeepCopy(self.GetMatrix()) 4215 M.Invert() 4216 tr = vtk.vtkTransform() 4217 tr.SetMatrix(M) 4218 tf = vtk.vtkTransformPolyDataFilter() 4219 tf.SetTransform(tr) 4220 tf.SetInputData(cpoly) 4221 tf.Update() 4222 self._update(tf.GetOutput()) 4223 4224 self.pipeline = utils.OperationNode("cut_with_sphere", parents=[self]) 4225 return self 4226 4227 def cut_with_mesh(self, mesh, invert=False, keep=False): 4228 """ 4229 Cut an `Mesh` mesh with another `Mesh`. 4230 4231 Use `invert` to invert the selection. 4232 4233 Use `keep` to keep the cutoff part, in this case an `Assembly` is returned: 4234 the "cut" object and the "discarded" part of the original object. 4235 You can access both via `assembly.unpack()` method. 4236 4237 Example: 4238 ```python 4239 from vedo import * 4240 arr = np.random.randn(100000, 3)/2 4241 pts = Points(arr).c('red3').pos(5,0,0) 4242 cube = Cube().pos(4,0.5,0) 4243 assem = pts.cut_with_mesh(cube, keep=True) 4244 show(assem.unpack(), axes=1).close() 4245 ``` 4246 ![](https://vedo.embl.es/images/feats/cut_with_mesh.png) 4247 4248 Check out also: 4249 `cut_with_box()`, `cut_with_plane()`, `cut_with_cylinder()` 4250 """ 4251 polymesh = mesh.polydata() 4252 poly = self.polydata() 4253 4254 # Create an array to hold distance information 4255 signed_distances = vtk.vtkFloatArray() 4256 signed_distances.SetNumberOfComponents(1) 4257 signed_distances.SetName("SignedDistances") 4258 4259 # implicit function that will be used to slice the mesh 4260 ippd = vtk.vtkImplicitPolyDataDistance() 4261 ippd.SetInput(polymesh) 4262 4263 # Evaluate the signed distance function at all of the grid points 4264 for pointId in range(poly.GetNumberOfPoints()): 4265 p = poly.GetPoint(pointId) 4266 signed_distance = ippd.EvaluateFunction(p) 4267 signed_distances.InsertNextValue(signed_distance) 4268 4269 currentscals = poly.GetPointData().GetScalars() 4270 if currentscals: 4271 currentscals = currentscals.GetName() 4272 4273 poly.GetPointData().AddArray(signed_distances) 4274 poly.GetPointData().SetActiveScalars("SignedDistances") 4275 4276 clipper = vtk.vtkClipPolyData() 4277 clipper.SetInputData(poly) 4278 clipper.SetInsideOut(not invert) 4279 clipper.SetGenerateClippedOutput(keep) 4280 clipper.SetValue(0.0) 4281 clipper.Update() 4282 cpoly = clipper.GetOutput() 4283 if keep: 4284 kpoly = clipper.GetOutput(1) 4285 4286 vis = False 4287 if currentscals: 4288 cpoly.GetPointData().SetActiveScalars(currentscals) 4289 vis = self.mapper().GetScalarVisibility() 4290 4291 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 4292 self._update(cpoly) 4293 else: 4294 # bring the underlying polydata to where _data is 4295 M = vtk.vtkMatrix4x4() 4296 M.DeepCopy(self.GetMatrix()) 4297 M.Invert() 4298 tr = vtk.vtkTransform() 4299 tr.SetMatrix(M) 4300 tf = vtk.vtkTransformPolyDataFilter() 4301 tf.SetTransform(tr) 4302 tf.SetInputData(cpoly) 4303 tf.Update() 4304 self._update(tf.GetOutput()) 4305 4306 self.pointdata.remove("SignedDistances") 4307 self.mapper().SetScalarVisibility(vis) 4308 if keep: 4309 if isinstance(self, vedo.Mesh): 4310 cutoff = vedo.Mesh(kpoly) 4311 else: 4312 cutoff = vedo.Points(kpoly) 4313 cutoff.property = vtk.vtkProperty() 4314 cutoff.property.DeepCopy(self.property) 4315 cutoff.SetProperty(cutoff.property) 4316 cutoff.c("k5").alpha(0.2) 4317 return vedo.Assembly([self, cutoff]) 4318 4319 self.pipeline = utils.OperationNode("cut_with_mesh", parents=[self, mesh]) 4320 return self 4321 4322 def cut_with_point_loop(self, points, invert=False, on="points", include_boundary=False): 4323 """ 4324 Cut an `Mesh` object with a set of points forming a closed loop. 4325 4326 Arguments: 4327 invert : (bool) 4328 invert selection (inside-out) 4329 on : (str) 4330 if 'cells' will extract the whole cells lying inside (or outside) the point loop 4331 include_boundary : (bool) 4332 include cells lying exactly on the boundary line. Only relevant on 'cells' mode 4333 4334 Examples: 4335 - [cut_with_points1.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/cut_with_points1.py) 4336 4337 ![](https://vedo.embl.es/images/advanced/cutWithPoints1.png) 4338 4339 - [cut_with_points2.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/cut_with_points2.py) 4340 4341 ![](https://vedo.embl.es/images/advanced/cutWithPoints2.png) 4342 """ 4343 if isinstance(points, Points): 4344 parents = [points] 4345 vpts = points.polydata().GetPoints() 4346 points = points.points() 4347 else: 4348 parents = [self] 4349 vpts = vtk.vtkPoints() 4350 points = utils.make3d(points) 4351 for p in points: 4352 vpts.InsertNextPoint(p) 4353 4354 if "cell" in on: 4355 ippd = vtk.vtkImplicitSelectionLoop() 4356 ippd.SetLoop(vpts) 4357 ippd.AutomaticNormalGenerationOn() 4358 clipper = vtk.vtkExtractPolyDataGeometry() 4359 clipper.SetInputData(self.polydata()) 4360 clipper.SetImplicitFunction(ippd) 4361 clipper.SetExtractInside(not invert) 4362 clipper.SetExtractBoundaryCells(include_boundary) 4363 else: 4364 spol = vtk.vtkSelectPolyData() 4365 spol.SetLoop(vpts) 4366 spol.GenerateSelectionScalarsOn() 4367 spol.GenerateUnselectedOutputOff() 4368 spol.SetInputData(self.polydata()) 4369 spol.Update() 4370 clipper = vtk.vtkClipPolyData() 4371 clipper.SetInputData(spol.GetOutput()) 4372 clipper.SetInsideOut(not invert) 4373 clipper.SetValue(0.0) 4374 clipper.Update() 4375 cpoly = clipper.GetOutput() 4376 4377 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 4378 self._update(cpoly) 4379 else: 4380 # bring the underlying polydata to where _data is 4381 M = vtk.vtkMatrix4x4() 4382 M.DeepCopy(self.GetMatrix()) 4383 M.Invert() 4384 tr = vtk.vtkTransform() 4385 tr.SetMatrix(M) 4386 tf = vtk.vtkTransformPolyDataFilter() 4387 tf.SetTransform(tr) 4388 tf.SetInputData(clipper.GetOutput()) 4389 tf.Update() 4390 self._update(tf.GetOutput()) 4391 4392 self.pipeline = utils.OperationNode("cut_with_pointloop", parents=parents) 4393 return self 4394 4395 def cut_with_scalar(self, value, name="", invert=False): 4396 """ 4397 Cut a mesh or point cloud with some input scalar point-data. 4398 4399 Arguments: 4400 value : (float) 4401 cutting value 4402 name : (str) 4403 array name of the scalars to be used 4404 invert : (bool) 4405 flip selection 4406 4407 Example: 4408 ```python 4409 from vedo import * 4410 s = Sphere().lw(1) 4411 pts = s.points() 4412 scalars = np.sin(3*pts[:,2]) + pts[:,0] 4413 s.pointdata["somevalues"] = scalars 4414 s.cut_with_scalar(0.3) 4415 s.cmap("Spectral", "somevalues").add_scalarbar() 4416 s.show(axes=1).close() 4417 ``` 4418 ![](https://vedo.embl.es/images/feats/cut_with_scalars.png) 4419 """ 4420 if name: 4421 self.pointdata.select(name) 4422 clipper = vtk.vtkClipPolyData() 4423 clipper.SetInputData(self._data) 4424 clipper.SetValue(value) 4425 clipper.GenerateClippedOutputOff() 4426 clipper.SetInsideOut(not invert) 4427 clipper.Update() 4428 self._update(clipper.GetOutput()) 4429 4430 self.pipeline = utils.OperationNode("cut_with_scalars", parents=[self]) 4431 return self 4432 4433 def implicit_modeller(self, distance=0.05, res=(50, 50, 50), bounds=(), maxdist=None): 4434 """Find the surface which sits at the specified distance from the input one.""" 4435 if not bounds: 4436 bounds = self.bounds() 4437 4438 if not maxdist: 4439 maxdist = self.diagonal_size() / 2 4440 4441 imp = vtk.vtkImplicitModeller() 4442 imp.SetInputData(self.polydata()) 4443 imp.SetSampleDimensions(res) 4444 imp.SetMaximumDistance(maxdist) 4445 imp.SetModelBounds(bounds) 4446 contour = vtk.vtkContourFilter() 4447 contour.SetInputConnection(imp.GetOutputPort()) 4448 contour.SetValue(0, distance) 4449 contour.Update() 4450 poly = contour.GetOutput() 4451 out = vedo.Mesh(poly, c="lb") 4452 4453 out.pipeline = utils.OperationNode("implicit_modeller", parents=[self]) 4454 return out 4455 4456 4457 def generate_mesh( 4458 self, 4459 line_resolution=None, 4460 mesh_resolution=None, 4461 smooth=0.0, 4462 jitter=0.001, 4463 grid=None, 4464 quads=False, 4465 invert=False, 4466 ): 4467 """ 4468 Generate a polygonal Mesh from a closed contour line. 4469 If line is not closed it will be closed with a straight segment. 4470 4471 Arguments: 4472 line_resolution : (int) 4473 resolution of the contour line. The default is None, in this case 4474 the contour is not resampled. 4475 mesh_resolution : (int) 4476 resolution of the internal triangles not touching the boundary. 4477 smooth : (float) 4478 smoothing of the contour before meshing. 4479 jitter : (float) 4480 add a small noise to the internal points. 4481 grid : (Grid) 4482 manually pass a Grid object. The default is True. 4483 quads : (bool) 4484 generate a mesh of quads instead of triangles. 4485 invert : (bool) 4486 flip the line orientation. The default is False. 4487 4488 Examples: 4489 - [line2mesh_tri.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/line2mesh_tri.py) 4490 4491 ![](https://vedo.embl.es/images/advanced/line2mesh_tri.jpg) 4492 4493 - [line2mesh_quads.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/line2mesh_quads.py) 4494 4495 ![](https://vedo.embl.es/images/advanced/line2mesh_quads.png) 4496 """ 4497 if line_resolution is None: 4498 contour = vedo.shapes.Line(self.points()) 4499 else: 4500 contour = vedo.shapes.Spline(self.points(), smooth=smooth, res=line_resolution) 4501 contour.clean() 4502 4503 length = contour.length() 4504 density = length / contour.npoints 4505 vedo.logger.debug(f"tomesh():\n\tline length = {length}") 4506 vedo.logger.debug(f"\tdensity = {density} length/pt_separation") 4507 4508 x0, x1 = contour.xbounds() 4509 y0, y1 = contour.ybounds() 4510 4511 if grid is None: 4512 if mesh_resolution is None: 4513 resx = int((x1 - x0) / density + 0.5) 4514 resy = int((y1 - y0) / density + 0.5) 4515 vedo.logger.debug(f"tmesh_resolution = {[resx, resy]}") 4516 else: 4517 if utils.is_sequence(mesh_resolution): 4518 resx, resy = mesh_resolution 4519 else: 4520 resx, resy = mesh_resolution, mesh_resolution 4521 grid = vedo.shapes.Grid( 4522 [(x0 + x1) / 2, (y0 + y1) / 2, 0], 4523 s=((x1 - x0) * 1.025, (y1 - y0) * 1.025), 4524 res=(resx, resy), 4525 ) 4526 else: 4527 grid = grid.clone() 4528 4529 cpts = contour.points() 4530 4531 # make sure it's closed 4532 p0, p1 = cpts[0], cpts[-1] 4533 nj = max(2, int(utils.mag(p1 - p0) / density + 0.5)) 4534 joinline = vedo.shapes.Line(p1, p0, res=nj) 4535 contour = vedo.merge(contour, joinline).subsample(0.0001) 4536 4537 ####################################### quads 4538 if quads: 4539 cmesh = grid.clone().cut_with_point_loop(contour, on="cells", invert=invert) 4540 cmesh.wireframe(False).lw(0.5) 4541 cmesh.pipeline = utils.OperationNode( 4542 "generate_mesh", 4543 parents=[self, contour], 4544 comment=f"#quads {cmesh.inputdata().GetNumberOfCells()}", 4545 ) 4546 return cmesh 4547 ############################################# 4548 4549 grid_tmp = grid.points() 4550 4551 if jitter: 4552 np.random.seed(0) 4553 sigma = 1.0 / np.sqrt(grid.npoints) * grid.diagonal_size() * jitter 4554 vedo.logger.debug(f"\tsigma jittering = {sigma}") 4555 grid_tmp += np.random.rand(grid.npoints, 3) * sigma 4556 grid_tmp[:, 2] = 0.0 4557 4558 todel = [] 4559 density /= np.sqrt(3) 4560 vgrid_tmp = Points(grid_tmp) 4561 4562 for p in contour.points(): 4563 out = vgrid_tmp.closest_point(p, radius=density, return_point_id=True) 4564 todel += out.tolist() 4565 # cpoints = contour.points() 4566 # for i, p in enumerate(cpoints): 4567 # if i: 4568 # den = utils.mag(p-cpoints[i-1])/1.732 4569 # else: 4570 # den = density 4571 # todel += vgrid_tmp.closest_point(p, radius=den, return_point_id=True) 4572 4573 grid_tmp = grid_tmp.tolist() 4574 for index in sorted(list(set(todel)), reverse=True): 4575 del grid_tmp[index] 4576 4577 points = contour.points().tolist() + grid_tmp 4578 if invert: 4579 boundary = reversed(range(contour.npoints)) 4580 else: 4581 boundary = range(contour.npoints) 4582 4583 dln = delaunay2d(points, mode="xy", boundaries=[boundary]) 4584 dln.compute_normals(points=False) # fixes reversd faces 4585 dln.lw(0.5) 4586 4587 dln.pipeline = utils.OperationNode( 4588 "generate_mesh", 4589 parents=[self, contour], 4590 comment=f"#cells {dln.inputdata().GetNumberOfCells()}", 4591 ) 4592 return dln 4593 4594 def reconstruct_surface( 4595 self, 4596 dims=(100, 100, 100), 4597 radius=None, 4598 sample_size=None, 4599 hole_filling=True, 4600 bounds=(), 4601 padding=0.05, 4602 ): 4603 """ 4604 Surface reconstruction from a scattered cloud of points. 4605 4606 Arguments: 4607 dims : (int) 4608 number of voxels in x, y and z to control precision. 4609 radius : (float) 4610 radius of influence of each point. 4611 Smaller values generally improve performance markedly. 4612 Note that after the signed distance function is computed, 4613 any voxel taking on the value >= radius 4614 is presumed to be "unseen" or uninitialized. 4615 sample_size : (int) 4616 if normals are not present 4617 they will be calculated using this sample size per point. 4618 hole_filling : (bool) 4619 enables hole filling, this generates 4620 separating surfaces between the empty and unseen portions of the volume. 4621 bounds : (list) 4622 region in space in which to perform the sampling 4623 in format (xmin,xmax, ymin,ymax, zim, zmax) 4624 padding : (float) 4625 increase by this fraction the bounding box 4626 4627 Examples: 4628 - [recosurface.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/recosurface.py) 4629 4630 ![](https://vedo.embl.es/images/advanced/recosurface.png) 4631 """ 4632 if not utils.is_sequence(dims): 4633 dims = (dims, dims, dims) 4634 4635 sdf = vtk.vtkSignedDistance() 4636 4637 if len(bounds) == 6: 4638 sdf.SetBounds(bounds) 4639 else: 4640 x0, x1, y0, y1, z0, z1 = self.bounds() 4641 sdf.SetBounds( 4642 x0 - (x1 - x0) * padding, 4643 x1 + (x1 - x0) * padding, 4644 y0 - (y1 - y0) * padding, 4645 y1 + (y1 - y0) * padding, 4646 z0 - (z1 - z0) * padding, 4647 z1 + (z1 - z0) * padding, 4648 ) 4649 4650 pd = self.polydata() 4651 4652 if pd.GetPointData().GetNormals(): 4653 sdf.SetInputData(pd) 4654 else: 4655 normals = vtk.vtkPCANormalEstimation() 4656 normals.SetInputData(pd) 4657 if not sample_size: 4658 sample_size = int(pd.GetNumberOfPoints() / 50) 4659 normals.SetSampleSize(sample_size) 4660 normals.SetNormalOrientationToGraphTraversal() 4661 sdf.SetInputConnection(normals.GetOutputPort()) 4662 # print("Recalculating normals with sample size =", sample_size) 4663 4664 if radius is None: 4665 radius = self.diagonal_size() / (sum(dims) / 3) * 5 4666 # print("Calculating mesh from points with radius =", radius) 4667 4668 sdf.SetRadius(radius) 4669 sdf.SetDimensions(dims) 4670 sdf.Update() 4671 4672 surface = vtk.vtkExtractSurface() 4673 surface.SetRadius(radius * 0.99) 4674 surface.SetHoleFilling(hole_filling) 4675 surface.ComputeNormalsOff() 4676 surface.ComputeGradientsOff() 4677 surface.SetInputConnection(sdf.GetOutputPort()) 4678 surface.Update() 4679 m = vedo.mesh.Mesh(surface.GetOutput(), c=self.color()) 4680 4681 m.pipeline = utils.OperationNode( 4682 "reconstruct_surface", parents=[self], 4683 comment=f"#pts {m.inputdata().GetNumberOfPoints()}" 4684 ) 4685 return m 4686 4687 def compute_clustering(self, radius): 4688 """ 4689 Cluster points in space. The `radius` is the radius of local search. 4690 An array named "ClusterId" is added to the vertex points. 4691 4692 Examples: 4693 - [clustering.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/clustering.py) 4694 4695 ![](https://vedo.embl.es/images/basic/clustering.png) 4696 """ 4697 cluster = vtk.vtkEuclideanClusterExtraction() 4698 cluster.SetInputData(self.inputdata()) 4699 cluster.SetExtractionModeToAllClusters() 4700 cluster.SetRadius(radius) 4701 cluster.ColorClustersOn() 4702 cluster.Update() 4703 idsarr = cluster.GetOutput().GetPointData().GetArray("ClusterId") 4704 self.inputdata().GetPointData().AddArray(idsarr) 4705 4706 self.pipeline = utils.OperationNode( 4707 "compute_clustering", parents=[self], comment=f"radius = {radius}" 4708 ) 4709 return self 4710 4711 def compute_connections(self, radius, mode=0, regions=(), vrange=(0, 1), seeds=(), angle=0): 4712 """ 4713 Extracts and/or segments points from a point cloud based on geometric distance measures 4714 (e.g., proximity, normal alignments, etc.) and optional measures such as scalar range. 4715 The default operation is to segment the points into "connected" regions where the connection 4716 is determined by an appropriate distance measure. Each region is given a region id. 4717 4718 Optionally, the filter can output the largest connected region of points; a particular region 4719 (via id specification); those regions that are seeded using a list of input point ids; 4720 or the region of points closest to a specified position. 4721 4722 The key parameter of this filter is the radius defining a sphere around each point which defines 4723 a local neighborhood: any other points in the local neighborhood are assumed connected to the point. 4724 Note that the radius is defined in absolute terms. 4725 4726 Other parameters are used to further qualify what it means to be a neighboring point. 4727 For example, scalar range and/or point normals can be used to further constrain the neighborhood. 4728 Also the extraction mode defines how the filter operates. 4729 By default, all regions are extracted but it is possible to extract particular regions; 4730 the region closest to a seed point; seeded regions; or the largest region found while processing. 4731 By default, all regions are extracted. 4732 4733 On output, all points are labeled with a region number. 4734 However note that the number of input and output points may not be the same: 4735 if not extracting all regions then the output size may be less than the input size. 4736 4737 Arguments: 4738 radius : (float) 4739 variable specifying a local sphere used to define local point neighborhood 4740 mode : (int) 4741 - 0, Extract all regions 4742 - 1, Extract point seeded regions 4743 - 2, Extract largest region 4744 - 3, Test specified regions 4745 - 4, Extract all regions with scalar connectivity 4746 - 5, Extract point seeded regions 4747 regions : (list) 4748 a list of non-negative regions id to extract 4749 vrange : (list) 4750 scalar range to use to extract points based on scalar connectivity 4751 seeds : (list) 4752 a list of non-negative point seed ids 4753 angle : (list) 4754 points are connected if the angle between their normals is 4755 within this angle threshold (expressed in degrees). 4756 """ 4757 # https://vtk.org/doc/nightly/html/classvtkConnectedPointsFilter.html 4758 cpf = vtk.vtkConnectedPointsFilter() 4759 cpf.SetInputData(self.polydata()) 4760 cpf.SetRadius(radius) 4761 if mode == 0: # Extract all regions 4762 pass 4763 4764 elif mode == 1: # Extract point seeded regions 4765 cpf.SetExtractionModeToPointSeededRegions() 4766 for s in seeds: 4767 cpf.AddSeed(s) 4768 4769 elif mode == 2: # Test largest region 4770 cpf.SetExtractionModeToLargestRegion() 4771 4772 elif mode == 3: # Test specified regions 4773 cpf.SetExtractionModeToSpecifiedRegions() 4774 for r in regions: 4775 cpf.AddSpecifiedRegion(r) 4776 4777 elif mode == 4: # Extract all regions with scalar connectivity 4778 cpf.SetExtractionModeToLargestRegion() 4779 cpf.ScalarConnectivityOn() 4780 cpf.SetScalarRange(vrange[0], vrange[1]) 4781 4782 elif mode == 5: # Extract point seeded regions 4783 cpf.SetExtractionModeToLargestRegion() 4784 cpf.ScalarConnectivityOn() 4785 cpf.SetScalarRange(vrange[0], vrange[1]) 4786 cpf.AlignedNormalsOn() 4787 cpf.SetNormalAngle(angle) 4788 4789 cpf.Update() 4790 return self._update(cpf.GetOutput()) 4791 4792 def compute_camera_distance(self): 4793 """ 4794 Calculate the distance from points to the camera. 4795 A pointdata array is created with name 'DistanceToCamera'. 4796 """ 4797 if vedo.plotter_instance.renderer: 4798 poly = self.polydata() 4799 dc = vtk.vtkDistanceToCamera() 4800 dc.SetInputData(poly) 4801 dc.SetRenderer(vedo.plotter_instance.renderer) 4802 dc.Update() 4803 return self._update(dc.GetOutput()) 4804 return self 4805 4806 def density( 4807 self, dims=(40, 40, 40), bounds=None, radius=None, compute_gradient=False, locator=None 4808 ): 4809 """ 4810 Generate a density field from a point cloud. Input can also be a set of 3D coordinates. 4811 Output is a `Volume`. 4812 4813 The local neighborhood is specified as the `radius` around each sample position (each voxel). 4814 The density is expressed as the number of counts in the radius search. 4815 4816 Arguments: 4817 dims : (int,list) 4818 number of voxels in x, y and z of the output Volume. 4819 compute_gradient : (bool) 4820 Turn on/off the generation of the gradient vector, 4821 gradient magnitude scalar, and function classification scalar. 4822 By default this is off. Note that this will increase execution time 4823 and the size of the output. (The names of these point data arrays are: 4824 "Gradient", "Gradient Magnitude", and "Classification") 4825 locator : (vtkPointLocator) 4826 can be assigned from a previous call for speed (access it via `object.point_locator`). 4827 4828 Examples: 4829 - [plot_density3d.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/plot_density3d.py) 4830 4831 ![](https://vedo.embl.es/images/pyplot/plot_density3d.png) 4832 """ 4833 pdf = vtk.vtkPointDensityFilter() 4834 pdf.SetInputData(self.polydata()) 4835 4836 if not utils.is_sequence(dims): 4837 dims = [dims, dims, dims] 4838 4839 if bounds is None: 4840 bounds = list(self.bounds()) 4841 elif len(bounds) == 4: 4842 bounds = [*bounds, 0, 0] 4843 4844 if bounds[5] - bounds[4] == 0 or len(dims) == 2: # its 2D 4845 dims = list(dims) 4846 dims = [dims[0], dims[1], 2] 4847 diag = self.diagonal_size() 4848 bounds[5] = bounds[4] + diag / 1000 4849 pdf.SetModelBounds(bounds) 4850 4851 pdf.SetSampleDimensions(dims) 4852 4853 if locator: 4854 pdf.SetLocator(locator) 4855 4856 pdf.SetDensityEstimateToFixedRadius() 4857 if radius is None: 4858 radius = self.diagonal_size() / 20 4859 pdf.SetRadius(radius) 4860 4861 pdf.SetComputeGradient(compute_gradient) 4862 pdf.Update() 4863 img = pdf.GetOutput() 4864 vol = vedo.volume.Volume(img).mode(1) 4865 vol.name = "PointDensity" 4866 vol.info["radius"] = radius 4867 vol.locator = pdf.GetLocator() 4868 4869 vol.pipeline = utils.OperationNode( 4870 "density", parents=[self], comment=f"dims = {tuple(vol.dimensions())}" 4871 ) 4872 return vol 4873 4874 def densify(self, target_distance=0.1, nclosest=6, radius=None, niter=1, nmax=None): 4875 """ 4876 Return a copy of the cloud with new added points. 4877 The new points are created in such a way that all points in any local neighborhood are 4878 within a target distance of one another. 4879 4880 For each input point, the distance to all points in its neighborhood is computed. 4881 If any of its neighbors is further than the target distance, 4882 the edge connecting the point and its neighbor is bisected and 4883 a new point is inserted at the bisection point. 4884 A single pass is completed once all the input points are visited. 4885 Then the process repeats to the number of iterations. 4886 4887 Examples: 4888 - [densifycloud.py](https://github.com/marcomusy/vedo/blob/master/examples/volumetric/densifycloud.py) 4889 4890 ![](https://vedo.embl.es/images/volumetric/densifycloud.png) 4891 4892 .. note:: 4893 Points will be created in an iterative fashion until all points in their 4894 local neighborhood are the target distance apart or less. 4895 Note that the process may terminate early due to the 4896 number of iterations. By default the target distance is set to 0.5. 4897 Note that the target_distance should be less than the radius 4898 or nothing will change on output. 4899 4900 .. warning:: 4901 This class can generate a lot of points very quickly. 4902 The maximum number of iterations is by default set to =1.0 for this reason. 4903 Increase the number of iterations very carefully. 4904 Also, `nmax` can be set to limit the explosion of points. 4905 It is also recommended that a N closest neighborhood is used. 4906 4907 """ 4908 src = vtk.vtkProgrammableSource() 4909 opts = self.points() 4910 4911 def _readPoints(): 4912 output = src.GetPolyDataOutput() 4913 points = vtk.vtkPoints() 4914 for p in opts: 4915 points.InsertNextPoint(p) 4916 output.SetPoints(points) 4917 4918 src.SetExecuteMethod(_readPoints) 4919 4920 dens = vtk.vtkDensifyPointCloudFilter() 4921 dens.SetInputConnection(src.GetOutputPort()) 4922 dens.InterpolateAttributeDataOn() 4923 dens.SetTargetDistance(target_distance) 4924 dens.SetMaximumNumberOfIterations(niter) 4925 if nmax: 4926 dens.SetMaximumNumberOfPoints(nmax) 4927 4928 if radius: 4929 dens.SetNeighborhoodTypeToRadius() 4930 dens.SetRadius(radius) 4931 elif nclosest: 4932 dens.SetNeighborhoodTypeToNClosest() 4933 dens.SetNumberOfClosestPoints(nclosest) 4934 else: 4935 vedo.logger.error("set either radius or nclosest") 4936 raise RuntimeError() 4937 dens.Update() 4938 pts = utils.vtk2numpy(dens.GetOutput().GetPoints().GetData()) 4939 cld = Points(pts, c=None).point_size(self.GetProperty().GetPointSize()) 4940 cld.interpolate_data_from(self, n=nclosest, radius=radius) 4941 cld.name = "densifiedCloud" 4942 4943 cld.pipeline = utils.OperationNode( 4944 "densify", parents=[self], c="#e9c46a:", 4945 comment=f"#pts {cld.inputdata().GetNumberOfPoints()}" 4946 ) 4947 return cld 4948 4949 4950 ############################################################################### 4951 ## stuff returning Volume 4952 4953 def signed_distance(self, bounds=None, dims=(20, 20, 20), invert=False, maxradius=None): 4954 """ 4955 Compute the `Volume` object whose voxels contains the signed distance from 4956 the point cloud. The point cloud must have Normals. 4957 4958 Arguments: 4959 bounds : (list, actor) 4960 bounding box sizes 4961 dims : (list) 4962 dimensions (nr. of voxels) of the output volume. 4963 invert : (bool) 4964 flip the sign 4965 maxradius : (float) 4966 specify how far out to propagate distance calculation 4967 4968 Examples: 4969 - [distance2mesh.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/distance2mesh.py) 4970 4971 ![](https://vedo.embl.es/images/basic/distance2mesh.png) 4972 """ 4973 if bounds is None: 4974 bounds = self.bounds() 4975 if maxradius is None: 4976 maxradius = self.diagonal_size() / 2 4977 dist = vtk.vtkSignedDistance() 4978 dist.SetInputData(self.polydata()) 4979 dist.SetRadius(maxradius) 4980 dist.SetBounds(bounds) 4981 dist.SetDimensions(dims) 4982 dist.Update() 4983 img = dist.GetOutput() 4984 if invert: 4985 mat = vtk.vtkImageMathematics() 4986 mat.SetInput1Data(img) 4987 mat.SetOperationToMultiplyByK() 4988 mat.SetConstantK(-1) 4989 mat.Update() 4990 img = mat.GetOutput() 4991 4992 vol = vedo.Volume(img) 4993 vol.name = "SignedDistanceVolume" 4994 4995 vol.pipeline = utils.OperationNode( 4996 "signed_distance", 4997 parents=[self], 4998 comment=f"dim = {tuple(vol.dimensions())}", 4999 c="#e9c46a:#0096c7", 5000 ) 5001 return vol 5002 5003 def tovolume( 5004 self, kernel="shepard", radius=None, n=None, bounds=None, null_value=None, dims=(25, 25, 25) 5005 ): 5006 """ 5007 Generate a `Volume` by interpolating a scalar 5008 or vector field which is only known on a scattered set of points or mesh. 5009 Available interpolation kernels are: shepard, gaussian, or linear. 5010 5011 Arguments: 5012 kernel : (str) 5013 interpolation kernel type [shepard] 5014 radius : (float) 5015 radius of the local search 5016 n : (int) 5017 number of point to use for interpolation 5018 bounds : (list) 5019 bounding box of the output Volume object 5020 dims : (list) 5021 dimensions of the output Volume object 5022 null_value : (float) 5023 value to be assigned to invalid points 5024 5025 Examples: 5026 - [interpolate_volume.py](https://github.com/marcomusy/vedo/blob/master/examples/volumetric/interpolate_volume.py) 5027 5028 ![](https://vedo.embl.es/images/volumetric/59095175-1ec5a300-8918-11e9-8bc0-fd35c8981e2b.jpg) 5029 """ 5030 if radius is None and not n: 5031 vedo.logger.error("please set either radius or n") 5032 raise RuntimeError 5033 5034 poly = self.polydata() 5035 5036 # Create a probe volume 5037 probe = vtk.vtkImageData() 5038 probe.SetDimensions(dims) 5039 if bounds is None: 5040 bounds = self.bounds() 5041 probe.SetOrigin(bounds[0], bounds[2], bounds[4]) 5042 probe.SetSpacing( 5043 (bounds[1] - bounds[0]) / dims[0], 5044 (bounds[3] - bounds[2]) / dims[1], 5045 (bounds[5] - bounds[4]) / dims[2], 5046 ) 5047 5048 if not self.point_locator: 5049 self.point_locator = vtk.vtkPointLocator() 5050 self.point_locator.SetDataSet(poly) 5051 self.point_locator.BuildLocator() 5052 5053 if kernel == "shepard": 5054 kern = vtk.vtkShepardKernel() 5055 kern.SetPowerParameter(2) 5056 elif kernel == "gaussian": 5057 kern = vtk.vtkGaussianKernel() 5058 elif kernel == "linear": 5059 kern = vtk.vtkLinearKernel() 5060 else: 5061 vedo.logger.error("Error in tovolume(), available kernels are:") 5062 vedo.logger.error(" [shepard, gaussian, linear]") 5063 raise RuntimeError() 5064 5065 if radius: 5066 kern.SetRadius(radius) 5067 5068 interpolator = vtk.vtkPointInterpolator() 5069 interpolator.SetInputData(probe) 5070 interpolator.SetSourceData(poly) 5071 interpolator.SetKernel(kern) 5072 interpolator.SetLocator(self.point_locator) 5073 5074 if n: 5075 kern.SetNumberOfPoints(n) 5076 kern.SetKernelFootprintToNClosest() 5077 else: 5078 kern.SetRadius(radius) 5079 5080 if null_value is not None: 5081 interpolator.SetNullValue(null_value) 5082 else: 5083 interpolator.SetNullPointsStrategyToClosestPoint() 5084 interpolator.Update() 5085 5086 vol = vedo.Volume(interpolator.GetOutput()) 5087 5088 vol.pipeline = utils.OperationNode( 5089 "signed_distance", 5090 parents=[self], 5091 comment=f"dim = {tuple(vol.dimensions())}", 5092 c="#e9c46a:#0096c7", 5093 ) 5094 return vol 5095 5096 def generate_random_data(self): 5097 """Fill a dataset with random attributes""" 5098 gen = vtk.vtkRandomAttributeGenerator() 5099 gen.SetInputData(self._data) 5100 gen.GenerateAllDataOn() 5101 gen.SetDataTypeToFloat() 5102 gen.GeneratePointNormalsOff() 5103 gen.GeneratePointTensorsOn() 5104 gen.GenerateCellScalarsOn() 5105 gen.Update() 5106 5107 m = self._update(gen.GetOutput()) 5108 5109 m.pipeline = utils.OperationNode("generate\nrandom data", parents=[self]) 5110 return m
716class Points(BaseActor, vtk.vtkActor): 717 """Work with pointclouds.""" 718 719 def __init__(self, inputobj=None, r=4, c=(0.2, 0.2, 0.2), alpha=1, blur=False, emissive=True): 720 """ 721 Build an object made of only vertex points for a list of 2D/3D points. 722 Both shapes (N, 3) or (3, N) are accepted as input, if N>3. 723 For very large point clouds a list of colors and alpha can be assigned to each 724 point in the form c=[(R,G,B,A), ... ] where 0<=R<256, ... 0<=A<256. 725 726 Arguments: 727 inputobj : (list, tuple) 728 r : (int) 729 Point radius in units of pixels. 730 c : (str, list) 731 Color name or rgb tuple. 732 alpha : (float) 733 Transparency in range [0,1]. 734 blur : (bool) 735 Apply a gaussian convolution filter to the points. 736 In this case the radius `r` is in absolute units of the mesh coordinates. 737 emissive : (bool) 738 Halo of point becomes emissive. 739 740 Example: 741 ```python 742 from vedo import * 743 744 def fibonacci_sphere(n): 745 s = np.linspace(0, n, num=n, endpoint=False) 746 theta = s * 2.399963229728653 747 y = 1 - s * (2/(n-1)) 748 r = np.sqrt(1 - y * y) 749 x = np.cos(theta) * r 750 z = np.sin(theta) * r 751 return [x,y,z] 752 753 Points(fibonacci_sphere(1000)).show(axes=1).close() 754 ``` 755 ![](https://vedo.embl.es/images/feats/fibonacci.png) 756 """ 757 758 vtk.vtkActor.__init__(self) 759 BaseActor.__init__(self) 760 761 self._data = None 762 763 if blur: 764 self._mapper = vtk.vtkPointGaussianMapper() 765 if emissive: 766 self._mapper.SetEmissive(bool(emissive)) 767 self._mapper.SetScaleFactor(r * 1.4142) 768 769 # https://kitware.github.io/vtk-examples/site/Python/Meshes/PointInterpolator/ 770 if alpha < 1: 771 self._mapper.SetSplatShaderCode( 772 "//VTK::Color::Impl\n" 773 "float dist = dot(offsetVCVSOutput.xy,offsetVCVSOutput.xy);\n" 774 "if (dist > 1.0) {\n" 775 " discard;\n" 776 "} else {\n" 777 f" float scale = ({alpha} - dist);\n" 778 " ambientColor *= scale;\n" 779 " diffuseColor *= scale;\n" 780 "}\n" 781 ) 782 alpha = 1 783 784 else: 785 self._mapper = vtk.vtkPolyDataMapper() 786 self.SetMapper(self._mapper) 787 788 self._bfprop = None # backface property holder 789 790 self._scals_idx = 0 # index of the active scalar changed from CLI 791 self._ligthingnr = 0 # index of the lighting mode changed from CLI 792 self._cmap_name = "" # remember the name for self._keypress 793 # self.name = "Points" # better not to give it a name here 794 795 self.property = self.GetProperty() 796 797 try: 798 if not blur: 799 self.property.RenderPointsAsSpheresOn() 800 except AttributeError: 801 pass 802 803 if inputobj is None: #################### 804 self._data = vtk.vtkPolyData() 805 return 806 ######################################## 807 808 self.property.SetRepresentationToPoints() 809 self.property.SetPointSize(r) 810 self.property.LightingOff() 811 812 if isinstance(inputobj, vedo.BaseActor): 813 inputobj = inputobj.points() # numpy 814 815 ###### 816 if isinstance(inputobj, vtk.vtkActor): 817 poly_copy = vtk.vtkPolyData() 818 pr = vtk.vtkProperty() 819 pr.DeepCopy(inputobj.GetProperty()) 820 poly_copy.DeepCopy(inputobj.GetMapper().GetInput()) 821 pr.SetRepresentationToPoints() 822 pr.SetPointSize(r) 823 self._data = poly_copy 824 self._mapper.SetInputData(poly_copy) 825 self._mapper.SetScalarVisibility(inputobj.GetMapper().GetScalarVisibility()) 826 self.SetProperty(pr) 827 self.property = pr 828 829 elif isinstance(inputobj, vtk.vtkPolyData): 830 if inputobj.GetNumberOfCells() == 0: 831 carr = vtk.vtkCellArray() 832 for i in range(inputobj.GetNumberOfPoints()): 833 carr.InsertNextCell(1) 834 carr.InsertCellPoint(i) 835 inputobj.SetVerts(carr) 836 self._data = inputobj # cache vtkPolyData and mapper for speed 837 838 elif utils.is_sequence(inputobj): # passing point coords 839 plist = inputobj 840 n = len(plist) 841 842 if n == 3: # assume plist is in the format [all_x, all_y, all_z] 843 if utils.is_sequence(plist[0]) and len(plist[0]) > 3: 844 plist = np.stack((plist[0], plist[1], plist[2]), axis=1) 845 elif n == 2: # assume plist is in the format [all_x, all_y, 0] 846 if utils.is_sequence(plist[0]) and len(plist[0]) > 3: 847 plist = np.stack((plist[0], plist[1], np.zeros(len(plist[0]))), axis=1) 848 849 # if n and len(plist[0]) == 2: # make it 3d 850 # plist = np.c_[np.array(plist), np.zeros(len(plist))] 851 plist = utils.make3d(plist) 852 853 if ( 854 utils.is_sequence(c) 855 and (len(c) > 3 or (utils.is_sequence(c[0]) and len(c[0]) == 4)) 856 ) or utils.is_sequence(alpha): 857 858 cols = c 859 860 n = len(plist) 861 if n != len(cols): 862 vedo.logger.error(f"mismatch in Points() colors array lengths {n} and {len(cols)}") 863 raise RuntimeError() 864 865 src = vtk.vtkPointSource() 866 src.SetNumberOfPoints(n) 867 src.Update() 868 869 vgf = vtk.vtkVertexGlyphFilter() 870 vgf.SetInputData(src.GetOutput()) 871 vgf.Update() 872 pd = vgf.GetOutput() 873 874 pd.GetPoints().SetData(utils.numpy2vtk(plist, dtype=np.float32)) 875 876 ucols = vtk.vtkUnsignedCharArray() 877 ucols.SetNumberOfComponents(4) 878 ucols.SetName("Points_RGBA") 879 if utils.is_sequence(alpha): 880 if len(alpha) != n: 881 vedo.logger.error(f"mismatch in Points() alpha array lengths {n} and {len(cols)}") 882 raise RuntimeError() 883 alphas = alpha 884 alpha = 1 885 else: 886 alphas = (alpha,) * n 887 888 if utils.is_sequence(cols): 889 c = None 890 if len(cols[0]) == 4: 891 for i in range(n): # FAST 892 rc, gc, bc, ac = cols[i] 893 ucols.InsertNextTuple4(rc, gc, bc, ac) 894 else: 895 for i in range(n): # SLOW 896 rc, gc, bc = colors.get_color(cols[i]) 897 ucols.InsertNextTuple4(rc * 255, gc * 255, bc * 255, alphas[i] * 255) 898 else: 899 c = cols 900 901 pd.GetPointData().AddArray(ucols) 902 pd.GetPointData().SetActiveScalars("Points_RGBA") 903 self._mapper.SetInputData(pd) 904 self._mapper.ScalarVisibilityOn() 905 self._data = pd 906 907 else: 908 909 pd = utils.buildPolyData(plist) 910 self._mapper.SetInputData(pd) 911 c = colors.get_color(c) 912 self.property.SetColor(c) 913 self.property.SetOpacity(alpha) 914 self._data = pd 915 916 ########## 917 self.pipeline = utils.OperationNode( 918 self, parents=[], comment=f"#pts {self._data.GetNumberOfPoints()}" 919 ) 920 return 921 ########## 922 923 elif isinstance(inputobj, str): 924 verts = vedo.file_io.load(inputobj) 925 self.filename = inputobj 926 self._data = verts.polydata() 927 928 else: 929 930 # try to extract the points from the VTK input data object 931 try: 932 vvpts = inputobj.GetPoints() 933 pd = vtk.vtkPolyData() 934 pd.SetPoints(vvpts) 935 for i in range(inputobj.GetPointData().GetNumberOfArrays()): 936 arr = inputobj.GetPointData().GetArray(i) 937 pd.GetPointData().AddArray(arr) 938 939 self._mapper.SetInputData(pd) 940 c = colors.get_color(c) 941 self.property.SetColor(c) 942 self.property.SetOpacity(alpha) 943 self._data = pd 944 except: 945 vedo.logger.error(f"cannot build Points from type {type(inputobj)}") 946 raise RuntimeError() 947 948 c = colors.get_color(c) 949 self.property.SetColor(c) 950 self.property.SetOpacity(alpha) 951 952 self._mapper.SetInputData(self._data) 953 954 self.pipeline = utils.OperationNode( 955 self, parents=[], comment=f"#pts {self._data.GetNumberOfPoints()}" 956 ) 957 return 958 959 def _repr_html_(self): 960 """ 961 HTML representation of the Point cloud object for Jupyter Notebooks. 962 963 Returns: 964 HTML text with the image and some properties. 965 """ 966 import io 967 import base64 968 from PIL import Image 969 970 library_name = "vedo.pointcloud.Points" 971 help_url = "https://vedo.embl.es/docs/vedo/pointcloud.html" 972 973 arr = self.thumbnail() 974 im = Image.fromarray(arr) 975 buffered = io.BytesIO() 976 im.save(buffered, format="PNG", quality=100) 977 encoded = base64.b64encode(buffered.getvalue()).decode("utf-8") 978 url = "data:image/png;base64," + encoded 979 image = f"<img src='{url}'></img>" 980 981 bounds = "<br/>".join( 982 [ 983 utils.precision(min_x, 4) + " ... " + utils.precision(max_x, 4) 984 for min_x, max_x in zip(self.bounds()[::2], self.bounds()[1::2]) 985 ] 986 ) 987 average_size = "{size:.3f}".format(size=self.average_size()) 988 989 help_text = "" 990 if self.name: 991 help_text += f"<b> {self.name}:   </b>" 992 help_text += '<b><a href="' + help_url + '" target="_blank">' + library_name + "</a></b>" 993 if self.filename: 994 dots = "" 995 if len(self.filename) > 30: 996 dots = "..." 997 help_text += f"<br/><code><i>({dots}{self.filename[-30:]})</i></code>" 998 999 pdata = "" 1000 if self._data.GetPointData().GetScalars(): 1001 if self._data.GetPointData().GetScalars().GetName(): 1002 name = self._data.GetPointData().GetScalars().GetName() 1003 pdata = "<tr><td><b> point data array </b></td><td>" + name + "</td></tr>" 1004 1005 cdata = "" 1006 if self._data.GetCellData().GetScalars(): 1007 if self._data.GetCellData().GetScalars().GetName(): 1008 name = self._data.GetCellData().GetScalars().GetName() 1009 cdata = "<tr><td><b> cell data array </b></td><td>" + name + "</td></tr>" 1010 1011 allt = [ 1012 "<table>", 1013 "<tr>", 1014 "<td>", 1015 image, 1016 "</td>", 1017 "<td style='text-align: center; vertical-align: center;'><br/>", 1018 help_text, 1019 "<table>", 1020 "<tr><td><b> bounds </b> <br/> (x/y/z) </td><td>" + str(bounds) + "</td></tr>", 1021 "<tr><td><b> center of mass </b></td><td>" 1022 + utils.precision(self.center_of_mass(), 3) 1023 + "</td></tr>", 1024 "<tr><td><b> average size </b></td><td>" + str(average_size) + "</td></tr>", 1025 "<tr><td><b> nr. points </b></td><td>" + str(self.npoints) + "</td></tr>", 1026 pdata, 1027 cdata, 1028 "</table>", 1029 "</table>", 1030 ] 1031 return "\n".join(allt) 1032 1033 1034 ################################################################################## 1035 def _update(self, polydata): 1036 # Overwrite the polygonal mesh with a new vtkPolyData 1037 self._data = polydata 1038 self.mapper().SetInputData(polydata) 1039 self.mapper().Modified() 1040 return self 1041 1042 def __add__(self, meshs): 1043 if isinstance(meshs, list): 1044 alist = [self] 1045 for l in meshs: 1046 if isinstance(l, vedo.Assembly): 1047 alist += l.unpack() 1048 else: 1049 alist += l 1050 return vedo.assembly.Assembly(alist) 1051 1052 if isinstance(meshs, vedo.Assembly): 1053 return meshs + self # use Assembly.__add__ 1054 1055 return vedo.assembly.Assembly([self, meshs]) 1056 1057 def polydata(self, transformed=True): 1058 """ 1059 Returns the `vtkPolyData` object associated to a `Mesh`. 1060 1061 .. note:: 1062 If `transformed=True` return a copy of polydata that corresponds 1063 to the current mesh position in space. 1064 """ 1065 if not self._data: 1066 self._data = self.mapper().GetInput() 1067 return self._data 1068 1069 if transformed: 1070 # if self.GetIsIdentity() or self._data.GetNumberOfPoints()==0: # commmentd out on 15th feb 2020 1071 if self._data.GetNumberOfPoints() == 0: 1072 # no need to do much 1073 return self._data 1074 1075 # otherwise make a copy that corresponds to 1076 # the actual position in space of the mesh 1077 M = self.GetMatrix() 1078 transform = vtk.vtkTransform() 1079 transform.SetMatrix(M) 1080 tp = vtk.vtkTransformPolyDataFilter() 1081 tp.SetTransform(transform) 1082 tp.SetInputData(self._data) 1083 tp.Update() 1084 return tp.GetOutput() 1085 1086 return self._data 1087 1088 1089 def clone(self, deep=True, transformed=False): 1090 """ 1091 Clone a `PointCloud` or `Mesh` object to make an exact copy of it. 1092 1093 Arguments: 1094 deep : (bool) 1095 if False only build a shallow copy of the object (faster copy). 1096 1097 transformed : (bool) 1098 if True reset the current transformation of the copy to unit. 1099 1100 Examples: 1101 - [mirror.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mirror.py) 1102 1103 ![](https://vedo.embl.es/images/basic/mirror.png) 1104 """ 1105 poly = self.polydata(transformed) 1106 poly_copy = vtk.vtkPolyData() 1107 if deep: 1108 poly_copy.DeepCopy(poly) 1109 else: 1110 poly_copy.ShallowCopy(poly) 1111 1112 if isinstance(self, vedo.Mesh): 1113 cloned = vedo.Mesh(poly_copy) 1114 else: 1115 cloned = Points(poly_copy) 1116 1117 pr = vtk.vtkProperty() 1118 pr.DeepCopy(self.GetProperty()) 1119 cloned.SetProperty(pr) 1120 cloned.property = pr 1121 1122 if self.GetBackfaceProperty(): 1123 bfpr = vtk.vtkProperty() 1124 bfpr.DeepCopy(self.GetBackfaceProperty()) 1125 cloned.SetBackfaceProperty(bfpr) 1126 1127 if not transformed: 1128 if self.transform: 1129 # already has a transform which can be non linear, so use that 1130 cloned.SetUserTransform(self.transform) 1131 else: 1132 # assign the same transformation to the copy 1133 cloned.SetOrigin(self.GetOrigin()) 1134 cloned.SetScale(self.GetScale()) 1135 cloned.SetOrientation(self.GetOrientation()) 1136 cloned.SetPosition(self.GetPosition()) 1137 1138 mp = cloned.mapper() 1139 sm = self.mapper() 1140 mp.SetScalarVisibility(sm.GetScalarVisibility()) 1141 mp.SetScalarRange(sm.GetScalarRange()) 1142 mp.SetColorMode(sm.GetColorMode()) 1143 lsr = sm.GetUseLookupTableScalarRange() 1144 mp.SetUseLookupTableScalarRange(lsr) 1145 mp.SetScalarMode(sm.GetScalarMode()) 1146 lut = sm.GetLookupTable() 1147 if lut: 1148 mp.SetLookupTable(lut) 1149 1150 if self.GetTexture(): 1151 cloned.texture(self.GetTexture()) 1152 1153 cloned.SetPickable(self.GetPickable()) 1154 1155 cloned.base = np.array(self.base) 1156 cloned.top = np.array(self.top) 1157 cloned.name = str(self.name) 1158 cloned.filename = str(self.filename) 1159 cloned.info = dict(self.info) 1160 1161 # better not to share the same locators with original obj 1162 cloned.point_locator = None 1163 cloned.cell_locator = None 1164 1165 cloned.pipeline = utils.OperationNode("clone", parents=[self], shape="diamond", c="#edede9") 1166 return cloned 1167 1168 def clone2d( 1169 self, 1170 pos=(0, 0), 1171 coordsys=4, 1172 scale=None, 1173 c=None, 1174 alpha=None, 1175 ps=2, 1176 lw=1, 1177 sendback=False, 1178 layer=0, 1179 ): 1180 """ 1181 Copy a 3D Mesh into a static 2D image. Returns a `vtkActor2D`. 1182 1183 Arguments: 1184 coordsys : (int) 1185 the coordinate system, options are 1186 - 0 = Displays 1187 - 1 = Normalized Display 1188 - 2 = Viewport (origin is the bottom-left corner of the window) 1189 - 3 = Normalized Viewport 1190 - 4 = View (origin is the center of the window) 1191 - 5 = World (anchor the 2d image to mesh) 1192 1193 ps : (int) 1194 point size in pixel units 1195 1196 lw : (int) 1197 line width in pixel units 1198 1199 sendback : (bool) 1200 put it behind any other 3D object 1201 1202 Examples: 1203 - [clone2d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/clone2d.py) 1204 1205 ![](https://vedo.embl.es/images/other/clone2d.png) 1206 """ 1207 if scale is None: 1208 msiz = self.diagonal_size() 1209 if vedo.plotter_instance and vedo.plotter_instance.window: 1210 sz = vedo.plotter_instance.window.GetSize() 1211 dsiz = utils.mag(sz) 1212 scale = dsiz / msiz / 10 1213 else: 1214 scale = 350 / msiz 1215 1216 cmsh = self.clone() 1217 poly = cmsh.pos(0, 0, 0).scale(scale).polydata() 1218 1219 mapper3d = self.mapper() 1220 cm = mapper3d.GetColorMode() 1221 lut = mapper3d.GetLookupTable() 1222 sv = mapper3d.GetScalarVisibility() 1223 use_lut = mapper3d.GetUseLookupTableScalarRange() 1224 vrange = mapper3d.GetScalarRange() 1225 sm = mapper3d.GetScalarMode() 1226 1227 mapper2d = vtk.vtkPolyDataMapper2D() 1228 mapper2d.ShallowCopy(mapper3d) 1229 mapper2d.SetInputData(poly) 1230 mapper2d.SetColorMode(cm) 1231 mapper2d.SetLookupTable(lut) 1232 mapper2d.SetScalarVisibility(sv) 1233 mapper2d.SetUseLookupTableScalarRange(use_lut) 1234 mapper2d.SetScalarRange(vrange) 1235 mapper2d.SetScalarMode(sm) 1236 1237 act2d = vtk.vtkActor2D() 1238 act2d.SetMapper(mapper2d) 1239 act2d.SetLayerNumber(layer) 1240 csys = act2d.GetPositionCoordinate() 1241 csys.SetCoordinateSystem(coordsys) 1242 act2d.SetPosition(pos) 1243 if c is not None: 1244 c = colors.get_color(c) 1245 act2d.GetProperty().SetColor(c) 1246 mapper2d.SetScalarVisibility(False) 1247 else: 1248 act2d.GetProperty().SetColor(cmsh.color()) 1249 if alpha is not None: 1250 act2d.GetProperty().SetOpacity(alpha) 1251 else: 1252 act2d.GetProperty().SetOpacity(cmsh.alpha()) 1253 act2d.GetProperty().SetPointSize(ps) 1254 act2d.GetProperty().SetLineWidth(lw) 1255 act2d.GetProperty().SetDisplayLocationToForeground() 1256 if sendback: 1257 act2d.GetProperty().SetDisplayLocationToBackground() 1258 1259 # print(csys.GetCoordinateSystemAsString()) 1260 # print(act2d.GetHeight(), act2d.GetWidth(), act2d.GetLayerNumber()) 1261 return act2d 1262 1263 def add_trail(self, offset=(0, 0, 0), n=50, c=None, alpha=1.0, lw=2): 1264 """ 1265 Add a trailing line to mesh. 1266 This new mesh is accessible through `mesh.trail`. 1267 1268 Arguments: 1269 offset : (float) 1270 set an offset vector from the object center. 1271 n : (int) 1272 number of segments 1273 lw : (float) 1274 line width of the trail 1275 1276 Examples: 1277 - [trail.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/trail.py) 1278 1279 ![](https://vedo.embl.es/images/simulations/trail.gif) 1280 1281 - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py) 1282 - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py) 1283 """ 1284 if self.trail is None: 1285 pos = self.GetPosition() 1286 self.trail_offset = np.asarray(offset) 1287 self.trail_points = [pos] * n 1288 1289 if c is None: 1290 col = self.GetProperty().GetColor() 1291 else: 1292 col = colors.get_color(c) 1293 1294 tline = vedo.shapes.Line(pos, pos, res=n, c=col, alpha=alpha, lw=lw) 1295 self.trail = tline # holds the Line 1296 return self 1297 1298 def update_trail(self): 1299 """ 1300 Update the trailing line of a moving object. 1301 """ 1302 if isinstance(self, vedo.shapes.Arrow): 1303 currentpos = self.tipPoint() # the tip of Arrow 1304 else: 1305 currentpos = np.array(self.GetPosition()) 1306 1307 self.trail_points.append(currentpos) # cycle 1308 self.trail_points.pop(0) 1309 1310 data = np.array(self.trail_points) - currentpos + self.trail_offset 1311 tpoly = self.trail.polydata(False) 1312 tpoly.GetPoints().SetData(utils.numpy2vtk(data, dtype=np.float32)) 1313 self.trail.SetPosition(currentpos) 1314 return self 1315 1316 1317 def _compute_shadow(self, plane, point, direction): 1318 shad = self.clone() 1319 shad._data.GetPointData().SetTCoords(None) # remove any texture coords 1320 shad.name = "Shadow" 1321 1322 pts = shad.points() 1323 if plane == 'x': 1324 # shad = shad.project_on_plane('x') 1325 # instead do it manually so in case of alpha<1 1326 # we dont see glitches due to coplanar points 1327 # we leave a small tolerance of 0.1% in thickness 1328 x0, x1 = self.xbounds() 1329 pts[:, 0] = (pts[:, 0] - (x0 + x1) / 2) / 1000 + self.GetOrigin()[0] 1330 shad.points(pts) 1331 shad.x(point) 1332 elif plane == 'y': 1333 x0, x1 = self.ybounds() 1334 pts[:, 1] = (pts[:, 1] - (x0 + x1) / 2) / 1000 + self.GetOrigin()[1] 1335 shad.points(pts) 1336 shad.y(point) 1337 elif plane == "z": 1338 x0, x1 = self.zbounds() 1339 pts[:, 2] = (pts[:, 2] - (x0 + x1) / 2) / 1000 + self.GetOrigin()[2] 1340 shad.points(pts) 1341 shad.z(point) 1342 else: 1343 shad = shad.project_on_plane(plane, point, direction) 1344 return shad 1345 1346 def add_shadow(self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0): 1347 """ 1348 Generate a shadow out of an `Mesh` on one of the three Cartesian planes. 1349 The output is a new `Mesh` representing the shadow. 1350 This new mesh is accessible through `mesh.shadow`. 1351 By default the shadow mesh is placed on the bottom wall of the bounding box. 1352 1353 See also `pointcloud.project_on_plane()`. 1354 1355 Arguments: 1356 plane : (str, Plane) 1357 if plane is `str`, plane can be one of `['x', 'y', 'z']`, 1358 represents x-plane, y-plane and z-plane, respectively. 1359 Otherwise, plane should be an instance of `vedo.shapes.Plane` 1360 point : (float, array) 1361 if plane is `str`, point should be a float represents the intercept. 1362 Otherwise, point is the camera point of perspective projection 1363 direction : (list) 1364 direction of oblique projection 1365 culling : (int) 1366 choose between front [1] or backface [-1] culling or None. 1367 1368 Examples: 1369 - [shadow1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shadow1.py) 1370 - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py) 1371 - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py) 1372 1373 ![](https://vedo.embl.es/images/simulations/57341963-b8910900-713c-11e9-898a-84b6d3712bce.gif) 1374 """ 1375 shad = self._compute_shadow(plane, point, direction) 1376 shad.c(c).alpha(alpha) 1377 1378 try: 1379 # Points dont have these methods 1380 shad.flat() 1381 if culling in (1, True): 1382 shad.frontface_culling() 1383 elif culling == -1: 1384 shad.backface_culling() 1385 except AttributeError: 1386 pass 1387 1388 shad.GetProperty().LightingOff() 1389 shad.SetPickable(False) 1390 shad.SetUseBounds(True) 1391 1392 if shad not in self.shadows: 1393 self.shadows.append(shad) 1394 shad.info = dict(plane=plane, point=point, direction=direction) 1395 return self 1396 1397 def update_shadows(self): 1398 """ 1399 Update the shadows of a moving object. 1400 """ 1401 for sha in self.shadows: 1402 plane = sha.info['plane'] 1403 point = sha.info['point'] 1404 direction = sha.info['direction'] 1405 new_sha = self._compute_shadow(plane, point, direction) 1406 sha._update(new_sha._data) 1407 return self 1408 1409 1410 def delete_cells_by_point_index(self, indices): 1411 """ 1412 Delete a list of vertices identified by any of their vertex index. 1413 1414 See also `delete_cells()`. 1415 1416 Examples: 1417 - [elete_mesh_pts.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/elete_mesh_pts.py) 1418 1419 ![](https://vedo.embl.es/images/basic/deleteMeshPoints.png) 1420 """ 1421 cell_ids = vtk.vtkIdList() 1422 data = self.inputdata() 1423 data.BuildLinks() 1424 n = 0 1425 for i in np.unique(indices): 1426 data.GetPointCells(i, cell_ids) 1427 for j in range(cell_ids.GetNumberOfIds()): 1428 data.DeleteCell(cell_ids.GetId(j)) # flag cell 1429 n += 1 1430 1431 data.RemoveDeletedCells() 1432 self.mapper().Modified() 1433 self.pipeline = utils.OperationNode(f"delete {n} cells\nby point index", parents=[self]) 1434 return self 1435 1436 def compute_normals_with_pca(self, n=20, orientation_point=None, invert=False): 1437 """ 1438 Generate point normals using PCA (principal component analysis). 1439 Basically this estimates a local tangent plane around each sample point p 1440 by considering a small neighborhood of points around p, and fitting a plane 1441 to the neighborhood (via PCA). 1442 1443 Arguments: 1444 n : (int) 1445 neighborhood size to calculate the normal 1446 orientation_point : (list) 1447 adjust the +/- sign of the normals so that 1448 the normals all point towards a specified point. If None, perform a traversal 1449 of the point cloud and flip neighboring normals so that they are mutually consistent. 1450 invert : (bool) 1451 flip all normals 1452 """ 1453 poly = self.polydata() 1454 pcan = vtk.vtkPCANormalEstimation() 1455 pcan.SetInputData(poly) 1456 pcan.SetSampleSize(n) 1457 1458 if orientation_point is not None: 1459 pcan.SetNormalOrientationToPoint() 1460 pcan.SetOrientationPoint(orientation_point) 1461 else: 1462 pcan.SetNormalOrientationToGraphTraversal() 1463 1464 if invert: 1465 pcan.FlipNormalsOn() 1466 pcan.Update() 1467 1468 varr = pcan.GetOutput().GetPointData().GetNormals() 1469 varr.SetName("Normals") 1470 self.inputdata().GetPointData().SetNormals(varr) 1471 self.inputdata().GetPointData().Modified() 1472 return self 1473 1474 def compute_acoplanarity(self, n=25, radius=None, on="points"): 1475 """ 1476 Compute acoplanarity which is a measure of how much a local region of the mesh 1477 differs from a plane. 1478 The information is stored in a `pointdata` or `celldata` array with name 'Acoplanarity'. 1479 Either `n` (number of neighbour points) or `radius` (radius of local search) can be specified. 1480 If a radius value is given and not enough points fall inside it, then a -1 is stored. 1481 1482 Example: 1483 ```python 1484 from vedo import * 1485 msh = ParametricShape('RandomHills') 1486 msh.compute_acoplanarity(radius=0.1, on='cells') 1487 msh.cmap("coolwarm", on='cells').add_scalarbar() 1488 msh.show(axes=1).close() 1489 ``` 1490 ![](https://vedo.embl.es/images/feats/acoplanarity.jpg) 1491 """ 1492 acoplanarities = [] 1493 if "point" in on: 1494 pts = self.points() 1495 elif "cell" in on: 1496 pts = self.cell_centers() 1497 else: 1498 raise ValueError(f"In compute_acoplanarity() set on to either 'cells' or 'points', not {on}") 1499 1500 for p in utils.progressbar(pts, delay=5, width=15, title=f"{on} acoplanarity"): 1501 if n: 1502 data = self.closest_point(p, n=n) 1503 npts = n 1504 elif radius: 1505 data = self.closest_point(p, radius=radius) 1506 npts = len(data) 1507 1508 try: 1509 center = data.mean(axis=0) 1510 res = np.linalg.svd(data - center) 1511 acoplanarities.append(res[1][2] / npts) 1512 except: 1513 acoplanarities.append(-1.0) 1514 1515 if "point" in on: 1516 self.pointdata["Acoplanarity"] = np.array(acoplanarities, dtype=float) 1517 else: 1518 self.celldata["Acoplanarity"] = np.array(acoplanarities, dtype=float) 1519 return self 1520 1521 def distance_to(self, pcloud, signed=False, invert=False, name="Distance"): 1522 """ 1523 Computes the distance from one point cloud or mesh to another point cloud or mesh. 1524 This new `pointdata` array is saved with default name "Distance". 1525 1526 Keywords `signed` and `invert` are used to compute signed distance, 1527 but the mesh in that case must have polygonal faces (not a simple point cloud), 1528 and normals must also be computed. 1529 1530 Examples: 1531 - [distance2mesh.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/distance2mesh.py) 1532 1533 ![](https://vedo.embl.es/images/basic/distance2mesh.png) 1534 """ 1535 if pcloud.inputdata().GetNumberOfPolys(): 1536 1537 poly1 = self.polydata() 1538 poly2 = pcloud.polydata() 1539 df = vtk.vtkDistancePolyDataFilter() 1540 df.ComputeSecondDistanceOff() 1541 df.SetInputData(0, poly1) 1542 df.SetInputData(1, poly2) 1543 df.SetSignedDistance(signed) 1544 df.SetNegateDistance(invert) 1545 df.Update() 1546 scals = df.GetOutput().GetPointData().GetScalars() 1547 dists = utils.vtk2numpy(scals) 1548 1549 else: # has no polygons and vtkDistancePolyDataFilter wants them (dont know why) 1550 1551 if signed: 1552 vedo.logger.warning("distanceTo() called with signed=True but input object has no polygons") 1553 1554 if not pcloud.point_locator: 1555 pcloud.point_locator = vtk.vtkPointLocator() 1556 pcloud.point_locator.SetDataSet(pcloud.polydata()) 1557 pcloud.point_locator.BuildLocator() 1558 1559 ids = [] 1560 ps1 = self.points() 1561 ps2 = pcloud.points() 1562 for p in ps1: 1563 pid = pcloud.point_locator.FindClosestPoint(p) 1564 ids.append(pid) 1565 1566 deltas = ps2[ids] - ps1 1567 dists = np.linalg.norm(deltas, axis=1).astype(np.float32) 1568 scals = utils.numpy2vtk(dists) 1569 1570 scals.SetName(name) 1571 self.inputdata().GetPointData().AddArray(scals) # must be self.inputdata() ! 1572 self.inputdata().GetPointData().SetActiveScalars(scals.GetName()) 1573 rng = scals.GetRange() 1574 self.mapper().SetScalarRange(rng[0], rng[1]) 1575 self.mapper().ScalarVisibilityOn() 1576 1577 self.pipeline = utils.OperationNode( 1578 "distance_to", 1579 parents=[self, pcloud], 1580 shape="cylinder", 1581 comment=f"#pts {self._data.GetNumberOfPoints()}", 1582 ) 1583 return dists 1584 1585 def alpha(self, opacity=None): 1586 """Set/get mesh's transparency. Same as `mesh.opacity()`.""" 1587 if opacity is None: 1588 return self.GetProperty().GetOpacity() 1589 1590 self.GetProperty().SetOpacity(opacity) 1591 bfp = self.GetBackfaceProperty() 1592 if bfp: 1593 if opacity < 1: 1594 self._bfprop = bfp 1595 self.SetBackfaceProperty(None) 1596 else: 1597 self.SetBackfaceProperty(self._bfprop) 1598 return self 1599 1600 def opacity(self, alpha=None): 1601 """Set/get mesh's transparency. Same as `mesh.alpha()`.""" 1602 return self.alpha(alpha) 1603 1604 def force_opaque(self, value=True): 1605 """ Force the Mesh, Line or point cloud to be treated as opaque""" 1606 ## force the opaque pass, fixes picking in vtk9 1607 # but causes other bad troubles with lines.. 1608 self.SetForceOpaque(value) 1609 return self 1610 1611 def force_translucent(self, value=True): 1612 """ Force the Mesh, Line or point cloud to be treated as translucent""" 1613 self.SetForceTranslucent(value) 1614 return self 1615 1616 def point_size(self, value=None): 1617 """Set/get mesh's point size of vertices. Same as `mesh.ps()`""" 1618 if value is None: 1619 return self.GetProperty().GetPointSize() 1620 #self.GetProperty().SetRepresentationToSurface() 1621 else: 1622 self.GetProperty().SetRepresentationToPoints() 1623 self.GetProperty().SetPointSize(value) 1624 return self 1625 1626 def ps(self, pointsize=None): 1627 """Set/get mesh's point size of vertices. Same as `mesh.point_size()`""" 1628 return self.point_size(pointsize) 1629 1630 def render_points_as_spheres(self, value=True): 1631 """Make points look spheric or make them look as squares.""" 1632 self.GetProperty().SetRenderPointsAsSpheres(value) 1633 return self 1634 1635 def color(self, c=False, alpha=None): 1636 """ 1637 Set/get mesh's color. 1638 If None is passed as input, will use colors from active scalars. 1639 Same as `mesh.c()`. 1640 """ 1641 # overrides base.color() 1642 if c is False: 1643 return np.array(self.GetProperty().GetColor()) 1644 if c is None: 1645 self.mapper().ScalarVisibilityOn() 1646 return self 1647 self.mapper().ScalarVisibilityOff() 1648 cc = colors.get_color(c) 1649 self.GetProperty().SetColor(cc) 1650 if self.trail: 1651 self.trail.GetProperty().SetColor(cc) 1652 if alpha is not None: 1653 self.alpha(alpha) 1654 return self 1655 1656 def clean(self): 1657 """ 1658 Clean pointcloud or mesh by removing coincident points. 1659 """ 1660 cpd = vtk.vtkCleanPolyData() 1661 cpd.PointMergingOn() 1662 cpd.ConvertLinesToPointsOn() 1663 cpd.ConvertPolysToLinesOn() 1664 cpd.ConvertStripsToPolysOn() 1665 cpd.SetInputData(self.inputdata()) 1666 cpd.Update() 1667 out = self._update(cpd.GetOutput()) 1668 1669 out.pipeline = utils.OperationNode( 1670 "clean", parents=[self], 1671 comment=f"#pts {out.inputdata().GetNumberOfPoints()}" 1672 ) 1673 return out 1674 1675 def subsample(self, fraction, absolute=False): 1676 """ 1677 Subsample a point cloud by requiring that the points 1678 or vertices are far apart at least by the specified fraction of the object size. 1679 If a Mesh is passed the polygonal faces are not removed 1680 but holes can appear as vertices are removed. 1681 1682 Examples: 1683 - [moving_least_squares1D.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/moving_least_squares1D.py) 1684 1685 ![](https://vedo.embl.es/images/advanced/moving_least_squares1D.png) 1686 1687 - [recosurface.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/recosurface.py) 1688 1689 ![](https://vedo.embl.es/images/advanced/recosurface.png) 1690 """ 1691 if not absolute: 1692 if fraction > 1: 1693 vedo.logger.warning( 1694 f"subsample(fraction=...), fraction must be < 1, but is {fraction}" 1695 ) 1696 if fraction <= 0: 1697 return self 1698 1699 cpd = vtk.vtkCleanPolyData() 1700 cpd.PointMergingOn() 1701 cpd.ConvertLinesToPointsOn() 1702 cpd.ConvertPolysToLinesOn() 1703 cpd.ConvertStripsToPolysOn() 1704 cpd.SetInputData(self.inputdata()) 1705 if absolute: 1706 cpd.SetTolerance(fraction / self.diagonal_size()) 1707 # cpd.SetToleranceIsAbsolute(absolute) 1708 else: 1709 cpd.SetTolerance(fraction) 1710 cpd.Update() 1711 1712 ps = 2 1713 if self.GetProperty().GetRepresentation() == 0: 1714 ps = self.GetProperty().GetPointSize() 1715 1716 out = self._update(cpd.GetOutput()).ps(ps) 1717 1718 out.pipeline = utils.OperationNode( 1719 "subsample", parents=[self], comment=f"#pts {out.inputdata().GetNumberOfPoints()}" 1720 ) 1721 return out 1722 1723 def threshold(self, scalars, above=None, below=None, on="points"): 1724 """ 1725 Extracts cells where scalar value satisfies threshold criterion. 1726 1727 Arguments: 1728 scalars : (str) 1729 name of the scalars array. 1730 above : (float) 1731 minimum value of the scalar 1732 below : (float) 1733 maximum value of the scalar 1734 on : (str) 1735 if 'cells' assume array of scalars refers to cell data. 1736 1737 Examples: 1738 - [mesh_threshold.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_threshold.py) 1739 """ 1740 thres = vtk.vtkThreshold() 1741 thres.SetInputData(self.inputdata()) 1742 1743 if on.startswith("c"): 1744 asso = vtk.vtkDataObject.FIELD_ASSOCIATION_CELLS 1745 else: 1746 asso = vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS 1747 1748 thres.SetInputArrayToProcess(0, 0, 0, asso, scalars) 1749 1750 if above is None and below is not None: 1751 thres.ThresholdByLower(below) 1752 elif below is None and above is not None: 1753 thres.ThresholdByUpper(above) 1754 else: 1755 thres.ThresholdBetween(above, below) 1756 thres.Update() 1757 1758 gf = vtk.vtkGeometryFilter() 1759 gf.SetInputData(thres.GetOutput()) 1760 gf.Update() 1761 return self._update(gf.GetOutput()) 1762 1763 def quantize(self, value): 1764 """ 1765 The user should input a value and all {x,y,z} coordinates 1766 will be quantized to that absolute grain size. 1767 """ 1768 poly = self.inputdata() 1769 qp = vtk.vtkQuantizePolyDataPoints() 1770 qp.SetInputData(poly) 1771 qp.SetQFactor(value) 1772 qp.Update() 1773 out = self._update(qp.GetOutput()).flat() 1774 out.pipeline = utils.OperationNode("quantize", parents=[self]) 1775 return out 1776 1777 def average_size(self): 1778 """ 1779 Calculate the average size of a mesh. 1780 This is the mean of the vertex distances from the center of mass. 1781 """ 1782 coords = self.points() 1783 cm = np.mean(coords, axis=0) 1784 if coords.shape[0] == 0: 1785 return 0.0 1786 cc = coords - cm 1787 return np.mean(np.linalg.norm(cc, axis=1)) 1788 1789 def center_of_mass(self): 1790 """Get the center of mass of mesh.""" 1791 cmf = vtk.vtkCenterOfMass() 1792 cmf.SetInputData(self.polydata()) 1793 cmf.Update() 1794 c = cmf.GetCenter() 1795 return np.array(c) 1796 1797 def normal_at(self, i): 1798 """Return the normal vector at vertex point `i`.""" 1799 normals = self.polydata().GetPointData().GetNormals() 1800 return np.array(normals.GetTuple(i)) 1801 1802 def normals(self, cells=False, recompute=True): 1803 """Retrieve vertex normals as a numpy array. 1804 1805 Arguments: 1806 cells : (bool) 1807 if `True` return cell normals. 1808 1809 recompute : (bool) 1810 if `True` normals are recalculated if not already present. 1811 Note that this might modify the number of mesh points. 1812 """ 1813 if cells: 1814 vtknormals = self.polydata().GetCellData().GetNormals() 1815 else: 1816 vtknormals = self.polydata().GetPointData().GetNormals() 1817 if not vtknormals and recompute: 1818 try: 1819 self.compute_normals(cells=cells) 1820 if cells: 1821 vtknormals = self.polydata().GetCellData().GetNormals() 1822 else: 1823 vtknormals = self.polydata().GetPointData().GetNormals() 1824 except AttributeError: 1825 # can be that 'Points' object has no attribute 'compute_normals' 1826 pass 1827 1828 if not vtknormals: 1829 return np.array([]) 1830 return utils.vtk2numpy(vtknormals) 1831 1832 def labels( 1833 self, 1834 content=None, 1835 on="points", 1836 scale=None, 1837 xrot=0.0, 1838 yrot=0.0, 1839 zrot=0.0, 1840 ratio=1, 1841 precision=None, 1842 italic=False, 1843 font="", 1844 justify="bottom-left", 1845 c="black", 1846 alpha=1.0, 1847 cells=None, 1848 ): 1849 """ 1850 Generate value or ID labels for mesh cells or points. 1851 For large nr. of labels use `font="VTK"` which is much faster. 1852 1853 See also: 1854 `labels2d()`, `flagpole()`, `caption()` and `legend()`. 1855 1856 Arguments: 1857 content : (list,int,str) 1858 either 'id', 'cellid', array name or array number. 1859 A array can also be passed (must match the nr. of points or cells). 1860 on : (str) 1861 generate labels for "cells" instead of "points" 1862 scale : (float) 1863 absolute size of labels, if left as None it is automatic 1864 zrot : (float) 1865 local rotation angle of label in degrees 1866 ratio : (int) 1867 skipping ratio, to reduce nr of labels for large meshes 1868 precision : (int) 1869 numeric precision of labels 1870 1871 ```python 1872 from vedo import * 1873 s = Sphere(res=10).linewidth(1).c("orange").compute_normals() 1874 point_ids = s.labels('id', on="points").c('green') 1875 cell_ids = s.labels('id', on="cells" ).c('black') 1876 show(s, point_ids, cell_ids) 1877 ``` 1878 ![](https://vedo.embl.es/images/feats/labels.png) 1879 1880 Examples: 1881 - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py) 1882 1883 ![](https://vedo.embl.es/images/basic/boundaries.png) 1884 """ 1885 if cells is not None: # deprecation message 1886 vedo.logger.warning("In labels(cells=...) please use labels(on='cells') instead") 1887 1888 if "cell" in on or "face" in on: 1889 cells = True 1890 1891 if isinstance(content, str): 1892 if content in ("cellid", "cellsid"): 1893 cells = True 1894 content = "id" 1895 1896 if cells: 1897 elems = self.cell_centers() 1898 norms = self.normals(cells=True, recompute=False) 1899 ns = np.sqrt(self.ncells) 1900 else: 1901 elems = self.points() 1902 norms = self.normals(cells=False, recompute=False) 1903 ns = np.sqrt(self.npoints) 1904 1905 hasnorms = False 1906 if len(norms) > 0: 1907 hasnorms = True 1908 1909 if scale is None: 1910 if not ns: 1911 ns = 100 1912 scale = self.diagonal_size() / ns / 10 1913 1914 arr = None 1915 mode = 0 1916 if content is None: 1917 mode = 0 1918 if cells: 1919 if self.inputdata().GetCellData().GetScalars(): 1920 name = self.inputdata().GetCellData().GetScalars().GetName() 1921 arr = self.celldata[name] 1922 else: 1923 if self.inputdata().GetPointData().GetScalars(): 1924 name = self.inputdata().GetPointData().GetScalars().GetName() 1925 arr = self.pointdata[name] 1926 elif isinstance(content, (str, int)): 1927 if content == "id": 1928 mode = 1 1929 elif cells: 1930 mode = 0 1931 arr = self.celldata[content] 1932 else: 1933 mode = 0 1934 arr = self.pointdata[content] 1935 elif utils.is_sequence(content): 1936 mode = 0 1937 arr = content 1938 # print('WEIRD labels() test', content) 1939 # exit() 1940 1941 if arr is None and mode == 0: 1942 vedo.logger.error("in labels(), array not found for points or cells") 1943 return None 1944 1945 tapp = vtk.vtkAppendPolyData() 1946 ninputs = 0 1947 1948 for i, e in enumerate(elems): 1949 if i % ratio: 1950 continue 1951 1952 if mode == 1: 1953 txt_lab = str(i) 1954 else: 1955 if precision: 1956 txt_lab = utils.precision(arr[i], precision) 1957 else: 1958 txt_lab = str(arr[i]) 1959 1960 if not txt_lab: 1961 continue 1962 1963 if font == "VTK": 1964 tx = vtk.vtkVectorText() 1965 tx.SetText(txt_lab) 1966 tx.Update() 1967 tx_poly = tx.GetOutput() 1968 else: 1969 tx_poly = vedo.shapes.Text3D(txt_lab, font=font, justify=justify) 1970 tx_poly = tx_poly.inputdata() 1971 1972 if tx_poly.GetNumberOfPoints() == 0: 1973 continue ####################### 1974 ninputs += 1 1975 1976 T = vtk.vtkTransform() 1977 T.PostMultiply() 1978 if italic: 1979 T.Concatenate([1,0.2,0,0, 1980 0,1,0,0, 1981 0,0,1,0, 1982 0,0,0,1]) 1983 if hasnorms: 1984 ni = norms[i] 1985 if cells: # center-justify 1986 bb = tx_poly.GetBounds() 1987 dx, dy = (bb[1] - bb[0]) / 2, (bb[3] - bb[2]) / 2 1988 T.Translate(-dx, -dy, 0) 1989 if xrot: 1990 T.RotateX(xrot) 1991 if yrot: 1992 T.RotateY(yrot) 1993 if zrot: 1994 T.RotateZ(zrot) 1995 crossvec = np.cross([0, 0, 1], ni) 1996 angle = np.arccos(np.dot([0, 0, 1], ni)) * 57.3 1997 T.RotateWXYZ(angle, crossvec) 1998 if cells: # small offset along normal only for cells 1999 T.Translate(ni * scale / 2) 2000 else: 2001 if xrot: 2002 T.RotateX(xrot) 2003 if yrot: 2004 T.RotateY(yrot) 2005 if zrot: 2006 T.RotateZ(zrot) 2007 T.Scale(scale, scale, scale) 2008 T.Translate(e) 2009 tf = vtk.vtkTransformPolyDataFilter() 2010 tf.SetInputData(tx_poly) 2011 tf.SetTransform(T) 2012 tf.Update() 2013 tapp.AddInputData(tf.GetOutput()) 2014 2015 if ninputs: 2016 tapp.Update() 2017 lpoly = tapp.GetOutput() 2018 else: # return an empty obj 2019 lpoly = vtk.vtkPolyData() 2020 2021 ids = vedo.mesh.Mesh(lpoly, c=c, alpha=alpha) 2022 ids.GetProperty().LightingOff() 2023 ids.PickableOff() 2024 ids.SetUseBounds(False) 2025 return ids 2026 2027 def labels2d( 2028 self, 2029 content="id", 2030 on="points", 2031 scale=1.0, 2032 precision=4, 2033 font="Calco", 2034 justify="bottom-left", 2035 angle=0.0, 2036 frame=False, 2037 c="black", 2038 bc=None, 2039 alpha=1.0, 2040 ): 2041 """ 2042 Generate value or ID bi-dimensional labels for mesh cells or points. 2043 2044 See also: `labels()`, `flagpole()`, `caption()` and `legend()`. 2045 2046 Arguments: 2047 content : (str) 2048 either 'id', 'cellid', or array name 2049 on : (str) 2050 generate labels for "cells" instead of "points" (the default) 2051 scale : (float) 2052 size scaling of labels 2053 precision : (int) 2054 precision of numeric labels 2055 angle : (float) 2056 local rotation angle of label in degrees 2057 frame : (bool) 2058 draw a frame around the label 2059 bc : (str) 2060 background color of the label 2061 2062 ```python 2063 from vedo import Sphere, show 2064 sph = Sphere(quads=True, res=4).compute_normals().wireframe() 2065 sph.celldata["zvals"] = sph.cell_centers()[:,2] 2066 l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9') 2067 show(sph, l2d, axes=1).close() 2068 ``` 2069 ![](https://vedo.embl.es/images/feats/labels2d.png) 2070 """ 2071 cells = False 2072 if isinstance(content, str): 2073 if content in ("cellid", "cellsid"): 2074 cells = True 2075 content = "id" 2076 2077 if "cell" in on: 2078 cells = True 2079 elif "point" in on: 2080 cells = False 2081 2082 if cells: 2083 if content != "id" and content not in self.celldata.keys(): 2084 vedo.logger.error(f"In labels2d: cell array {content} does not exist.") 2085 return None 2086 cellcloud = Points(self.cell_centers()) 2087 arr = self.inputdata().GetCellData().GetScalars() 2088 poly = cellcloud.polydata(False) 2089 poly.GetPointData().SetScalars(arr) 2090 else: 2091 poly = self.polydata() 2092 if content != "id" and content not in self.pointdata.keys(): 2093 vedo.logger.error(f"In labels2d: point array {content} does not exist.") 2094 return None 2095 self.pointdata.select(content) 2096 2097 mp = vtk.vtkLabeledDataMapper() 2098 2099 if content == "id": 2100 mp.SetLabelModeToLabelIds() 2101 else: 2102 mp.SetLabelModeToLabelScalars() 2103 if precision is not None: 2104 mp.SetLabelFormat(f"%-#.{precision}g") 2105 2106 pr = mp.GetLabelTextProperty() 2107 c = colors.get_color(c) 2108 pr.SetColor(c) 2109 pr.SetOpacity(alpha) 2110 pr.SetFrame(frame) 2111 pr.SetFrameColor(c) 2112 pr.SetItalic(False) 2113 pr.BoldOff() 2114 pr.ShadowOff() 2115 pr.UseTightBoundingBoxOn() 2116 pr.SetOrientation(angle) 2117 pr.SetFontFamily(vtk.VTK_FONT_FILE) 2118 fl = utils.get_font_path(font) 2119 pr.SetFontFile(fl) 2120 pr.SetFontSize(int(20 * scale)) 2121 2122 if "cent" in justify or "mid" in justify: 2123 pr.SetJustificationToCentered() 2124 elif "rig" in justify: 2125 pr.SetJustificationToRight() 2126 elif "left" in justify: 2127 pr.SetJustificationToLeft() 2128 # ------ 2129 if "top" in justify: 2130 pr.SetVerticalJustificationToTop() 2131 else: 2132 pr.SetVerticalJustificationToBottom() 2133 2134 if bc is not None: 2135 bc = colors.get_color(bc) 2136 pr.SetBackgroundColor(bc) 2137 pr.SetBackgroundOpacity(alpha) 2138 2139 mp.SetInputData(poly) 2140 a2d = vtk.vtkActor2D() 2141 a2d.PickableOff() 2142 a2d.SetMapper(mp) 2143 return a2d 2144 2145 def legend(self, txt): 2146 """Book a legend text.""" 2147 self.info["legend"] = txt 2148 return self 2149 2150 def flagpole( 2151 self, 2152 txt=None, 2153 point=None, 2154 offset=None, 2155 s=None, 2156 font="", 2157 rounded=True, 2158 c=None, 2159 alpha=1.0, 2160 lw=2, 2161 italic=0.0, 2162 padding=0.1, 2163 ): 2164 """ 2165 Generate a flag pole style element to describe an object. 2166 Returns a `Mesh` object. 2167 2168 Use flagpole.follow_camera() to make it face the camera in the scene. 2169 2170 See also `flagpost()`. 2171 2172 Arguments: 2173 txt : (str) 2174 Text to display. The default is the filename or the object name. 2175 point : (list) 2176 position of the flagpole pointer. 2177 offset : (list) 2178 text offset wrt the application point. 2179 s : (float) 2180 size of the flagpole. 2181 font : (str) 2182 font face. Check [available fonts here](https://vedo.embl.es/fonts). 2183 rounded : (bool) 2184 draw a rounded or squared box around the text. 2185 c : (list) 2186 text and box color. 2187 alpha : (float) 2188 opacity of text and box. 2189 lw : (float) 2190 line with of box frame. 2191 italic : (float) 2192 italicness of text. 2193 2194 Examples: 2195 - [intersect2d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/intersect2d.py) 2196 2197 ![](https://vedo.embl.es/images/pyplot/intersect2d.png) 2198 2199 - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py) 2200 - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py) 2201 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py) 2202 """ 2203 acts = [] 2204 2205 if txt is None: 2206 if self.filename: 2207 txt = self.filename.split("/")[-1] 2208 elif self.name: 2209 txt = self.name 2210 else: 2211 return None 2212 2213 x0, x1, y0, y1, z0, z1 = self.bounds() 2214 d = self.diagonal_size() 2215 if point is None: 2216 if d: 2217 point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1]) 2218 else: # it's a Point 2219 point = self.GetPosition() 2220 2221 pt = utils.make3d(point) 2222 2223 if offset is None: 2224 offset = [(x1 - x0) / 2, (y1 - y0) / 6, 0] 2225 offset = utils.make3d(offset) 2226 2227 if s is None: 2228 s = d / 20 2229 2230 sph = None 2231 if d and (z1 - z0) / d > 0.1: 2232 sph = vedo.shapes.Sphere(pt, r=s * 0.4, res=6) 2233 2234 if c is None: 2235 c = np.array(self.color()) / 1.4 2236 2237 lb = vedo.shapes.Text3D( 2238 txt, pos=pt + offset, s=s, font=font, italic=italic, justify="center-left" 2239 ) 2240 acts.append(lb) 2241 2242 if d and not sph: 2243 sph = vedo.shapes.Circle(pt, r=s / 3, res=15) 2244 acts.append(sph) 2245 2246 x0, x1, y0, y1, z0, z1 = lb.GetBounds() 2247 if rounded: 2248 box = vedo.shapes.KSpline( 2249 [(x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0)], closed=True 2250 ) 2251 else: 2252 box = vedo.shapes.Line( 2253 [(x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0), (x0, y0, z0)] 2254 ) 2255 2256 cnt = [(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2] 2257 2258 box.SetOrigin(cnt) 2259 box.scale([1 + padding, 1 + 2 * padding, 1]) 2260 acts.append(box) 2261 2262 # pts = box.points() 2263 # bfaces = [] 2264 # for i, pt in enumerate(pts): 2265 # if i: 2266 # face = [i-1, i, 0] 2267 # bfaces.append(face) 2268 # bpts = [cnt] + pts.tolist() 2269 # box2 = vedo.Mesh([bpts, bfaces]).z(-cnt[0]/10)#.c('w').alpha(0.1) 2270 # #should be made assembly otherwise later merge() nullifies it 2271 # box2.SetOrigin(cnt) 2272 # acts.append(box2) 2273 2274 x0, x1, y0, y1, z0, z1 = box.bounds() 2275 if x0 < pt[0] < x1: 2276 c0 = box.closest_point(pt) 2277 c1 = [c0[0], c0[1] + (pt[1] - y0) / 4, pt[2]] 2278 elif (pt[0] - x0) < (x1 - pt[0]): 2279 c0 = [x0, (y0 + y1) / 2, pt[2]] 2280 c1 = [x0 + (pt[0] - x0) / 4, (y0 + y1) / 2, pt[2]] 2281 else: 2282 c0 = [x1, (y0 + y1) / 2, pt[2]] 2283 c1 = [x1 + (pt[0] - x1) / 4, (y0 + y1) / 2, pt[2]] 2284 2285 con = vedo.shapes.Line([c0, c1, pt]) 2286 acts.append(con) 2287 2288 macts = vedo.merge(acts).c(c).alpha(alpha) 2289 macts.SetOrigin(pt) 2290 macts.bc("tomato").pickable(False) 2291 macts.GetProperty().LightingOff() 2292 macts.GetProperty().SetLineWidth(lw) 2293 macts.UseBoundsOff() 2294 macts.name = "FlagPole" 2295 return macts 2296 2297 def flagpost( 2298 self, 2299 txt=None, 2300 point=None, 2301 offset=None, 2302 s=1.0, 2303 c="k9", 2304 bc="k1", 2305 alpha=1, 2306 lw=0, 2307 font="Calco", 2308 justify="center-left", 2309 vspacing=1.0, 2310 ): 2311 """ 2312 Generate a flag post style element to describe an object. 2313 2314 Arguments: 2315 txt : (str) 2316 Text to display. The default is the filename or the object name. 2317 point : (list) 2318 position of the flag anchor point. The default is None. 2319 offset : (list) 2320 a 3D displacement or offset. The default is None. 2321 s : (float) 2322 size of the text to be shown 2323 c : (list) 2324 color of text and line 2325 bc : (list) 2326 color of the flag background 2327 alpha : (float) 2328 opacity of text and box. 2329 lw : (int) 2330 line with of box frame. The default is 0. 2331 font : (str) 2332 font name. Use a monospace font for better rendering. The default is "Calco". 2333 Type `vedo -r fonts` for a font demo. 2334 Check [available fonts here](https://vedo.embl.es/fonts). 2335 justify : (str) 2336 internal text justification. The default is "center-left". 2337 vspacing : (float) 2338 vertical spacing between lines. 2339 2340 Examples: 2341 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py) 2342 2343 ![](https://vedo.embl.es/images/other/flag_labels2.png) 2344 """ 2345 if txt is None: 2346 if self.filename: 2347 txt = self.filename.split("/")[-1] 2348 elif self.name: 2349 txt = self.name 2350 else: 2351 return None 2352 2353 x0, x1, y0, y1, z0, z1 = self.bounds() 2354 d = self.diagonal_size() 2355 if point is None: 2356 if d: 2357 point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1]) 2358 else: # it's a Point 2359 point = self.GetPosition() 2360 2361 point = utils.make3d(point) 2362 2363 if offset is None: 2364 offset = [0, 0, (z1 - z0) / 2] 2365 offset = utils.make3d(offset) 2366 2367 fpost = vedo.addons.Flagpost( 2368 txt, point, point + offset, s, c, bc, alpha, lw, font, justify, vspacing 2369 ) 2370 self._caption = fpost 2371 return fpost 2372 2373 def caption( 2374 self, 2375 txt=None, 2376 point=None, 2377 size=(0.30, 0.15), 2378 padding=5, 2379 font="Calco", 2380 justify="center-right", 2381 vspacing=1.0, 2382 c=None, 2383 alpha=1.0, 2384 lw=1, 2385 ontop=True, 2386 ): 2387 """ 2388 Add a 2D caption to an object which follows the camera movements. 2389 Latex is not supported. Returns the same input object for concatenation. 2390 2391 See also `flagpole()`, `flagpost()`, `labels()` and `legend()` 2392 with similar functionality. 2393 2394 Arguments: 2395 txt : (str) 2396 text to be rendered. The default is the file name. 2397 point : (list) 2398 anchoring point. The default is None. 2399 size : (list) 2400 (width, height) of the caption box. The default is (0.30, 0.15). 2401 padding : (float) 2402 padding space of the caption box in pixels. The default is 5. 2403 font : (str) 2404 font name. Use a monospace font for better rendering. The default is "VictorMono". 2405 Type `vedo -r fonts` for a font demo. 2406 Check [available fonts here](https://vedo.embl.es/fonts). 2407 justify : (str) 2408 internal text justification. The default is "center-right". 2409 vspacing : (float) 2410 vertical spacing between lines. The default is 1. 2411 c : (str) 2412 text and box color. The default is 'lb'. 2413 alpha : (float) 2414 text and box transparency. The default is 1. 2415 lw : (int) 2416 line width in pixels. The default is 1. 2417 ontop : (bool) 2418 keep the 2d caption always on top. The default is True. 2419 2420 Examples: 2421 - [caption.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/caption.py) 2422 2423 ![](https://vedo.embl.es/images/pyplot/caption.png) 2424 2425 - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py) 2426 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py) 2427 """ 2428 if txt is None: 2429 if self.filename: 2430 txt = self.filename.split("/")[-1] 2431 elif self.name: 2432 txt = self.name 2433 2434 if not txt: # disable it 2435 self._caption = None 2436 return self 2437 2438 for r in vedo.shapes._reps: 2439 txt = txt.replace(r[0], r[1]) 2440 2441 if c is None: 2442 c = np.array(self.GetProperty().GetColor()) / 2 2443 else: 2444 c = colors.get_color(c) 2445 2446 if point is None: 2447 x0, x1, y0, y1, _, z1 = self.GetBounds() 2448 pt = [(x0 + x1) / 2, (y0 + y1) / 2, z1] 2449 point = self.closest_point(pt) 2450 2451 capt = vtk.vtkCaptionActor2D() 2452 capt.SetAttachmentPoint(point) 2453 capt.SetBorder(True) 2454 capt.SetLeader(True) 2455 sph = vtk.vtkSphereSource() 2456 sph.Update() 2457 capt.SetLeaderGlyphData(sph.GetOutput()) 2458 capt.SetMaximumLeaderGlyphSize(5) 2459 capt.SetPadding(int(padding)) 2460 capt.SetCaption(txt) 2461 capt.SetWidth(size[0]) 2462 capt.SetHeight(size[1]) 2463 capt.SetThreeDimensionalLeader(not ontop) 2464 2465 pra = capt.GetProperty() 2466 pra.SetColor(c) 2467 pra.SetOpacity(alpha) 2468 pra.SetLineWidth(lw) 2469 2470 pr = capt.GetCaptionTextProperty() 2471 pr.SetFontFamily(vtk.VTK_FONT_FILE) 2472 fl = utils.get_font_path(font) 2473 pr.SetFontFile(fl) 2474 pr.ShadowOff() 2475 pr.BoldOff() 2476 pr.FrameOff() 2477 pr.SetColor(c) 2478 pr.SetOpacity(alpha) 2479 pr.SetJustificationToLeft() 2480 if "top" in justify: 2481 pr.SetVerticalJustificationToTop() 2482 if "bottom" in justify: 2483 pr.SetVerticalJustificationToBottom() 2484 if "cent" in justify: 2485 pr.SetVerticalJustificationToCentered() 2486 pr.SetJustificationToCentered() 2487 if "left" in justify: 2488 pr.SetJustificationToLeft() 2489 if "right" in justify: 2490 pr.SetJustificationToRight() 2491 pr.SetLineSpacing(vspacing) 2492 self._caption = capt 2493 return self 2494 2495 2496 def align_to(self, target, iters=100, rigid=False, invert=False, use_centroids=False): 2497 """ 2498 Aligned to target mesh through the `Iterative Closest Point` algorithm. 2499 2500 The core of the algorithm is to match each vertex in one surface with 2501 the closest surface point on the other, then apply the transformation 2502 that modify one surface to best match the other (in the least-square sense). 2503 2504 Arguments: 2505 rigid : (bool) 2506 if True do not allow scaling 2507 invert : (bool) 2508 if True start by aligning the target to the source but 2509 invert the transformation finally. Useful when the target is smaller 2510 than the source. 2511 use_centroids : (bool) 2512 start by matching the centroids of the two objects. 2513 2514 Examples: 2515 - [align1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align1.py) 2516 2517 ![](https://vedo.embl.es/images/basic/align1.png) 2518 2519 - [align2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align2.py) 2520 2521 ![](https://vedo.embl.es/images/basic/align2.png) 2522 """ 2523 icp = vtk.vtkIterativeClosestPointTransform() 2524 icp.SetSource(self.polydata()) 2525 icp.SetTarget(target.polydata()) 2526 if invert: 2527 icp.Inverse() 2528 icp.SetMaximumNumberOfIterations(iters) 2529 if rigid: 2530 icp.GetLandmarkTransform().SetModeToRigidBody() 2531 icp.SetStartByMatchingCentroids(use_centroids) 2532 icp.Update() 2533 2534 M = icp.GetMatrix() 2535 if invert: 2536 M.Invert() # icp.GetInverse() doesnt work! 2537 # self.apply_transform(M) 2538 self.SetUserMatrix(M) 2539 2540 self.transform = self.GetUserTransform() 2541 self.point_locator = None 2542 self.cell_locator = None 2543 2544 self.pipeline = utils.OperationNode( 2545 "align_to", parents=[self, target], comment=f"rigid = {rigid}" 2546 ) 2547 return self 2548 2549 def transform_with_landmarks( 2550 self, source_landmarks, target_landmarks, rigid=False, affine=False, least_squares=False 2551 ): 2552 """ 2553 Transform mesh orientation and position based on a set of landmarks points. 2554 The algorithm finds the best matching of source points to target points 2555 in the mean least square sense, in one single step. 2556 2557 If affine is True the x, y and z axes can scale independently but stay collinear. 2558 With least_squares they can vary orientation. 2559 2560 Examples: 2561 - [align5.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align5.py) 2562 2563 ![](https://vedo.embl.es/images/basic/align5.png) 2564 """ 2565 2566 if utils.is_sequence(source_landmarks): 2567 ss = vtk.vtkPoints() 2568 for p in source_landmarks: 2569 ss.InsertNextPoint(p) 2570 else: 2571 ss = source_landmarks.polydata().GetPoints() 2572 if least_squares: 2573 source_landmarks = source_landmarks.points() 2574 2575 if utils.is_sequence(target_landmarks): 2576 st = vtk.vtkPoints() 2577 for p in target_landmarks: 2578 st.InsertNextPoint(p) 2579 else: 2580 st = target_landmarks.polydata().GetPoints() 2581 if least_squares: 2582 target_landmarks = target_landmarks.points() 2583 2584 if ss.GetNumberOfPoints() != st.GetNumberOfPoints(): 2585 n1 = ss.GetNumberOfPoints() 2586 n2 = st.GetNumberOfPoints() 2587 vedo.logger.error(f"source and target have different nr of points {n1} vs {n2}") 2588 raise RuntimeError() 2589 2590 lmt = vtk.vtkLandmarkTransform() 2591 lmt.SetSourceLandmarks(ss) 2592 lmt.SetTargetLandmarks(st) 2593 lmt.SetModeToSimilarity() 2594 if rigid: 2595 lmt.SetModeToRigidBody() 2596 lmt.Update() 2597 self.SetUserTransform(lmt) 2598 2599 elif affine: 2600 lmt.SetModeToAffine() 2601 lmt.Update() 2602 self.SetUserTransform(lmt) 2603 2604 elif least_squares: 2605 cms = source_landmarks.mean(axis=0) 2606 cmt = target_landmarks.mean(axis=0) 2607 m = np.linalg.lstsq(source_landmarks - cms, target_landmarks - cmt, rcond=None)[0] 2608 M = vtk.vtkMatrix4x4() 2609 for i in range(3): 2610 for j in range(3): 2611 M.SetElement(j, i, m[i][j]) 2612 lmt = vtk.vtkTransform() 2613 lmt.Translate(cmt) 2614 lmt.Concatenate(M) 2615 lmt.Translate(-cms) 2616 self.apply_transform(lmt, concatenate=True) 2617 else: 2618 self.SetUserTransform(lmt) 2619 2620 self.transform = lmt 2621 self.point_locator = None 2622 self.cell_locator = None 2623 self.pipeline = utils.OperationNode("transform_with_landmarks", parents=[self]) 2624 return self 2625 2626 2627 def apply_transform(self, T, reset=False, concatenate=False): 2628 """ 2629 Apply a linear or non-linear transformation to the mesh polygonal data. 2630 2631 Arguments: 2632 T : (matrix) 2633 `vtkTransform`, `vtkMatrix4x4` or a 4x4 or 3x3 python or numpy matrix. 2634 reset : (bool) 2635 if True reset the current transformation matrix 2636 to identity after having moved the object, otherwise the internal 2637 matrix will stay the same (to only affect visualization). 2638 It the input transformation has no internal defined matrix (ie. non linear) 2639 then reset will be assumed as True. 2640 concatenate : (bool) 2641 concatenate the transformation with the current existing one 2642 2643 Example: 2644 ```python 2645 from vedo import Cube, show 2646 c1 = Cube().rotate_z(5).x(2).y(1) 2647 print("cube1 position", c1.pos()) 2648 T = c1.get_transform() # rotate by 5 degrees, sum 2 to x and 1 to y 2649 c2 = Cube().c('r4') 2650 c2.apply_transform(T) # ignore previous movements 2651 c2.apply_transform(T, concatenate=True) 2652 c2.apply_transform(T, concatenate=True) 2653 print("cube2 position", c2.pos()) 2654 show(c1, c2, axes=1).close() 2655 ``` 2656 ![](https://vedo.embl.es/images/feats/apply_transform.png) 2657 """ 2658 self.point_locator = None 2659 self.cell_locator = None 2660 2661 if isinstance(T, vtk.vtkMatrix4x4): 2662 tr = vtk.vtkTransform() 2663 tr.SetMatrix(T) 2664 T = tr 2665 2666 elif utils.is_sequence(T): 2667 M = vtk.vtkMatrix4x4() 2668 n = len(T[0]) 2669 for i in range(n): 2670 for j in range(n): 2671 M.SetElement(i, j, T[i][j]) 2672 tr = vtk.vtkTransform() 2673 tr.SetMatrix(M) 2674 T = tr 2675 2676 if reset or not hasattr(T, "GetScale"): # might be non-linear 2677 2678 tf = vtk.vtkTransformPolyDataFilter() 2679 tf.SetTransform(T) 2680 tf.SetInputData(self.polydata()) 2681 tf.Update() 2682 2683 I = vtk.vtkMatrix4x4() 2684 self.PokeMatrix(I) # reset to identity 2685 self.SetUserTransform(None) 2686 2687 self._update(tf.GetOutput()) ### UPDATE 2688 self.transform = T 2689 2690 else: 2691 2692 if concatenate: 2693 2694 M = vtk.vtkTransform() 2695 M.PostMultiply() 2696 M.SetMatrix(self.GetMatrix()) 2697 2698 M.Concatenate(T) 2699 2700 self.SetScale(M.GetScale()) 2701 self.SetOrientation(M.GetOrientation()) 2702 self.SetPosition(M.GetPosition()) 2703 self.transform = M 2704 self.SetUserTransform(None) 2705 2706 else: 2707 2708 self.SetScale(T.GetScale()) 2709 self.SetOrientation(T.GetOrientation()) 2710 self.SetPosition(T.GetPosition()) 2711 self.SetUserTransform(None) 2712 2713 self.transform = T 2714 2715 return self 2716 2717 def normalize(self): 2718 """Scale Mesh average size to unit.""" 2719 coords = self.points() 2720 if not coords.shape[0]: 2721 return self 2722 cm = np.mean(coords, axis=0) 2723 pts = coords - cm 2724 xyz2 = np.sum(pts * pts, axis=0) 2725 scale = 1 / np.sqrt(np.sum(xyz2) / len(pts)) 2726 t = vtk.vtkTransform() 2727 t.PostMultiply() 2728 # t.Translate(-cm) 2729 t.Scale(scale, scale, scale) 2730 # t.Translate(cm) 2731 tf = vtk.vtkTransformPolyDataFilter() 2732 tf.SetInputData(self.inputdata()) 2733 tf.SetTransform(t) 2734 tf.Update() 2735 self.point_locator = None 2736 self.cell_locator = None 2737 return self._update(tf.GetOutput()) 2738 2739 def mirror(self, axis="x", origin=(0, 0, 0), reset=False): 2740 """ 2741 Mirror the mesh along one of the cartesian axes 2742 2743 Arguments: 2744 axis : (str) 2745 axis to use for mirroring, must be set to x, y, z or n. 2746 Or any combination of those. Adding 'n' reverses mesh faces (hence normals). 2747 origin : (list) 2748 use this point as the origin of the mirroring transformation. 2749 reset : (bool) 2750 if True keep into account the current position of the object, 2751 and then reset its internal transformation matrix to Identity. 2752 2753 Examples: 2754 - [mirror.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mirror.py) 2755 2756 ![](https://vedo.embl.es/images/basic/mirror.png) 2757 """ 2758 sx, sy, sz = 1, 1, 1 2759 if "x" in axis.lower(): sx = -1 2760 if "y" in axis.lower(): sy = -1 2761 if "z" in axis.lower(): sz = -1 2762 origin = np.array(origin) 2763 tr = vtk.vtkTransform() 2764 tr.PostMultiply() 2765 tr.Translate(-origin) 2766 tr.Scale(sx, sy, sz) 2767 tr.Translate(origin) 2768 tf = vtk.vtkTransformPolyDataFilter() 2769 tf.SetInputData(self.polydata(reset)) 2770 tf.SetTransform(tr) 2771 tf.Update() 2772 outpoly = tf.GetOutput() 2773 if reset: 2774 self.PokeMatrix(vtk.vtkMatrix4x4()) # reset to identity 2775 if sx * sy * sz < 0 or "n" in axis: 2776 rs = vtk.vtkReverseSense() 2777 rs.SetInputData(outpoly) 2778 rs.ReverseNormalsOff() 2779 rs.Update() 2780 outpoly = rs.GetOutput() 2781 2782 self.point_locator = None 2783 self.cell_locator = None 2784 2785 out = self._update(outpoly) 2786 2787 out.pipeline = utils.OperationNode(f"mirror\naxis = {axis}", parents=[self]) 2788 return out 2789 2790 def shear(self, x=0, y=0, z=0): 2791 """Apply a shear deformation along one of the main axes""" 2792 t = vtk.vtkTransform() 2793 sx, sy, sz = self.GetScale() 2794 t.SetMatrix([sx, x, 0, 0, 2795 y,sy, z, 0, 2796 0, 0,sz, 0, 2797 0, 0, 0, 1]) 2798 self.apply_transform(t, reset=True) 2799 return self 2800 2801 def flip_normals(self): 2802 """Flip all mesh normals. Same as `mesh.mirror('n')`.""" 2803 rs = vtk.vtkReverseSense() 2804 rs.SetInputData(self.inputdata()) 2805 rs.ReverseCellsOff() 2806 rs.ReverseNormalsOn() 2807 rs.Update() 2808 out = self._update(rs.GetOutput()) 2809 self.pipeline = utils.OperationNode("flip_normals", parents=[self]) 2810 return out 2811 2812 ##################################################################################### 2813 def cmap( 2814 self, 2815 input_cmap, 2816 input_array=None, 2817 on="points", 2818 name="Scalars", 2819 vmin=None, 2820 vmax=None, 2821 n_colors=256, 2822 alpha=1.0, 2823 logscale=False, 2824 ): 2825 """ 2826 Set individual point/cell colors by providing a list of scalar values and a color map. 2827 2828 Arguments: 2829 input_cmap : (str, list, vtkLookupTable, matplotlib.colors.LinearSegmentedColormap) 2830 color map scheme to transform a real number into a color. 2831 input_array : (str, list, vtkArray) 2832 can be the string name of an existing array, a numpy array or a `vtkArray`. 2833 on : (str) 2834 either 'points' or 'cells'. 2835 Apply the color map to data which is defined on either points or cells. 2836 name : (str) 2837 give a name to the provided numpy array (if input_array is a numpy array) 2838 vmin : (float) 2839 clip scalars to this minimum value 2840 vmax : (float) 2841 clip scalars to this maximum value 2842 n_colors : (int) 2843 number of distinct colors to be used in colormap table. 2844 alpha : (float, list) 2845 Mesh transparency. Can be a `list` of values one for each vertex. 2846 logscale : (bool) 2847 Use logscale 2848 2849 Examples: 2850 - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py) 2851 - [mesh_alphas.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_alphas.py) 2852 - [mesh_custom.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_custom.py) 2853 (and many others) 2854 2855 ![](https://vedo.embl.es/images/basic/mesh_custom.png) 2856 """ 2857 self._cmap_name = input_cmap 2858 poly = self.inputdata() 2859 2860 if input_array is None: 2861 if not self.pointdata.keys() and self.celldata.keys(): 2862 on = "cells" 2863 if not poly.GetCellData().GetScalars(): 2864 input_array = 0 # pick the first at hand 2865 2866 if on.startswith("point"): 2867 data = poly.GetPointData() 2868 n = poly.GetNumberOfPoints() 2869 elif on.startswith("cell"): 2870 data = poly.GetCellData() 2871 n = poly.GetNumberOfCells() 2872 else: 2873 vedo.logger.error("Must specify in cmap(on=...) to either 'cells' or 'points'") 2874 raise RuntimeError() 2875 2876 if input_array is None: # if None try to fetch the active scalars 2877 arr = data.GetScalars() 2878 if not arr: 2879 vedo.logger.error(f"in cmap(), cannot find any {on} active array ...skip coloring.") 2880 return self 2881 2882 if not arr.GetName(): # sometimes arrays dont have a name.. 2883 arr.SetName(name) 2884 2885 elif isinstance(input_array, str): # if a string is passed 2886 arr = data.GetArray(input_array) 2887 if not arr: 2888 vedo.logger.error(f"in cmap(), cannot find {on} array {input_array} ...skip coloring.") 2889 return self 2890 2891 elif isinstance(input_array, int): # if an int is passed 2892 if input_array < data.GetNumberOfArrays(): 2893 arr = data.GetArray(input_array) 2894 else: 2895 vedo.logger.error(f"in cmap(), cannot find {on} array at {input_array} ...skip coloring.") 2896 return self 2897 2898 elif utils.is_sequence(input_array): # if a numpy array is passed 2899 npts = len(input_array) 2900 if npts != n: 2901 vedo.logger.error(f"in cmap(), nr. of input {on} scalars {npts} != {n} ...skip coloring.") 2902 return self 2903 arr = utils.numpy2vtk(input_array, name=name, dtype=float) 2904 data.AddArray(arr) 2905 data.Modified() 2906 2907 elif isinstance(input_array, vtk.vtkArray): # if a vtkArray is passed 2908 arr = input_array 2909 data.AddArray(arr) 2910 data.Modified() 2911 2912 else: 2913 vedo.logger.error(f"in cmap(), cannot understand input type {type(input_array)}") 2914 raise RuntimeError() 2915 2916 # Now we have array "arr" 2917 array_name = arr.GetName() 2918 2919 if arr.GetNumberOfComponents() == 1: 2920 if vmin is None: 2921 vmin = arr.GetRange()[0] 2922 if vmax is None: 2923 vmax = arr.GetRange()[1] 2924 else: 2925 if vmin is None or vmax is None: 2926 vn = utils.mag(utils.vtk2numpy(arr)) 2927 if vmin is None: 2928 vmin = vn.min() 2929 if vmax is None: 2930 vmax = vn.max() 2931 2932 # interpolate alphas if they are not constant 2933 if not utils.is_sequence(alpha): 2934 alpha = [alpha] * n_colors 2935 else: 2936 v = np.linspace(0, 1, n_colors, endpoint=True) 2937 xp = np.linspace(0, 1, len(alpha), endpoint=True) 2938 alpha = np.interp(v, xp, alpha) 2939 2940 ########################### build the look-up table 2941 if isinstance(input_cmap, vtk.vtkLookupTable): # vtkLookupTable 2942 lut = input_cmap 2943 2944 elif utils.is_sequence(input_cmap): # manual sequence of colors 2945 lut = vtk.vtkLookupTable() 2946 if logscale: 2947 lut.SetScaleToLog10() 2948 lut.SetRange(vmin, vmax) 2949 ncols = len(input_cmap) 2950 lut.SetNumberOfTableValues(ncols) 2951 2952 for i, c in enumerate(input_cmap): 2953 r, g, b = colors.get_color(c) 2954 lut.SetTableValue(i, r, g, b, alpha[i]) 2955 lut.Build() 2956 2957 else: # assume string cmap name OR matplotlib.colors.LinearSegmentedColormap 2958 lut = vtk.vtkLookupTable() 2959 if logscale: 2960 lut.SetScaleToLog10() 2961 lut.SetVectorModeToMagnitude() 2962 lut.SetRange(vmin, vmax) 2963 lut.SetNumberOfTableValues(n_colors) 2964 mycols = colors.color_map(range(n_colors), input_cmap, 0, n_colors) 2965 for i, c in enumerate(mycols): 2966 r, g, b = c 2967 lut.SetTableValue(i, r, g, b, alpha[i]) 2968 lut.Build() 2969 2970 arr.SetLookupTable(lut) 2971 2972 data.SetActiveScalars(array_name) 2973 # data.SetScalars(arr) # wrong! it deletes array in position 0, never use SetScalars 2974 # data.SetActiveAttribute(array_name, 0) # boh! 2975 2976 if data.GetScalars(): 2977 data.GetScalars().SetLookupTable(lut) 2978 data.GetScalars().Modified() 2979 2980 self._mapper.SetLookupTable(lut) 2981 self._mapper.SetColorModeToMapScalars() # so we dont need to convert uint8 scalars 2982 2983 self._mapper.ScalarVisibilityOn() 2984 self._mapper.SetScalarRange(lut.GetRange()) 2985 if on.startswith("point"): 2986 self._mapper.SetScalarModeToUsePointData() 2987 else: 2988 self._mapper.SetScalarModeToUseCellData() 2989 if hasattr(self._mapper, "SetArrayName"): 2990 self._mapper.SetArrayName(array_name) 2991 2992 return self 2993 2994 def cell_individual_colors(self, colorlist): 2995 # DEPRECATED 2996 self.cellcolors = colorlist 2997 print("Please use property mesh.cellcolors=... instead of mesh.cell_individual_colors()") 2998 return self 2999 3000 @property 3001 def cellcolors(self): 3002 """ 3003 Colorize each cell (face) of a mesh by passing 3004 a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A]. 3005 Colors levels and opacities must be in the range [0,255]. 3006 3007 A single constant color can also be passed as string or RGBA. 3008 3009 A cell array named "CellsRGBA" is automatically created. 3010 3011 Examples: 3012 - [color_mesh_cells1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells1.py) 3013 - [color_mesh_cells2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/color_mesh_cells2.py) 3014 3015 ![](https://vedo.embl.es/images/basic/colorMeshCells.png) 3016 """ 3017 if "CellsRGBA" not in self.celldata.keys(): 3018 lut = self.mapper().GetLookupTable() 3019 vscalars = self._data.GetCellData().GetScalars() 3020 if vscalars is None or lut is None: 3021 arr = np.zeros([self.ncells, 4], dtype=np.uint8) 3022 col = np.array(self.property.GetColor()) 3023 col = np.round(col * 255).astype(np.uint8) 3024 alf = self.property.GetOpacity() 3025 alf = np.round(alf * 255).astype(np.uint8) 3026 arr[:, (0, 1, 2)] = col 3027 arr[:, 3] = alf 3028 else: 3029 cols = lut.MapScalars(vscalars, 0, 0) 3030 arr = utils.vtk2numpy(cols) 3031 self.celldata["CellsRGBA"] = arr 3032 self.celldata.select("CellsRGBA") 3033 return self.celldata["CellsRGBA"] 3034 3035 @cellcolors.setter 3036 def cellcolors(self, value): 3037 if isinstance(value, str): 3038 c = colors.get_color(value) 3039 value = np.array([*c, 1]) * 255 3040 value = np.round(value) 3041 3042 value = np.asarray(value) 3043 n = self.ncells 3044 3045 if value.ndim == 1: 3046 value = np.repeat([value], n, axis=0) 3047 3048 if value.shape[1] == 3: 3049 z = np.zeros((n, 1), dtype=np.uint8) 3050 value = np.append(value, z + 255, axis=1) 3051 3052 assert n == value.shape[0] 3053 3054 self.celldata["CellsRGBA"] = value.astype(np.uint8) 3055 self.celldata.select("CellsRGBA") 3056 3057 3058 @property 3059 def pointcolors(self): 3060 """ 3061 Colorize each point (or vertex of a mesh) by passing 3062 a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A]. 3063 Colors levels and opacities must be in the range [0,255]. 3064 3065 A single constant color can also be passed as string or RGBA. 3066 3067 A point array named "PointsRGBA" is automatically created. 3068 """ 3069 if "PointsRGBA" not in self.pointdata.keys(): 3070 lut = self.mapper().GetLookupTable() 3071 vscalars = self._data.GetPointData().GetScalars() 3072 if vscalars is None or lut is None: 3073 arr = np.zeros([self.npoints, 4], dtype=np.uint8) 3074 col = np.array(self.property.GetColor()) 3075 col = np.round(col * 255).astype(np.uint8) 3076 alf = self.property.GetOpacity() 3077 alf = np.round(alf * 255).astype(np.uint8) 3078 arr[:, (0, 1, 2)] = col 3079 arr[:, 3] = alf 3080 else: 3081 cols = lut.MapScalars(vscalars, 0, 0) 3082 arr = utils.vtk2numpy(cols) 3083 self.pointdata["PointsRGBA"] = arr 3084 self.pointdata.select("PointsRGBA") 3085 return self.pointdata["PointsRGBA"] 3086 3087 @pointcolors.setter 3088 def pointcolors(self, value): 3089 if isinstance(value, str): 3090 c = colors.get_color(value) 3091 value = np.array([*c, 1]) * 255 3092 value = np.round(value) 3093 3094 value = np.asarray(value) 3095 n = self.npoints 3096 3097 if value.ndim == 1: 3098 value = np.repeat([value], n, axis=0) 3099 3100 if value.shape[1] == 3: 3101 z = np.zeros((n, 1), dtype=np.uint8) 3102 value = np.append(value, z + 255, axis=1) 3103 3104 assert n == value.shape[0] 3105 3106 self.pointdata["PointsRGBA"] = value.astype(np.uint8) 3107 self.pointdata.select("PointsRGBA") 3108 3109 3110 def interpolate_data_from( 3111 self, 3112 source, 3113 radius=None, 3114 n=None, 3115 kernel="shepard", 3116 exclude=("Normals",), 3117 on="points", 3118 null_strategy=1, 3119 null_value=0, 3120 ): 3121 """ 3122 Interpolate over source to port its data onto the current object using various kernels. 3123 3124 If n (number of closest points to use) is set then radius value is ignored. 3125 3126 Arguments: 3127 kernel : (str) 3128 available kernels are [shepard, gaussian, linear] 3129 null_strategy : (int) 3130 specify a strategy to use when encountering a "null" point 3131 during the interpolation process. Null points occur when the local neighborhood 3132 (of nearby points to interpolate from) is empty. 3133 3134 - Case 0: an output array is created that marks points 3135 as being valid (=1) or null (invalid =0), and the null_value is set as well 3136 - Case 1: the output data value(s) are set to the provided null_value 3137 - Case 2: simply use the closest point to perform the interpolation. 3138 null_value : (float) 3139 see above. 3140 3141 Examples: 3142 - [interpolateMeshArray.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolateMeshArray.py) 3143 3144 ![](https://vedo.embl.es/images/advanced/interpolateMeshArray.png) 3145 """ 3146 if radius is None and not n: 3147 vedo.logger.error("in interpolate_data_from(): please set either radius or n") 3148 raise RuntimeError 3149 3150 if on == "points": 3151 points = source.polydata() 3152 elif on == "cells": 3153 poly2 = vtk.vtkPolyData() 3154 poly2.ShallowCopy(source.polydata()) 3155 c2p = vtk.vtkCellDataToPointData() 3156 c2p.SetInputData(poly2) 3157 c2p.Update() 3158 points = c2p.GetOutput() 3159 else: 3160 vedo.logger.error("in interpolate_data_from(), on must be on points or cells") 3161 raise RuntimeError() 3162 3163 locator = vtk.vtkPointLocator() 3164 locator.SetDataSet(points) 3165 locator.BuildLocator() 3166 3167 if kernel.lower() == "shepard": 3168 kern = vtk.vtkShepardKernel() 3169 kern.SetPowerParameter(2) 3170 elif kernel.lower() == "gaussian": 3171 kern = vtk.vtkGaussianKernel() 3172 kern.SetSharpness(2) 3173 elif kernel.lower() == "linear": 3174 kern = vtk.vtkLinearKernel() 3175 else: 3176 vedo.logger.error("available kernels are: [shepard, gaussian, linear]") 3177 raise RuntimeError() 3178 3179 if n: 3180 kern.SetNumberOfPoints(n) 3181 kern.SetKernelFootprintToNClosest() 3182 else: 3183 kern.SetRadius(radius) 3184 3185 interpolator = vtk.vtkPointInterpolator() 3186 interpolator.SetInputData(self.polydata()) 3187 interpolator.SetSourceData(points) 3188 interpolator.SetKernel(kern) 3189 interpolator.SetLocator(locator) 3190 interpolator.PassFieldArraysOff() 3191 interpolator.SetNullPointsStrategy(null_strategy) 3192 interpolator.SetNullValue(null_value) 3193 interpolator.SetValidPointsMaskArrayName("ValidPointMask") 3194 for ex in exclude: 3195 interpolator.AddExcludedArray(ex) 3196 interpolator.Update() 3197 3198 if on == "cells": 3199 p2c = vtk.vtkPointDataToCellData() 3200 p2c.SetInputData(interpolator.GetOutput()) 3201 p2c.Update() 3202 cpoly = p2c.GetOutput() 3203 else: 3204 cpoly = interpolator.GetOutput() 3205 3206 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 3207 self._update(cpoly) 3208 else: 3209 # bring the underlying polydata to where _data is 3210 M = vtk.vtkMatrix4x4() 3211 M.DeepCopy(self.GetMatrix()) 3212 M.Invert() 3213 tr = vtk.vtkTransform() 3214 tr.SetMatrix(M) 3215 tf = vtk.vtkTransformPolyDataFilter() 3216 tf.SetTransform(tr) 3217 tf.SetInputData(cpoly) 3218 tf.Update() 3219 self._update(tf.GetOutput()) 3220 3221 self.pipeline = utils.OperationNode("interpolate_data_from", parents=[self, source]) 3222 return self 3223 3224 def add_gaussian_noise(self, sigma=1.0): 3225 """ 3226 Add gaussian noise to point positions. 3227 An extra array is added named "GaussianNoise" with the shifts. 3228 3229 Arguments: 3230 sigma : (float) 3231 nr. of standard deviations, expressed in percent of the diagonal size of mesh. 3232 Can also be a list [sigma_x, sigma_y, sigma_z]. 3233 3234 Examples: 3235 ```python 3236 from vedo import Sphere 3237 Sphere().add_gaussian_noise(1.0).point_size(8).show().close() 3238 ``` 3239 """ 3240 sz = self.diagonal_size() 3241 pts = self.points() 3242 n = len(pts) 3243 ns = (np.random.randn(n, 3) * sigma) * (sz / 100) 3244 vpts = vtk.vtkPoints() 3245 vpts.SetNumberOfPoints(n) 3246 vpts.SetData(utils.numpy2vtk(pts + ns, dtype=np.float32)) 3247 self.inputdata().SetPoints(vpts) 3248 self.inputdata().GetPoints().Modified() 3249 self.pointdata["GaussianNoise"] = -ns 3250 self.pipeline = utils.OperationNode( 3251 "gaussian_noise", parents=[self], shape="egg", comment=f"sigma = {sigma}" 3252 ) 3253 return self 3254 3255 3256 def closest_point(self, pt, n=1, radius=None, return_point_id=False, return_cell_id=False): 3257 """ 3258 Find the closest point(s) on a mesh given from the input point `pt`. 3259 3260 Arguments: 3261 n : (int) 3262 if greater than 1, return a list of n ordered closest points 3263 radius : (float) 3264 if given, get all points within that radius. Then n is ignored. 3265 return_point_id : (bool) 3266 return point ID instead of coordinates 3267 return_cell_id : (bool) 3268 return cell ID in which the closest point sits 3269 3270 Examples: 3271 - [align1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align1.py) 3272 - [fitplanes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/fitplanes.py) 3273 - [quadratic_morphing.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/quadratic_morphing.py) 3274 3275 .. note:: 3276 The appropriate tree search locator is built on the fly and cached for speed. 3277 3278 If you want to reset it use `mymesh.point_locator=None` 3279 """ 3280 # NB: every time the mesh moves or is warped the locators are set to None 3281 if (n > 1 or radius) or (n == 1 and return_point_id): 3282 3283 poly = None 3284 if not self.point_locator: 3285 poly = self.polydata() 3286 self.point_locator = vtk.vtkStaticPointLocator() 3287 self.point_locator.SetDataSet(poly) 3288 self.point_locator.BuildLocator() 3289 3290 ########## 3291 if radius: 3292 vtklist = vtk.vtkIdList() 3293 self.point_locator.FindPointsWithinRadius(radius, pt, vtklist) 3294 elif n > 1: 3295 vtklist = vtk.vtkIdList() 3296 self.point_locator.FindClosestNPoints(n, pt, vtklist) 3297 else: # n==1 hence return_point_id==True 3298 ######## 3299 return self.point_locator.FindClosestPoint(pt) 3300 ######## 3301 3302 if return_point_id: 3303 ######## 3304 return utils.vtk2numpy(vtklist) 3305 ######## 3306 3307 if not poly: 3308 poly = self.polydata() 3309 trgp = [] 3310 for i in range(vtklist.GetNumberOfIds()): 3311 trgp_ = [0, 0, 0] 3312 vi = vtklist.GetId(i) 3313 poly.GetPoints().GetPoint(vi, trgp_) 3314 trgp.append(trgp_) 3315 ######## 3316 return np.array(trgp) 3317 ######## 3318 3319 else: 3320 3321 if not self.cell_locator: 3322 poly = self.polydata() 3323 3324 # As per Miquel example with limbs the vtkStaticCellLocator doesnt work !! 3325 # https://discourse.vtk.org/t/vtkstaticcelllocator-problem-vtk9-0-3/7854/4 3326 if vedo.vtk_version[0] >= 9 and vedo.vtk_version[0] > 0: 3327 self.cell_locator = vtk.vtkStaticCellLocator() 3328 else: 3329 self.cell_locator = vtk.vtkCellLocator() 3330 3331 self.cell_locator.SetDataSet(poly) 3332 self.cell_locator.BuildLocator() 3333 3334 trgp = [0, 0, 0] 3335 cid = vtk.mutable(0) 3336 dist2 = vtk.mutable(0) 3337 subid = vtk.mutable(0) 3338 self.cell_locator.FindClosestPoint(pt, trgp, cid, subid, dist2) 3339 3340 if return_cell_id: 3341 return int(cid) 3342 3343 return np.array(trgp) 3344 3345 3346 def hausdorff_distance(self, points): 3347 """ 3348 Compute the Hausdorff distance to the input point set. 3349 Returns a single `float`. 3350 3351 Example: 3352 ```python 3353 from vedo import * 3354 t = np.linspace(0, 2*np.pi, 100) 3355 x = 4/3 * sin(t)**3 3356 y = cos(t) - cos(2*t)/3 - cos(3*t)/6 - cos(4*t)/12 3357 pol1 = Line(np.c_[x,y], closed=True).triangulate() 3358 pol2 = Polygon(nsides=5).pos(2,2) 3359 d12 = pol1.distance_to(pol2) 3360 d21 = pol2.distance_to(pol1) 3361 pol1.lw(0).cmap("viridis") 3362 pol2.lw(0).cmap("viridis") 3363 print("distance d12, d21 :", min(d12), min(d21)) 3364 print("hausdorff distance:", pol1.hausdorff_distance(pol2)) 3365 print("chamfer distance :", pol1.chamfer_distance(pol2)) 3366 show(pol1, pol2, axes=1) 3367 ``` 3368 ![](https://vedo.embl.es/images/feats/heart.png) 3369 """ 3370 hp = vtk.vtkHausdorffDistancePointSetFilter() 3371 hp.SetInputData(0, self.polydata()) 3372 hp.SetInputData(1, points.polydata()) 3373 hp.SetTargetDistanceMethodToPointToCell() 3374 hp.Update() 3375 return hp.GetHausdorffDistance() 3376 3377 def chamfer_distance(self, pcloud): 3378 """ 3379 Compute the Chamfer distance to the input point set. 3380 Returns a single `float`. 3381 """ 3382 if not pcloud.point_locator: 3383 pcloud.point_locator = vtk.vtkPointLocator() 3384 pcloud.point_locator.SetDataSet(pcloud.polydata()) 3385 pcloud.point_locator.BuildLocator() 3386 if not self.point_locator: 3387 self.point_locator = vtk.vtkPointLocator() 3388 self.point_locator.SetDataSet(self.polydata()) 3389 self.point_locator.BuildLocator() 3390 3391 ps1 = self.points() 3392 ps2 = pcloud.points() 3393 3394 ids12 = [] 3395 for p in ps1: 3396 pid12 = pcloud.point_locator.FindClosestPoint(p) 3397 ids12.append(pid12) 3398 deltav = ps2[ids12] - ps1 3399 da = np.mean(np.linalg.norm(deltav, axis=1)) 3400 3401 ids21 = [] 3402 for p in ps2: 3403 pid21 = self.point_locator.FindClosestPoint(p) 3404 ids21.append(pid21) 3405 deltav = ps1[ids21] - ps2 3406 db = np.mean(np.linalg.norm(deltav, axis=1)) 3407 return (da + db) / 2 3408 3409 def remove_outliers(self, radius, neighbors=5): 3410 """ 3411 Remove outliers from a cloud of points within the specified `radius` search. 3412 3413 Arguments: 3414 radius : (float) 3415 Specify the local search radius. 3416 neighbors : (int) 3417 Specify the number of neighbors that a point must have, 3418 within the specified radius, for the point to not be considered isolated. 3419 3420 Examples: 3421 - [clustering.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/clustering.py) 3422 3423 ![](https://vedo.embl.es/images/basic/clustering.png) 3424 """ 3425 removal = vtk.vtkRadiusOutlierRemoval() 3426 removal.SetInputData(self.polydata()) 3427 removal.SetRadius(radius) 3428 removal.SetNumberOfNeighbors(neighbors) 3429 removal.GenerateOutliersOff() 3430 removal.Update() 3431 inputobj = removal.GetOutput() 3432 if inputobj.GetNumberOfCells() == 0: 3433 carr = vtk.vtkCellArray() 3434 for i in range(inputobj.GetNumberOfPoints()): 3435 carr.InsertNextCell(1) 3436 carr.InsertCellPoint(i) 3437 inputobj.SetVerts(carr) 3438 self._update(inputobj) 3439 self.mapper().ScalarVisibilityOff() 3440 self.pipeline = utils.OperationNode("remove_outliers", parents=[self]) 3441 return self 3442 3443 def smooth_mls_1d(self, f=0.2, radius=None): 3444 """ 3445 Smooth mesh or points with a `Moving Least Squares` variant. 3446 The point data array "Variances" will contain the residue calculated for each point. 3447 Input mesh's polydata is modified. 3448 3449 Arguments: 3450 f : (float) 3451 smoothing factor - typical range is [0,2]. 3452 radius : (float) 3453 radius search in absolute units. If set then `f` is ignored. 3454 3455 Examples: 3456 - [moving_least_squares1D.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/moving_least_squares1D.py) 3457 - [skeletonize.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/skeletonize.py) 3458 3459 ![](https://vedo.embl.es/images/advanced/moving_least_squares1D.png) 3460 """ 3461 coords = self.points() 3462 ncoords = len(coords) 3463 3464 if radius: 3465 Ncp = 0 3466 else: 3467 Ncp = int(ncoords * f / 10) 3468 if Ncp < 5: 3469 vedo.logger.warning(f"Please choose a fraction higher than {f}") 3470 Ncp = 5 3471 3472 variances, newline = [], [] 3473 for p in coords: 3474 points = self.closest_point(p, n=Ncp, radius=radius) 3475 if len(points) < 4: 3476 continue 3477 3478 points = np.array(points) 3479 pointsmean = points.mean(axis=0) # plane center 3480 _, dd, vv = np.linalg.svd(points - pointsmean) 3481 newp = np.dot(p - pointsmean, vv[0]) * vv[0] + pointsmean 3482 variances.append(dd[1] + dd[2]) 3483 newline.append(newp) 3484 3485 vdata = utils.numpy2vtk(np.array(variances)) 3486 vdata.SetName("Variances") 3487 self.inputdata().GetPointData().AddArray(vdata) 3488 self.inputdata().GetPointData().Modified() 3489 self.points(newline) 3490 self.pipeline = utils.OperationNode("smooth_mls_1d", parents=[self]) 3491 return self 3492 3493 def smooth_mls_2d(self, f=0.2, radius=None): 3494 """ 3495 Smooth mesh or points with a `Moving Least Squares` algorithm variant. 3496 The list `mesh.info['variances']` contains the residue calculated for each point. 3497 When a radius is specified points that are isolated will not be moved and will get 3498 a False entry in array `mesh.info['is_valid']`. 3499 3500 Arguments: 3501 f : (float) 3502 smoothing factor - typical range is [0,2]. 3503 radius : (float) 3504 radius search in absolute units. If set then `f` is ignored. 3505 3506 Examples: 3507 - [moving_least_squares2D.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/moving_least_squares2D.py) 3508 - [recosurface.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/recosurface.py) 3509 3510 ![](https://vedo.embl.es/images/advanced/recosurface.png) 3511 """ 3512 coords = self.points() 3513 ncoords = len(coords) 3514 3515 if radius: 3516 Ncp = 1 3517 else: 3518 Ncp = int(ncoords * f / 100) 3519 if Ncp < 4: 3520 vedo.logger.error(f"MLS2D: Please choose a fraction higher than {f}") 3521 Ncp = 4 3522 3523 variances, newpts, valid = [], [], [] 3524 pb = None 3525 if ncoords > 10000: 3526 pb = utils.ProgressBar(0, ncoords) 3527 for p in coords: 3528 if pb: 3529 pb.print("smoothMLS2D working ...") 3530 pts = self.closest_point(p, n=Ncp, radius=radius) 3531 if len(pts) > 3: 3532 ptsmean = pts.mean(axis=0) # plane center 3533 _, dd, vv = np.linalg.svd(pts - ptsmean) 3534 cv = np.cross(vv[0], vv[1]) 3535 t = (np.dot(cv, ptsmean) - np.dot(cv, p)) / np.dot(cv, cv) 3536 newp = p + cv * t 3537 newpts.append(newp) 3538 variances.append(dd[2]) 3539 if radius: 3540 valid.append(True) 3541 else: 3542 newpts.append(p) 3543 variances.append(0) 3544 if radius: 3545 valid.append(False) 3546 3547 self.info["variances"] = np.array(variances) 3548 self.info["is_valid"] = np.array(valid) 3549 self.points(newpts) 3550 3551 self.pipeline = utils.OperationNode("smooth_mls_2d", parents=[self]) 3552 return self 3553 3554 def smooth_lloyd_2d(self, iterations=2, bounds=None, options="Qbb Qc Qx"): 3555 """Lloyd relaxation of a 2D pointcloud.""" 3556 # Credits: https://hatarilabs.com/ih-en/ 3557 # tutorial-to-create-a-geospatial-voronoi-sh-mesh-with-python-scipy-and-geopandas 3558 from scipy.spatial import Voronoi as scipy_voronoi 3559 3560 def _constrain_points(points): 3561 # Update any points that have drifted beyond the boundaries of this space 3562 if bounds is not None: 3563 for point in points: 3564 if point[0] < bounds[0]: point[0] = bounds[0] 3565 if point[0] > bounds[1]: point[0] = bounds[1] 3566 if point[1] < bounds[2]: point[1] = bounds[2] 3567 if point[1] > bounds[3]: point[1] = bounds[3] 3568 return points 3569 3570 def _find_centroid(vertices): 3571 # The equation for the method used here to find the centroid of a 3572 # 2D polygon is given here: https://en.wikipedia.org/wiki/Centroid#Of_a_polygon 3573 area = 0 3574 centroid_x = 0 3575 centroid_y = 0 3576 for i in range(len(vertices) - 1): 3577 step = (vertices[i, 0] * vertices[i + 1, 1]) - (vertices[i + 1, 0] * vertices[i, 1]) 3578 centroid_x += (vertices[i, 0] + vertices[i + 1, 0]) * step 3579 centroid_y += (vertices[i, 1] + vertices[i + 1, 1]) * step 3580 area += step 3581 if area: 3582 centroid_x = (1.0 / (3.0 * area)) * centroid_x 3583 centroid_y = (1.0 / (3.0 * area)) * centroid_y 3584 # prevent centroids from escaping bounding box 3585 return _constrain_points([[centroid_x, centroid_y]])[0] 3586 3587 def _relax(voron): 3588 # Moves each point to the centroid of its cell in the voronoi 3589 # map to "relax" the points (i.e. jitter the points so as 3590 # to spread them out within the space). 3591 centroids = [] 3592 for idx in voron.point_region: 3593 # the region is a series of indices into voronoi.vertices 3594 # remove point at infinity, designated by index -1 3595 region = [i for i in voron.regions[idx] if i != -1] 3596 # enclose the polygon 3597 region = region + [region[0]] 3598 verts = voron.vertices[region] 3599 # find the centroid of those vertices 3600 centroids.append(_find_centroid(verts)) 3601 return _constrain_points(centroids) 3602 3603 if bounds is None: 3604 bounds = self.bounds() 3605 3606 pts = self.points()[:, (0, 1)] 3607 for i in range(iterations): 3608 vor = scipy_voronoi(pts, qhull_options=options) 3609 _constrain_points(vor.vertices) 3610 pts = _relax(vor) 3611 # m = vedo.Mesh([pts, self.faces()]) # not yet working properly 3612 out = Points(pts, c="k") 3613 out.pipeline = utils.OperationNode("smooth_lloyd", parents=[self]) 3614 return out 3615 3616 def project_on_plane(self, plane="z", point=None, direction=None): 3617 """ 3618 Project the mesh on one of the Cartesian planes. 3619 3620 Arguments: 3621 plane : (str, Plane) 3622 if plane is `str`, plane can be one of ['x', 'y', 'z'], 3623 represents x-plane, y-plane and z-plane, respectively. 3624 Otherwise, plane should be an instance of `vedo.shapes.Plane`. 3625 point : (float, array) 3626 if plane is `str`, point should be a float represents the intercept. 3627 Otherwise, point is the camera point of perspective projection 3628 direction : (array) 3629 direction of oblique projection 3630 3631 Note: 3632 Parameters `point` and `direction` are only used if the given plane 3633 is an instance of `vedo.shapes.Plane`. And one of these two params 3634 should be left as `None` to specify the projection type. 3635 3636 Example: 3637 ```python 3638 s.project_on_plane(plane='z') # project to z-plane 3639 plane = Plane(pos=(4, 8, -4), normal=(-1, 0, 1), s=(5,5)) 3640 s.project_on_plane(plane=plane) # orthogonal projection 3641 s.project_on_plane(plane=plane, point=(6, 6, 6)) # perspective projection 3642 s.project_on_plane(plane=plane, direction=(1, 2, -1)) # oblique projection 3643 ``` 3644 3645 Examples: 3646 - [silhouette2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/silhouette2.py) 3647 3648 ![](https://vedo.embl.es/images/basic/silhouette2.png) 3649 """ 3650 coords = self.points() 3651 3652 if plane == "x": 3653 coords[:, 0] = self.GetOrigin()[0] 3654 intercept = self.xbounds()[0] if point is None else point 3655 self.x(intercept) 3656 elif plane == "y": 3657 coords[:, 1] = self.GetOrigin()[1] 3658 intercept = self.ybounds()[0] if point is None else point 3659 self.y(intercept) 3660 elif plane == "z": 3661 coords[:, 2] = self.GetOrigin()[2] 3662 intercept = self.zbounds()[0] if point is None else point 3663 self.z(intercept) 3664 3665 elif isinstance(plane, vedo.shapes.Plane): 3666 normal = plane.normal / np.linalg.norm(plane.normal) 3667 pl = np.hstack((normal, -np.dot(plane.pos(), normal))).reshape(4, 1) 3668 if direction is None and point is None: 3669 # orthogonal projection 3670 pt = np.hstack((normal, [0])).reshape(4, 1) 3671 # proj_mat = pt.T @ pl * np.eye(4) - pt @ pl.T # python3 only 3672 proj_mat = np.matmul(pt.T, pl) * np.eye(4) - np.matmul(pt, pl.T) 3673 3674 elif direction is None: 3675 # perspective projection 3676 pt = np.hstack((np.array(point), [1])).reshape(4, 1) 3677 # proj_mat = pt.T @ pl * np.eye(4) - pt @ pl.T 3678 proj_mat = np.matmul(pt.T, pl) * np.eye(4) - np.matmul(pt, pl.T) 3679 3680 elif point is None: 3681 # oblique projection 3682 pt = np.hstack((np.array(direction), [0])).reshape(4, 1) 3683 # proj_mat = pt.T @ pl * np.eye(4) - pt @ pl.T 3684 proj_mat = np.matmul(pt.T, pl) * np.eye(4) - np.matmul(pt, pl.T) 3685 3686 coords = np.concatenate([coords, np.ones((coords.shape[:-1] + (1,)))], axis=-1) 3687 # coords = coords @ proj_mat.T 3688 coords = np.matmul(coords, proj_mat.T) 3689 coords = coords[:, :3] / coords[:, 3:] 3690 3691 else: 3692 vedo.logger.error(f"unknown plane {plane}") 3693 raise RuntimeError() 3694 3695 self.alpha(0.1) 3696 self.points(coords) 3697 return self 3698 3699 def warp(self, source, target, sigma=1.0, mode="3d"): 3700 """ 3701 `Thin Plate Spline` transformations describe a nonlinear warp transform defined by a set 3702 of source and target landmarks. Any point on the mesh close to a source landmark will 3703 be moved to a place close to the corresponding target landmark. 3704 The points in between are interpolated smoothly using 3705 Bookstein's Thin Plate Spline algorithm. 3706 3707 Transformation object can be accessed with `mesh.transform`. 3708 3709 Arguments: 3710 sigma : (float) 3711 specify the 'stiffness' of the spline. 3712 mode : (str) 3713 set the basis function to either abs(R) (for 3d) or R2LogR (for 2d meshes) 3714 3715 Examples: 3716 - [interpolate_field.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolate_field.py) 3717 - [warp1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/warp1.py) 3718 - [warp2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/warp2.py) 3719 3720 ![](https://vedo.embl.es/images/advanced/warp2.png) 3721 """ 3722 parents = [self] 3723 if isinstance(source, Points): 3724 parents.append(source) 3725 source = source.points() 3726 else: 3727 source = utils.make3d(source) 3728 3729 if isinstance(target, Points): 3730 parents.append(target) 3731 target = target.points() 3732 else: 3733 target = utils.make3d(target) 3734 3735 ns = len(source) 3736 ptsou = vtk.vtkPoints() 3737 ptsou.SetNumberOfPoints(ns) 3738 for i in range(ns): 3739 ptsou.SetPoint(i, source[i]) 3740 3741 nt = len(target) 3742 if ns != nt: 3743 vedo.logger.error(f"#source {ns} != {nt} #target points") 3744 raise RuntimeError() 3745 3746 pttar = vtk.vtkPoints() 3747 pttar.SetNumberOfPoints(nt) 3748 for i in range(ns): 3749 pttar.SetPoint(i, target[i]) 3750 3751 T = vtk.vtkThinPlateSplineTransform() 3752 if mode.lower() == "3d": 3753 T.SetBasisToR() 3754 elif mode.lower() == "2d": 3755 T.SetBasisToR2LogR() 3756 else: 3757 vedo.logger.error(f"unknown mode {mode}") 3758 raise RuntimeError() 3759 3760 T.SetSigma(sigma) 3761 T.SetSourceLandmarks(ptsou) 3762 T.SetTargetLandmarks(pttar) 3763 self.transform = T 3764 self.apply_transform(T, reset=True) 3765 3766 self.pipeline = utils.OperationNode("warp", parents=parents) 3767 return self 3768 3769 def cut_with_plane(self, origin=(0, 0, 0), normal=(1, 0, 0), invert=False): 3770 """ 3771 Cut the mesh with the plane defined by a point and a normal. 3772 3773 Arguments: 3774 origin : (array) 3775 the cutting plane goes through this point 3776 normal : (array) 3777 normal of the cutting plane 3778 3779 Example: 3780 ```python 3781 from vedo import Cube 3782 cube = Cube().cut_with_plane(normal=(1,1,1)) 3783 cube.back_color('pink').show() 3784 ``` 3785 ![](https://vedo.embl.es/images/feats/cut_with_plane_cube.png) 3786 3787 Examples: 3788 - [trail.py](https://github.com/marcomusy/vedo/blob/master/examples/simulations/trail.py) 3789 3790 ![](https://vedo.embl.es/images/simulations/trail.gif) 3791 3792 Check out also: 3793 `cut_with_box()`, `cut_with_cylinder()`, `cut_with_sphere()`. 3794 """ 3795 s = str(normal) 3796 if "x" in s: 3797 normal = (1, 0, 0) 3798 if "-" in s: 3799 normal = -np.array(normal) 3800 elif "y" in s: 3801 normal = (0, 1, 0) 3802 if "-" in s: 3803 normal = -np.array(normal) 3804 elif "z" in s: 3805 normal = (0, 0, 1) 3806 if "-" in s: 3807 normal = -np.array(normal) 3808 plane = vtk.vtkPlane() 3809 plane.SetOrigin(origin) 3810 plane.SetNormal(normal) 3811 3812 clipper = vtk.vtkClipPolyData() 3813 clipper.SetInputData(self.polydata(True)) # must be True 3814 clipper.SetClipFunction(plane) 3815 clipper.GenerateClippedOutputOff() 3816 clipper.GenerateClipScalarsOff() 3817 clipper.SetInsideOut(invert) 3818 clipper.SetValue(0) 3819 clipper.Update() 3820 3821 cpoly = clipper.GetOutput() 3822 3823 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 3824 self._update(cpoly) 3825 else: 3826 # bring the underlying polydata to where _data is 3827 M = vtk.vtkMatrix4x4() 3828 M.DeepCopy(self.GetMatrix()) 3829 M.Invert() 3830 tr = vtk.vtkTransform() 3831 tr.SetMatrix(M) 3832 tf = vtk.vtkTransformPolyDataFilter() 3833 tf.SetTransform(tr) 3834 tf.SetInputData(cpoly) 3835 tf.Update() 3836 self._update(tf.GetOutput()) 3837 3838 self.pipeline = utils.OperationNode("cut_with_plane", parents=[self]) 3839 return self 3840 3841 def cut_with_planes(self, origins, normals, invert=False): 3842 """ 3843 Cut the mesh with a convex set of planes defined by points and normals. 3844 3845 Arguments: 3846 origins : (array) 3847 each cutting plane goes through this point 3848 normals : (array) 3849 normal of each of the cutting planes 3850 3851 Check out also: 3852 `cut_with_box()`, `cut_with_cylinder()`, `cut_with_sphere()` 3853 """ 3854 3855 vpoints = vtk.vtkPoints() 3856 for p in utils.make3d(origins): 3857 vpoints.InsertNextPoint(p) 3858 normals = utils.make3d(normals) 3859 3860 planes = vtk.vtkPlanes() 3861 planes.SetPoints(vpoints) 3862 planes.SetNormals(utils.numpy2vtk(normals, dtype=float)) 3863 3864 clipper = vtk.vtkClipPolyData() 3865 clipper.SetInputData(self.polydata(True)) # must be True 3866 clipper.SetInsideOut(invert) 3867 clipper.SetClipFunction(planes) 3868 clipper.GenerateClippedOutputOff() 3869 clipper.GenerateClipScalarsOff() 3870 clipper.SetValue(0) 3871 clipper.Update() 3872 3873 cpoly = clipper.GetOutput() 3874 3875 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 3876 self._update(cpoly) 3877 else: 3878 # bring the underlying polydata to where _data is 3879 M = vtk.vtkMatrix4x4() 3880 M.DeepCopy(self.GetMatrix()) 3881 M.Invert() 3882 tr = vtk.vtkTransform() 3883 tr.SetMatrix(M) 3884 tf = vtk.vtkTransformPolyDataFilter() 3885 tf.SetTransform(tr) 3886 tf.SetInputData(cpoly) 3887 tf.Update() 3888 self._update(tf.GetOutput()) 3889 3890 self.pipeline = utils.OperationNode("cut_with_planes", parents=[self]) 3891 return self 3892 3893 def cut_with_box(self, bounds, invert=False): 3894 """ 3895 Cut the current mesh with a box or a set of boxes. 3896 This is much faster than `cut_with_mesh()`. 3897 3898 Input `bounds` can be either: 3899 - a Mesh or Points object 3900 - a list of 6 number representing a bounding box `[xmin,xmax, ymin,ymax, zmin,zmax]` 3901 - a list of bounding boxes like the above: `[[xmin1,...], [xmin2,...], ...]` 3902 3903 Example: 3904 ```python 3905 from vedo import Sphere, Cube, show 3906 mesh = Sphere(r=1, res=50) 3907 box = Cube(side=1.5).wireframe() 3908 mesh.cut_with_box(box) 3909 show(mesh, box, axes=1) 3910 ``` 3911 ![](https://vedo.embl.es/images/feats/cut_with_box_cube.png) 3912 3913 Check out also: 3914 `cut_with_line()`, `cut_with_plane()`, `cut_with_cylinder()` 3915 """ 3916 if isinstance(bounds, Points): 3917 bounds = bounds.bounds() 3918 3919 box = vtk.vtkBox() 3920 if utils.is_sequence(bounds[0]): 3921 for bs in bounds: 3922 box.AddBounds(bs) 3923 else: 3924 box.SetBounds(bounds) 3925 3926 clipper = vtk.vtkClipPolyData() 3927 clipper.SetInputData(self.polydata(True)) # must be True 3928 clipper.SetClipFunction(box) 3929 clipper.SetInsideOut(not invert) 3930 clipper.GenerateClippedOutputOff() 3931 clipper.GenerateClipScalarsOff() 3932 clipper.SetValue(0) 3933 clipper.Update() 3934 cpoly = clipper.GetOutput() 3935 3936 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 3937 self._update(cpoly) 3938 else: 3939 # bring the underlying polydata to where _data is 3940 M = vtk.vtkMatrix4x4() 3941 M.DeepCopy(self.GetMatrix()) 3942 M.Invert() 3943 tr = vtk.vtkTransform() 3944 tr.SetMatrix(M) 3945 tf = vtk.vtkTransformPolyDataFilter() 3946 tf.SetTransform(tr) 3947 tf.SetInputData(cpoly) 3948 tf.Update() 3949 self._update(tf.GetOutput()) 3950 3951 self.pipeline = utils.OperationNode("cut_with_box", parents=[self]) 3952 return self 3953 3954 def cut_with_line(self, points, invert=False, closed=True): 3955 """ 3956 Cut the current mesh with a line vertically in the z-axis direction like a cookie cutter. 3957 The polyline is defined by a set of points (z-coordinates are ignored). 3958 This is much faster than `cut_with_mesh()`. 3959 3960 Check out also: 3961 `cut_with_box()`, `cut_with_plane()`, `cut_with_sphere()` 3962 """ 3963 pplane = vtk.vtkPolyPlane() 3964 if isinstance(points, Points): 3965 points = points.points().tolist() 3966 3967 if closed: 3968 if isinstance(points, np.ndarray): 3969 points = points.tolist() 3970 points.append(points[0]) 3971 3972 vpoints = vtk.vtkPoints() 3973 for p in points: 3974 if len(p) == 2: 3975 p = [p[0], p[1], 0.0] 3976 vpoints.InsertNextPoint(p) 3977 3978 n = len(points) 3979 polyline = vtk.vtkPolyLine() 3980 polyline.Initialize(n, vpoints) 3981 polyline.GetPointIds().SetNumberOfIds(n) 3982 for i in range(n): 3983 polyline.GetPointIds().SetId(i, i) 3984 pplane.SetPolyLine(polyline) 3985 3986 clipper = vtk.vtkClipPolyData() 3987 clipper.SetInputData(self.polydata(True)) # must be True 3988 clipper.SetClipFunction(pplane) 3989 clipper.SetInsideOut(invert) 3990 clipper.GenerateClippedOutputOff() 3991 clipper.GenerateClipScalarsOff() 3992 clipper.SetValue(0) 3993 clipper.Update() 3994 cpoly = clipper.GetOutput() 3995 3996 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 3997 self._update(cpoly) 3998 else: 3999 # bring the underlying polydata to where _data is 4000 M = vtk.vtkMatrix4x4() 4001 M.DeepCopy(self.GetMatrix()) 4002 M.Invert() 4003 tr = vtk.vtkTransform() 4004 tr.SetMatrix(M) 4005 tf = vtk.vtkTransformPolyDataFilter() 4006 tf.SetTransform(tr) 4007 tf.SetInputData(cpoly) 4008 tf.Update() 4009 self._update(tf.GetOutput()) 4010 4011 self.pipeline = utils.OperationNode("cut_with_line", parents=[self]) 4012 return self 4013 4014 def cut_with_cookiecutter(self, lines): 4015 """ 4016 Cut the current mesh with a single line or a set of lines. 4017 4018 Input `lines` can be either: 4019 - a `Mesh` or `Points` object 4020 - a list of 3D points: `[(x1,y1,z1), (x2,y2,z2), ...]` 4021 - a list of 2D points: `[(x1,y1), (x2,y2), ...]` 4022 4023 Example: 4024 ```python 4025 from vedo import * 4026 grid = Mesh(dataurl + "dolfin_fine.vtk") 4027 grid.compute_quality().cmap("Greens") 4028 pols = merge( 4029 Polygon(nsides=10, r=0.3).pos(0.7, 0.3), 4030 Polygon(nsides=10, r=0.2).pos(0.3, 0.7), 4031 ) 4032 lines = pols.boundaries() 4033 grid.cut_with_cookiecutter(lines) 4034 show(grid, lines, axes=8, bg='blackboard').close() 4035 ``` 4036 ![](https://vedo.embl.es/images/feats/cookiecutter.png) 4037 4038 Check out also: 4039 `cut_with_line()` and `cut_with_point_loop()` 4040 4041 Note: 4042 In case of a warning message like: 4043 "Mesh and trim loop point data attributes are different" 4044 consider interpolating the mesh point data to the loop points, 4045 Eg. (in the above example): 4046 ```python 4047 lines = pols.boundaries().interpolate_data_from(grid, n=2) 4048 ``` 4049 4050 Note: 4051 trying to invert the selection by reversing the loop order 4052 will have no effect in this method, hence it does not have 4053 the `invert` option. 4054 """ 4055 if utils.is_sequence(lines): 4056 lines = utils.make3d(lines) 4057 iline = list(range(len(lines))) + [0] 4058 poly = utils.buildPolyData(lines, lines=[iline]) 4059 else: 4060 poly = lines.polydata() 4061 4062 # if invert: # not working 4063 # rev = vtk.vtkReverseSense() 4064 # rev.ReverseCellsOn() 4065 # rev.SetInputData(poly) 4066 # rev.Update() 4067 # poly = rev.GetOutput() 4068 4069 # Build loops from the polyline 4070 build_loops = vtk.vtkContourLoopExtraction() 4071 build_loops.SetInputData(poly) 4072 build_loops.Update() 4073 boundaryPoly = build_loops.GetOutput() 4074 4075 ccut = vtk.vtkCookieCutter() 4076 ccut.SetInputData(self.polydata()) 4077 ccut.SetLoopsData(boundaryPoly) 4078 ccut.SetPointInterpolationToMeshEdges() 4079 # ccut.SetPointInterpolationToLoopEdges() 4080 ccut.PassCellDataOn() 4081 # ccut.PassPointDataOn() 4082 ccut.Update() 4083 cpoly = ccut.GetOutput() 4084 4085 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 4086 self._update(cpoly) 4087 else: 4088 # bring the underlying polydata to where _data is 4089 M = vtk.vtkMatrix4x4() 4090 M.DeepCopy(self.GetMatrix()) 4091 M.Invert() 4092 tr = vtk.vtkTransform() 4093 tr.SetMatrix(M) 4094 tf = vtk.vtkTransformPolyDataFilter() 4095 tf.SetTransform(tr) 4096 tf.SetInputData(cpoly) 4097 tf.Update() 4098 self._update(tf.GetOutput()) 4099 4100 self.pipeline = utils.OperationNode("cut_with_cookiecutter", parents=[self]) 4101 return self 4102 4103 def cut_with_cylinder(self, center=(0, 0, 0), axis=(0, 0, 1), r=1, invert=False): 4104 """ 4105 Cut the current mesh with an infinite cylinder. 4106 This is much faster than `cut_with_mesh()`. 4107 4108 Arguments: 4109 center : (array) 4110 the center of the cylinder 4111 normal : (array) 4112 direction of the cylinder axis 4113 r : (float) 4114 radius of the cylinder 4115 4116 Example: 4117 ```python 4118 from vedo import Disc, show 4119 disc = Disc(r1=1, r2=1.2) 4120 mesh = disc.extrude(3, res=50).linewidth(1) 4121 mesh.cut_with_cylinder([0,0,2], r=0.4, axis='y', invert=True) 4122 show(mesh, axes=1) 4123 ``` 4124 ![](https://vedo.embl.es/images/feats/cut_with_cylinder.png) 4125 4126 Examples: 4127 - [optics_main1.py](https://github.com/marcomusy/vedo/blob/master/examples/simulations/optics_main1.py) 4128 4129 Check out also: 4130 `cut_with_box()`, `cut_with_plane()`, `cut_with_sphere()` 4131 """ 4132 s = str(axis) 4133 if "x" in s: 4134 axis = (1, 0, 0) 4135 elif "y" in s: 4136 axis = (0, 1, 0) 4137 elif "z" in s: 4138 axis = (0, 0, 1) 4139 cyl = vtk.vtkCylinder() 4140 cyl.SetCenter(center) 4141 cyl.SetAxis(axis[0], axis[1], axis[2]) 4142 cyl.SetRadius(r) 4143 4144 clipper = vtk.vtkClipPolyData() 4145 clipper.SetInputData(self.polydata(True)) # must be True 4146 clipper.SetClipFunction(cyl) 4147 clipper.SetInsideOut(not invert) 4148 clipper.GenerateClippedOutputOff() 4149 clipper.GenerateClipScalarsOff() 4150 clipper.SetValue(0) 4151 clipper.Update() 4152 cpoly = clipper.GetOutput() 4153 4154 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 4155 self._update(cpoly) 4156 else: 4157 # bring the underlying polydata to where _data is 4158 M = vtk.vtkMatrix4x4() 4159 M.DeepCopy(self.GetMatrix()) 4160 M.Invert() 4161 tr = vtk.vtkTransform() 4162 tr.SetMatrix(M) 4163 tf = vtk.vtkTransformPolyDataFilter() 4164 tf.SetTransform(tr) 4165 tf.SetInputData(cpoly) 4166 tf.Update() 4167 self._update(tf.GetOutput()) 4168 4169 self.pipeline = utils.OperationNode("cut_with_cylinder", parents=[self]) 4170 return self 4171 4172 def cut_with_sphere(self, center=(0, 0, 0), r=1.0, invert=False): 4173 """ 4174 Cut the current mesh with an sphere. 4175 This is much faster than `cut_with_mesh()`. 4176 4177 Arguments: 4178 center : (array) 4179 the center of the sphere 4180 r : (float) 4181 radius of the sphere 4182 4183 Example: 4184 ```python 4185 from vedo import Disc, show 4186 disc = Disc(r1=1, r2=1.2) 4187 mesh = disc.extrude(3, res=50).linewidth(1) 4188 mesh.cut_with_sphere([1,-0.7,2], r=1.5, invert=True) 4189 show(mesh, axes=1) 4190 ``` 4191 ![](https://vedo.embl.es/images/feats/cut_with_sphere.png) 4192 4193 Check out also: 4194 `cut_with_box()`, `cut_with_plane()`, `cut_with_cylinder()` 4195 """ 4196 sph = vtk.vtkSphere() 4197 sph.SetCenter(center) 4198 sph.SetRadius(r) 4199 4200 clipper = vtk.vtkClipPolyData() 4201 clipper.SetInputData(self.polydata(True)) # must be True 4202 clipper.SetClipFunction(sph) 4203 clipper.SetInsideOut(not invert) 4204 clipper.GenerateClippedOutputOff() 4205 clipper.GenerateClipScalarsOff() 4206 clipper.SetValue(0) 4207 clipper.Update() 4208 cpoly = clipper.GetOutput() 4209 4210 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 4211 self._update(cpoly) 4212 else: 4213 # bring the underlying polydata to where _data is 4214 M = vtk.vtkMatrix4x4() 4215 M.DeepCopy(self.GetMatrix()) 4216 M.Invert() 4217 tr = vtk.vtkTransform() 4218 tr.SetMatrix(M) 4219 tf = vtk.vtkTransformPolyDataFilter() 4220 tf.SetTransform(tr) 4221 tf.SetInputData(cpoly) 4222 tf.Update() 4223 self._update(tf.GetOutput()) 4224 4225 self.pipeline = utils.OperationNode("cut_with_sphere", parents=[self]) 4226 return self 4227 4228 def cut_with_mesh(self, mesh, invert=False, keep=False): 4229 """ 4230 Cut an `Mesh` mesh with another `Mesh`. 4231 4232 Use `invert` to invert the selection. 4233 4234 Use `keep` to keep the cutoff part, in this case an `Assembly` is returned: 4235 the "cut" object and the "discarded" part of the original object. 4236 You can access both via `assembly.unpack()` method. 4237 4238 Example: 4239 ```python 4240 from vedo import * 4241 arr = np.random.randn(100000, 3)/2 4242 pts = Points(arr).c('red3').pos(5,0,0) 4243 cube = Cube().pos(4,0.5,0) 4244 assem = pts.cut_with_mesh(cube, keep=True) 4245 show(assem.unpack(), axes=1).close() 4246 ``` 4247 ![](https://vedo.embl.es/images/feats/cut_with_mesh.png) 4248 4249 Check out also: 4250 `cut_with_box()`, `cut_with_plane()`, `cut_with_cylinder()` 4251 """ 4252 polymesh = mesh.polydata() 4253 poly = self.polydata() 4254 4255 # Create an array to hold distance information 4256 signed_distances = vtk.vtkFloatArray() 4257 signed_distances.SetNumberOfComponents(1) 4258 signed_distances.SetName("SignedDistances") 4259 4260 # implicit function that will be used to slice the mesh 4261 ippd = vtk.vtkImplicitPolyDataDistance() 4262 ippd.SetInput(polymesh) 4263 4264 # Evaluate the signed distance function at all of the grid points 4265 for pointId in range(poly.GetNumberOfPoints()): 4266 p = poly.GetPoint(pointId) 4267 signed_distance = ippd.EvaluateFunction(p) 4268 signed_distances.InsertNextValue(signed_distance) 4269 4270 currentscals = poly.GetPointData().GetScalars() 4271 if currentscals: 4272 currentscals = currentscals.GetName() 4273 4274 poly.GetPointData().AddArray(signed_distances) 4275 poly.GetPointData().SetActiveScalars("SignedDistances") 4276 4277 clipper = vtk.vtkClipPolyData() 4278 clipper.SetInputData(poly) 4279 clipper.SetInsideOut(not invert) 4280 clipper.SetGenerateClippedOutput(keep) 4281 clipper.SetValue(0.0) 4282 clipper.Update() 4283 cpoly = clipper.GetOutput() 4284 if keep: 4285 kpoly = clipper.GetOutput(1) 4286 4287 vis = False 4288 if currentscals: 4289 cpoly.GetPointData().SetActiveScalars(currentscals) 4290 vis = self.mapper().GetScalarVisibility() 4291 4292 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 4293 self._update(cpoly) 4294 else: 4295 # bring the underlying polydata to where _data is 4296 M = vtk.vtkMatrix4x4() 4297 M.DeepCopy(self.GetMatrix()) 4298 M.Invert() 4299 tr = vtk.vtkTransform() 4300 tr.SetMatrix(M) 4301 tf = vtk.vtkTransformPolyDataFilter() 4302 tf.SetTransform(tr) 4303 tf.SetInputData(cpoly) 4304 tf.Update() 4305 self._update(tf.GetOutput()) 4306 4307 self.pointdata.remove("SignedDistances") 4308 self.mapper().SetScalarVisibility(vis) 4309 if keep: 4310 if isinstance(self, vedo.Mesh): 4311 cutoff = vedo.Mesh(kpoly) 4312 else: 4313 cutoff = vedo.Points(kpoly) 4314 cutoff.property = vtk.vtkProperty() 4315 cutoff.property.DeepCopy(self.property) 4316 cutoff.SetProperty(cutoff.property) 4317 cutoff.c("k5").alpha(0.2) 4318 return vedo.Assembly([self, cutoff]) 4319 4320 self.pipeline = utils.OperationNode("cut_with_mesh", parents=[self, mesh]) 4321 return self 4322 4323 def cut_with_point_loop(self, points, invert=False, on="points", include_boundary=False): 4324 """ 4325 Cut an `Mesh` object with a set of points forming a closed loop. 4326 4327 Arguments: 4328 invert : (bool) 4329 invert selection (inside-out) 4330 on : (str) 4331 if 'cells' will extract the whole cells lying inside (or outside) the point loop 4332 include_boundary : (bool) 4333 include cells lying exactly on the boundary line. Only relevant on 'cells' mode 4334 4335 Examples: 4336 - [cut_with_points1.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/cut_with_points1.py) 4337 4338 ![](https://vedo.embl.es/images/advanced/cutWithPoints1.png) 4339 4340 - [cut_with_points2.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/cut_with_points2.py) 4341 4342 ![](https://vedo.embl.es/images/advanced/cutWithPoints2.png) 4343 """ 4344 if isinstance(points, Points): 4345 parents = [points] 4346 vpts = points.polydata().GetPoints() 4347 points = points.points() 4348 else: 4349 parents = [self] 4350 vpts = vtk.vtkPoints() 4351 points = utils.make3d(points) 4352 for p in points: 4353 vpts.InsertNextPoint(p) 4354 4355 if "cell" in on: 4356 ippd = vtk.vtkImplicitSelectionLoop() 4357 ippd.SetLoop(vpts) 4358 ippd.AutomaticNormalGenerationOn() 4359 clipper = vtk.vtkExtractPolyDataGeometry() 4360 clipper.SetInputData(self.polydata()) 4361 clipper.SetImplicitFunction(ippd) 4362 clipper.SetExtractInside(not invert) 4363 clipper.SetExtractBoundaryCells(include_boundary) 4364 else: 4365 spol = vtk.vtkSelectPolyData() 4366 spol.SetLoop(vpts) 4367 spol.GenerateSelectionScalarsOn() 4368 spol.GenerateUnselectedOutputOff() 4369 spol.SetInputData(self.polydata()) 4370 spol.Update() 4371 clipper = vtk.vtkClipPolyData() 4372 clipper.SetInputData(spol.GetOutput()) 4373 clipper.SetInsideOut(not invert) 4374 clipper.SetValue(0.0) 4375 clipper.Update() 4376 cpoly = clipper.GetOutput() 4377 4378 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 4379 self._update(cpoly) 4380 else: 4381 # bring the underlying polydata to where _data is 4382 M = vtk.vtkMatrix4x4() 4383 M.DeepCopy(self.GetMatrix()) 4384 M.Invert() 4385 tr = vtk.vtkTransform() 4386 tr.SetMatrix(M) 4387 tf = vtk.vtkTransformPolyDataFilter() 4388 tf.SetTransform(tr) 4389 tf.SetInputData(clipper.GetOutput()) 4390 tf.Update() 4391 self._update(tf.GetOutput()) 4392 4393 self.pipeline = utils.OperationNode("cut_with_pointloop", parents=parents) 4394 return self 4395 4396 def cut_with_scalar(self, value, name="", invert=False): 4397 """ 4398 Cut a mesh or point cloud with some input scalar point-data. 4399 4400 Arguments: 4401 value : (float) 4402 cutting value 4403 name : (str) 4404 array name of the scalars to be used 4405 invert : (bool) 4406 flip selection 4407 4408 Example: 4409 ```python 4410 from vedo import * 4411 s = Sphere().lw(1) 4412 pts = s.points() 4413 scalars = np.sin(3*pts[:,2]) + pts[:,0] 4414 s.pointdata["somevalues"] = scalars 4415 s.cut_with_scalar(0.3) 4416 s.cmap("Spectral", "somevalues").add_scalarbar() 4417 s.show(axes=1).close() 4418 ``` 4419 ![](https://vedo.embl.es/images/feats/cut_with_scalars.png) 4420 """ 4421 if name: 4422 self.pointdata.select(name) 4423 clipper = vtk.vtkClipPolyData() 4424 clipper.SetInputData(self._data) 4425 clipper.SetValue(value) 4426 clipper.GenerateClippedOutputOff() 4427 clipper.SetInsideOut(not invert) 4428 clipper.Update() 4429 self._update(clipper.GetOutput()) 4430 4431 self.pipeline = utils.OperationNode("cut_with_scalars", parents=[self]) 4432 return self 4433 4434 def implicit_modeller(self, distance=0.05, res=(50, 50, 50), bounds=(), maxdist=None): 4435 """Find the surface which sits at the specified distance from the input one.""" 4436 if not bounds: 4437 bounds = self.bounds() 4438 4439 if not maxdist: 4440 maxdist = self.diagonal_size() / 2 4441 4442 imp = vtk.vtkImplicitModeller() 4443 imp.SetInputData(self.polydata()) 4444 imp.SetSampleDimensions(res) 4445 imp.SetMaximumDistance(maxdist) 4446 imp.SetModelBounds(bounds) 4447 contour = vtk.vtkContourFilter() 4448 contour.SetInputConnection(imp.GetOutputPort()) 4449 contour.SetValue(0, distance) 4450 contour.Update() 4451 poly = contour.GetOutput() 4452 out = vedo.Mesh(poly, c="lb") 4453 4454 out.pipeline = utils.OperationNode("implicit_modeller", parents=[self]) 4455 return out 4456 4457 4458 def generate_mesh( 4459 self, 4460 line_resolution=None, 4461 mesh_resolution=None, 4462 smooth=0.0, 4463 jitter=0.001, 4464 grid=None, 4465 quads=False, 4466 invert=False, 4467 ): 4468 """ 4469 Generate a polygonal Mesh from a closed contour line. 4470 If line is not closed it will be closed with a straight segment. 4471 4472 Arguments: 4473 line_resolution : (int) 4474 resolution of the contour line. The default is None, in this case 4475 the contour is not resampled. 4476 mesh_resolution : (int) 4477 resolution of the internal triangles not touching the boundary. 4478 smooth : (float) 4479 smoothing of the contour before meshing. 4480 jitter : (float) 4481 add a small noise to the internal points. 4482 grid : (Grid) 4483 manually pass a Grid object. The default is True. 4484 quads : (bool) 4485 generate a mesh of quads instead of triangles. 4486 invert : (bool) 4487 flip the line orientation. The default is False. 4488 4489 Examples: 4490 - [line2mesh_tri.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/line2mesh_tri.py) 4491 4492 ![](https://vedo.embl.es/images/advanced/line2mesh_tri.jpg) 4493 4494 - [line2mesh_quads.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/line2mesh_quads.py) 4495 4496 ![](https://vedo.embl.es/images/advanced/line2mesh_quads.png) 4497 """ 4498 if line_resolution is None: 4499 contour = vedo.shapes.Line(self.points()) 4500 else: 4501 contour = vedo.shapes.Spline(self.points(), smooth=smooth, res=line_resolution) 4502 contour.clean() 4503 4504 length = contour.length() 4505 density = length / contour.npoints 4506 vedo.logger.debug(f"tomesh():\n\tline length = {length}") 4507 vedo.logger.debug(f"\tdensity = {density} length/pt_separation") 4508 4509 x0, x1 = contour.xbounds() 4510 y0, y1 = contour.ybounds() 4511 4512 if grid is None: 4513 if mesh_resolution is None: 4514 resx = int((x1 - x0) / density + 0.5) 4515 resy = int((y1 - y0) / density + 0.5) 4516 vedo.logger.debug(f"tmesh_resolution = {[resx, resy]}") 4517 else: 4518 if utils.is_sequence(mesh_resolution): 4519 resx, resy = mesh_resolution 4520 else: 4521 resx, resy = mesh_resolution, mesh_resolution 4522 grid = vedo.shapes.Grid( 4523 [(x0 + x1) / 2, (y0 + y1) / 2, 0], 4524 s=((x1 - x0) * 1.025, (y1 - y0) * 1.025), 4525 res=(resx, resy), 4526 ) 4527 else: 4528 grid = grid.clone() 4529 4530 cpts = contour.points() 4531 4532 # make sure it's closed 4533 p0, p1 = cpts[0], cpts[-1] 4534 nj = max(2, int(utils.mag(p1 - p0) / density + 0.5)) 4535 joinline = vedo.shapes.Line(p1, p0, res=nj) 4536 contour = vedo.merge(contour, joinline).subsample(0.0001) 4537 4538 ####################################### quads 4539 if quads: 4540 cmesh = grid.clone().cut_with_point_loop(contour, on="cells", invert=invert) 4541 cmesh.wireframe(False).lw(0.5) 4542 cmesh.pipeline = utils.OperationNode( 4543 "generate_mesh", 4544 parents=[self, contour], 4545 comment=f"#quads {cmesh.inputdata().GetNumberOfCells()}", 4546 ) 4547 return cmesh 4548 ############################################# 4549 4550 grid_tmp = grid.points() 4551 4552 if jitter: 4553 np.random.seed(0) 4554 sigma = 1.0 / np.sqrt(grid.npoints) * grid.diagonal_size() * jitter 4555 vedo.logger.debug(f"\tsigma jittering = {sigma}") 4556 grid_tmp += np.random.rand(grid.npoints, 3) * sigma 4557 grid_tmp[:, 2] = 0.0 4558 4559 todel = [] 4560 density /= np.sqrt(3) 4561 vgrid_tmp = Points(grid_tmp) 4562 4563 for p in contour.points(): 4564 out = vgrid_tmp.closest_point(p, radius=density, return_point_id=True) 4565 todel += out.tolist() 4566 # cpoints = contour.points() 4567 # for i, p in enumerate(cpoints): 4568 # if i: 4569 # den = utils.mag(p-cpoints[i-1])/1.732 4570 # else: 4571 # den = density 4572 # todel += vgrid_tmp.closest_point(p, radius=den, return_point_id=True) 4573 4574 grid_tmp = grid_tmp.tolist() 4575 for index in sorted(list(set(todel)), reverse=True): 4576 del grid_tmp[index] 4577 4578 points = contour.points().tolist() + grid_tmp 4579 if invert: 4580 boundary = reversed(range(contour.npoints)) 4581 else: 4582 boundary = range(contour.npoints) 4583 4584 dln = delaunay2d(points, mode="xy", boundaries=[boundary]) 4585 dln.compute_normals(points=False) # fixes reversd faces 4586 dln.lw(0.5) 4587 4588 dln.pipeline = utils.OperationNode( 4589 "generate_mesh", 4590 parents=[self, contour], 4591 comment=f"#cells {dln.inputdata().GetNumberOfCells()}", 4592 ) 4593 return dln 4594 4595 def reconstruct_surface( 4596 self, 4597 dims=(100, 100, 100), 4598 radius=None, 4599 sample_size=None, 4600 hole_filling=True, 4601 bounds=(), 4602 padding=0.05, 4603 ): 4604 """ 4605 Surface reconstruction from a scattered cloud of points. 4606 4607 Arguments: 4608 dims : (int) 4609 number of voxels in x, y and z to control precision. 4610 radius : (float) 4611 radius of influence of each point. 4612 Smaller values generally improve performance markedly. 4613 Note that after the signed distance function is computed, 4614 any voxel taking on the value >= radius 4615 is presumed to be "unseen" or uninitialized. 4616 sample_size : (int) 4617 if normals are not present 4618 they will be calculated using this sample size per point. 4619 hole_filling : (bool) 4620 enables hole filling, this generates 4621 separating surfaces between the empty and unseen portions of the volume. 4622 bounds : (list) 4623 region in space in which to perform the sampling 4624 in format (xmin,xmax, ymin,ymax, zim, zmax) 4625 padding : (float) 4626 increase by this fraction the bounding box 4627 4628 Examples: 4629 - [recosurface.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/recosurface.py) 4630 4631 ![](https://vedo.embl.es/images/advanced/recosurface.png) 4632 """ 4633 if not utils.is_sequence(dims): 4634 dims = (dims, dims, dims) 4635 4636 sdf = vtk.vtkSignedDistance() 4637 4638 if len(bounds) == 6: 4639 sdf.SetBounds(bounds) 4640 else: 4641 x0, x1, y0, y1, z0, z1 = self.bounds() 4642 sdf.SetBounds( 4643 x0 - (x1 - x0) * padding, 4644 x1 + (x1 - x0) * padding, 4645 y0 - (y1 - y0) * padding, 4646 y1 + (y1 - y0) * padding, 4647 z0 - (z1 - z0) * padding, 4648 z1 + (z1 - z0) * padding, 4649 ) 4650 4651 pd = self.polydata() 4652 4653 if pd.GetPointData().GetNormals(): 4654 sdf.SetInputData(pd) 4655 else: 4656 normals = vtk.vtkPCANormalEstimation() 4657 normals.SetInputData(pd) 4658 if not sample_size: 4659 sample_size = int(pd.GetNumberOfPoints() / 50) 4660 normals.SetSampleSize(sample_size) 4661 normals.SetNormalOrientationToGraphTraversal() 4662 sdf.SetInputConnection(normals.GetOutputPort()) 4663 # print("Recalculating normals with sample size =", sample_size) 4664 4665 if radius is None: 4666 radius = self.diagonal_size() / (sum(dims) / 3) * 5 4667 # print("Calculating mesh from points with radius =", radius) 4668 4669 sdf.SetRadius(radius) 4670 sdf.SetDimensions(dims) 4671 sdf.Update() 4672 4673 surface = vtk.vtkExtractSurface() 4674 surface.SetRadius(radius * 0.99) 4675 surface.SetHoleFilling(hole_filling) 4676 surface.ComputeNormalsOff() 4677 surface.ComputeGradientsOff() 4678 surface.SetInputConnection(sdf.GetOutputPort()) 4679 surface.Update() 4680 m = vedo.mesh.Mesh(surface.GetOutput(), c=self.color()) 4681 4682 m.pipeline = utils.OperationNode( 4683 "reconstruct_surface", parents=[self], 4684 comment=f"#pts {m.inputdata().GetNumberOfPoints()}" 4685 ) 4686 return m 4687 4688 def compute_clustering(self, radius): 4689 """ 4690 Cluster points in space. The `radius` is the radius of local search. 4691 An array named "ClusterId" is added to the vertex points. 4692 4693 Examples: 4694 - [clustering.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/clustering.py) 4695 4696 ![](https://vedo.embl.es/images/basic/clustering.png) 4697 """ 4698 cluster = vtk.vtkEuclideanClusterExtraction() 4699 cluster.SetInputData(self.inputdata()) 4700 cluster.SetExtractionModeToAllClusters() 4701 cluster.SetRadius(radius) 4702 cluster.ColorClustersOn() 4703 cluster.Update() 4704 idsarr = cluster.GetOutput().GetPointData().GetArray("ClusterId") 4705 self.inputdata().GetPointData().AddArray(idsarr) 4706 4707 self.pipeline = utils.OperationNode( 4708 "compute_clustering", parents=[self], comment=f"radius = {radius}" 4709 ) 4710 return self 4711 4712 def compute_connections(self, radius, mode=0, regions=(), vrange=(0, 1), seeds=(), angle=0): 4713 """ 4714 Extracts and/or segments points from a point cloud based on geometric distance measures 4715 (e.g., proximity, normal alignments, etc.) and optional measures such as scalar range. 4716 The default operation is to segment the points into "connected" regions where the connection 4717 is determined by an appropriate distance measure. Each region is given a region id. 4718 4719 Optionally, the filter can output the largest connected region of points; a particular region 4720 (via id specification); those regions that are seeded using a list of input point ids; 4721 or the region of points closest to a specified position. 4722 4723 The key parameter of this filter is the radius defining a sphere around each point which defines 4724 a local neighborhood: any other points in the local neighborhood are assumed connected to the point. 4725 Note that the radius is defined in absolute terms. 4726 4727 Other parameters are used to further qualify what it means to be a neighboring point. 4728 For example, scalar range and/or point normals can be used to further constrain the neighborhood. 4729 Also the extraction mode defines how the filter operates. 4730 By default, all regions are extracted but it is possible to extract particular regions; 4731 the region closest to a seed point; seeded regions; or the largest region found while processing. 4732 By default, all regions are extracted. 4733 4734 On output, all points are labeled with a region number. 4735 However note that the number of input and output points may not be the same: 4736 if not extracting all regions then the output size may be less than the input size. 4737 4738 Arguments: 4739 radius : (float) 4740 variable specifying a local sphere used to define local point neighborhood 4741 mode : (int) 4742 - 0, Extract all regions 4743 - 1, Extract point seeded regions 4744 - 2, Extract largest region 4745 - 3, Test specified regions 4746 - 4, Extract all regions with scalar connectivity 4747 - 5, Extract point seeded regions 4748 regions : (list) 4749 a list of non-negative regions id to extract 4750 vrange : (list) 4751 scalar range to use to extract points based on scalar connectivity 4752 seeds : (list) 4753 a list of non-negative point seed ids 4754 angle : (list) 4755 points are connected if the angle between their normals is 4756 within this angle threshold (expressed in degrees). 4757 """ 4758 # https://vtk.org/doc/nightly/html/classvtkConnectedPointsFilter.html 4759 cpf = vtk.vtkConnectedPointsFilter() 4760 cpf.SetInputData(self.polydata()) 4761 cpf.SetRadius(radius) 4762 if mode == 0: # Extract all regions 4763 pass 4764 4765 elif mode == 1: # Extract point seeded regions 4766 cpf.SetExtractionModeToPointSeededRegions() 4767 for s in seeds: 4768 cpf.AddSeed(s) 4769 4770 elif mode == 2: # Test largest region 4771 cpf.SetExtractionModeToLargestRegion() 4772 4773 elif mode == 3: # Test specified regions 4774 cpf.SetExtractionModeToSpecifiedRegions() 4775 for r in regions: 4776 cpf.AddSpecifiedRegion(r) 4777 4778 elif mode == 4: # Extract all regions with scalar connectivity 4779 cpf.SetExtractionModeToLargestRegion() 4780 cpf.ScalarConnectivityOn() 4781 cpf.SetScalarRange(vrange[0], vrange[1]) 4782 4783 elif mode == 5: # Extract point seeded regions 4784 cpf.SetExtractionModeToLargestRegion() 4785 cpf.ScalarConnectivityOn() 4786 cpf.SetScalarRange(vrange[0], vrange[1]) 4787 cpf.AlignedNormalsOn() 4788 cpf.SetNormalAngle(angle) 4789 4790 cpf.Update() 4791 return self._update(cpf.GetOutput()) 4792 4793 def compute_camera_distance(self): 4794 """ 4795 Calculate the distance from points to the camera. 4796 A pointdata array is created with name 'DistanceToCamera'. 4797 """ 4798 if vedo.plotter_instance.renderer: 4799 poly = self.polydata() 4800 dc = vtk.vtkDistanceToCamera() 4801 dc.SetInputData(poly) 4802 dc.SetRenderer(vedo.plotter_instance.renderer) 4803 dc.Update() 4804 return self._update(dc.GetOutput()) 4805 return self 4806 4807 def density( 4808 self, dims=(40, 40, 40), bounds=None, radius=None, compute_gradient=False, locator=None 4809 ): 4810 """ 4811 Generate a density field from a point cloud. Input can also be a set of 3D coordinates. 4812 Output is a `Volume`. 4813 4814 The local neighborhood is specified as the `radius` around each sample position (each voxel). 4815 The density is expressed as the number of counts in the radius search. 4816 4817 Arguments: 4818 dims : (int,list) 4819 number of voxels in x, y and z of the output Volume. 4820 compute_gradient : (bool) 4821 Turn on/off the generation of the gradient vector, 4822 gradient magnitude scalar, and function classification scalar. 4823 By default this is off. Note that this will increase execution time 4824 and the size of the output. (The names of these point data arrays are: 4825 "Gradient", "Gradient Magnitude", and "Classification") 4826 locator : (vtkPointLocator) 4827 can be assigned from a previous call for speed (access it via `object.point_locator`). 4828 4829 Examples: 4830 - [plot_density3d.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/plot_density3d.py) 4831 4832 ![](https://vedo.embl.es/images/pyplot/plot_density3d.png) 4833 """ 4834 pdf = vtk.vtkPointDensityFilter() 4835 pdf.SetInputData(self.polydata()) 4836 4837 if not utils.is_sequence(dims): 4838 dims = [dims, dims, dims] 4839 4840 if bounds is None: 4841 bounds = list(self.bounds()) 4842 elif len(bounds) == 4: 4843 bounds = [*bounds, 0, 0] 4844 4845 if bounds[5] - bounds[4] == 0 or len(dims) == 2: # its 2D 4846 dims = list(dims) 4847 dims = [dims[0], dims[1], 2] 4848 diag = self.diagonal_size() 4849 bounds[5] = bounds[4] + diag / 1000 4850 pdf.SetModelBounds(bounds) 4851 4852 pdf.SetSampleDimensions(dims) 4853 4854 if locator: 4855 pdf.SetLocator(locator) 4856 4857 pdf.SetDensityEstimateToFixedRadius() 4858 if radius is None: 4859 radius = self.diagonal_size() / 20 4860 pdf.SetRadius(radius) 4861 4862 pdf.SetComputeGradient(compute_gradient) 4863 pdf.Update() 4864 img = pdf.GetOutput() 4865 vol = vedo.volume.Volume(img).mode(1) 4866 vol.name = "PointDensity" 4867 vol.info["radius"] = radius 4868 vol.locator = pdf.GetLocator() 4869 4870 vol.pipeline = utils.OperationNode( 4871 "density", parents=[self], comment=f"dims = {tuple(vol.dimensions())}" 4872 ) 4873 return vol 4874 4875 def densify(self, target_distance=0.1, nclosest=6, radius=None, niter=1, nmax=None): 4876 """ 4877 Return a copy of the cloud with new added points. 4878 The new points are created in such a way that all points in any local neighborhood are 4879 within a target distance of one another. 4880 4881 For each input point, the distance to all points in its neighborhood is computed. 4882 If any of its neighbors is further than the target distance, 4883 the edge connecting the point and its neighbor is bisected and 4884 a new point is inserted at the bisection point. 4885 A single pass is completed once all the input points are visited. 4886 Then the process repeats to the number of iterations. 4887 4888 Examples: 4889 - [densifycloud.py](https://github.com/marcomusy/vedo/blob/master/examples/volumetric/densifycloud.py) 4890 4891 ![](https://vedo.embl.es/images/volumetric/densifycloud.png) 4892 4893 .. note:: 4894 Points will be created in an iterative fashion until all points in their 4895 local neighborhood are the target distance apart or less. 4896 Note that the process may terminate early due to the 4897 number of iterations. By default the target distance is set to 0.5. 4898 Note that the target_distance should be less than the radius 4899 or nothing will change on output. 4900 4901 .. warning:: 4902 This class can generate a lot of points very quickly. 4903 The maximum number of iterations is by default set to =1.0 for this reason. 4904 Increase the number of iterations very carefully. 4905 Also, `nmax` can be set to limit the explosion of points. 4906 It is also recommended that a N closest neighborhood is used. 4907 4908 """ 4909 src = vtk.vtkProgrammableSource() 4910 opts = self.points() 4911 4912 def _readPoints(): 4913 output = src.GetPolyDataOutput() 4914 points = vtk.vtkPoints() 4915 for p in opts: 4916 points.InsertNextPoint(p) 4917 output.SetPoints(points) 4918 4919 src.SetExecuteMethod(_readPoints) 4920 4921 dens = vtk.vtkDensifyPointCloudFilter() 4922 dens.SetInputConnection(src.GetOutputPort()) 4923 dens.InterpolateAttributeDataOn() 4924 dens.SetTargetDistance(target_distance) 4925 dens.SetMaximumNumberOfIterations(niter) 4926 if nmax: 4927 dens.SetMaximumNumberOfPoints(nmax) 4928 4929 if radius: 4930 dens.SetNeighborhoodTypeToRadius() 4931 dens.SetRadius(radius) 4932 elif nclosest: 4933 dens.SetNeighborhoodTypeToNClosest() 4934 dens.SetNumberOfClosestPoints(nclosest) 4935 else: 4936 vedo.logger.error("set either radius or nclosest") 4937 raise RuntimeError() 4938 dens.Update() 4939 pts = utils.vtk2numpy(dens.GetOutput().GetPoints().GetData()) 4940 cld = Points(pts, c=None).point_size(self.GetProperty().GetPointSize()) 4941 cld.interpolate_data_from(self, n=nclosest, radius=radius) 4942 cld.name = "densifiedCloud" 4943 4944 cld.pipeline = utils.OperationNode( 4945 "densify", parents=[self], c="#e9c46a:", 4946 comment=f"#pts {cld.inputdata().GetNumberOfPoints()}" 4947 ) 4948 return cld 4949 4950 4951 ############################################################################### 4952 ## stuff returning Volume 4953 4954 def signed_distance(self, bounds=None, dims=(20, 20, 20), invert=False, maxradius=None): 4955 """ 4956 Compute the `Volume` object whose voxels contains the signed distance from 4957 the point cloud. The point cloud must have Normals. 4958 4959 Arguments: 4960 bounds : (list, actor) 4961 bounding box sizes 4962 dims : (list) 4963 dimensions (nr. of voxels) of the output volume. 4964 invert : (bool) 4965 flip the sign 4966 maxradius : (float) 4967 specify how far out to propagate distance calculation 4968 4969 Examples: 4970 - [distance2mesh.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/distance2mesh.py) 4971 4972 ![](https://vedo.embl.es/images/basic/distance2mesh.png) 4973 """ 4974 if bounds is None: 4975 bounds = self.bounds() 4976 if maxradius is None: 4977 maxradius = self.diagonal_size() / 2 4978 dist = vtk.vtkSignedDistance() 4979 dist.SetInputData(self.polydata()) 4980 dist.SetRadius(maxradius) 4981 dist.SetBounds(bounds) 4982 dist.SetDimensions(dims) 4983 dist.Update() 4984 img = dist.GetOutput() 4985 if invert: 4986 mat = vtk.vtkImageMathematics() 4987 mat.SetInput1Data(img) 4988 mat.SetOperationToMultiplyByK() 4989 mat.SetConstantK(-1) 4990 mat.Update() 4991 img = mat.GetOutput() 4992 4993 vol = vedo.Volume(img) 4994 vol.name = "SignedDistanceVolume" 4995 4996 vol.pipeline = utils.OperationNode( 4997 "signed_distance", 4998 parents=[self], 4999 comment=f"dim = {tuple(vol.dimensions())}", 5000 c="#e9c46a:#0096c7", 5001 ) 5002 return vol 5003 5004 def tovolume( 5005 self, kernel="shepard", radius=None, n=None, bounds=None, null_value=None, dims=(25, 25, 25) 5006 ): 5007 """ 5008 Generate a `Volume` by interpolating a scalar 5009 or vector field which is only known on a scattered set of points or mesh. 5010 Available interpolation kernels are: shepard, gaussian, or linear. 5011 5012 Arguments: 5013 kernel : (str) 5014 interpolation kernel type [shepard] 5015 radius : (float) 5016 radius of the local search 5017 n : (int) 5018 number of point to use for interpolation 5019 bounds : (list) 5020 bounding box of the output Volume object 5021 dims : (list) 5022 dimensions of the output Volume object 5023 null_value : (float) 5024 value to be assigned to invalid points 5025 5026 Examples: 5027 - [interpolate_volume.py](https://github.com/marcomusy/vedo/blob/master/examples/volumetric/interpolate_volume.py) 5028 5029 ![](https://vedo.embl.es/images/volumetric/59095175-1ec5a300-8918-11e9-8bc0-fd35c8981e2b.jpg) 5030 """ 5031 if radius is None and not n: 5032 vedo.logger.error("please set either radius or n") 5033 raise RuntimeError 5034 5035 poly = self.polydata() 5036 5037 # Create a probe volume 5038 probe = vtk.vtkImageData() 5039 probe.SetDimensions(dims) 5040 if bounds is None: 5041 bounds = self.bounds() 5042 probe.SetOrigin(bounds[0], bounds[2], bounds[4]) 5043 probe.SetSpacing( 5044 (bounds[1] - bounds[0]) / dims[0], 5045 (bounds[3] - bounds[2]) / dims[1], 5046 (bounds[5] - bounds[4]) / dims[2], 5047 ) 5048 5049 if not self.point_locator: 5050 self.point_locator = vtk.vtkPointLocator() 5051 self.point_locator.SetDataSet(poly) 5052 self.point_locator.BuildLocator() 5053 5054 if kernel == "shepard": 5055 kern = vtk.vtkShepardKernel() 5056 kern.SetPowerParameter(2) 5057 elif kernel == "gaussian": 5058 kern = vtk.vtkGaussianKernel() 5059 elif kernel == "linear": 5060 kern = vtk.vtkLinearKernel() 5061 else: 5062 vedo.logger.error("Error in tovolume(), available kernels are:") 5063 vedo.logger.error(" [shepard, gaussian, linear]") 5064 raise RuntimeError() 5065 5066 if radius: 5067 kern.SetRadius(radius) 5068 5069 interpolator = vtk.vtkPointInterpolator() 5070 interpolator.SetInputData(probe) 5071 interpolator.SetSourceData(poly) 5072 interpolator.SetKernel(kern) 5073 interpolator.SetLocator(self.point_locator) 5074 5075 if n: 5076 kern.SetNumberOfPoints(n) 5077 kern.SetKernelFootprintToNClosest() 5078 else: 5079 kern.SetRadius(radius) 5080 5081 if null_value is not None: 5082 interpolator.SetNullValue(null_value) 5083 else: 5084 interpolator.SetNullPointsStrategyToClosestPoint() 5085 interpolator.Update() 5086 5087 vol = vedo.Volume(interpolator.GetOutput()) 5088 5089 vol.pipeline = utils.OperationNode( 5090 "signed_distance", 5091 parents=[self], 5092 comment=f"dim = {tuple(vol.dimensions())}", 5093 c="#e9c46a:#0096c7", 5094 ) 5095 return vol 5096 5097 def generate_random_data(self): 5098 """Fill a dataset with random attributes""" 5099 gen = vtk.vtkRandomAttributeGenerator() 5100 gen.SetInputData(self._data) 5101 gen.GenerateAllDataOn() 5102 gen.SetDataTypeToFloat() 5103 gen.GeneratePointNormalsOff() 5104 gen.GeneratePointTensorsOn() 5105 gen.GenerateCellScalarsOn() 5106 gen.Update() 5107 5108 m = self._update(gen.GetOutput()) 5109 5110 m.pipeline = utils.OperationNode("generate\nrandom data", parents=[self]) 5111 return m
Work with pointclouds.
719 def __init__(self, inputobj=None, r=4, c=(0.2, 0.2, 0.2), alpha=1, blur=False, emissive=True): 720 """ 721 Build an object made of only vertex points for a list of 2D/3D points. 722 Both shapes (N, 3) or (3, N) are accepted as input, if N>3. 723 For very large point clouds a list of colors and alpha can be assigned to each 724 point in the form c=[(R,G,B,A), ... ] where 0<=R<256, ... 0<=A<256. 725 726 Arguments: 727 inputobj : (list, tuple) 728 r : (int) 729 Point radius in units of pixels. 730 c : (str, list) 731 Color name or rgb tuple. 732 alpha : (float) 733 Transparency in range [0,1]. 734 blur : (bool) 735 Apply a gaussian convolution filter to the points. 736 In this case the radius `r` is in absolute units of the mesh coordinates. 737 emissive : (bool) 738 Halo of point becomes emissive. 739 740 Example: 741 ```python 742 from vedo import * 743 744 def fibonacci_sphere(n): 745 s = np.linspace(0, n, num=n, endpoint=False) 746 theta = s * 2.399963229728653 747 y = 1 - s * (2/(n-1)) 748 r = np.sqrt(1 - y * y) 749 x = np.cos(theta) * r 750 z = np.sin(theta) * r 751 return [x,y,z] 752 753 Points(fibonacci_sphere(1000)).show(axes=1).close() 754 ``` 755 ![](https://vedo.embl.es/images/feats/fibonacci.png) 756 """ 757 758 vtk.vtkActor.__init__(self) 759 BaseActor.__init__(self) 760 761 self._data = None 762 763 if blur: 764 self._mapper = vtk.vtkPointGaussianMapper() 765 if emissive: 766 self._mapper.SetEmissive(bool(emissive)) 767 self._mapper.SetScaleFactor(r * 1.4142) 768 769 # https://kitware.github.io/vtk-examples/site/Python/Meshes/PointInterpolator/ 770 if alpha < 1: 771 self._mapper.SetSplatShaderCode( 772 "//VTK::Color::Impl\n" 773 "float dist = dot(offsetVCVSOutput.xy,offsetVCVSOutput.xy);\n" 774 "if (dist > 1.0) {\n" 775 " discard;\n" 776 "} else {\n" 777 f" float scale = ({alpha} - dist);\n" 778 " ambientColor *= scale;\n" 779 " diffuseColor *= scale;\n" 780 "}\n" 781 ) 782 alpha = 1 783 784 else: 785 self._mapper = vtk.vtkPolyDataMapper() 786 self.SetMapper(self._mapper) 787 788 self._bfprop = None # backface property holder 789 790 self._scals_idx = 0 # index of the active scalar changed from CLI 791 self._ligthingnr = 0 # index of the lighting mode changed from CLI 792 self._cmap_name = "" # remember the name for self._keypress 793 # self.name = "Points" # better not to give it a name here 794 795 self.property = self.GetProperty() 796 797 try: 798 if not blur: 799 self.property.RenderPointsAsSpheresOn() 800 except AttributeError: 801 pass 802 803 if inputobj is None: #################### 804 self._data = vtk.vtkPolyData() 805 return 806 ######################################## 807 808 self.property.SetRepresentationToPoints() 809 self.property.SetPointSize(r) 810 self.property.LightingOff() 811 812 if isinstance(inputobj, vedo.BaseActor): 813 inputobj = inputobj.points() # numpy 814 815 ###### 816 if isinstance(inputobj, vtk.vtkActor): 817 poly_copy = vtk.vtkPolyData() 818 pr = vtk.vtkProperty() 819 pr.DeepCopy(inputobj.GetProperty()) 820 poly_copy.DeepCopy(inputobj.GetMapper().GetInput()) 821 pr.SetRepresentationToPoints() 822 pr.SetPointSize(r) 823 self._data = poly_copy 824 self._mapper.SetInputData(poly_copy) 825 self._mapper.SetScalarVisibility(inputobj.GetMapper().GetScalarVisibility()) 826 self.SetProperty(pr) 827 self.property = pr 828 829 elif isinstance(inputobj, vtk.vtkPolyData): 830 if inputobj.GetNumberOfCells() == 0: 831 carr = vtk.vtkCellArray() 832 for i in range(inputobj.GetNumberOfPoints()): 833 carr.InsertNextCell(1) 834 carr.InsertCellPoint(i) 835 inputobj.SetVerts(carr) 836 self._data = inputobj # cache vtkPolyData and mapper for speed 837 838 elif utils.is_sequence(inputobj): # passing point coords 839 plist = inputobj 840 n = len(plist) 841 842 if n == 3: # assume plist is in the format [all_x, all_y, all_z] 843 if utils.is_sequence(plist[0]) and len(plist[0]) > 3: 844 plist = np.stack((plist[0], plist[1], plist[2]), axis=1) 845 elif n == 2: # assume plist is in the format [all_x, all_y, 0] 846 if utils.is_sequence(plist[0]) and len(plist[0]) > 3: 847 plist = np.stack((plist[0], plist[1], np.zeros(len(plist[0]))), axis=1) 848 849 # if n and len(plist[0]) == 2: # make it 3d 850 # plist = np.c_[np.array(plist), np.zeros(len(plist))] 851 plist = utils.make3d(plist) 852 853 if ( 854 utils.is_sequence(c) 855 and (len(c) > 3 or (utils.is_sequence(c[0]) and len(c[0]) == 4)) 856 ) or utils.is_sequence(alpha): 857 858 cols = c 859 860 n = len(plist) 861 if n != len(cols): 862 vedo.logger.error(f"mismatch in Points() colors array lengths {n} and {len(cols)}") 863 raise RuntimeError() 864 865 src = vtk.vtkPointSource() 866 src.SetNumberOfPoints(n) 867 src.Update() 868 869 vgf = vtk.vtkVertexGlyphFilter() 870 vgf.SetInputData(src.GetOutput()) 871 vgf.Update() 872 pd = vgf.GetOutput() 873 874 pd.GetPoints().SetData(utils.numpy2vtk(plist, dtype=np.float32)) 875 876 ucols = vtk.vtkUnsignedCharArray() 877 ucols.SetNumberOfComponents(4) 878 ucols.SetName("Points_RGBA") 879 if utils.is_sequence(alpha): 880 if len(alpha) != n: 881 vedo.logger.error(f"mismatch in Points() alpha array lengths {n} and {len(cols)}") 882 raise RuntimeError() 883 alphas = alpha 884 alpha = 1 885 else: 886 alphas = (alpha,) * n 887 888 if utils.is_sequence(cols): 889 c = None 890 if len(cols[0]) == 4: 891 for i in range(n): # FAST 892 rc, gc, bc, ac = cols[i] 893 ucols.InsertNextTuple4(rc, gc, bc, ac) 894 else: 895 for i in range(n): # SLOW 896 rc, gc, bc = colors.get_color(cols[i]) 897 ucols.InsertNextTuple4(rc * 255, gc * 255, bc * 255, alphas[i] * 255) 898 else: 899 c = cols 900 901 pd.GetPointData().AddArray(ucols) 902 pd.GetPointData().SetActiveScalars("Points_RGBA") 903 self._mapper.SetInputData(pd) 904 self._mapper.ScalarVisibilityOn() 905 self._data = pd 906 907 else: 908 909 pd = utils.buildPolyData(plist) 910 self._mapper.SetInputData(pd) 911 c = colors.get_color(c) 912 self.property.SetColor(c) 913 self.property.SetOpacity(alpha) 914 self._data = pd 915 916 ########## 917 self.pipeline = utils.OperationNode( 918 self, parents=[], comment=f"#pts {self._data.GetNumberOfPoints()}" 919 ) 920 return 921 ########## 922 923 elif isinstance(inputobj, str): 924 verts = vedo.file_io.load(inputobj) 925 self.filename = inputobj 926 self._data = verts.polydata() 927 928 else: 929 930 # try to extract the points from the VTK input data object 931 try: 932 vvpts = inputobj.GetPoints() 933 pd = vtk.vtkPolyData() 934 pd.SetPoints(vvpts) 935 for i in range(inputobj.GetPointData().GetNumberOfArrays()): 936 arr = inputobj.GetPointData().GetArray(i) 937 pd.GetPointData().AddArray(arr) 938 939 self._mapper.SetInputData(pd) 940 c = colors.get_color(c) 941 self.property.SetColor(c) 942 self.property.SetOpacity(alpha) 943 self._data = pd 944 except: 945 vedo.logger.error(f"cannot build Points from type {type(inputobj)}") 946 raise RuntimeError() 947 948 c = colors.get_color(c) 949 self.property.SetColor(c) 950 self.property.SetOpacity(alpha) 951 952 self._mapper.SetInputData(self._data) 953 954 self.pipeline = utils.OperationNode( 955 self, parents=[], comment=f"#pts {self._data.GetNumberOfPoints()}" 956 ) 957 return
Build an object made of only vertex points for a list of 2D/3D points. Both shapes (N, 3) or (3, N) are accepted as input, if N>3. For very large point clouds a list of colors and alpha can be assigned to each point in the form c=[(R,G,B,A), ... ] where 0<=R<256, ... 0<=A<256.
Arguments:
- inputobj : (list, tuple)
- r : (int) Point radius in units of pixels.
- c : (str, list) Color name or rgb tuple.
- alpha : (float) Transparency in range [0,1].
- blur : (bool)
Apply a gaussian convolution filter to the points.
In this case the radius
r
is in absolute units of the mesh coordinates. - emissive : (bool) Halo of point becomes emissive.
Example:
from vedo import * def fibonacci_sphere(n): s = np.linspace(0, n, num=n, endpoint=False) theta = s * 2.399963229728653 y = 1 - s * (2/(n-1)) r = np.sqrt(1 - y * y) x = np.cos(theta) * r z = np.sin(theta) * r return [x,y,z] Points(fibonacci_sphere(1000)).show(axes=1).close()
1057 def polydata(self, transformed=True): 1058 """ 1059 Returns the `vtkPolyData` object associated to a `Mesh`. 1060 1061 .. note:: 1062 If `transformed=True` return a copy of polydata that corresponds 1063 to the current mesh position in space. 1064 """ 1065 if not self._data: 1066 self._data = self.mapper().GetInput() 1067 return self._data 1068 1069 if transformed: 1070 # if self.GetIsIdentity() or self._data.GetNumberOfPoints()==0: # commmentd out on 15th feb 2020 1071 if self._data.GetNumberOfPoints() == 0: 1072 # no need to do much 1073 return self._data 1074 1075 # otherwise make a copy that corresponds to 1076 # the actual position in space of the mesh 1077 M = self.GetMatrix() 1078 transform = vtk.vtkTransform() 1079 transform.SetMatrix(M) 1080 tp = vtk.vtkTransformPolyDataFilter() 1081 tp.SetTransform(transform) 1082 tp.SetInputData(self._data) 1083 tp.Update() 1084 return tp.GetOutput() 1085 1086 return self._data
Returns the vtkPolyData
object associated to a Mesh
.
If transformed=True
return a copy of polydata that corresponds
to the current mesh position in space.
1089 def clone(self, deep=True, transformed=False): 1090 """ 1091 Clone a `PointCloud` or `Mesh` object to make an exact copy of it. 1092 1093 Arguments: 1094 deep : (bool) 1095 if False only build a shallow copy of the object (faster copy). 1096 1097 transformed : (bool) 1098 if True reset the current transformation of the copy to unit. 1099 1100 Examples: 1101 - [mirror.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mirror.py) 1102 1103 ![](https://vedo.embl.es/images/basic/mirror.png) 1104 """ 1105 poly = self.polydata(transformed) 1106 poly_copy = vtk.vtkPolyData() 1107 if deep: 1108 poly_copy.DeepCopy(poly) 1109 else: 1110 poly_copy.ShallowCopy(poly) 1111 1112 if isinstance(self, vedo.Mesh): 1113 cloned = vedo.Mesh(poly_copy) 1114 else: 1115 cloned = Points(poly_copy) 1116 1117 pr = vtk.vtkProperty() 1118 pr.DeepCopy(self.GetProperty()) 1119 cloned.SetProperty(pr) 1120 cloned.property = pr 1121 1122 if self.GetBackfaceProperty(): 1123 bfpr = vtk.vtkProperty() 1124 bfpr.DeepCopy(self.GetBackfaceProperty()) 1125 cloned.SetBackfaceProperty(bfpr) 1126 1127 if not transformed: 1128 if self.transform: 1129 # already has a transform which can be non linear, so use that 1130 cloned.SetUserTransform(self.transform) 1131 else: 1132 # assign the same transformation to the copy 1133 cloned.SetOrigin(self.GetOrigin()) 1134 cloned.SetScale(self.GetScale()) 1135 cloned.SetOrientation(self.GetOrientation()) 1136 cloned.SetPosition(self.GetPosition()) 1137 1138 mp = cloned.mapper() 1139 sm = self.mapper() 1140 mp.SetScalarVisibility(sm.GetScalarVisibility()) 1141 mp.SetScalarRange(sm.GetScalarRange()) 1142 mp.SetColorMode(sm.GetColorMode()) 1143 lsr = sm.GetUseLookupTableScalarRange() 1144 mp.SetUseLookupTableScalarRange(lsr) 1145 mp.SetScalarMode(sm.GetScalarMode()) 1146 lut = sm.GetLookupTable() 1147 if lut: 1148 mp.SetLookupTable(lut) 1149 1150 if self.GetTexture(): 1151 cloned.texture(self.GetTexture()) 1152 1153 cloned.SetPickable(self.GetPickable()) 1154 1155 cloned.base = np.array(self.base) 1156 cloned.top = np.array(self.top) 1157 cloned.name = str(self.name) 1158 cloned.filename = str(self.filename) 1159 cloned.info = dict(self.info) 1160 1161 # better not to share the same locators with original obj 1162 cloned.point_locator = None 1163 cloned.cell_locator = None 1164 1165 cloned.pipeline = utils.OperationNode("clone", parents=[self], shape="diamond", c="#edede9") 1166 return cloned
Clone a PointCloud
or Mesh
object to make an exact copy of it.
Arguments:
- deep : (bool) if False only build a shallow copy of the object (faster copy).
- transformed : (bool) if True reset the current transformation of the copy to unit.
Examples:
1168 def clone2d( 1169 self, 1170 pos=(0, 0), 1171 coordsys=4, 1172 scale=None, 1173 c=None, 1174 alpha=None, 1175 ps=2, 1176 lw=1, 1177 sendback=False, 1178 layer=0, 1179 ): 1180 """ 1181 Copy a 3D Mesh into a static 2D image. Returns a `vtkActor2D`. 1182 1183 Arguments: 1184 coordsys : (int) 1185 the coordinate system, options are 1186 - 0 = Displays 1187 - 1 = Normalized Display 1188 - 2 = Viewport (origin is the bottom-left corner of the window) 1189 - 3 = Normalized Viewport 1190 - 4 = View (origin is the center of the window) 1191 - 5 = World (anchor the 2d image to mesh) 1192 1193 ps : (int) 1194 point size in pixel units 1195 1196 lw : (int) 1197 line width in pixel units 1198 1199 sendback : (bool) 1200 put it behind any other 3D object 1201 1202 Examples: 1203 - [clone2d.py](https://github.com/marcomusy/vedo/tree/master/examples/other/clone2d.py) 1204 1205 ![](https://vedo.embl.es/images/other/clone2d.png) 1206 """ 1207 if scale is None: 1208 msiz = self.diagonal_size() 1209 if vedo.plotter_instance and vedo.plotter_instance.window: 1210 sz = vedo.plotter_instance.window.GetSize() 1211 dsiz = utils.mag(sz) 1212 scale = dsiz / msiz / 10 1213 else: 1214 scale = 350 / msiz 1215 1216 cmsh = self.clone() 1217 poly = cmsh.pos(0, 0, 0).scale(scale).polydata() 1218 1219 mapper3d = self.mapper() 1220 cm = mapper3d.GetColorMode() 1221 lut = mapper3d.GetLookupTable() 1222 sv = mapper3d.GetScalarVisibility() 1223 use_lut = mapper3d.GetUseLookupTableScalarRange() 1224 vrange = mapper3d.GetScalarRange() 1225 sm = mapper3d.GetScalarMode() 1226 1227 mapper2d = vtk.vtkPolyDataMapper2D() 1228 mapper2d.ShallowCopy(mapper3d) 1229 mapper2d.SetInputData(poly) 1230 mapper2d.SetColorMode(cm) 1231 mapper2d.SetLookupTable(lut) 1232 mapper2d.SetScalarVisibility(sv) 1233 mapper2d.SetUseLookupTableScalarRange(use_lut) 1234 mapper2d.SetScalarRange(vrange) 1235 mapper2d.SetScalarMode(sm) 1236 1237 act2d = vtk.vtkActor2D() 1238 act2d.SetMapper(mapper2d) 1239 act2d.SetLayerNumber(layer) 1240 csys = act2d.GetPositionCoordinate() 1241 csys.SetCoordinateSystem(coordsys) 1242 act2d.SetPosition(pos) 1243 if c is not None: 1244 c = colors.get_color(c) 1245 act2d.GetProperty().SetColor(c) 1246 mapper2d.SetScalarVisibility(False) 1247 else: 1248 act2d.GetProperty().SetColor(cmsh.color()) 1249 if alpha is not None: 1250 act2d.GetProperty().SetOpacity(alpha) 1251 else: 1252 act2d.GetProperty().SetOpacity(cmsh.alpha()) 1253 act2d.GetProperty().SetPointSize(ps) 1254 act2d.GetProperty().SetLineWidth(lw) 1255 act2d.GetProperty().SetDisplayLocationToForeground() 1256 if sendback: 1257 act2d.GetProperty().SetDisplayLocationToBackground() 1258 1259 # print(csys.GetCoordinateSystemAsString()) 1260 # print(act2d.GetHeight(), act2d.GetWidth(), act2d.GetLayerNumber()) 1261 return act2d
Copy a 3D Mesh into a static 2D image. Returns a vtkActor2D
.
Arguments:
- coordsys : (int)
the coordinate system, options are
- 0 = Displays
- 1 = Normalized Display
- 2 = Viewport (origin is the bottom-left corner of the window)
- 3 = Normalized Viewport
- 4 = View (origin is the center of the window)
- 5 = World (anchor the 2d image to mesh)
- ps : (int) point size in pixel units
- lw : (int) line width in pixel units
- sendback : (bool) put it behind any other 3D object
Examples:
1263 def add_trail(self, offset=(0, 0, 0), n=50, c=None, alpha=1.0, lw=2): 1264 """ 1265 Add a trailing line to mesh. 1266 This new mesh is accessible through `mesh.trail`. 1267 1268 Arguments: 1269 offset : (float) 1270 set an offset vector from the object center. 1271 n : (int) 1272 number of segments 1273 lw : (float) 1274 line width of the trail 1275 1276 Examples: 1277 - [trail.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/trail.py) 1278 1279 ![](https://vedo.embl.es/images/simulations/trail.gif) 1280 1281 - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py) 1282 - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py) 1283 """ 1284 if self.trail is None: 1285 pos = self.GetPosition() 1286 self.trail_offset = np.asarray(offset) 1287 self.trail_points = [pos] * n 1288 1289 if c is None: 1290 col = self.GetProperty().GetColor() 1291 else: 1292 col = colors.get_color(c) 1293 1294 tline = vedo.shapes.Line(pos, pos, res=n, c=col, alpha=alpha, lw=lw) 1295 self.trail = tline # holds the Line 1296 return self
Add a trailing line to mesh.
This new mesh is accessible through mesh.trail
.
Arguments:
- offset : (float) set an offset vector from the object center.
- n : (int) number of segments
- lw : (float) line width of the trail
Examples:
1298 def update_trail(self): 1299 """ 1300 Update the trailing line of a moving object. 1301 """ 1302 if isinstance(self, vedo.shapes.Arrow): 1303 currentpos = self.tipPoint() # the tip of Arrow 1304 else: 1305 currentpos = np.array(self.GetPosition()) 1306 1307 self.trail_points.append(currentpos) # cycle 1308 self.trail_points.pop(0) 1309 1310 data = np.array(self.trail_points) - currentpos + self.trail_offset 1311 tpoly = self.trail.polydata(False) 1312 tpoly.GetPoints().SetData(utils.numpy2vtk(data, dtype=np.float32)) 1313 self.trail.SetPosition(currentpos) 1314 return self
Update the trailing line of a moving object.
1346 def add_shadow(self, plane, point, direction=None, c=(0.6, 0.6, 0.6), alpha=1, culling=0): 1347 """ 1348 Generate a shadow out of an `Mesh` on one of the three Cartesian planes. 1349 The output is a new `Mesh` representing the shadow. 1350 This new mesh is accessible through `mesh.shadow`. 1351 By default the shadow mesh is placed on the bottom wall of the bounding box. 1352 1353 See also `pointcloud.project_on_plane()`. 1354 1355 Arguments: 1356 plane : (str, Plane) 1357 if plane is `str`, plane can be one of `['x', 'y', 'z']`, 1358 represents x-plane, y-plane and z-plane, respectively. 1359 Otherwise, plane should be an instance of `vedo.shapes.Plane` 1360 point : (float, array) 1361 if plane is `str`, point should be a float represents the intercept. 1362 Otherwise, point is the camera point of perspective projection 1363 direction : (list) 1364 direction of oblique projection 1365 culling : (int) 1366 choose between front [1] or backface [-1] culling or None. 1367 1368 Examples: 1369 - [shadow1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/shadow1.py) 1370 - [airplane1.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane1.py) 1371 - [airplane2.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/airplane2.py) 1372 1373 ![](https://vedo.embl.es/images/simulations/57341963-b8910900-713c-11e9-898a-84b6d3712bce.gif) 1374 """ 1375 shad = self._compute_shadow(plane, point, direction) 1376 shad.c(c).alpha(alpha) 1377 1378 try: 1379 # Points dont have these methods 1380 shad.flat() 1381 if culling in (1, True): 1382 shad.frontface_culling() 1383 elif culling == -1: 1384 shad.backface_culling() 1385 except AttributeError: 1386 pass 1387 1388 shad.GetProperty().LightingOff() 1389 shad.SetPickable(False) 1390 shad.SetUseBounds(True) 1391 1392 if shad not in self.shadows: 1393 self.shadows.append(shad) 1394 shad.info = dict(plane=plane, point=point, direction=direction) 1395 return self
Generate a shadow out of an Mesh
on one of the three Cartesian planes.
The output is a new Mesh
representing the shadow.
This new mesh is accessible through mesh.shadow
.
By default the shadow mesh is placed on the bottom wall of the bounding box.
See also pointcloud.project_on_plane()
.
Arguments:
- plane : (str, Plane)
if plane is
str
, plane can be one of['x', 'y', 'z']
, represents x-plane, y-plane and z-plane, respectively. Otherwise, plane should be an instance ofvedo.shapes.Plane
- point : (float, array)
if plane is
str
, point should be a float represents the intercept. Otherwise, point is the camera point of perspective projection - direction : (list) direction of oblique projection
- culling : (int) choose between front [1] or backface [-1] culling or None.
Examples:
1397 def update_shadows(self): 1398 """ 1399 Update the shadows of a moving object. 1400 """ 1401 for sha in self.shadows: 1402 plane = sha.info['plane'] 1403 point = sha.info['point'] 1404 direction = sha.info['direction'] 1405 new_sha = self._compute_shadow(plane, point, direction) 1406 sha._update(new_sha._data) 1407 return self
Update the shadows of a moving object.
1410 def delete_cells_by_point_index(self, indices): 1411 """ 1412 Delete a list of vertices identified by any of their vertex index. 1413 1414 See also `delete_cells()`. 1415 1416 Examples: 1417 - [elete_mesh_pts.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/elete_mesh_pts.py) 1418 1419 ![](https://vedo.embl.es/images/basic/deleteMeshPoints.png) 1420 """ 1421 cell_ids = vtk.vtkIdList() 1422 data = self.inputdata() 1423 data.BuildLinks() 1424 n = 0 1425 for i in np.unique(indices): 1426 data.GetPointCells(i, cell_ids) 1427 for j in range(cell_ids.GetNumberOfIds()): 1428 data.DeleteCell(cell_ids.GetId(j)) # flag cell 1429 n += 1 1430 1431 data.RemoveDeletedCells() 1432 self.mapper().Modified() 1433 self.pipeline = utils.OperationNode(f"delete {n} cells\nby point index", parents=[self]) 1434 return self
Delete a list of vertices identified by any of their vertex index.
See also delete_cells()
.
Examples:
1436 def compute_normals_with_pca(self, n=20, orientation_point=None, invert=False): 1437 """ 1438 Generate point normals using PCA (principal component analysis). 1439 Basically this estimates a local tangent plane around each sample point p 1440 by considering a small neighborhood of points around p, and fitting a plane 1441 to the neighborhood (via PCA). 1442 1443 Arguments: 1444 n : (int) 1445 neighborhood size to calculate the normal 1446 orientation_point : (list) 1447 adjust the +/- sign of the normals so that 1448 the normals all point towards a specified point. If None, perform a traversal 1449 of the point cloud and flip neighboring normals so that they are mutually consistent. 1450 invert : (bool) 1451 flip all normals 1452 """ 1453 poly = self.polydata() 1454 pcan = vtk.vtkPCANormalEstimation() 1455 pcan.SetInputData(poly) 1456 pcan.SetSampleSize(n) 1457 1458 if orientation_point is not None: 1459 pcan.SetNormalOrientationToPoint() 1460 pcan.SetOrientationPoint(orientation_point) 1461 else: 1462 pcan.SetNormalOrientationToGraphTraversal() 1463 1464 if invert: 1465 pcan.FlipNormalsOn() 1466 pcan.Update() 1467 1468 varr = pcan.GetOutput().GetPointData().GetNormals() 1469 varr.SetName("Normals") 1470 self.inputdata().GetPointData().SetNormals(varr) 1471 self.inputdata().GetPointData().Modified() 1472 return self
Generate point normals using PCA (principal component analysis). Basically this estimates a local tangent plane around each sample point p by considering a small neighborhood of points around p, and fitting a plane to the neighborhood (via PCA).
Arguments:
- n : (int) neighborhood size to calculate the normal
- orientation_point : (list) adjust the +/- sign of the normals so that the normals all point towards a specified point. If None, perform a traversal of the point cloud and flip neighboring normals so that they are mutually consistent.
- invert : (bool) flip all normals
1474 def compute_acoplanarity(self, n=25, radius=None, on="points"): 1475 """ 1476 Compute acoplanarity which is a measure of how much a local region of the mesh 1477 differs from a plane. 1478 The information is stored in a `pointdata` or `celldata` array with name 'Acoplanarity'. 1479 Either `n` (number of neighbour points) or `radius` (radius of local search) can be specified. 1480 If a radius value is given and not enough points fall inside it, then a -1 is stored. 1481 1482 Example: 1483 ```python 1484 from vedo import * 1485 msh = ParametricShape('RandomHills') 1486 msh.compute_acoplanarity(radius=0.1, on='cells') 1487 msh.cmap("coolwarm", on='cells').add_scalarbar() 1488 msh.show(axes=1).close() 1489 ``` 1490 ![](https://vedo.embl.es/images/feats/acoplanarity.jpg) 1491 """ 1492 acoplanarities = [] 1493 if "point" in on: 1494 pts = self.points() 1495 elif "cell" in on: 1496 pts = self.cell_centers() 1497 else: 1498 raise ValueError(f"In compute_acoplanarity() set on to either 'cells' or 'points', not {on}") 1499 1500 for p in utils.progressbar(pts, delay=5, width=15, title=f"{on} acoplanarity"): 1501 if n: 1502 data = self.closest_point(p, n=n) 1503 npts = n 1504 elif radius: 1505 data = self.closest_point(p, radius=radius) 1506 npts = len(data) 1507 1508 try: 1509 center = data.mean(axis=0) 1510 res = np.linalg.svd(data - center) 1511 acoplanarities.append(res[1][2] / npts) 1512 except: 1513 acoplanarities.append(-1.0) 1514 1515 if "point" in on: 1516 self.pointdata["Acoplanarity"] = np.array(acoplanarities, dtype=float) 1517 else: 1518 self.celldata["Acoplanarity"] = np.array(acoplanarities, dtype=float) 1519 return self
Compute acoplanarity which is a measure of how much a local region of the mesh
differs from a plane.
The information is stored in a pointdata
or celldata
array with name 'Acoplanarity'.
Either n
(number of neighbour points) or radius
(radius of local search) can be specified.
If a radius value is given and not enough points fall inside it, then a -1 is stored.
Example:
from vedo import * msh = ParametricShape('RandomHills') msh.compute_acoplanarity(radius=0.1, on='cells') msh.cmap("coolwarm", on='cells').add_scalarbar() msh.show(axes=1).close()
1521 def distance_to(self, pcloud, signed=False, invert=False, name="Distance"): 1522 """ 1523 Computes the distance from one point cloud or mesh to another point cloud or mesh. 1524 This new `pointdata` array is saved with default name "Distance". 1525 1526 Keywords `signed` and `invert` are used to compute signed distance, 1527 but the mesh in that case must have polygonal faces (not a simple point cloud), 1528 and normals must also be computed. 1529 1530 Examples: 1531 - [distance2mesh.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/distance2mesh.py) 1532 1533 ![](https://vedo.embl.es/images/basic/distance2mesh.png) 1534 """ 1535 if pcloud.inputdata().GetNumberOfPolys(): 1536 1537 poly1 = self.polydata() 1538 poly2 = pcloud.polydata() 1539 df = vtk.vtkDistancePolyDataFilter() 1540 df.ComputeSecondDistanceOff() 1541 df.SetInputData(0, poly1) 1542 df.SetInputData(1, poly2) 1543 df.SetSignedDistance(signed) 1544 df.SetNegateDistance(invert) 1545 df.Update() 1546 scals = df.GetOutput().GetPointData().GetScalars() 1547 dists = utils.vtk2numpy(scals) 1548 1549 else: # has no polygons and vtkDistancePolyDataFilter wants them (dont know why) 1550 1551 if signed: 1552 vedo.logger.warning("distanceTo() called with signed=True but input object has no polygons") 1553 1554 if not pcloud.point_locator: 1555 pcloud.point_locator = vtk.vtkPointLocator() 1556 pcloud.point_locator.SetDataSet(pcloud.polydata()) 1557 pcloud.point_locator.BuildLocator() 1558 1559 ids = [] 1560 ps1 = self.points() 1561 ps2 = pcloud.points() 1562 for p in ps1: 1563 pid = pcloud.point_locator.FindClosestPoint(p) 1564 ids.append(pid) 1565 1566 deltas = ps2[ids] - ps1 1567 dists = np.linalg.norm(deltas, axis=1).astype(np.float32) 1568 scals = utils.numpy2vtk(dists) 1569 1570 scals.SetName(name) 1571 self.inputdata().GetPointData().AddArray(scals) # must be self.inputdata() ! 1572 self.inputdata().GetPointData().SetActiveScalars(scals.GetName()) 1573 rng = scals.GetRange() 1574 self.mapper().SetScalarRange(rng[0], rng[1]) 1575 self.mapper().ScalarVisibilityOn() 1576 1577 self.pipeline = utils.OperationNode( 1578 "distance_to", 1579 parents=[self, pcloud], 1580 shape="cylinder", 1581 comment=f"#pts {self._data.GetNumberOfPoints()}", 1582 ) 1583 return dists
Computes the distance from one point cloud or mesh to another point cloud or mesh.
This new pointdata
array is saved with default name "Distance".
Keywords signed
and invert
are used to compute signed distance,
but the mesh in that case must have polygonal faces (not a simple point cloud),
and normals must also be computed.
Examples:
1585 def alpha(self, opacity=None): 1586 """Set/get mesh's transparency. Same as `mesh.opacity()`.""" 1587 if opacity is None: 1588 return self.GetProperty().GetOpacity() 1589 1590 self.GetProperty().SetOpacity(opacity) 1591 bfp = self.GetBackfaceProperty() 1592 if bfp: 1593 if opacity < 1: 1594 self._bfprop = bfp 1595 self.SetBackfaceProperty(None) 1596 else: 1597 self.SetBackfaceProperty(self._bfprop) 1598 return self
Set/get mesh's transparency. Same as mesh.opacity()
.
1600 def opacity(self, alpha=None): 1601 """Set/get mesh's transparency. Same as `mesh.alpha()`.""" 1602 return self.alpha(alpha)
Set/get mesh's transparency. Same as mesh.alpha()
.
1604 def force_opaque(self, value=True): 1605 """ Force the Mesh, Line or point cloud to be treated as opaque""" 1606 ## force the opaque pass, fixes picking in vtk9 1607 # but causes other bad troubles with lines.. 1608 self.SetForceOpaque(value) 1609 return self
Force the Mesh, Line or point cloud to be treated as opaque
1611 def force_translucent(self, value=True): 1612 """ Force the Mesh, Line or point cloud to be treated as translucent""" 1613 self.SetForceTranslucent(value) 1614 return self
Force the Mesh, Line or point cloud to be treated as translucent
1616 def point_size(self, value=None): 1617 """Set/get mesh's point size of vertices. Same as `mesh.ps()`""" 1618 if value is None: 1619 return self.GetProperty().GetPointSize() 1620 #self.GetProperty().SetRepresentationToSurface() 1621 else: 1622 self.GetProperty().SetRepresentationToPoints() 1623 self.GetProperty().SetPointSize(value) 1624 return self
Set/get mesh's point size of vertices. Same as mesh.ps()
1626 def ps(self, pointsize=None): 1627 """Set/get mesh's point size of vertices. Same as `mesh.point_size()`""" 1628 return self.point_size(pointsize)
Set/get mesh's point size of vertices. Same as mesh.point_size()
1630 def render_points_as_spheres(self, value=True): 1631 """Make points look spheric or make them look as squares.""" 1632 self.GetProperty().SetRenderPointsAsSpheres(value) 1633 return self
Make points look spheric or make them look as squares.
1635 def color(self, c=False, alpha=None): 1636 """ 1637 Set/get mesh's color. 1638 If None is passed as input, will use colors from active scalars. 1639 Same as `mesh.c()`. 1640 """ 1641 # overrides base.color() 1642 if c is False: 1643 return np.array(self.GetProperty().GetColor()) 1644 if c is None: 1645 self.mapper().ScalarVisibilityOn() 1646 return self 1647 self.mapper().ScalarVisibilityOff() 1648 cc = colors.get_color(c) 1649 self.GetProperty().SetColor(cc) 1650 if self.trail: 1651 self.trail.GetProperty().SetColor(cc) 1652 if alpha is not None: 1653 self.alpha(alpha) 1654 return self
Set/get mesh's color.
If None is passed as input, will use colors from active scalars.
Same as mesh.c()
.
1656 def clean(self): 1657 """ 1658 Clean pointcloud or mesh by removing coincident points. 1659 """ 1660 cpd = vtk.vtkCleanPolyData() 1661 cpd.PointMergingOn() 1662 cpd.ConvertLinesToPointsOn() 1663 cpd.ConvertPolysToLinesOn() 1664 cpd.ConvertStripsToPolysOn() 1665 cpd.SetInputData(self.inputdata()) 1666 cpd.Update() 1667 out = self._update(cpd.GetOutput()) 1668 1669 out.pipeline = utils.OperationNode( 1670 "clean", parents=[self], 1671 comment=f"#pts {out.inputdata().GetNumberOfPoints()}" 1672 ) 1673 return out
Clean pointcloud or mesh by removing coincident points.
1675 def subsample(self, fraction, absolute=False): 1676 """ 1677 Subsample a point cloud by requiring that the points 1678 or vertices are far apart at least by the specified fraction of the object size. 1679 If a Mesh is passed the polygonal faces are not removed 1680 but holes can appear as vertices are removed. 1681 1682 Examples: 1683 - [moving_least_squares1D.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/moving_least_squares1D.py) 1684 1685 ![](https://vedo.embl.es/images/advanced/moving_least_squares1D.png) 1686 1687 - [recosurface.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/recosurface.py) 1688 1689 ![](https://vedo.embl.es/images/advanced/recosurface.png) 1690 """ 1691 if not absolute: 1692 if fraction > 1: 1693 vedo.logger.warning( 1694 f"subsample(fraction=...), fraction must be < 1, but is {fraction}" 1695 ) 1696 if fraction <= 0: 1697 return self 1698 1699 cpd = vtk.vtkCleanPolyData() 1700 cpd.PointMergingOn() 1701 cpd.ConvertLinesToPointsOn() 1702 cpd.ConvertPolysToLinesOn() 1703 cpd.ConvertStripsToPolysOn() 1704 cpd.SetInputData(self.inputdata()) 1705 if absolute: 1706 cpd.SetTolerance(fraction / self.diagonal_size()) 1707 # cpd.SetToleranceIsAbsolute(absolute) 1708 else: 1709 cpd.SetTolerance(fraction) 1710 cpd.Update() 1711 1712 ps = 2 1713 if self.GetProperty().GetRepresentation() == 0: 1714 ps = self.GetProperty().GetPointSize() 1715 1716 out = self._update(cpd.GetOutput()).ps(ps) 1717 1718 out.pipeline = utils.OperationNode( 1719 "subsample", parents=[self], comment=f"#pts {out.inputdata().GetNumberOfPoints()}" 1720 ) 1721 return out
Subsample a point cloud by requiring that the points or vertices are far apart at least by the specified fraction of the object size. If a Mesh is passed the polygonal faces are not removed but holes can appear as vertices are removed.
Examples:
1723 def threshold(self, scalars, above=None, below=None, on="points"): 1724 """ 1725 Extracts cells where scalar value satisfies threshold criterion. 1726 1727 Arguments: 1728 scalars : (str) 1729 name of the scalars array. 1730 above : (float) 1731 minimum value of the scalar 1732 below : (float) 1733 maximum value of the scalar 1734 on : (str) 1735 if 'cells' assume array of scalars refers to cell data. 1736 1737 Examples: 1738 - [mesh_threshold.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_threshold.py) 1739 """ 1740 thres = vtk.vtkThreshold() 1741 thres.SetInputData(self.inputdata()) 1742 1743 if on.startswith("c"): 1744 asso = vtk.vtkDataObject.FIELD_ASSOCIATION_CELLS 1745 else: 1746 asso = vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS 1747 1748 thres.SetInputArrayToProcess(0, 0, 0, asso, scalars) 1749 1750 if above is None and below is not None: 1751 thres.ThresholdByLower(below) 1752 elif below is None and above is not None: 1753 thres.ThresholdByUpper(above) 1754 else: 1755 thres.ThresholdBetween(above, below) 1756 thres.Update() 1757 1758 gf = vtk.vtkGeometryFilter() 1759 gf.SetInputData(thres.GetOutput()) 1760 gf.Update() 1761 return self._update(gf.GetOutput())
Extracts cells where scalar value satisfies threshold criterion.
Arguments:
- scalars : (str) name of the scalars array.
- above : (float) minimum value of the scalar
- below : (float) maximum value of the scalar
- on : (str) if 'cells' assume array of scalars refers to cell data.
Examples:
1763 def quantize(self, value): 1764 """ 1765 The user should input a value and all {x,y,z} coordinates 1766 will be quantized to that absolute grain size. 1767 """ 1768 poly = self.inputdata() 1769 qp = vtk.vtkQuantizePolyDataPoints() 1770 qp.SetInputData(poly) 1771 qp.SetQFactor(value) 1772 qp.Update() 1773 out = self._update(qp.GetOutput()).flat() 1774 out.pipeline = utils.OperationNode("quantize", parents=[self]) 1775 return out
The user should input a value and all {x,y,z} coordinates will be quantized to that absolute grain size.
1777 def average_size(self): 1778 """ 1779 Calculate the average size of a mesh. 1780 This is the mean of the vertex distances from the center of mass. 1781 """ 1782 coords = self.points() 1783 cm = np.mean(coords, axis=0) 1784 if coords.shape[0] == 0: 1785 return 0.0 1786 cc = coords - cm 1787 return np.mean(np.linalg.norm(cc, axis=1))
Calculate the average size of a mesh. This is the mean of the vertex distances from the center of mass.
1789 def center_of_mass(self): 1790 """Get the center of mass of mesh.""" 1791 cmf = vtk.vtkCenterOfMass() 1792 cmf.SetInputData(self.polydata()) 1793 cmf.Update() 1794 c = cmf.GetCenter() 1795 return np.array(c)
Get the center of mass of mesh.
1797 def normal_at(self, i): 1798 """Return the normal vector at vertex point `i`.""" 1799 normals = self.polydata().GetPointData().GetNormals() 1800 return np.array(normals.GetTuple(i))
Return the normal vector at vertex point i
.
1802 def normals(self, cells=False, recompute=True): 1803 """Retrieve vertex normals as a numpy array. 1804 1805 Arguments: 1806 cells : (bool) 1807 if `True` return cell normals. 1808 1809 recompute : (bool) 1810 if `True` normals are recalculated if not already present. 1811 Note that this might modify the number of mesh points. 1812 """ 1813 if cells: 1814 vtknormals = self.polydata().GetCellData().GetNormals() 1815 else: 1816 vtknormals = self.polydata().GetPointData().GetNormals() 1817 if not vtknormals and recompute: 1818 try: 1819 self.compute_normals(cells=cells) 1820 if cells: 1821 vtknormals = self.polydata().GetCellData().GetNormals() 1822 else: 1823 vtknormals = self.polydata().GetPointData().GetNormals() 1824 except AttributeError: 1825 # can be that 'Points' object has no attribute 'compute_normals' 1826 pass 1827 1828 if not vtknormals: 1829 return np.array([]) 1830 return utils.vtk2numpy(vtknormals)
Retrieve vertex normals as a numpy array.
Arguments:
- cells : (bool)
if
True
return cell normals. - recompute : (bool)
if
True
normals are recalculated if not already present. Note that this might modify the number of mesh points.
1832 def labels( 1833 self, 1834 content=None, 1835 on="points", 1836 scale=None, 1837 xrot=0.0, 1838 yrot=0.0, 1839 zrot=0.0, 1840 ratio=1, 1841 precision=None, 1842 italic=False, 1843 font="", 1844 justify="bottom-left", 1845 c="black", 1846 alpha=1.0, 1847 cells=None, 1848 ): 1849 """ 1850 Generate value or ID labels for mesh cells or points. 1851 For large nr. of labels use `font="VTK"` which is much faster. 1852 1853 See also: 1854 `labels2d()`, `flagpole()`, `caption()` and `legend()`. 1855 1856 Arguments: 1857 content : (list,int,str) 1858 either 'id', 'cellid', array name or array number. 1859 A array can also be passed (must match the nr. of points or cells). 1860 on : (str) 1861 generate labels for "cells" instead of "points" 1862 scale : (float) 1863 absolute size of labels, if left as None it is automatic 1864 zrot : (float) 1865 local rotation angle of label in degrees 1866 ratio : (int) 1867 skipping ratio, to reduce nr of labels for large meshes 1868 precision : (int) 1869 numeric precision of labels 1870 1871 ```python 1872 from vedo import * 1873 s = Sphere(res=10).linewidth(1).c("orange").compute_normals() 1874 point_ids = s.labels('id', on="points").c('green') 1875 cell_ids = s.labels('id', on="cells" ).c('black') 1876 show(s, point_ids, cell_ids) 1877 ``` 1878 ![](https://vedo.embl.es/images/feats/labels.png) 1879 1880 Examples: 1881 - [boundaries.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/boundaries.py) 1882 1883 ![](https://vedo.embl.es/images/basic/boundaries.png) 1884 """ 1885 if cells is not None: # deprecation message 1886 vedo.logger.warning("In labels(cells=...) please use labels(on='cells') instead") 1887 1888 if "cell" in on or "face" in on: 1889 cells = True 1890 1891 if isinstance(content, str): 1892 if content in ("cellid", "cellsid"): 1893 cells = True 1894 content = "id" 1895 1896 if cells: 1897 elems = self.cell_centers() 1898 norms = self.normals(cells=True, recompute=False) 1899 ns = np.sqrt(self.ncells) 1900 else: 1901 elems = self.points() 1902 norms = self.normals(cells=False, recompute=False) 1903 ns = np.sqrt(self.npoints) 1904 1905 hasnorms = False 1906 if len(norms) > 0: 1907 hasnorms = True 1908 1909 if scale is None: 1910 if not ns: 1911 ns = 100 1912 scale = self.diagonal_size() / ns / 10 1913 1914 arr = None 1915 mode = 0 1916 if content is None: 1917 mode = 0 1918 if cells: 1919 if self.inputdata().GetCellData().GetScalars(): 1920 name = self.inputdata().GetCellData().GetScalars().GetName() 1921 arr = self.celldata[name] 1922 else: 1923 if self.inputdata().GetPointData().GetScalars(): 1924 name = self.inputdata().GetPointData().GetScalars().GetName() 1925 arr = self.pointdata[name] 1926 elif isinstance(content, (str, int)): 1927 if content == "id": 1928 mode = 1 1929 elif cells: 1930 mode = 0 1931 arr = self.celldata[content] 1932 else: 1933 mode = 0 1934 arr = self.pointdata[content] 1935 elif utils.is_sequence(content): 1936 mode = 0 1937 arr = content 1938 # print('WEIRD labels() test', content) 1939 # exit() 1940 1941 if arr is None and mode == 0: 1942 vedo.logger.error("in labels(), array not found for points or cells") 1943 return None 1944 1945 tapp = vtk.vtkAppendPolyData() 1946 ninputs = 0 1947 1948 for i, e in enumerate(elems): 1949 if i % ratio: 1950 continue 1951 1952 if mode == 1: 1953 txt_lab = str(i) 1954 else: 1955 if precision: 1956 txt_lab = utils.precision(arr[i], precision) 1957 else: 1958 txt_lab = str(arr[i]) 1959 1960 if not txt_lab: 1961 continue 1962 1963 if font == "VTK": 1964 tx = vtk.vtkVectorText() 1965 tx.SetText(txt_lab) 1966 tx.Update() 1967 tx_poly = tx.GetOutput() 1968 else: 1969 tx_poly = vedo.shapes.Text3D(txt_lab, font=font, justify=justify) 1970 tx_poly = tx_poly.inputdata() 1971 1972 if tx_poly.GetNumberOfPoints() == 0: 1973 continue ####################### 1974 ninputs += 1 1975 1976 T = vtk.vtkTransform() 1977 T.PostMultiply() 1978 if italic: 1979 T.Concatenate([1,0.2,0,0, 1980 0,1,0,0, 1981 0,0,1,0, 1982 0,0,0,1]) 1983 if hasnorms: 1984 ni = norms[i] 1985 if cells: # center-justify 1986 bb = tx_poly.GetBounds() 1987 dx, dy = (bb[1] - bb[0]) / 2, (bb[3] - bb[2]) / 2 1988 T.Translate(-dx, -dy, 0) 1989 if xrot: 1990 T.RotateX(xrot) 1991 if yrot: 1992 T.RotateY(yrot) 1993 if zrot: 1994 T.RotateZ(zrot) 1995 crossvec = np.cross([0, 0, 1], ni) 1996 angle = np.arccos(np.dot([0, 0, 1], ni)) * 57.3 1997 T.RotateWXYZ(angle, crossvec) 1998 if cells: # small offset along normal only for cells 1999 T.Translate(ni * scale / 2) 2000 else: 2001 if xrot: 2002 T.RotateX(xrot) 2003 if yrot: 2004 T.RotateY(yrot) 2005 if zrot: 2006 T.RotateZ(zrot) 2007 T.Scale(scale, scale, scale) 2008 T.Translate(e) 2009 tf = vtk.vtkTransformPolyDataFilter() 2010 tf.SetInputData(tx_poly) 2011 tf.SetTransform(T) 2012 tf.Update() 2013 tapp.AddInputData(tf.GetOutput()) 2014 2015 if ninputs: 2016 tapp.Update() 2017 lpoly = tapp.GetOutput() 2018 else: # return an empty obj 2019 lpoly = vtk.vtkPolyData() 2020 2021 ids = vedo.mesh.Mesh(lpoly, c=c, alpha=alpha) 2022 ids.GetProperty().LightingOff() 2023 ids.PickableOff() 2024 ids.SetUseBounds(False) 2025 return ids
Generate value or ID labels for mesh cells or points.
For large nr. of labels use font="VTK"
which is much faster.
See also:
labels2d()
,flagpole()
,caption()
andlegend()
.
Arguments:
- content : (list,int,str) either 'id', 'cellid', array name or array number. A array can also be passed (must match the nr. of points or cells).
- on : (str) generate labels for "cells" instead of "points"
- scale : (float) absolute size of labels, if left as None it is automatic
- zrot : (float) local rotation angle of label in degrees
- ratio : (int) skipping ratio, to reduce nr of labels for large meshes
- precision : (int) numeric precision of labels
from vedo import *
s = Sphere(res=10).linewidth(1).c("orange").compute_normals()
point_ids = s.labels('id', on="points").c('green')
cell_ids = s.labels('id', on="cells" ).c('black')
show(s, point_ids, cell_ids)
Examples:
2027 def labels2d( 2028 self, 2029 content="id", 2030 on="points", 2031 scale=1.0, 2032 precision=4, 2033 font="Calco", 2034 justify="bottom-left", 2035 angle=0.0, 2036 frame=False, 2037 c="black", 2038 bc=None, 2039 alpha=1.0, 2040 ): 2041 """ 2042 Generate value or ID bi-dimensional labels for mesh cells or points. 2043 2044 See also: `labels()`, `flagpole()`, `caption()` and `legend()`. 2045 2046 Arguments: 2047 content : (str) 2048 either 'id', 'cellid', or array name 2049 on : (str) 2050 generate labels for "cells" instead of "points" (the default) 2051 scale : (float) 2052 size scaling of labels 2053 precision : (int) 2054 precision of numeric labels 2055 angle : (float) 2056 local rotation angle of label in degrees 2057 frame : (bool) 2058 draw a frame around the label 2059 bc : (str) 2060 background color of the label 2061 2062 ```python 2063 from vedo import Sphere, show 2064 sph = Sphere(quads=True, res=4).compute_normals().wireframe() 2065 sph.celldata["zvals"] = sph.cell_centers()[:,2] 2066 l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9') 2067 show(sph, l2d, axes=1).close() 2068 ``` 2069 ![](https://vedo.embl.es/images/feats/labels2d.png) 2070 """ 2071 cells = False 2072 if isinstance(content, str): 2073 if content in ("cellid", "cellsid"): 2074 cells = True 2075 content = "id" 2076 2077 if "cell" in on: 2078 cells = True 2079 elif "point" in on: 2080 cells = False 2081 2082 if cells: 2083 if content != "id" and content not in self.celldata.keys(): 2084 vedo.logger.error(f"In labels2d: cell array {content} does not exist.") 2085 return None 2086 cellcloud = Points(self.cell_centers()) 2087 arr = self.inputdata().GetCellData().GetScalars() 2088 poly = cellcloud.polydata(False) 2089 poly.GetPointData().SetScalars(arr) 2090 else: 2091 poly = self.polydata() 2092 if content != "id" and content not in self.pointdata.keys(): 2093 vedo.logger.error(f"In labels2d: point array {content} does not exist.") 2094 return None 2095 self.pointdata.select(content) 2096 2097 mp = vtk.vtkLabeledDataMapper() 2098 2099 if content == "id": 2100 mp.SetLabelModeToLabelIds() 2101 else: 2102 mp.SetLabelModeToLabelScalars() 2103 if precision is not None: 2104 mp.SetLabelFormat(f"%-#.{precision}g") 2105 2106 pr = mp.GetLabelTextProperty() 2107 c = colors.get_color(c) 2108 pr.SetColor(c) 2109 pr.SetOpacity(alpha) 2110 pr.SetFrame(frame) 2111 pr.SetFrameColor(c) 2112 pr.SetItalic(False) 2113 pr.BoldOff() 2114 pr.ShadowOff() 2115 pr.UseTightBoundingBoxOn() 2116 pr.SetOrientation(angle) 2117 pr.SetFontFamily(vtk.VTK_FONT_FILE) 2118 fl = utils.get_font_path(font) 2119 pr.SetFontFile(fl) 2120 pr.SetFontSize(int(20 * scale)) 2121 2122 if "cent" in justify or "mid" in justify: 2123 pr.SetJustificationToCentered() 2124 elif "rig" in justify: 2125 pr.SetJustificationToRight() 2126 elif "left" in justify: 2127 pr.SetJustificationToLeft() 2128 # ------ 2129 if "top" in justify: 2130 pr.SetVerticalJustificationToTop() 2131 else: 2132 pr.SetVerticalJustificationToBottom() 2133 2134 if bc is not None: 2135 bc = colors.get_color(bc) 2136 pr.SetBackgroundColor(bc) 2137 pr.SetBackgroundOpacity(alpha) 2138 2139 mp.SetInputData(poly) 2140 a2d = vtk.vtkActor2D() 2141 a2d.PickableOff() 2142 a2d.SetMapper(mp) 2143 return a2d
Generate value or ID bi-dimensional labels for mesh cells or points.
See also: labels()
, flagpole()
, caption()
and legend()
.
Arguments:
- content : (str) either 'id', 'cellid', or array name
- on : (str) generate labels for "cells" instead of "points" (the default)
- scale : (float) size scaling of labels
- precision : (int) precision of numeric labels
- angle : (float) local rotation angle of label in degrees
- frame : (bool) draw a frame around the label
- bc : (str) background color of the label
from vedo import Sphere, show
sph = Sphere(quads=True, res=4).compute_normals().wireframe()
sph.celldata["zvals"] = sph.cell_centers()[:,2]
l2d = sph.labels("zvals", on="cells", precision=2).backcolor('orange9')
show(sph, l2d, axes=1).close()
2145 def legend(self, txt): 2146 """Book a legend text.""" 2147 self.info["legend"] = txt 2148 return self
Book a legend text.
2150 def flagpole( 2151 self, 2152 txt=None, 2153 point=None, 2154 offset=None, 2155 s=None, 2156 font="", 2157 rounded=True, 2158 c=None, 2159 alpha=1.0, 2160 lw=2, 2161 italic=0.0, 2162 padding=0.1, 2163 ): 2164 """ 2165 Generate a flag pole style element to describe an object. 2166 Returns a `Mesh` object. 2167 2168 Use flagpole.follow_camera() to make it face the camera in the scene. 2169 2170 See also `flagpost()`. 2171 2172 Arguments: 2173 txt : (str) 2174 Text to display. The default is the filename or the object name. 2175 point : (list) 2176 position of the flagpole pointer. 2177 offset : (list) 2178 text offset wrt the application point. 2179 s : (float) 2180 size of the flagpole. 2181 font : (str) 2182 font face. Check [available fonts here](https://vedo.embl.es/fonts). 2183 rounded : (bool) 2184 draw a rounded or squared box around the text. 2185 c : (list) 2186 text and box color. 2187 alpha : (float) 2188 opacity of text and box. 2189 lw : (float) 2190 line with of box frame. 2191 italic : (float) 2192 italicness of text. 2193 2194 Examples: 2195 - [intersect2d.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/intersect2d.py) 2196 2197 ![](https://vedo.embl.es/images/pyplot/intersect2d.png) 2198 2199 - [goniometer.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/goniometer.py) 2200 - [flag_labels1.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels1.py) 2201 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/other/flag_labels2.py) 2202 """ 2203 acts = [] 2204 2205 if txt is None: 2206 if self.filename: 2207 txt = self.filename.split("/")[-1] 2208 elif self.name: 2209 txt = self.name 2210 else: 2211 return None 2212 2213 x0, x1, y0, y1, z0, z1 = self.bounds() 2214 d = self.diagonal_size() 2215 if point is None: 2216 if d: 2217 point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1]) 2218 else: # it's a Point 2219 point = self.GetPosition() 2220 2221 pt = utils.make3d(point) 2222 2223 if offset is None: 2224 offset = [(x1 - x0) / 2, (y1 - y0) / 6, 0] 2225 offset = utils.make3d(offset) 2226 2227 if s is None: 2228 s = d / 20 2229 2230 sph = None 2231 if d and (z1 - z0) / d > 0.1: 2232 sph = vedo.shapes.Sphere(pt, r=s * 0.4, res=6) 2233 2234 if c is None: 2235 c = np.array(self.color()) / 1.4 2236 2237 lb = vedo.shapes.Text3D( 2238 txt, pos=pt + offset, s=s, font=font, italic=italic, justify="center-left" 2239 ) 2240 acts.append(lb) 2241 2242 if d and not sph: 2243 sph = vedo.shapes.Circle(pt, r=s / 3, res=15) 2244 acts.append(sph) 2245 2246 x0, x1, y0, y1, z0, z1 = lb.GetBounds() 2247 if rounded: 2248 box = vedo.shapes.KSpline( 2249 [(x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0)], closed=True 2250 ) 2251 else: 2252 box = vedo.shapes.Line( 2253 [(x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0), (x0, y0, z0)] 2254 ) 2255 2256 cnt = [(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2] 2257 2258 box.SetOrigin(cnt) 2259 box.scale([1 + padding, 1 + 2 * padding, 1]) 2260 acts.append(box) 2261 2262 # pts = box.points() 2263 # bfaces = [] 2264 # for i, pt in enumerate(pts): 2265 # if i: 2266 # face = [i-1, i, 0] 2267 # bfaces.append(face) 2268 # bpts = [cnt] + pts.tolist() 2269 # box2 = vedo.Mesh([bpts, bfaces]).z(-cnt[0]/10)#.c('w').alpha(0.1) 2270 # #should be made assembly otherwise later merge() nullifies it 2271 # box2.SetOrigin(cnt) 2272 # acts.append(box2) 2273 2274 x0, x1, y0, y1, z0, z1 = box.bounds() 2275 if x0 < pt[0] < x1: 2276 c0 = box.closest_point(pt) 2277 c1 = [c0[0], c0[1] + (pt[1] - y0) / 4, pt[2]] 2278 elif (pt[0] - x0) < (x1 - pt[0]): 2279 c0 = [x0, (y0 + y1) / 2, pt[2]] 2280 c1 = [x0 + (pt[0] - x0) / 4, (y0 + y1) / 2, pt[2]] 2281 else: 2282 c0 = [x1, (y0 + y1) / 2, pt[2]] 2283 c1 = [x1 + (pt[0] - x1) / 4, (y0 + y1) / 2, pt[2]] 2284 2285 con = vedo.shapes.Line([c0, c1, pt]) 2286 acts.append(con) 2287 2288 macts = vedo.merge(acts).c(c).alpha(alpha) 2289 macts.SetOrigin(pt) 2290 macts.bc("tomato").pickable(False) 2291 macts.GetProperty().LightingOff() 2292 macts.GetProperty().SetLineWidth(lw) 2293 macts.UseBoundsOff() 2294 macts.name = "FlagPole" 2295 return macts
Generate a flag pole style element to describe an object.
Returns a Mesh
object.
Use flagpole.follow_camera() to make it face the camera in the scene.
See also flagpost()
.
Arguments:
- txt : (str) Text to display. The default is the filename or the object name.
- point : (list) position of the flagpole pointer.
- offset : (list) text offset wrt the application point.
- s : (float) size of the flagpole.
- font : (str) font face. Check available fonts here.
- rounded : (bool) draw a rounded or squared box around the text.
- c : (list) text and box color.
- alpha : (float) opacity of text and box.
- lw : (float) line with of box frame.
- italic : (float) italicness of text.
Examples:
2297 def flagpost( 2298 self, 2299 txt=None, 2300 point=None, 2301 offset=None, 2302 s=1.0, 2303 c="k9", 2304 bc="k1", 2305 alpha=1, 2306 lw=0, 2307 font="Calco", 2308 justify="center-left", 2309 vspacing=1.0, 2310 ): 2311 """ 2312 Generate a flag post style element to describe an object. 2313 2314 Arguments: 2315 txt : (str) 2316 Text to display. The default is the filename or the object name. 2317 point : (list) 2318 position of the flag anchor point. The default is None. 2319 offset : (list) 2320 a 3D displacement or offset. The default is None. 2321 s : (float) 2322 size of the text to be shown 2323 c : (list) 2324 color of text and line 2325 bc : (list) 2326 color of the flag background 2327 alpha : (float) 2328 opacity of text and box. 2329 lw : (int) 2330 line with of box frame. The default is 0. 2331 font : (str) 2332 font name. Use a monospace font for better rendering. The default is "Calco". 2333 Type `vedo -r fonts` for a font demo. 2334 Check [available fonts here](https://vedo.embl.es/fonts). 2335 justify : (str) 2336 internal text justification. The default is "center-left". 2337 vspacing : (float) 2338 vertical spacing between lines. 2339 2340 Examples: 2341 - [flag_labels2.py](https://github.com/marcomusy/vedo/tree/master/examples/examples/other/flag_labels2.py) 2342 2343 ![](https://vedo.embl.es/images/other/flag_labels2.png) 2344 """ 2345 if txt is None: 2346 if self.filename: 2347 txt = self.filename.split("/")[-1] 2348 elif self.name: 2349 txt = self.name 2350 else: 2351 return None 2352 2353 x0, x1, y0, y1, z0, z1 = self.bounds() 2354 d = self.diagonal_size() 2355 if point is None: 2356 if d: 2357 point = self.closest_point([(x0 + x1) / 2, (y0 + y1) / 2, z1]) 2358 else: # it's a Point 2359 point = self.GetPosition() 2360 2361 point = utils.make3d(point) 2362 2363 if offset is None: 2364 offset = [0, 0, (z1 - z0) / 2] 2365 offset = utils.make3d(offset) 2366 2367 fpost = vedo.addons.Flagpost( 2368 txt, point, point + offset, s, c, bc, alpha, lw, font, justify, vspacing 2369 ) 2370 self._caption = fpost 2371 return fpost
Generate a flag post style element to describe an object.
Arguments:
- txt : (str) Text to display. The default is the filename or the object name.
- point : (list) position of the flag anchor point. The default is None.
- offset : (list) a 3D displacement or offset. The default is None.
- s : (float) size of the text to be shown
- c : (list) color of text and line
- bc : (list) color of the flag background
- alpha : (float) opacity of text and box.
- lw : (int) line with of box frame. The default is 0.
- font : (str)
font name. Use a monospace font for better rendering. The default is "Calco".
Type
vedo -r fonts
for a font demo. Check available fonts here. - justify : (str) internal text justification. The default is "center-left".
- vspacing : (float) vertical spacing between lines.
Examples:
2496 def align_to(self, target, iters=100, rigid=False, invert=False, use_centroids=False): 2497 """ 2498 Aligned to target mesh through the `Iterative Closest Point` algorithm. 2499 2500 The core of the algorithm is to match each vertex in one surface with 2501 the closest surface point on the other, then apply the transformation 2502 that modify one surface to best match the other (in the least-square sense). 2503 2504 Arguments: 2505 rigid : (bool) 2506 if True do not allow scaling 2507 invert : (bool) 2508 if True start by aligning the target to the source but 2509 invert the transformation finally. Useful when the target is smaller 2510 than the source. 2511 use_centroids : (bool) 2512 start by matching the centroids of the two objects. 2513 2514 Examples: 2515 - [align1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align1.py) 2516 2517 ![](https://vedo.embl.es/images/basic/align1.png) 2518 2519 - [align2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align2.py) 2520 2521 ![](https://vedo.embl.es/images/basic/align2.png) 2522 """ 2523 icp = vtk.vtkIterativeClosestPointTransform() 2524 icp.SetSource(self.polydata()) 2525 icp.SetTarget(target.polydata()) 2526 if invert: 2527 icp.Inverse() 2528 icp.SetMaximumNumberOfIterations(iters) 2529 if rigid: 2530 icp.GetLandmarkTransform().SetModeToRigidBody() 2531 icp.SetStartByMatchingCentroids(use_centroids) 2532 icp.Update() 2533 2534 M = icp.GetMatrix() 2535 if invert: 2536 M.Invert() # icp.GetInverse() doesnt work! 2537 # self.apply_transform(M) 2538 self.SetUserMatrix(M) 2539 2540 self.transform = self.GetUserTransform() 2541 self.point_locator = None 2542 self.cell_locator = None 2543 2544 self.pipeline = utils.OperationNode( 2545 "align_to", parents=[self, target], comment=f"rigid = {rigid}" 2546 ) 2547 return self
Aligned to target mesh through the Iterative Closest Point
algorithm.
The core of the algorithm is to match each vertex in one surface with the closest surface point on the other, then apply the transformation that modify one surface to best match the other (in the least-square sense).
Arguments:
- rigid : (bool) if True do not allow scaling
- invert : (bool) if True start by aligning the target to the source but invert the transformation finally. Useful when the target is smaller than the source.
- use_centroids : (bool) start by matching the centroids of the two objects.
Examples:
2549 def transform_with_landmarks( 2550 self, source_landmarks, target_landmarks, rigid=False, affine=False, least_squares=False 2551 ): 2552 """ 2553 Transform mesh orientation and position based on a set of landmarks points. 2554 The algorithm finds the best matching of source points to target points 2555 in the mean least square sense, in one single step. 2556 2557 If affine is True the x, y and z axes can scale independently but stay collinear. 2558 With least_squares they can vary orientation. 2559 2560 Examples: 2561 - [align5.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align5.py) 2562 2563 ![](https://vedo.embl.es/images/basic/align5.png) 2564 """ 2565 2566 if utils.is_sequence(source_landmarks): 2567 ss = vtk.vtkPoints() 2568 for p in source_landmarks: 2569 ss.InsertNextPoint(p) 2570 else: 2571 ss = source_landmarks.polydata().GetPoints() 2572 if least_squares: 2573 source_landmarks = source_landmarks.points() 2574 2575 if utils.is_sequence(target_landmarks): 2576 st = vtk.vtkPoints() 2577 for p in target_landmarks: 2578 st.InsertNextPoint(p) 2579 else: 2580 st = target_landmarks.polydata().GetPoints() 2581 if least_squares: 2582 target_landmarks = target_landmarks.points() 2583 2584 if ss.GetNumberOfPoints() != st.GetNumberOfPoints(): 2585 n1 = ss.GetNumberOfPoints() 2586 n2 = st.GetNumberOfPoints() 2587 vedo.logger.error(f"source and target have different nr of points {n1} vs {n2}") 2588 raise RuntimeError() 2589 2590 lmt = vtk.vtkLandmarkTransform() 2591 lmt.SetSourceLandmarks(ss) 2592 lmt.SetTargetLandmarks(st) 2593 lmt.SetModeToSimilarity() 2594 if rigid: 2595 lmt.SetModeToRigidBody() 2596 lmt.Update() 2597 self.SetUserTransform(lmt) 2598 2599 elif affine: 2600 lmt.SetModeToAffine() 2601 lmt.Update() 2602 self.SetUserTransform(lmt) 2603 2604 elif least_squares: 2605 cms = source_landmarks.mean(axis=0) 2606 cmt = target_landmarks.mean(axis=0) 2607 m = np.linalg.lstsq(source_landmarks - cms, target_landmarks - cmt, rcond=None)[0] 2608 M = vtk.vtkMatrix4x4() 2609 for i in range(3): 2610 for j in range(3): 2611 M.SetElement(j, i, m[i][j]) 2612 lmt = vtk.vtkTransform() 2613 lmt.Translate(cmt) 2614 lmt.Concatenate(M) 2615 lmt.Translate(-cms) 2616 self.apply_transform(lmt, concatenate=True) 2617 else: 2618 self.SetUserTransform(lmt) 2619 2620 self.transform = lmt 2621 self.point_locator = None 2622 self.cell_locator = None 2623 self.pipeline = utils.OperationNode("transform_with_landmarks", parents=[self]) 2624 return self
Transform mesh orientation and position based on a set of landmarks points. The algorithm finds the best matching of source points to target points in the mean least square sense, in one single step.
If affine is True the x, y and z axes can scale independently but stay collinear. With least_squares they can vary orientation.
Examples:
2627 def apply_transform(self, T, reset=False, concatenate=False): 2628 """ 2629 Apply a linear or non-linear transformation to the mesh polygonal data. 2630 2631 Arguments: 2632 T : (matrix) 2633 `vtkTransform`, `vtkMatrix4x4` or a 4x4 or 3x3 python or numpy matrix. 2634 reset : (bool) 2635 if True reset the current transformation matrix 2636 to identity after having moved the object, otherwise the internal 2637 matrix will stay the same (to only affect visualization). 2638 It the input transformation has no internal defined matrix (ie. non linear) 2639 then reset will be assumed as True. 2640 concatenate : (bool) 2641 concatenate the transformation with the current existing one 2642 2643 Example: 2644 ```python 2645 from vedo import Cube, show 2646 c1 = Cube().rotate_z(5).x(2).y(1) 2647 print("cube1 position", c1.pos()) 2648 T = c1.get_transform() # rotate by 5 degrees, sum 2 to x and 1 to y 2649 c2 = Cube().c('r4') 2650 c2.apply_transform(T) # ignore previous movements 2651 c2.apply_transform(T, concatenate=True) 2652 c2.apply_transform(T, concatenate=True) 2653 print("cube2 position", c2.pos()) 2654 show(c1, c2, axes=1).close() 2655 ``` 2656 ![](https://vedo.embl.es/images/feats/apply_transform.png) 2657 """ 2658 self.point_locator = None 2659 self.cell_locator = None 2660 2661 if isinstance(T, vtk.vtkMatrix4x4): 2662 tr = vtk.vtkTransform() 2663 tr.SetMatrix(T) 2664 T = tr 2665 2666 elif utils.is_sequence(T): 2667 M = vtk.vtkMatrix4x4() 2668 n = len(T[0]) 2669 for i in range(n): 2670 for j in range(n): 2671 M.SetElement(i, j, T[i][j]) 2672 tr = vtk.vtkTransform() 2673 tr.SetMatrix(M) 2674 T = tr 2675 2676 if reset or not hasattr(T, "GetScale"): # might be non-linear 2677 2678 tf = vtk.vtkTransformPolyDataFilter() 2679 tf.SetTransform(T) 2680 tf.SetInputData(self.polydata()) 2681 tf.Update() 2682 2683 I = vtk.vtkMatrix4x4() 2684 self.PokeMatrix(I) # reset to identity 2685 self.SetUserTransform(None) 2686 2687 self._update(tf.GetOutput()) ### UPDATE 2688 self.transform = T 2689 2690 else: 2691 2692 if concatenate: 2693 2694 M = vtk.vtkTransform() 2695 M.PostMultiply() 2696 M.SetMatrix(self.GetMatrix()) 2697 2698 M.Concatenate(T) 2699 2700 self.SetScale(M.GetScale()) 2701 self.SetOrientation(M.GetOrientation()) 2702 self.SetPosition(M.GetPosition()) 2703 self.transform = M 2704 self.SetUserTransform(None) 2705 2706 else: 2707 2708 self.SetScale(T.GetScale()) 2709 self.SetOrientation(T.GetOrientation()) 2710 self.SetPosition(T.GetPosition()) 2711 self.SetUserTransform(None) 2712 2713 self.transform = T 2714 2715 return self
Apply a linear or non-linear transformation to the mesh polygonal data.
Arguments:
- T : (matrix)
vtkTransform
,vtkMatrix4x4
or a 4x4 or 3x3 python or numpy matrix. - reset : (bool) if True reset the current transformation matrix to identity after having moved the object, otherwise the internal matrix will stay the same (to only affect visualization). It the input transformation has no internal defined matrix (ie. non linear) then reset will be assumed as True.
- concatenate : (bool) concatenate the transformation with the current existing one
Example:
from vedo import Cube, show c1 = Cube().rotate_z(5).x(2).y(1) print("cube1 position", c1.pos()) T = c1.get_transform() # rotate by 5 degrees, sum 2 to x and 1 to y c2 = Cube().c('r4') c2.apply_transform(T) # ignore previous movements c2.apply_transform(T, concatenate=True) c2.apply_transform(T, concatenate=True) print("cube2 position", c2.pos()) show(c1, c2, axes=1).close()
2717 def normalize(self): 2718 """Scale Mesh average size to unit.""" 2719 coords = self.points() 2720 if not coords.shape[0]: 2721 return self 2722 cm = np.mean(coords, axis=0) 2723 pts = coords - cm 2724 xyz2 = np.sum(pts * pts, axis=0) 2725 scale = 1 / np.sqrt(np.sum(xyz2) / len(pts)) 2726 t = vtk.vtkTransform() 2727 t.PostMultiply() 2728 # t.Translate(-cm) 2729 t.Scale(scale, scale, scale) 2730 # t.Translate(cm) 2731 tf = vtk.vtkTransformPolyDataFilter() 2732 tf.SetInputData(self.inputdata()) 2733 tf.SetTransform(t) 2734 tf.Update() 2735 self.point_locator = None 2736 self.cell_locator = None 2737 return self._update(tf.GetOutput())
Scale Mesh average size to unit.
2739 def mirror(self, axis="x", origin=(0, 0, 0), reset=False): 2740 """ 2741 Mirror the mesh along one of the cartesian axes 2742 2743 Arguments: 2744 axis : (str) 2745 axis to use for mirroring, must be set to x, y, z or n. 2746 Or any combination of those. Adding 'n' reverses mesh faces (hence normals). 2747 origin : (list) 2748 use this point as the origin of the mirroring transformation. 2749 reset : (bool) 2750 if True keep into account the current position of the object, 2751 and then reset its internal transformation matrix to Identity. 2752 2753 Examples: 2754 - [mirror.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mirror.py) 2755 2756 ![](https://vedo.embl.es/images/basic/mirror.png) 2757 """ 2758 sx, sy, sz = 1, 1, 1 2759 if "x" in axis.lower(): sx = -1 2760 if "y" in axis.lower(): sy = -1 2761 if "z" in axis.lower(): sz = -1 2762 origin = np.array(origin) 2763 tr = vtk.vtkTransform() 2764 tr.PostMultiply() 2765 tr.Translate(-origin) 2766 tr.Scale(sx, sy, sz) 2767 tr.Translate(origin) 2768 tf = vtk.vtkTransformPolyDataFilter() 2769 tf.SetInputData(self.polydata(reset)) 2770 tf.SetTransform(tr) 2771 tf.Update() 2772 outpoly = tf.GetOutput() 2773 if reset: 2774 self.PokeMatrix(vtk.vtkMatrix4x4()) # reset to identity 2775 if sx * sy * sz < 0 or "n" in axis: 2776 rs = vtk.vtkReverseSense() 2777 rs.SetInputData(outpoly) 2778 rs.ReverseNormalsOff() 2779 rs.Update() 2780 outpoly = rs.GetOutput() 2781 2782 self.point_locator = None 2783 self.cell_locator = None 2784 2785 out = self._update(outpoly) 2786 2787 out.pipeline = utils.OperationNode(f"mirror\naxis = {axis}", parents=[self]) 2788 return out
Mirror the mesh along one of the cartesian axes
Arguments:
- axis : (str) axis to use for mirroring, must be set to x, y, z or n. Or any combination of those. Adding 'n' reverses mesh faces (hence normals).
- origin : (list) use this point as the origin of the mirroring transformation.
- reset : (bool) if True keep into account the current position of the object, and then reset its internal transformation matrix to Identity.
Examples:
2790 def shear(self, x=0, y=0, z=0): 2791 """Apply a shear deformation along one of the main axes""" 2792 t = vtk.vtkTransform() 2793 sx, sy, sz = self.GetScale() 2794 t.SetMatrix([sx, x, 0, 0, 2795 y,sy, z, 0, 2796 0, 0,sz, 0, 2797 0, 0, 0, 1]) 2798 self.apply_transform(t, reset=True) 2799 return self
Apply a shear deformation along one of the main axes
2801 def flip_normals(self): 2802 """Flip all mesh normals. Same as `mesh.mirror('n')`.""" 2803 rs = vtk.vtkReverseSense() 2804 rs.SetInputData(self.inputdata()) 2805 rs.ReverseCellsOff() 2806 rs.ReverseNormalsOn() 2807 rs.Update() 2808 out = self._update(rs.GetOutput()) 2809 self.pipeline = utils.OperationNode("flip_normals", parents=[self]) 2810 return out
Flip all mesh normals. Same as mesh.mirror('n')
.
2813 def cmap( 2814 self, 2815 input_cmap, 2816 input_array=None, 2817 on="points", 2818 name="Scalars", 2819 vmin=None, 2820 vmax=None, 2821 n_colors=256, 2822 alpha=1.0, 2823 logscale=False, 2824 ): 2825 """ 2826 Set individual point/cell colors by providing a list of scalar values and a color map. 2827 2828 Arguments: 2829 input_cmap : (str, list, vtkLookupTable, matplotlib.colors.LinearSegmentedColormap) 2830 color map scheme to transform a real number into a color. 2831 input_array : (str, list, vtkArray) 2832 can be the string name of an existing array, a numpy array or a `vtkArray`. 2833 on : (str) 2834 either 'points' or 'cells'. 2835 Apply the color map to data which is defined on either points or cells. 2836 name : (str) 2837 give a name to the provided numpy array (if input_array is a numpy array) 2838 vmin : (float) 2839 clip scalars to this minimum value 2840 vmax : (float) 2841 clip scalars to this maximum value 2842 n_colors : (int) 2843 number of distinct colors to be used in colormap table. 2844 alpha : (float, list) 2845 Mesh transparency. Can be a `list` of values one for each vertex. 2846 logscale : (bool) 2847 Use logscale 2848 2849 Examples: 2850 - [mesh_coloring.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_coloring.py) 2851 - [mesh_alphas.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_alphas.py) 2852 - [mesh_custom.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_custom.py) 2853 (and many others) 2854 2855 ![](https://vedo.embl.es/images/basic/mesh_custom.png) 2856 """ 2857 self._cmap_name = input_cmap 2858 poly = self.inputdata() 2859 2860 if input_array is None: 2861 if not self.pointdata.keys() and self.celldata.keys(): 2862 on = "cells" 2863 if not poly.GetCellData().GetScalars(): 2864 input_array = 0 # pick the first at hand 2865 2866 if on.startswith("point"): 2867 data = poly.GetPointData() 2868 n = poly.GetNumberOfPoints() 2869 elif on.startswith("cell"): 2870 data = poly.GetCellData() 2871 n = poly.GetNumberOfCells() 2872 else: 2873 vedo.logger.error("Must specify in cmap(on=...) to either 'cells' or 'points'") 2874 raise RuntimeError() 2875 2876 if input_array is None: # if None try to fetch the active scalars 2877 arr = data.GetScalars() 2878 if not arr: 2879 vedo.logger.error(f"in cmap(), cannot find any {on} active array ...skip coloring.") 2880 return self 2881 2882 if not arr.GetName(): # sometimes arrays dont have a name.. 2883 arr.SetName(name) 2884 2885 elif isinstance(input_array, str): # if a string is passed 2886 arr = data.GetArray(input_array) 2887 if not arr: 2888 vedo.logger.error(f"in cmap(), cannot find {on} array {input_array} ...skip coloring.") 2889 return self 2890 2891 elif isinstance(input_array, int): # if an int is passed 2892 if input_array < data.GetNumberOfArrays(): 2893 arr = data.GetArray(input_array) 2894 else: 2895 vedo.logger.error(f"in cmap(), cannot find {on} array at {input_array} ...skip coloring.") 2896 return self 2897 2898 elif utils.is_sequence(input_array): # if a numpy array is passed 2899 npts = len(input_array) 2900 if npts != n: 2901 vedo.logger.error(f"in cmap(), nr. of input {on} scalars {npts} != {n} ...skip coloring.") 2902 return self 2903 arr = utils.numpy2vtk(input_array, name=name, dtype=float) 2904 data.AddArray(arr) 2905 data.Modified() 2906 2907 elif isinstance(input_array, vtk.vtkArray): # if a vtkArray is passed 2908 arr = input_array 2909 data.AddArray(arr) 2910 data.Modified() 2911 2912 else: 2913 vedo.logger.error(f"in cmap(), cannot understand input type {type(input_array)}") 2914 raise RuntimeError() 2915 2916 # Now we have array "arr" 2917 array_name = arr.GetName() 2918 2919 if arr.GetNumberOfComponents() == 1: 2920 if vmin is None: 2921 vmin = arr.GetRange()[0] 2922 if vmax is None: 2923 vmax = arr.GetRange()[1] 2924 else: 2925 if vmin is None or vmax is None: 2926 vn = utils.mag(utils.vtk2numpy(arr)) 2927 if vmin is None: 2928 vmin = vn.min() 2929 if vmax is None: 2930 vmax = vn.max() 2931 2932 # interpolate alphas if they are not constant 2933 if not utils.is_sequence(alpha): 2934 alpha = [alpha] * n_colors 2935 else: 2936 v = np.linspace(0, 1, n_colors, endpoint=True) 2937 xp = np.linspace(0, 1, len(alpha), endpoint=True) 2938 alpha = np.interp(v, xp, alpha) 2939 2940 ########################### build the look-up table 2941 if isinstance(input_cmap, vtk.vtkLookupTable): # vtkLookupTable 2942 lut = input_cmap 2943 2944 elif utils.is_sequence(input_cmap): # manual sequence of colors 2945 lut = vtk.vtkLookupTable() 2946 if logscale: 2947 lut.SetScaleToLog10() 2948 lut.SetRange(vmin, vmax) 2949 ncols = len(input_cmap) 2950 lut.SetNumberOfTableValues(ncols) 2951 2952 for i, c in enumerate(input_cmap): 2953 r, g, b = colors.get_color(c) 2954 lut.SetTableValue(i, r, g, b, alpha[i]) 2955 lut.Build() 2956 2957 else: # assume string cmap name OR matplotlib.colors.LinearSegmentedColormap 2958 lut = vtk.vtkLookupTable() 2959 if logscale: 2960 lut.SetScaleToLog10() 2961 lut.SetVectorModeToMagnitude() 2962 lut.SetRange(vmin, vmax) 2963 lut.SetNumberOfTableValues(n_colors) 2964 mycols = colors.color_map(range(n_colors), input_cmap, 0, n_colors) 2965 for i, c in enumerate(mycols): 2966 r, g, b = c 2967 lut.SetTableValue(i, r, g, b, alpha[i]) 2968 lut.Build() 2969 2970 arr.SetLookupTable(lut) 2971 2972 data.SetActiveScalars(array_name) 2973 # data.SetScalars(arr) # wrong! it deletes array in position 0, never use SetScalars 2974 # data.SetActiveAttribute(array_name, 0) # boh! 2975 2976 if data.GetScalars(): 2977 data.GetScalars().SetLookupTable(lut) 2978 data.GetScalars().Modified() 2979 2980 self._mapper.SetLookupTable(lut) 2981 self._mapper.SetColorModeToMapScalars() # so we dont need to convert uint8 scalars 2982 2983 self._mapper.ScalarVisibilityOn() 2984 self._mapper.SetScalarRange(lut.GetRange()) 2985 if on.startswith("point"): 2986 self._mapper.SetScalarModeToUsePointData() 2987 else: 2988 self._mapper.SetScalarModeToUseCellData() 2989 if hasattr(self._mapper, "SetArrayName"): 2990 self._mapper.SetArrayName(array_name) 2991 2992 return self
Set individual point/cell colors by providing a list of scalar values and a color map.
Arguments:
- input_cmap : (str, list, vtkLookupTable, matplotlib.colors.LinearSegmentedColormap) color map scheme to transform a real number into a color.
- input_array : (str, list, vtkArray)
can be the string name of an existing array, a numpy array or a
vtkArray
. - on : (str) either 'points' or 'cells'. Apply the color map to data which is defined on either points or cells.
- name : (str) give a name to the provided numpy array (if input_array is a numpy array)
- vmin : (float) clip scalars to this minimum value
- vmax : (float) clip scalars to this maximum value
- n_colors : (int) number of distinct colors to be used in colormap table.
- alpha : (float, list)
Mesh transparency. Can be a
list
of values one for each vertex. - logscale : (bool) Use logscale
Examples:
- mesh_coloring.py
- mesh_alphas.py
mesh_custom.py (and many others)
Colorize each cell (face) of a mesh by passing a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A]. Colors levels and opacities must be in the range [0,255].
A single constant color can also be passed as string or RGBA.
A cell array named "CellsRGBA" is automatically created.
Examples:
Colorize each point (or vertex of a mesh) by passing a 1-to-1 list of colors in format [R,G,B] or [R,G,B,A]. Colors levels and opacities must be in the range [0,255].
A single constant color can also be passed as string or RGBA.
A point array named "PointsRGBA" is automatically created.
3110 def interpolate_data_from( 3111 self, 3112 source, 3113 radius=None, 3114 n=None, 3115 kernel="shepard", 3116 exclude=("Normals",), 3117 on="points", 3118 null_strategy=1, 3119 null_value=0, 3120 ): 3121 """ 3122 Interpolate over source to port its data onto the current object using various kernels. 3123 3124 If n (number of closest points to use) is set then radius value is ignored. 3125 3126 Arguments: 3127 kernel : (str) 3128 available kernels are [shepard, gaussian, linear] 3129 null_strategy : (int) 3130 specify a strategy to use when encountering a "null" point 3131 during the interpolation process. Null points occur when the local neighborhood 3132 (of nearby points to interpolate from) is empty. 3133 3134 - Case 0: an output array is created that marks points 3135 as being valid (=1) or null (invalid =0), and the null_value is set as well 3136 - Case 1: the output data value(s) are set to the provided null_value 3137 - Case 2: simply use the closest point to perform the interpolation. 3138 null_value : (float) 3139 see above. 3140 3141 Examples: 3142 - [interpolateMeshArray.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolateMeshArray.py) 3143 3144 ![](https://vedo.embl.es/images/advanced/interpolateMeshArray.png) 3145 """ 3146 if radius is None and not n: 3147 vedo.logger.error("in interpolate_data_from(): please set either radius or n") 3148 raise RuntimeError 3149 3150 if on == "points": 3151 points = source.polydata() 3152 elif on == "cells": 3153 poly2 = vtk.vtkPolyData() 3154 poly2.ShallowCopy(source.polydata()) 3155 c2p = vtk.vtkCellDataToPointData() 3156 c2p.SetInputData(poly2) 3157 c2p.Update() 3158 points = c2p.GetOutput() 3159 else: 3160 vedo.logger.error("in interpolate_data_from(), on must be on points or cells") 3161 raise RuntimeError() 3162 3163 locator = vtk.vtkPointLocator() 3164 locator.SetDataSet(points) 3165 locator.BuildLocator() 3166 3167 if kernel.lower() == "shepard": 3168 kern = vtk.vtkShepardKernel() 3169 kern.SetPowerParameter(2) 3170 elif kernel.lower() == "gaussian": 3171 kern = vtk.vtkGaussianKernel() 3172 kern.SetSharpness(2) 3173 elif kernel.lower() == "linear": 3174 kern = vtk.vtkLinearKernel() 3175 else: 3176 vedo.logger.error("available kernels are: [shepard, gaussian, linear]") 3177 raise RuntimeError() 3178 3179 if n: 3180 kern.SetNumberOfPoints(n) 3181 kern.SetKernelFootprintToNClosest() 3182 else: 3183 kern.SetRadius(radius) 3184 3185 interpolator = vtk.vtkPointInterpolator() 3186 interpolator.SetInputData(self.polydata()) 3187 interpolator.SetSourceData(points) 3188 interpolator.SetKernel(kern) 3189 interpolator.SetLocator(locator) 3190 interpolator.PassFieldArraysOff() 3191 interpolator.SetNullPointsStrategy(null_strategy) 3192 interpolator.SetNullValue(null_value) 3193 interpolator.SetValidPointsMaskArrayName("ValidPointMask") 3194 for ex in exclude: 3195 interpolator.AddExcludedArray(ex) 3196 interpolator.Update() 3197 3198 if on == "cells": 3199 p2c = vtk.vtkPointDataToCellData() 3200 p2c.SetInputData(interpolator.GetOutput()) 3201 p2c.Update() 3202 cpoly = p2c.GetOutput() 3203 else: 3204 cpoly = interpolator.GetOutput() 3205 3206 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 3207 self._update(cpoly) 3208 else: 3209 # bring the underlying polydata to where _data is 3210 M = vtk.vtkMatrix4x4() 3211 M.DeepCopy(self.GetMatrix()) 3212 M.Invert() 3213 tr = vtk.vtkTransform() 3214 tr.SetMatrix(M) 3215 tf = vtk.vtkTransformPolyDataFilter() 3216 tf.SetTransform(tr) 3217 tf.SetInputData(cpoly) 3218 tf.Update() 3219 self._update(tf.GetOutput()) 3220 3221 self.pipeline = utils.OperationNode("interpolate_data_from", parents=[self, source]) 3222 return self
Interpolate over source to port its data onto the current object using various kernels.
If n (number of closest points to use) is set then radius value is ignored.
Arguments:
- kernel : (str) available kernels are [shepard, gaussian, linear]
null_strategy : (int) specify a strategy to use when encountering a "null" point during the interpolation process. Null points occur when the local neighborhood (of nearby points to interpolate from) is empty.
- Case 0: an output array is created that marks points as being valid (=1) or null (invalid =0), and the null_value is set as well
- Case 1: the output data value(s) are set to the provided null_value
- Case 2: simply use the closest point to perform the interpolation.
- null_value : (float) see above.
Examples:
3224 def add_gaussian_noise(self, sigma=1.0): 3225 """ 3226 Add gaussian noise to point positions. 3227 An extra array is added named "GaussianNoise" with the shifts. 3228 3229 Arguments: 3230 sigma : (float) 3231 nr. of standard deviations, expressed in percent of the diagonal size of mesh. 3232 Can also be a list [sigma_x, sigma_y, sigma_z]. 3233 3234 Examples: 3235 ```python 3236 from vedo import Sphere 3237 Sphere().add_gaussian_noise(1.0).point_size(8).show().close() 3238 ``` 3239 """ 3240 sz = self.diagonal_size() 3241 pts = self.points() 3242 n = len(pts) 3243 ns = (np.random.randn(n, 3) * sigma) * (sz / 100) 3244 vpts = vtk.vtkPoints() 3245 vpts.SetNumberOfPoints(n) 3246 vpts.SetData(utils.numpy2vtk(pts + ns, dtype=np.float32)) 3247 self.inputdata().SetPoints(vpts) 3248 self.inputdata().GetPoints().Modified() 3249 self.pointdata["GaussianNoise"] = -ns 3250 self.pipeline = utils.OperationNode( 3251 "gaussian_noise", parents=[self], shape="egg", comment=f"sigma = {sigma}" 3252 ) 3253 return self
Add gaussian noise to point positions. An extra array is added named "GaussianNoise" with the shifts.
Arguments:
- sigma : (float) nr. of standard deviations, expressed in percent of the diagonal size of mesh. Can also be a list [sigma_x, sigma_y, sigma_z].
Examples:
from vedo import Sphere Sphere().add_gaussian_noise(1.0).point_size(8).show().close()
3256 def closest_point(self, pt, n=1, radius=None, return_point_id=False, return_cell_id=False): 3257 """ 3258 Find the closest point(s) on a mesh given from the input point `pt`. 3259 3260 Arguments: 3261 n : (int) 3262 if greater than 1, return a list of n ordered closest points 3263 radius : (float) 3264 if given, get all points within that radius. Then n is ignored. 3265 return_point_id : (bool) 3266 return point ID instead of coordinates 3267 return_cell_id : (bool) 3268 return cell ID in which the closest point sits 3269 3270 Examples: 3271 - [align1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/align1.py) 3272 - [fitplanes.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/fitplanes.py) 3273 - [quadratic_morphing.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/quadratic_morphing.py) 3274 3275 .. note:: 3276 The appropriate tree search locator is built on the fly and cached for speed. 3277 3278 If you want to reset it use `mymesh.point_locator=None` 3279 """ 3280 # NB: every time the mesh moves or is warped the locators are set to None 3281 if (n > 1 or radius) or (n == 1 and return_point_id): 3282 3283 poly = None 3284 if not self.point_locator: 3285 poly = self.polydata() 3286 self.point_locator = vtk.vtkStaticPointLocator() 3287 self.point_locator.SetDataSet(poly) 3288 self.point_locator.BuildLocator() 3289 3290 ########## 3291 if radius: 3292 vtklist = vtk.vtkIdList() 3293 self.point_locator.FindPointsWithinRadius(radius, pt, vtklist) 3294 elif n > 1: 3295 vtklist = vtk.vtkIdList() 3296 self.point_locator.FindClosestNPoints(n, pt, vtklist) 3297 else: # n==1 hence return_point_id==True 3298 ######## 3299 return self.point_locator.FindClosestPoint(pt) 3300 ######## 3301 3302 if return_point_id: 3303 ######## 3304 return utils.vtk2numpy(vtklist) 3305 ######## 3306 3307 if not poly: 3308 poly = self.polydata() 3309 trgp = [] 3310 for i in range(vtklist.GetNumberOfIds()): 3311 trgp_ = [0, 0, 0] 3312 vi = vtklist.GetId(i) 3313 poly.GetPoints().GetPoint(vi, trgp_) 3314 trgp.append(trgp_) 3315 ######## 3316 return np.array(trgp) 3317 ######## 3318 3319 else: 3320 3321 if not self.cell_locator: 3322 poly = self.polydata() 3323 3324 # As per Miquel example with limbs the vtkStaticCellLocator doesnt work !! 3325 # https://discourse.vtk.org/t/vtkstaticcelllocator-problem-vtk9-0-3/7854/4 3326 if vedo.vtk_version[0] >= 9 and vedo.vtk_version[0] > 0: 3327 self.cell_locator = vtk.vtkStaticCellLocator() 3328 else: 3329 self.cell_locator = vtk.vtkCellLocator() 3330 3331 self.cell_locator.SetDataSet(poly) 3332 self.cell_locator.BuildLocator() 3333 3334 trgp = [0, 0, 0] 3335 cid = vtk.mutable(0) 3336 dist2 = vtk.mutable(0) 3337 subid = vtk.mutable(0) 3338 self.cell_locator.FindClosestPoint(pt, trgp, cid, subid, dist2) 3339 3340 if return_cell_id: 3341 return int(cid) 3342 3343 return np.array(trgp)
Find the closest point(s) on a mesh given from the input point pt
.
Arguments:
- n : (int) if greater than 1, return a list of n ordered closest points
- radius : (float) if given, get all points within that radius. Then n is ignored.
- return_point_id : (bool) return point ID instead of coordinates
- return_cell_id : (bool) return cell ID in which the closest point sits
Examples:
The appropriate tree search locator is built on the fly and cached for speed.
If you want to reset it use mymesh.point_locator=None
3346 def hausdorff_distance(self, points): 3347 """ 3348 Compute the Hausdorff distance to the input point set. 3349 Returns a single `float`. 3350 3351 Example: 3352 ```python 3353 from vedo import * 3354 t = np.linspace(0, 2*np.pi, 100) 3355 x = 4/3 * sin(t)**3 3356 y = cos(t) - cos(2*t)/3 - cos(3*t)/6 - cos(4*t)/12 3357 pol1 = Line(np.c_[x,y], closed=True).triangulate() 3358 pol2 = Polygon(nsides=5).pos(2,2) 3359 d12 = pol1.distance_to(pol2) 3360 d21 = pol2.distance_to(pol1) 3361 pol1.lw(0).cmap("viridis") 3362 pol2.lw(0).cmap("viridis") 3363 print("distance d12, d21 :", min(d12), min(d21)) 3364 print("hausdorff distance:", pol1.hausdorff_distance(pol2)) 3365 print("chamfer distance :", pol1.chamfer_distance(pol2)) 3366 show(pol1, pol2, axes=1) 3367 ``` 3368 ![](https://vedo.embl.es/images/feats/heart.png) 3369 """ 3370 hp = vtk.vtkHausdorffDistancePointSetFilter() 3371 hp.SetInputData(0, self.polydata()) 3372 hp.SetInputData(1, points.polydata()) 3373 hp.SetTargetDistanceMethodToPointToCell() 3374 hp.Update() 3375 return hp.GetHausdorffDistance()
Compute the Hausdorff distance to the input point set.
Returns a single float
.
Example:
from vedo import * t = np.linspace(0, 2*np.pi, 100) x = 4/3 * sin(t)**3 y = cos(t) - cos(2*t)/3 - cos(3*t)/6 - cos(4*t)/12 pol1 = Line(np.c_[x,y], closed=True).triangulate() pol2 = Polygon(nsides=5).pos(2,2) d12 = pol1.distance_to(pol2) d21 = pol2.distance_to(pol1) pol1.lw(0).cmap("viridis") pol2.lw(0).cmap("viridis") print("distance d12, d21 :", min(d12), min(d21)) print("hausdorff distance:", pol1.hausdorff_distance(pol2)) print("chamfer distance :", pol1.chamfer_distance(pol2)) show(pol1, pol2, axes=1)
3377 def chamfer_distance(self, pcloud): 3378 """ 3379 Compute the Chamfer distance to the input point set. 3380 Returns a single `float`. 3381 """ 3382 if not pcloud.point_locator: 3383 pcloud.point_locator = vtk.vtkPointLocator() 3384 pcloud.point_locator.SetDataSet(pcloud.polydata()) 3385 pcloud.point_locator.BuildLocator() 3386 if not self.point_locator: 3387 self.point_locator = vtk.vtkPointLocator() 3388 self.point_locator.SetDataSet(self.polydata()) 3389 self.point_locator.BuildLocator() 3390 3391 ps1 = self.points() 3392 ps2 = pcloud.points() 3393 3394 ids12 = [] 3395 for p in ps1: 3396 pid12 = pcloud.point_locator.FindClosestPoint(p) 3397 ids12.append(pid12) 3398 deltav = ps2[ids12] - ps1 3399 da = np.mean(np.linalg.norm(deltav, axis=1)) 3400 3401 ids21 = [] 3402 for p in ps2: 3403 pid21 = self.point_locator.FindClosestPoint(p) 3404 ids21.append(pid21) 3405 deltav = ps1[ids21] - ps2 3406 db = np.mean(np.linalg.norm(deltav, axis=1)) 3407 return (da + db) / 2
Compute the Chamfer distance to the input point set.
Returns a single float
.
3409 def remove_outliers(self, radius, neighbors=5): 3410 """ 3411 Remove outliers from a cloud of points within the specified `radius` search. 3412 3413 Arguments: 3414 radius : (float) 3415 Specify the local search radius. 3416 neighbors : (int) 3417 Specify the number of neighbors that a point must have, 3418 within the specified radius, for the point to not be considered isolated. 3419 3420 Examples: 3421 - [clustering.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/clustering.py) 3422 3423 ![](https://vedo.embl.es/images/basic/clustering.png) 3424 """ 3425 removal = vtk.vtkRadiusOutlierRemoval() 3426 removal.SetInputData(self.polydata()) 3427 removal.SetRadius(radius) 3428 removal.SetNumberOfNeighbors(neighbors) 3429 removal.GenerateOutliersOff() 3430 removal.Update() 3431 inputobj = removal.GetOutput() 3432 if inputobj.GetNumberOfCells() == 0: 3433 carr = vtk.vtkCellArray() 3434 for i in range(inputobj.GetNumberOfPoints()): 3435 carr.InsertNextCell(1) 3436 carr.InsertCellPoint(i) 3437 inputobj.SetVerts(carr) 3438 self._update(inputobj) 3439 self.mapper().ScalarVisibilityOff() 3440 self.pipeline = utils.OperationNode("remove_outliers", parents=[self]) 3441 return self
Remove outliers from a cloud of points within the specified radius
search.
Arguments:
- radius : (float) Specify the local search radius.
- neighbors : (int) Specify the number of neighbors that a point must have, within the specified radius, for the point to not be considered isolated.
Examples:
3443 def smooth_mls_1d(self, f=0.2, radius=None): 3444 """ 3445 Smooth mesh or points with a `Moving Least Squares` variant. 3446 The point data array "Variances" will contain the residue calculated for each point. 3447 Input mesh's polydata is modified. 3448 3449 Arguments: 3450 f : (float) 3451 smoothing factor - typical range is [0,2]. 3452 radius : (float) 3453 radius search in absolute units. If set then `f` is ignored. 3454 3455 Examples: 3456 - [moving_least_squares1D.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/moving_least_squares1D.py) 3457 - [skeletonize.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/skeletonize.py) 3458 3459 ![](https://vedo.embl.es/images/advanced/moving_least_squares1D.png) 3460 """ 3461 coords = self.points() 3462 ncoords = len(coords) 3463 3464 if radius: 3465 Ncp = 0 3466 else: 3467 Ncp = int(ncoords * f / 10) 3468 if Ncp < 5: 3469 vedo.logger.warning(f"Please choose a fraction higher than {f}") 3470 Ncp = 5 3471 3472 variances, newline = [], [] 3473 for p in coords: 3474 points = self.closest_point(p, n=Ncp, radius=radius) 3475 if len(points) < 4: 3476 continue 3477 3478 points = np.array(points) 3479 pointsmean = points.mean(axis=0) # plane center 3480 _, dd, vv = np.linalg.svd(points - pointsmean) 3481 newp = np.dot(p - pointsmean, vv[0]) * vv[0] + pointsmean 3482 variances.append(dd[1] + dd[2]) 3483 newline.append(newp) 3484 3485 vdata = utils.numpy2vtk(np.array(variances)) 3486 vdata.SetName("Variances") 3487 self.inputdata().GetPointData().AddArray(vdata) 3488 self.inputdata().GetPointData().Modified() 3489 self.points(newline) 3490 self.pipeline = utils.OperationNode("smooth_mls_1d", parents=[self]) 3491 return self
Smooth mesh or points with a Moving Least Squares
variant.
The point data array "Variances" will contain the residue calculated for each point.
Input mesh's polydata is modified.
Arguments:
- f : (float) smoothing factor - typical range is [0,2].
- radius : (float)
radius search in absolute units. If set then
f
is ignored.
Examples:
3493 def smooth_mls_2d(self, f=0.2, radius=None): 3494 """ 3495 Smooth mesh or points with a `Moving Least Squares` algorithm variant. 3496 The list `mesh.info['variances']` contains the residue calculated for each point. 3497 When a radius is specified points that are isolated will not be moved and will get 3498 a False entry in array `mesh.info['is_valid']`. 3499 3500 Arguments: 3501 f : (float) 3502 smoothing factor - typical range is [0,2]. 3503 radius : (float) 3504 radius search in absolute units. If set then `f` is ignored. 3505 3506 Examples: 3507 - [moving_least_squares2D.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/moving_least_squares2D.py) 3508 - [recosurface.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/recosurface.py) 3509 3510 ![](https://vedo.embl.es/images/advanced/recosurface.png) 3511 """ 3512 coords = self.points() 3513 ncoords = len(coords) 3514 3515 if radius: 3516 Ncp = 1 3517 else: 3518 Ncp = int(ncoords * f / 100) 3519 if Ncp < 4: 3520 vedo.logger.error(f"MLS2D: Please choose a fraction higher than {f}") 3521 Ncp = 4 3522 3523 variances, newpts, valid = [], [], [] 3524 pb = None 3525 if ncoords > 10000: 3526 pb = utils.ProgressBar(0, ncoords) 3527 for p in coords: 3528 if pb: 3529 pb.print("smoothMLS2D working ...") 3530 pts = self.closest_point(p, n=Ncp, radius=radius) 3531 if len(pts) > 3: 3532 ptsmean = pts.mean(axis=0) # plane center 3533 _, dd, vv = np.linalg.svd(pts - ptsmean) 3534 cv = np.cross(vv[0], vv[1]) 3535 t = (np.dot(cv, ptsmean) - np.dot(cv, p)) / np.dot(cv, cv) 3536 newp = p + cv * t 3537 newpts.append(newp) 3538 variances.append(dd[2]) 3539 if radius: 3540 valid.append(True) 3541 else: 3542 newpts.append(p) 3543 variances.append(0) 3544 if radius: 3545 valid.append(False) 3546 3547 self.info["variances"] = np.array(variances) 3548 self.info["is_valid"] = np.array(valid) 3549 self.points(newpts) 3550 3551 self.pipeline = utils.OperationNode("smooth_mls_2d", parents=[self]) 3552 return self
Smooth mesh or points with a Moving Least Squares
algorithm variant.
The list mesh.info['variances']
contains the residue calculated for each point.
When a radius is specified points that are isolated will not be moved and will get
a False entry in array mesh.info['is_valid']
.
Arguments:
- f : (float) smoothing factor - typical range is [0,2].
- radius : (float)
radius search in absolute units. If set then
f
is ignored.
Examples:
3554 def smooth_lloyd_2d(self, iterations=2, bounds=None, options="Qbb Qc Qx"): 3555 """Lloyd relaxation of a 2D pointcloud.""" 3556 # Credits: https://hatarilabs.com/ih-en/ 3557 # tutorial-to-create-a-geospatial-voronoi-sh-mesh-with-python-scipy-and-geopandas 3558 from scipy.spatial import Voronoi as scipy_voronoi 3559 3560 def _constrain_points(points): 3561 # Update any points that have drifted beyond the boundaries of this space 3562 if bounds is not None: 3563 for point in points: 3564 if point[0] < bounds[0]: point[0] = bounds[0] 3565 if point[0] > bounds[1]: point[0] = bounds[1] 3566 if point[1] < bounds[2]: point[1] = bounds[2] 3567 if point[1] > bounds[3]: point[1] = bounds[3] 3568 return points 3569 3570 def _find_centroid(vertices): 3571 # The equation for the method used here to find the centroid of a 3572 # 2D polygon is given here: https://en.wikipedia.org/wiki/Centroid#Of_a_polygon 3573 area = 0 3574 centroid_x = 0 3575 centroid_y = 0 3576 for i in range(len(vertices) - 1): 3577 step = (vertices[i, 0] * vertices[i + 1, 1]) - (vertices[i + 1, 0] * vertices[i, 1]) 3578 centroid_x += (vertices[i, 0] + vertices[i + 1, 0]) * step 3579 centroid_y += (vertices[i, 1] + vertices[i + 1, 1]) * step 3580 area += step 3581 if area: 3582 centroid_x = (1.0 / (3.0 * area)) * centroid_x 3583 centroid_y = (1.0 / (3.0 * area)) * centroid_y 3584 # prevent centroids from escaping bounding box 3585 return _constrain_points([[centroid_x, centroid_y]])[0] 3586 3587 def _relax(voron): 3588 # Moves each point to the centroid of its cell in the voronoi 3589 # map to "relax" the points (i.e. jitter the points so as 3590 # to spread them out within the space). 3591 centroids = [] 3592 for idx in voron.point_region: 3593 # the region is a series of indices into voronoi.vertices 3594 # remove point at infinity, designated by index -1 3595 region = [i for i in voron.regions[idx] if i != -1] 3596 # enclose the polygon 3597 region = region + [region[0]] 3598 verts = voron.vertices[region] 3599 # find the centroid of those vertices 3600 centroids.append(_find_centroid(verts)) 3601 return _constrain_points(centroids) 3602 3603 if bounds is None: 3604 bounds = self.bounds() 3605 3606 pts = self.points()[:, (0, 1)] 3607 for i in range(iterations): 3608 vor = scipy_voronoi(pts, qhull_options=options) 3609 _constrain_points(vor.vertices) 3610 pts = _relax(vor) 3611 # m = vedo.Mesh([pts, self.faces()]) # not yet working properly 3612 out = Points(pts, c="k") 3613 out.pipeline = utils.OperationNode("smooth_lloyd", parents=[self]) 3614 return out
Lloyd relaxation of a 2D pointcloud.
3616 def project_on_plane(self, plane="z", point=None, direction=None): 3617 """ 3618 Project the mesh on one of the Cartesian planes. 3619 3620 Arguments: 3621 plane : (str, Plane) 3622 if plane is `str`, plane can be one of ['x', 'y', 'z'], 3623 represents x-plane, y-plane and z-plane, respectively. 3624 Otherwise, plane should be an instance of `vedo.shapes.Plane`. 3625 point : (float, array) 3626 if plane is `str`, point should be a float represents the intercept. 3627 Otherwise, point is the camera point of perspective projection 3628 direction : (array) 3629 direction of oblique projection 3630 3631 Note: 3632 Parameters `point` and `direction` are only used if the given plane 3633 is an instance of `vedo.shapes.Plane`. And one of these two params 3634 should be left as `None` to specify the projection type. 3635 3636 Example: 3637 ```python 3638 s.project_on_plane(plane='z') # project to z-plane 3639 plane = Plane(pos=(4, 8, -4), normal=(-1, 0, 1), s=(5,5)) 3640 s.project_on_plane(plane=plane) # orthogonal projection 3641 s.project_on_plane(plane=plane, point=(6, 6, 6)) # perspective projection 3642 s.project_on_plane(plane=plane, direction=(1, 2, -1)) # oblique projection 3643 ``` 3644 3645 Examples: 3646 - [silhouette2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/silhouette2.py) 3647 3648 ![](https://vedo.embl.es/images/basic/silhouette2.png) 3649 """ 3650 coords = self.points() 3651 3652 if plane == "x": 3653 coords[:, 0] = self.GetOrigin()[0] 3654 intercept = self.xbounds()[0] if point is None else point 3655 self.x(intercept) 3656 elif plane == "y": 3657 coords[:, 1] = self.GetOrigin()[1] 3658 intercept = self.ybounds()[0] if point is None else point 3659 self.y(intercept) 3660 elif plane == "z": 3661 coords[:, 2] = self.GetOrigin()[2] 3662 intercept = self.zbounds()[0] if point is None else point 3663 self.z(intercept) 3664 3665 elif isinstance(plane, vedo.shapes.Plane): 3666 normal = plane.normal / np.linalg.norm(plane.normal) 3667 pl = np.hstack((normal, -np.dot(plane.pos(), normal))).reshape(4, 1) 3668 if direction is None and point is None: 3669 # orthogonal projection 3670 pt = np.hstack((normal, [0])).reshape(4, 1) 3671 # proj_mat = pt.T @ pl * np.eye(4) - pt @ pl.T # python3 only 3672 proj_mat = np.matmul(pt.T, pl) * np.eye(4) - np.matmul(pt, pl.T) 3673 3674 elif direction is None: 3675 # perspective projection 3676 pt = np.hstack((np.array(point), [1])).reshape(4, 1) 3677 # proj_mat = pt.T @ pl * np.eye(4) - pt @ pl.T 3678 proj_mat = np.matmul(pt.T, pl) * np.eye(4) - np.matmul(pt, pl.T) 3679 3680 elif point is None: 3681 # oblique projection 3682 pt = np.hstack((np.array(direction), [0])).reshape(4, 1) 3683 # proj_mat = pt.T @ pl * np.eye(4) - pt @ pl.T 3684 proj_mat = np.matmul(pt.T, pl) * np.eye(4) - np.matmul(pt, pl.T) 3685 3686 coords = np.concatenate([coords, np.ones((coords.shape[:-1] + (1,)))], axis=-1) 3687 # coords = coords @ proj_mat.T 3688 coords = np.matmul(coords, proj_mat.T) 3689 coords = coords[:, :3] / coords[:, 3:] 3690 3691 else: 3692 vedo.logger.error(f"unknown plane {plane}") 3693 raise RuntimeError() 3694 3695 self.alpha(0.1) 3696 self.points(coords) 3697 return self
Project the mesh on one of the Cartesian planes.
Arguments:
- plane : (str, Plane)
if plane is
str
, plane can be one of ['x', 'y', 'z'], represents x-plane, y-plane and z-plane, respectively. Otherwise, plane should be an instance ofvedo.shapes.Plane
. - point : (float, array)
if plane is
str
, point should be a float represents the intercept. Otherwise, point is the camera point of perspective projection - direction : (array) direction of oblique projection
Note:
Parameters
point
anddirection
are only used if the given plane is an instance ofvedo.shapes.Plane
. And one of these two params should be left asNone
to specify the projection type.
Example:
s.project_on_plane(plane='z') # project to z-plane plane = Plane(pos=(4, 8, -4), normal=(-1, 0, 1), s=(5,5)) s.project_on_plane(plane=plane) # orthogonal projection s.project_on_plane(plane=plane, point=(6, 6, 6)) # perspective projection s.project_on_plane(plane=plane, direction=(1, 2, -1)) # oblique projection
Examples:
3699 def warp(self, source, target, sigma=1.0, mode="3d"): 3700 """ 3701 `Thin Plate Spline` transformations describe a nonlinear warp transform defined by a set 3702 of source and target landmarks. Any point on the mesh close to a source landmark will 3703 be moved to a place close to the corresponding target landmark. 3704 The points in between are interpolated smoothly using 3705 Bookstein's Thin Plate Spline algorithm. 3706 3707 Transformation object can be accessed with `mesh.transform`. 3708 3709 Arguments: 3710 sigma : (float) 3711 specify the 'stiffness' of the spline. 3712 mode : (str) 3713 set the basis function to either abs(R) (for 3d) or R2LogR (for 2d meshes) 3714 3715 Examples: 3716 - [interpolate_field.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolate_field.py) 3717 - [warp1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/warp1.py) 3718 - [warp2.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/warp2.py) 3719 3720 ![](https://vedo.embl.es/images/advanced/warp2.png) 3721 """ 3722 parents = [self] 3723 if isinstance(source, Points): 3724 parents.append(source) 3725 source = source.points() 3726 else: 3727 source = utils.make3d(source) 3728 3729 if isinstance(target, Points): 3730 parents.append(target) 3731 target = target.points() 3732 else: 3733 target = utils.make3d(target) 3734 3735 ns = len(source) 3736 ptsou = vtk.vtkPoints() 3737 ptsou.SetNumberOfPoints(ns) 3738 for i in range(ns): 3739 ptsou.SetPoint(i, source[i]) 3740 3741 nt = len(target) 3742 if ns != nt: 3743 vedo.logger.error(f"#source {ns} != {nt} #target points") 3744 raise RuntimeError() 3745 3746 pttar = vtk.vtkPoints() 3747 pttar.SetNumberOfPoints(nt) 3748 for i in range(ns): 3749 pttar.SetPoint(i, target[i]) 3750 3751 T = vtk.vtkThinPlateSplineTransform() 3752 if mode.lower() == "3d": 3753 T.SetBasisToR() 3754 elif mode.lower() == "2d": 3755 T.SetBasisToR2LogR() 3756 else: 3757 vedo.logger.error(f"unknown mode {mode}") 3758 raise RuntimeError() 3759 3760 T.SetSigma(sigma) 3761 T.SetSourceLandmarks(ptsou) 3762 T.SetTargetLandmarks(pttar) 3763 self.transform = T 3764 self.apply_transform(T, reset=True) 3765 3766 self.pipeline = utils.OperationNode("warp", parents=parents) 3767 return self
Thin Plate Spline
transformations describe a nonlinear warp transform defined by a set
of source and target landmarks. Any point on the mesh close to a source landmark will
be moved to a place close to the corresponding target landmark.
The points in between are interpolated smoothly using
Bookstein's Thin Plate Spline algorithm.
Transformation object can be accessed with mesh.transform
.
Arguments:
- sigma : (float) specify the 'stiffness' of the spline.
- mode : (str) set the basis function to either abs(R) (for 3d) or R2LogR (for 2d meshes)
Examples:
3769 def cut_with_plane(self, origin=(0, 0, 0), normal=(1, 0, 0), invert=False): 3770 """ 3771 Cut the mesh with the plane defined by a point and a normal. 3772 3773 Arguments: 3774 origin : (array) 3775 the cutting plane goes through this point 3776 normal : (array) 3777 normal of the cutting plane 3778 3779 Example: 3780 ```python 3781 from vedo import Cube 3782 cube = Cube().cut_with_plane(normal=(1,1,1)) 3783 cube.back_color('pink').show() 3784 ``` 3785 ![](https://vedo.embl.es/images/feats/cut_with_plane_cube.png) 3786 3787 Examples: 3788 - [trail.py](https://github.com/marcomusy/vedo/blob/master/examples/simulations/trail.py) 3789 3790 ![](https://vedo.embl.es/images/simulations/trail.gif) 3791 3792 Check out also: 3793 `cut_with_box()`, `cut_with_cylinder()`, `cut_with_sphere()`. 3794 """ 3795 s = str(normal) 3796 if "x" in s: 3797 normal = (1, 0, 0) 3798 if "-" in s: 3799 normal = -np.array(normal) 3800 elif "y" in s: 3801 normal = (0, 1, 0) 3802 if "-" in s: 3803 normal = -np.array(normal) 3804 elif "z" in s: 3805 normal = (0, 0, 1) 3806 if "-" in s: 3807 normal = -np.array(normal) 3808 plane = vtk.vtkPlane() 3809 plane.SetOrigin(origin) 3810 plane.SetNormal(normal) 3811 3812 clipper = vtk.vtkClipPolyData() 3813 clipper.SetInputData(self.polydata(True)) # must be True 3814 clipper.SetClipFunction(plane) 3815 clipper.GenerateClippedOutputOff() 3816 clipper.GenerateClipScalarsOff() 3817 clipper.SetInsideOut(invert) 3818 clipper.SetValue(0) 3819 clipper.Update() 3820 3821 cpoly = clipper.GetOutput() 3822 3823 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 3824 self._update(cpoly) 3825 else: 3826 # bring the underlying polydata to where _data is 3827 M = vtk.vtkMatrix4x4() 3828 M.DeepCopy(self.GetMatrix()) 3829 M.Invert() 3830 tr = vtk.vtkTransform() 3831 tr.SetMatrix(M) 3832 tf = vtk.vtkTransformPolyDataFilter() 3833 tf.SetTransform(tr) 3834 tf.SetInputData(cpoly) 3835 tf.Update() 3836 self._update(tf.GetOutput()) 3837 3838 self.pipeline = utils.OperationNode("cut_with_plane", parents=[self]) 3839 return self
Cut the mesh with the plane defined by a point and a normal.
Arguments:
- origin : (array) the cutting plane goes through this point
- normal : (array) normal of the cutting plane
Example:
from vedo import Cube cube = Cube().cut_with_plane(normal=(1,1,1)) cube.back_color('pink').show()
Examples:
Check out also:
3841 def cut_with_planes(self, origins, normals, invert=False): 3842 """ 3843 Cut the mesh with a convex set of planes defined by points and normals. 3844 3845 Arguments: 3846 origins : (array) 3847 each cutting plane goes through this point 3848 normals : (array) 3849 normal of each of the cutting planes 3850 3851 Check out also: 3852 `cut_with_box()`, `cut_with_cylinder()`, `cut_with_sphere()` 3853 """ 3854 3855 vpoints = vtk.vtkPoints() 3856 for p in utils.make3d(origins): 3857 vpoints.InsertNextPoint(p) 3858 normals = utils.make3d(normals) 3859 3860 planes = vtk.vtkPlanes() 3861 planes.SetPoints(vpoints) 3862 planes.SetNormals(utils.numpy2vtk(normals, dtype=float)) 3863 3864 clipper = vtk.vtkClipPolyData() 3865 clipper.SetInputData(self.polydata(True)) # must be True 3866 clipper.SetInsideOut(invert) 3867 clipper.SetClipFunction(planes) 3868 clipper.GenerateClippedOutputOff() 3869 clipper.GenerateClipScalarsOff() 3870 clipper.SetValue(0) 3871 clipper.Update() 3872 3873 cpoly = clipper.GetOutput() 3874 3875 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 3876 self._update(cpoly) 3877 else: 3878 # bring the underlying polydata to where _data is 3879 M = vtk.vtkMatrix4x4() 3880 M.DeepCopy(self.GetMatrix()) 3881 M.Invert() 3882 tr = vtk.vtkTransform() 3883 tr.SetMatrix(M) 3884 tf = vtk.vtkTransformPolyDataFilter() 3885 tf.SetTransform(tr) 3886 tf.SetInputData(cpoly) 3887 tf.Update() 3888 self._update(tf.GetOutput()) 3889 3890 self.pipeline = utils.OperationNode("cut_with_planes", parents=[self]) 3891 return self
Cut the mesh with a convex set of planes defined by points and normals.
Arguments:
- origins : (array) each cutting plane goes through this point
- normals : (array) normal of each of the cutting planes
Check out also:
3893 def cut_with_box(self, bounds, invert=False): 3894 """ 3895 Cut the current mesh with a box or a set of boxes. 3896 This is much faster than `cut_with_mesh()`. 3897 3898 Input `bounds` can be either: 3899 - a Mesh or Points object 3900 - a list of 6 number representing a bounding box `[xmin,xmax, ymin,ymax, zmin,zmax]` 3901 - a list of bounding boxes like the above: `[[xmin1,...], [xmin2,...], ...]` 3902 3903 Example: 3904 ```python 3905 from vedo import Sphere, Cube, show 3906 mesh = Sphere(r=1, res=50) 3907 box = Cube(side=1.5).wireframe() 3908 mesh.cut_with_box(box) 3909 show(mesh, box, axes=1) 3910 ``` 3911 ![](https://vedo.embl.es/images/feats/cut_with_box_cube.png) 3912 3913 Check out also: 3914 `cut_with_line()`, `cut_with_plane()`, `cut_with_cylinder()` 3915 """ 3916 if isinstance(bounds, Points): 3917 bounds = bounds.bounds() 3918 3919 box = vtk.vtkBox() 3920 if utils.is_sequence(bounds[0]): 3921 for bs in bounds: 3922 box.AddBounds(bs) 3923 else: 3924 box.SetBounds(bounds) 3925 3926 clipper = vtk.vtkClipPolyData() 3927 clipper.SetInputData(self.polydata(True)) # must be True 3928 clipper.SetClipFunction(box) 3929 clipper.SetInsideOut(not invert) 3930 clipper.GenerateClippedOutputOff() 3931 clipper.GenerateClipScalarsOff() 3932 clipper.SetValue(0) 3933 clipper.Update() 3934 cpoly = clipper.GetOutput() 3935 3936 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 3937 self._update(cpoly) 3938 else: 3939 # bring the underlying polydata to where _data is 3940 M = vtk.vtkMatrix4x4() 3941 M.DeepCopy(self.GetMatrix()) 3942 M.Invert() 3943 tr = vtk.vtkTransform() 3944 tr.SetMatrix(M) 3945 tf = vtk.vtkTransformPolyDataFilter() 3946 tf.SetTransform(tr) 3947 tf.SetInputData(cpoly) 3948 tf.Update() 3949 self._update(tf.GetOutput()) 3950 3951 self.pipeline = utils.OperationNode("cut_with_box", parents=[self]) 3952 return self
Cut the current mesh with a box or a set of boxes.
This is much faster than cut_with_mesh()
.
Input bounds
can be either:
- a Mesh or Points object
- a list of 6 number representing a bounding box
[xmin,xmax, ymin,ymax, zmin,zmax]
- a list of bounding boxes like the above:
[[xmin1,...], [xmin2,...], ...]
Example:
from vedo import Sphere, Cube, show mesh = Sphere(r=1, res=50) box = Cube(side=1.5).wireframe() mesh.cut_with_box(box) show(mesh, box, axes=1)
Check out also:
3954 def cut_with_line(self, points, invert=False, closed=True): 3955 """ 3956 Cut the current mesh with a line vertically in the z-axis direction like a cookie cutter. 3957 The polyline is defined by a set of points (z-coordinates are ignored). 3958 This is much faster than `cut_with_mesh()`. 3959 3960 Check out also: 3961 `cut_with_box()`, `cut_with_plane()`, `cut_with_sphere()` 3962 """ 3963 pplane = vtk.vtkPolyPlane() 3964 if isinstance(points, Points): 3965 points = points.points().tolist() 3966 3967 if closed: 3968 if isinstance(points, np.ndarray): 3969 points = points.tolist() 3970 points.append(points[0]) 3971 3972 vpoints = vtk.vtkPoints() 3973 for p in points: 3974 if len(p) == 2: 3975 p = [p[0], p[1], 0.0] 3976 vpoints.InsertNextPoint(p) 3977 3978 n = len(points) 3979 polyline = vtk.vtkPolyLine() 3980 polyline.Initialize(n, vpoints) 3981 polyline.GetPointIds().SetNumberOfIds(n) 3982 for i in range(n): 3983 polyline.GetPointIds().SetId(i, i) 3984 pplane.SetPolyLine(polyline) 3985 3986 clipper = vtk.vtkClipPolyData() 3987 clipper.SetInputData(self.polydata(True)) # must be True 3988 clipper.SetClipFunction(pplane) 3989 clipper.SetInsideOut(invert) 3990 clipper.GenerateClippedOutputOff() 3991 clipper.GenerateClipScalarsOff() 3992 clipper.SetValue(0) 3993 clipper.Update() 3994 cpoly = clipper.GetOutput() 3995 3996 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 3997 self._update(cpoly) 3998 else: 3999 # bring the underlying polydata to where _data is 4000 M = vtk.vtkMatrix4x4() 4001 M.DeepCopy(self.GetMatrix()) 4002 M.Invert() 4003 tr = vtk.vtkTransform() 4004 tr.SetMatrix(M) 4005 tf = vtk.vtkTransformPolyDataFilter() 4006 tf.SetTransform(tr) 4007 tf.SetInputData(cpoly) 4008 tf.Update() 4009 self._update(tf.GetOutput()) 4010 4011 self.pipeline = utils.OperationNode("cut_with_line", parents=[self]) 4012 return self
Cut the current mesh with a line vertically in the z-axis direction like a cookie cutter.
The polyline is defined by a set of points (z-coordinates are ignored).
This is much faster than cut_with_mesh()
.
Check out also:
4103 def cut_with_cylinder(self, center=(0, 0, 0), axis=(0, 0, 1), r=1, invert=False): 4104 """ 4105 Cut the current mesh with an infinite cylinder. 4106 This is much faster than `cut_with_mesh()`. 4107 4108 Arguments: 4109 center : (array) 4110 the center of the cylinder 4111 normal : (array) 4112 direction of the cylinder axis 4113 r : (float) 4114 radius of the cylinder 4115 4116 Example: 4117 ```python 4118 from vedo import Disc, show 4119 disc = Disc(r1=1, r2=1.2) 4120 mesh = disc.extrude(3, res=50).linewidth(1) 4121 mesh.cut_with_cylinder([0,0,2], r=0.4, axis='y', invert=True) 4122 show(mesh, axes=1) 4123 ``` 4124 ![](https://vedo.embl.es/images/feats/cut_with_cylinder.png) 4125 4126 Examples: 4127 - [optics_main1.py](https://github.com/marcomusy/vedo/blob/master/examples/simulations/optics_main1.py) 4128 4129 Check out also: 4130 `cut_with_box()`, `cut_with_plane()`, `cut_with_sphere()` 4131 """ 4132 s = str(axis) 4133 if "x" in s: 4134 axis = (1, 0, 0) 4135 elif "y" in s: 4136 axis = (0, 1, 0) 4137 elif "z" in s: 4138 axis = (0, 0, 1) 4139 cyl = vtk.vtkCylinder() 4140 cyl.SetCenter(center) 4141 cyl.SetAxis(axis[0], axis[1], axis[2]) 4142 cyl.SetRadius(r) 4143 4144 clipper = vtk.vtkClipPolyData() 4145 clipper.SetInputData(self.polydata(True)) # must be True 4146 clipper.SetClipFunction(cyl) 4147 clipper.SetInsideOut(not invert) 4148 clipper.GenerateClippedOutputOff() 4149 clipper.GenerateClipScalarsOff() 4150 clipper.SetValue(0) 4151 clipper.Update() 4152 cpoly = clipper.GetOutput() 4153 4154 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 4155 self._update(cpoly) 4156 else: 4157 # bring the underlying polydata to where _data is 4158 M = vtk.vtkMatrix4x4() 4159 M.DeepCopy(self.GetMatrix()) 4160 M.Invert() 4161 tr = vtk.vtkTransform() 4162 tr.SetMatrix(M) 4163 tf = vtk.vtkTransformPolyDataFilter() 4164 tf.SetTransform(tr) 4165 tf.SetInputData(cpoly) 4166 tf.Update() 4167 self._update(tf.GetOutput()) 4168 4169 self.pipeline = utils.OperationNode("cut_with_cylinder", parents=[self]) 4170 return self
Cut the current mesh with an infinite cylinder.
This is much faster than cut_with_mesh()
.
Arguments:
- center : (array) the center of the cylinder
- normal : (array) direction of the cylinder axis
- r : (float) radius of the cylinder
Example:
from vedo import Disc, show disc = Disc(r1=1, r2=1.2) mesh = disc.extrude(3, res=50).linewidth(1) mesh.cut_with_cylinder([0,0,2], r=0.4, axis='y', invert=True) show(mesh, axes=1)
Examples:
Check out also:
4172 def cut_with_sphere(self, center=(0, 0, 0), r=1.0, invert=False): 4173 """ 4174 Cut the current mesh with an sphere. 4175 This is much faster than `cut_with_mesh()`. 4176 4177 Arguments: 4178 center : (array) 4179 the center of the sphere 4180 r : (float) 4181 radius of the sphere 4182 4183 Example: 4184 ```python 4185 from vedo import Disc, show 4186 disc = Disc(r1=1, r2=1.2) 4187 mesh = disc.extrude(3, res=50).linewidth(1) 4188 mesh.cut_with_sphere([1,-0.7,2], r=1.5, invert=True) 4189 show(mesh, axes=1) 4190 ``` 4191 ![](https://vedo.embl.es/images/feats/cut_with_sphere.png) 4192 4193 Check out also: 4194 `cut_with_box()`, `cut_with_plane()`, `cut_with_cylinder()` 4195 """ 4196 sph = vtk.vtkSphere() 4197 sph.SetCenter(center) 4198 sph.SetRadius(r) 4199 4200 clipper = vtk.vtkClipPolyData() 4201 clipper.SetInputData(self.polydata(True)) # must be True 4202 clipper.SetClipFunction(sph) 4203 clipper.SetInsideOut(not invert) 4204 clipper.GenerateClippedOutputOff() 4205 clipper.GenerateClipScalarsOff() 4206 clipper.SetValue(0) 4207 clipper.Update() 4208 cpoly = clipper.GetOutput() 4209 4210 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 4211 self._update(cpoly) 4212 else: 4213 # bring the underlying polydata to where _data is 4214 M = vtk.vtkMatrix4x4() 4215 M.DeepCopy(self.GetMatrix()) 4216 M.Invert() 4217 tr = vtk.vtkTransform() 4218 tr.SetMatrix(M) 4219 tf = vtk.vtkTransformPolyDataFilter() 4220 tf.SetTransform(tr) 4221 tf.SetInputData(cpoly) 4222 tf.Update() 4223 self._update(tf.GetOutput()) 4224 4225 self.pipeline = utils.OperationNode("cut_with_sphere", parents=[self]) 4226 return self
Cut the current mesh with an sphere.
This is much faster than cut_with_mesh()
.
Arguments:
- center : (array) the center of the sphere
- r : (float) radius of the sphere
Example:
from vedo import Disc, show disc = Disc(r1=1, r2=1.2) mesh = disc.extrude(3, res=50).linewidth(1) mesh.cut_with_sphere([1,-0.7,2], r=1.5, invert=True) show(mesh, axes=1)
Check out also:
4228 def cut_with_mesh(self, mesh, invert=False, keep=False): 4229 """ 4230 Cut an `Mesh` mesh with another `Mesh`. 4231 4232 Use `invert` to invert the selection. 4233 4234 Use `keep` to keep the cutoff part, in this case an `Assembly` is returned: 4235 the "cut" object and the "discarded" part of the original object. 4236 You can access both via `assembly.unpack()` method. 4237 4238 Example: 4239 ```python 4240 from vedo import * 4241 arr = np.random.randn(100000, 3)/2 4242 pts = Points(arr).c('red3').pos(5,0,0) 4243 cube = Cube().pos(4,0.5,0) 4244 assem = pts.cut_with_mesh(cube, keep=True) 4245 show(assem.unpack(), axes=1).close() 4246 ``` 4247 ![](https://vedo.embl.es/images/feats/cut_with_mesh.png) 4248 4249 Check out also: 4250 `cut_with_box()`, `cut_with_plane()`, `cut_with_cylinder()` 4251 """ 4252 polymesh = mesh.polydata() 4253 poly = self.polydata() 4254 4255 # Create an array to hold distance information 4256 signed_distances = vtk.vtkFloatArray() 4257 signed_distances.SetNumberOfComponents(1) 4258 signed_distances.SetName("SignedDistances") 4259 4260 # implicit function that will be used to slice the mesh 4261 ippd = vtk.vtkImplicitPolyDataDistance() 4262 ippd.SetInput(polymesh) 4263 4264 # Evaluate the signed distance function at all of the grid points 4265 for pointId in range(poly.GetNumberOfPoints()): 4266 p = poly.GetPoint(pointId) 4267 signed_distance = ippd.EvaluateFunction(p) 4268 signed_distances.InsertNextValue(signed_distance) 4269 4270 currentscals = poly.GetPointData().GetScalars() 4271 if currentscals: 4272 currentscals = currentscals.GetName() 4273 4274 poly.GetPointData().AddArray(signed_distances) 4275 poly.GetPointData().SetActiveScalars("SignedDistances") 4276 4277 clipper = vtk.vtkClipPolyData() 4278 clipper.SetInputData(poly) 4279 clipper.SetInsideOut(not invert) 4280 clipper.SetGenerateClippedOutput(keep) 4281 clipper.SetValue(0.0) 4282 clipper.Update() 4283 cpoly = clipper.GetOutput() 4284 if keep: 4285 kpoly = clipper.GetOutput(1) 4286 4287 vis = False 4288 if currentscals: 4289 cpoly.GetPointData().SetActiveScalars(currentscals) 4290 vis = self.mapper().GetScalarVisibility() 4291 4292 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 4293 self._update(cpoly) 4294 else: 4295 # bring the underlying polydata to where _data is 4296 M = vtk.vtkMatrix4x4() 4297 M.DeepCopy(self.GetMatrix()) 4298 M.Invert() 4299 tr = vtk.vtkTransform() 4300 tr.SetMatrix(M) 4301 tf = vtk.vtkTransformPolyDataFilter() 4302 tf.SetTransform(tr) 4303 tf.SetInputData(cpoly) 4304 tf.Update() 4305 self._update(tf.GetOutput()) 4306 4307 self.pointdata.remove("SignedDistances") 4308 self.mapper().SetScalarVisibility(vis) 4309 if keep: 4310 if isinstance(self, vedo.Mesh): 4311 cutoff = vedo.Mesh(kpoly) 4312 else: 4313 cutoff = vedo.Points(kpoly) 4314 cutoff.property = vtk.vtkProperty() 4315 cutoff.property.DeepCopy(self.property) 4316 cutoff.SetProperty(cutoff.property) 4317 cutoff.c("k5").alpha(0.2) 4318 return vedo.Assembly([self, cutoff]) 4319 4320 self.pipeline = utils.OperationNode("cut_with_mesh", parents=[self, mesh]) 4321 return self
Cut an Mesh
mesh with another Mesh
.
Use invert
to invert the selection.
Use keep
to keep the cutoff part, in this case an Assembly
is returned:
the "cut" object and the "discarded" part of the original object.
You can access both via assembly.unpack()
method.
Example:
from vedo import *
arr = np.random.randn(100000, 3)/2
pts = Points(arr).c('red3').pos(5,0,0)
cube = Cube().pos(4,0.5,0)
assem = pts.cut_with_mesh(cube, keep=True)
show(assem.unpack(), axes=1).close()
Check out also:
4323 def cut_with_point_loop(self, points, invert=False, on="points", include_boundary=False): 4324 """ 4325 Cut an `Mesh` object with a set of points forming a closed loop. 4326 4327 Arguments: 4328 invert : (bool) 4329 invert selection (inside-out) 4330 on : (str) 4331 if 'cells' will extract the whole cells lying inside (or outside) the point loop 4332 include_boundary : (bool) 4333 include cells lying exactly on the boundary line. Only relevant on 'cells' mode 4334 4335 Examples: 4336 - [cut_with_points1.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/cut_with_points1.py) 4337 4338 ![](https://vedo.embl.es/images/advanced/cutWithPoints1.png) 4339 4340 - [cut_with_points2.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/cut_with_points2.py) 4341 4342 ![](https://vedo.embl.es/images/advanced/cutWithPoints2.png) 4343 """ 4344 if isinstance(points, Points): 4345 parents = [points] 4346 vpts = points.polydata().GetPoints() 4347 points = points.points() 4348 else: 4349 parents = [self] 4350 vpts = vtk.vtkPoints() 4351 points = utils.make3d(points) 4352 for p in points: 4353 vpts.InsertNextPoint(p) 4354 4355 if "cell" in on: 4356 ippd = vtk.vtkImplicitSelectionLoop() 4357 ippd.SetLoop(vpts) 4358 ippd.AutomaticNormalGenerationOn() 4359 clipper = vtk.vtkExtractPolyDataGeometry() 4360 clipper.SetInputData(self.polydata()) 4361 clipper.SetImplicitFunction(ippd) 4362 clipper.SetExtractInside(not invert) 4363 clipper.SetExtractBoundaryCells(include_boundary) 4364 else: 4365 spol = vtk.vtkSelectPolyData() 4366 spol.SetLoop(vpts) 4367 spol.GenerateSelectionScalarsOn() 4368 spol.GenerateUnselectedOutputOff() 4369 spol.SetInputData(self.polydata()) 4370 spol.Update() 4371 clipper = vtk.vtkClipPolyData() 4372 clipper.SetInputData(spol.GetOutput()) 4373 clipper.SetInsideOut(not invert) 4374 clipper.SetValue(0.0) 4375 clipper.Update() 4376 cpoly = clipper.GetOutput() 4377 4378 if self.GetIsIdentity() or cpoly.GetNumberOfPoints() == 0: 4379 self._update(cpoly) 4380 else: 4381 # bring the underlying polydata to where _data is 4382 M = vtk.vtkMatrix4x4() 4383 M.DeepCopy(self.GetMatrix()) 4384 M.Invert() 4385 tr = vtk.vtkTransform() 4386 tr.SetMatrix(M) 4387 tf = vtk.vtkTransformPolyDataFilter() 4388 tf.SetTransform(tr) 4389 tf.SetInputData(clipper.GetOutput()) 4390 tf.Update() 4391 self._update(tf.GetOutput()) 4392 4393 self.pipeline = utils.OperationNode("cut_with_pointloop", parents=parents) 4394 return self
Cut an Mesh
object with a set of points forming a closed loop.
Arguments:
- invert : (bool) invert selection (inside-out)
- on : (str) if 'cells' will extract the whole cells lying inside (or outside) the point loop
- include_boundary : (bool) include cells lying exactly on the boundary line. Only relevant on 'cells' mode
Examples:
4396 def cut_with_scalar(self, value, name="", invert=False): 4397 """ 4398 Cut a mesh or point cloud with some input scalar point-data. 4399 4400 Arguments: 4401 value : (float) 4402 cutting value 4403 name : (str) 4404 array name of the scalars to be used 4405 invert : (bool) 4406 flip selection 4407 4408 Example: 4409 ```python 4410 from vedo import * 4411 s = Sphere().lw(1) 4412 pts = s.points() 4413 scalars = np.sin(3*pts[:,2]) + pts[:,0] 4414 s.pointdata["somevalues"] = scalars 4415 s.cut_with_scalar(0.3) 4416 s.cmap("Spectral", "somevalues").add_scalarbar() 4417 s.show(axes=1).close() 4418 ``` 4419 ![](https://vedo.embl.es/images/feats/cut_with_scalars.png) 4420 """ 4421 if name: 4422 self.pointdata.select(name) 4423 clipper = vtk.vtkClipPolyData() 4424 clipper.SetInputData(self._data) 4425 clipper.SetValue(value) 4426 clipper.GenerateClippedOutputOff() 4427 clipper.SetInsideOut(not invert) 4428 clipper.Update() 4429 self._update(clipper.GetOutput()) 4430 4431 self.pipeline = utils.OperationNode("cut_with_scalars", parents=[self]) 4432 return self
Cut a mesh or point cloud with some input scalar point-data.
Arguments:
- value : (float) cutting value
- name : (str) array name of the scalars to be used
- invert : (bool) flip selection
Example:
from vedo import * s = Sphere().lw(1) pts = s.points() scalars = np.sin(3*pts[:,2]) + pts[:,0] s.pointdata["somevalues"] = scalars s.cut_with_scalar(0.3) s.cmap("Spectral", "somevalues").add_scalarbar() s.show(axes=1).close()
4434 def implicit_modeller(self, distance=0.05, res=(50, 50, 50), bounds=(), maxdist=None): 4435 """Find the surface which sits at the specified distance from the input one.""" 4436 if not bounds: 4437 bounds = self.bounds() 4438 4439 if not maxdist: 4440 maxdist = self.diagonal_size() / 2 4441 4442 imp = vtk.vtkImplicitModeller() 4443 imp.SetInputData(self.polydata()) 4444 imp.SetSampleDimensions(res) 4445 imp.SetMaximumDistance(maxdist) 4446 imp.SetModelBounds(bounds) 4447 contour = vtk.vtkContourFilter() 4448 contour.SetInputConnection(imp.GetOutputPort()) 4449 contour.SetValue(0, distance) 4450 contour.Update() 4451 poly = contour.GetOutput() 4452 out = vedo.Mesh(poly, c="lb") 4453 4454 out.pipeline = utils.OperationNode("implicit_modeller", parents=[self]) 4455 return out
Find the surface which sits at the specified distance from the input one.
4458 def generate_mesh( 4459 self, 4460 line_resolution=None, 4461 mesh_resolution=None, 4462 smooth=0.0, 4463 jitter=0.001, 4464 grid=None, 4465 quads=False, 4466 invert=False, 4467 ): 4468 """ 4469 Generate a polygonal Mesh from a closed contour line. 4470 If line is not closed it will be closed with a straight segment. 4471 4472 Arguments: 4473 line_resolution : (int) 4474 resolution of the contour line. The default is None, in this case 4475 the contour is not resampled. 4476 mesh_resolution : (int) 4477 resolution of the internal triangles not touching the boundary. 4478 smooth : (float) 4479 smoothing of the contour before meshing. 4480 jitter : (float) 4481 add a small noise to the internal points. 4482 grid : (Grid) 4483 manually pass a Grid object. The default is True. 4484 quads : (bool) 4485 generate a mesh of quads instead of triangles. 4486 invert : (bool) 4487 flip the line orientation. The default is False. 4488 4489 Examples: 4490 - [line2mesh_tri.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/line2mesh_tri.py) 4491 4492 ![](https://vedo.embl.es/images/advanced/line2mesh_tri.jpg) 4493 4494 - [line2mesh_quads.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/line2mesh_quads.py) 4495 4496 ![](https://vedo.embl.es/images/advanced/line2mesh_quads.png) 4497 """ 4498 if line_resolution is None: 4499 contour = vedo.shapes.Line(self.points()) 4500 else: 4501 contour = vedo.shapes.Spline(self.points(), smooth=smooth, res=line_resolution) 4502 contour.clean() 4503 4504 length = contour.length() 4505 density = length / contour.npoints 4506 vedo.logger.debug(f"tomesh():\n\tline length = {length}") 4507 vedo.logger.debug(f"\tdensity = {density} length/pt_separation") 4508 4509 x0, x1 = contour.xbounds() 4510 y0, y1 = contour.ybounds() 4511 4512 if grid is None: 4513 if mesh_resolution is None: 4514 resx = int((x1 - x0) / density + 0.5) 4515 resy = int((y1 - y0) / density + 0.5) 4516 vedo.logger.debug(f"tmesh_resolution = {[resx, resy]}") 4517 else: 4518 if utils.is_sequence(mesh_resolution): 4519 resx, resy = mesh_resolution 4520 else: 4521 resx, resy = mesh_resolution, mesh_resolution 4522 grid = vedo.shapes.Grid( 4523 [(x0 + x1) / 2, (y0 + y1) / 2, 0], 4524 s=((x1 - x0) * 1.025, (y1 - y0) * 1.025), 4525 res=(resx, resy), 4526 ) 4527 else: 4528 grid = grid.clone() 4529 4530 cpts = contour.points() 4531 4532 # make sure it's closed 4533 p0, p1 = cpts[0], cpts[-1] 4534 nj = max(2, int(utils.mag(p1 - p0) / density + 0.5)) 4535 joinline = vedo.shapes.Line(p1, p0, res=nj) 4536 contour = vedo.merge(contour, joinline).subsample(0.0001) 4537 4538 ####################################### quads 4539 if quads: 4540 cmesh = grid.clone().cut_with_point_loop(contour, on="cells", invert=invert) 4541 cmesh.wireframe(False).lw(0.5) 4542 cmesh.pipeline = utils.OperationNode( 4543 "generate_mesh", 4544 parents=[self, contour], 4545 comment=f"#quads {cmesh.inputdata().GetNumberOfCells()}", 4546 ) 4547 return cmesh 4548 ############################################# 4549 4550 grid_tmp = grid.points() 4551 4552 if jitter: 4553 np.random.seed(0) 4554 sigma = 1.0 / np.sqrt(grid.npoints) * grid.diagonal_size() * jitter 4555 vedo.logger.debug(f"\tsigma jittering = {sigma}") 4556 grid_tmp += np.random.rand(grid.npoints, 3) * sigma 4557 grid_tmp[:, 2] = 0.0 4558 4559 todel = [] 4560 density /= np.sqrt(3) 4561 vgrid_tmp = Points(grid_tmp) 4562 4563 for p in contour.points(): 4564 out = vgrid_tmp.closest_point(p, radius=density, return_point_id=True) 4565 todel += out.tolist() 4566 # cpoints = contour.points() 4567 # for i, p in enumerate(cpoints): 4568 # if i: 4569 # den = utils.mag(p-cpoints[i-1])/1.732 4570 # else: 4571 # den = density 4572 # todel += vgrid_tmp.closest_point(p, radius=den, return_point_id=True) 4573 4574 grid_tmp = grid_tmp.tolist() 4575 for index in sorted(list(set(todel)), reverse=True): 4576 del grid_tmp[index] 4577 4578 points = contour.points().tolist() + grid_tmp 4579 if invert: 4580 boundary = reversed(range(contour.npoints)) 4581 else: 4582 boundary = range(contour.npoints) 4583 4584 dln = delaunay2d(points, mode="xy", boundaries=[boundary]) 4585 dln.compute_normals(points=False) # fixes reversd faces 4586 dln.lw(0.5) 4587 4588 dln.pipeline = utils.OperationNode( 4589 "generate_mesh", 4590 parents=[self, contour], 4591 comment=f"#cells {dln.inputdata().GetNumberOfCells()}", 4592 ) 4593 return dln
Generate a polygonal Mesh from a closed contour line. If line is not closed it will be closed with a straight segment.
Arguments:
- line_resolution : (int) resolution of the contour line. The default is None, in this case the contour is not resampled.
- mesh_resolution : (int) resolution of the internal triangles not touching the boundary.
- smooth : (float) smoothing of the contour before meshing.
- jitter : (float) add a small noise to the internal points.
- grid : (Grid) manually pass a Grid object. The default is True.
- quads : (bool) generate a mesh of quads instead of triangles.
- invert : (bool) flip the line orientation. The default is False.
Examples:
4595 def reconstruct_surface( 4596 self, 4597 dims=(100, 100, 100), 4598 radius=None, 4599 sample_size=None, 4600 hole_filling=True, 4601 bounds=(), 4602 padding=0.05, 4603 ): 4604 """ 4605 Surface reconstruction from a scattered cloud of points. 4606 4607 Arguments: 4608 dims : (int) 4609 number of voxels in x, y and z to control precision. 4610 radius : (float) 4611 radius of influence of each point. 4612 Smaller values generally improve performance markedly. 4613 Note that after the signed distance function is computed, 4614 any voxel taking on the value >= radius 4615 is presumed to be "unseen" or uninitialized. 4616 sample_size : (int) 4617 if normals are not present 4618 they will be calculated using this sample size per point. 4619 hole_filling : (bool) 4620 enables hole filling, this generates 4621 separating surfaces between the empty and unseen portions of the volume. 4622 bounds : (list) 4623 region in space in which to perform the sampling 4624 in format (xmin,xmax, ymin,ymax, zim, zmax) 4625 padding : (float) 4626 increase by this fraction the bounding box 4627 4628 Examples: 4629 - [recosurface.py](https://github.com/marcomusy/vedo/blob/master/examples/advanced/recosurface.py) 4630 4631 ![](https://vedo.embl.es/images/advanced/recosurface.png) 4632 """ 4633 if not utils.is_sequence(dims): 4634 dims = (dims, dims, dims) 4635 4636 sdf = vtk.vtkSignedDistance() 4637 4638 if len(bounds) == 6: 4639 sdf.SetBounds(bounds) 4640 else: 4641 x0, x1, y0, y1, z0, z1 = self.bounds() 4642 sdf.SetBounds( 4643 x0 - (x1 - x0) * padding, 4644 x1 + (x1 - x0) * padding, 4645 y0 - (y1 - y0) * padding, 4646 y1 + (y1 - y0) * padding, 4647 z0 - (z1 - z0) * padding, 4648 z1 + (z1 - z0) * padding, 4649 ) 4650 4651 pd = self.polydata() 4652 4653 if pd.GetPointData().GetNormals(): 4654 sdf.SetInputData(pd) 4655 else: 4656 normals = vtk.vtkPCANormalEstimation() 4657 normals.SetInputData(pd) 4658 if not sample_size: 4659 sample_size = int(pd.GetNumberOfPoints() / 50) 4660 normals.SetSampleSize(sample_size) 4661 normals.SetNormalOrientationToGraphTraversal() 4662 sdf.SetInputConnection(normals.GetOutputPort()) 4663 # print("Recalculating normals with sample size =", sample_size) 4664 4665 if radius is None: 4666 radius = self.diagonal_size() / (sum(dims) / 3) * 5 4667 # print("Calculating mesh from points with radius =", radius) 4668 4669 sdf.SetRadius(radius) 4670 sdf.SetDimensions(dims) 4671 sdf.Update() 4672 4673 surface = vtk.vtkExtractSurface() 4674 surface.SetRadius(radius * 0.99) 4675 surface.SetHoleFilling(hole_filling) 4676 surface.ComputeNormalsOff() 4677 surface.ComputeGradientsOff() 4678 surface.SetInputConnection(sdf.GetOutputPort()) 4679 surface.Update() 4680 m = vedo.mesh.Mesh(surface.GetOutput(), c=self.color()) 4681 4682 m.pipeline = utils.OperationNode( 4683 "reconstruct_surface", parents=[self], 4684 comment=f"#pts {m.inputdata().GetNumberOfPoints()}" 4685 ) 4686 return m
Surface reconstruction from a scattered cloud of points.
Arguments:
- dims : (int) number of voxels in x, y and z to control precision.
- radius : (float) radius of influence of each point. Smaller values generally improve performance markedly. Note that after the signed distance function is computed, any voxel taking on the value >= radius is presumed to be "unseen" or uninitialized.
- sample_size : (int) if normals are not present they will be calculated using this sample size per point.
- hole_filling : (bool) enables hole filling, this generates separating surfaces between the empty and unseen portions of the volume.
- bounds : (list) region in space in which to perform the sampling in format (xmin,xmax, ymin,ymax, zim, zmax)
- padding : (float) increase by this fraction the bounding box
Examples:
4688 def compute_clustering(self, radius): 4689 """ 4690 Cluster points in space. The `radius` is the radius of local search. 4691 An array named "ClusterId" is added to the vertex points. 4692 4693 Examples: 4694 - [clustering.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/clustering.py) 4695 4696 ![](https://vedo.embl.es/images/basic/clustering.png) 4697 """ 4698 cluster = vtk.vtkEuclideanClusterExtraction() 4699 cluster.SetInputData(self.inputdata()) 4700 cluster.SetExtractionModeToAllClusters() 4701 cluster.SetRadius(radius) 4702 cluster.ColorClustersOn() 4703 cluster.Update() 4704 idsarr = cluster.GetOutput().GetPointData().GetArray("ClusterId") 4705 self.inputdata().GetPointData().AddArray(idsarr) 4706 4707 self.pipeline = utils.OperationNode( 4708 "compute_clustering", parents=[self], comment=f"radius = {radius}" 4709 ) 4710 return self
Cluster points in space. The radius
is the radius of local search.
An array named "ClusterId" is added to the vertex points.
Examples:
4712 def compute_connections(self, radius, mode=0, regions=(), vrange=(0, 1), seeds=(), angle=0): 4713 """ 4714 Extracts and/or segments points from a point cloud based on geometric distance measures 4715 (e.g., proximity, normal alignments, etc.) and optional measures such as scalar range. 4716 The default operation is to segment the points into "connected" regions where the connection 4717 is determined by an appropriate distance measure. Each region is given a region id. 4718 4719 Optionally, the filter can output the largest connected region of points; a particular region 4720 (via id specification); those regions that are seeded using a list of input point ids; 4721 or the region of points closest to a specified position. 4722 4723 The key parameter of this filter is the radius defining a sphere around each point which defines 4724 a local neighborhood: any other points in the local neighborhood are assumed connected to the point. 4725 Note that the radius is defined in absolute terms. 4726 4727 Other parameters are used to further qualify what it means to be a neighboring point. 4728 For example, scalar range and/or point normals can be used to further constrain the neighborhood. 4729 Also the extraction mode defines how the filter operates. 4730 By default, all regions are extracted but it is possible to extract particular regions; 4731 the region closest to a seed point; seeded regions; or the largest region found while processing. 4732 By default, all regions are extracted. 4733 4734 On output, all points are labeled with a region number. 4735 However note that the number of input and output points may not be the same: 4736 if not extracting all regions then the output size may be less than the input size. 4737 4738 Arguments: 4739 radius : (float) 4740 variable specifying a local sphere used to define local point neighborhood 4741 mode : (int) 4742 - 0, Extract all regions 4743 - 1, Extract point seeded regions 4744 - 2, Extract largest region 4745 - 3, Test specified regions 4746 - 4, Extract all regions with scalar connectivity 4747 - 5, Extract point seeded regions 4748 regions : (list) 4749 a list of non-negative regions id to extract 4750 vrange : (list) 4751 scalar range to use to extract points based on scalar connectivity 4752 seeds : (list) 4753 a list of non-negative point seed ids 4754 angle : (list) 4755 points are connected if the angle between their normals is 4756 within this angle threshold (expressed in degrees). 4757 """ 4758 # https://vtk.org/doc/nightly/html/classvtkConnectedPointsFilter.html 4759 cpf = vtk.vtkConnectedPointsFilter() 4760 cpf.SetInputData(self.polydata()) 4761 cpf.SetRadius(radius) 4762 if mode == 0: # Extract all regions 4763 pass 4764 4765 elif mode == 1: # Extract point seeded regions 4766 cpf.SetExtractionModeToPointSeededRegions() 4767 for s in seeds: 4768 cpf.AddSeed(s) 4769 4770 elif mode == 2: # Test largest region 4771 cpf.SetExtractionModeToLargestRegion() 4772 4773 elif mode == 3: # Test specified regions 4774 cpf.SetExtractionModeToSpecifiedRegions() 4775 for r in regions: 4776 cpf.AddSpecifiedRegion(r) 4777 4778 elif mode == 4: # Extract all regions with scalar connectivity 4779 cpf.SetExtractionModeToLargestRegion() 4780 cpf.ScalarConnectivityOn() 4781 cpf.SetScalarRange(vrange[0], vrange[1]) 4782 4783 elif mode == 5: # Extract point seeded regions 4784 cpf.SetExtractionModeToLargestRegion() 4785 cpf.ScalarConnectivityOn() 4786 cpf.SetScalarRange(vrange[0], vrange[1]) 4787 cpf.AlignedNormalsOn() 4788 cpf.SetNormalAngle(angle) 4789 4790 cpf.Update() 4791 return self._update(cpf.GetOutput())
Extracts and/or segments points from a point cloud based on geometric distance measures (e.g., proximity, normal alignments, etc.) and optional measures such as scalar range. The default operation is to segment the points into "connected" regions where the connection is determined by an appropriate distance measure. Each region is given a region id.
Optionally, the filter can output the largest connected region of points; a particular region (via id specification); those regions that are seeded using a list of input point ids; or the region of points closest to a specified position.
The key parameter of this filter is the radius defining a sphere around each point which defines a local neighborhood: any other points in the local neighborhood are assumed connected to the point. Note that the radius is defined in absolute terms.
Other parameters are used to further qualify what it means to be a neighboring point. For example, scalar range and/or point normals can be used to further constrain the neighborhood. Also the extraction mode defines how the filter operates. By default, all regions are extracted but it is possible to extract particular regions; the region closest to a seed point; seeded regions; or the largest region found while processing. By default, all regions are extracted.
On output, all points are labeled with a region number. However note that the number of input and output points may not be the same: if not extracting all regions then the output size may be less than the input size.
Arguments:
- radius : (float) variable specifying a local sphere used to define local point neighborhood
- mode : (int)
- 0, Extract all regions
- 1, Extract point seeded regions
- 2, Extract largest region
- 3, Test specified regions
- 4, Extract all regions with scalar connectivity
- 5, Extract point seeded regions
- regions : (list) a list of non-negative regions id to extract
- vrange : (list) scalar range to use to extract points based on scalar connectivity
- seeds : (list) a list of non-negative point seed ids
- angle : (list) points are connected if the angle between their normals is within this angle threshold (expressed in degrees).
4793 def compute_camera_distance(self): 4794 """ 4795 Calculate the distance from points to the camera. 4796 A pointdata array is created with name 'DistanceToCamera'. 4797 """ 4798 if vedo.plotter_instance.renderer: 4799 poly = self.polydata() 4800 dc = vtk.vtkDistanceToCamera() 4801 dc.SetInputData(poly) 4802 dc.SetRenderer(vedo.plotter_instance.renderer) 4803 dc.Update() 4804 return self._update(dc.GetOutput()) 4805 return self
Calculate the distance from points to the camera. A pointdata array is created with name 'DistanceToCamera'.
4807 def density( 4808 self, dims=(40, 40, 40), bounds=None, radius=None, compute_gradient=False, locator=None 4809 ): 4810 """ 4811 Generate a density field from a point cloud. Input can also be a set of 3D coordinates. 4812 Output is a `Volume`. 4813 4814 The local neighborhood is specified as the `radius` around each sample position (each voxel). 4815 The density is expressed as the number of counts in the radius search. 4816 4817 Arguments: 4818 dims : (int,list) 4819 number of voxels in x, y and z of the output Volume. 4820 compute_gradient : (bool) 4821 Turn on/off the generation of the gradient vector, 4822 gradient magnitude scalar, and function classification scalar. 4823 By default this is off. Note that this will increase execution time 4824 and the size of the output. (The names of these point data arrays are: 4825 "Gradient", "Gradient Magnitude", and "Classification") 4826 locator : (vtkPointLocator) 4827 can be assigned from a previous call for speed (access it via `object.point_locator`). 4828 4829 Examples: 4830 - [plot_density3d.py](https://github.com/marcomusy/vedo/blob/master/examples/pyplot/plot_density3d.py) 4831 4832 ![](https://vedo.embl.es/images/pyplot/plot_density3d.png) 4833 """ 4834 pdf = vtk.vtkPointDensityFilter() 4835 pdf.SetInputData(self.polydata()) 4836 4837 if not utils.is_sequence(dims): 4838 dims = [dims, dims, dims] 4839 4840 if bounds is None: 4841 bounds = list(self.bounds()) 4842 elif len(bounds) == 4: 4843 bounds = [*bounds, 0, 0] 4844 4845 if bounds[5] - bounds[4] == 0 or len(dims) == 2: # its 2D 4846 dims = list(dims) 4847 dims = [dims[0], dims[1], 2] 4848 diag = self.diagonal_size() 4849 bounds[5] = bounds[4] + diag / 1000 4850 pdf.SetModelBounds(bounds) 4851 4852 pdf.SetSampleDimensions(dims) 4853 4854 if locator: 4855 pdf.SetLocator(locator) 4856 4857 pdf.SetDensityEstimateToFixedRadius() 4858 if radius is None: 4859 radius = self.diagonal_size() / 20 4860 pdf.SetRadius(radius) 4861 4862 pdf.SetComputeGradient(compute_gradient) 4863 pdf.Update() 4864 img = pdf.GetOutput() 4865 vol = vedo.volume.Volume(img).mode(1) 4866 vol.name = "PointDensity" 4867 vol.info["radius"] = radius 4868 vol.locator = pdf.GetLocator() 4869 4870 vol.pipeline = utils.OperationNode( 4871 "density", parents=[self], comment=f"dims = {tuple(vol.dimensions())}" 4872 ) 4873 return vol
Generate a density field from a point cloud. Input can also be a set of 3D coordinates.
Output is a Volume
.
The local neighborhood is specified as the radius
around each sample position (each voxel).
The density is expressed as the number of counts in the radius search.
Arguments:
- dims : (int,list) number of voxels in x, y and z of the output Volume.
- compute_gradient : (bool) Turn on/off the generation of the gradient vector, gradient magnitude scalar, and function classification scalar. By default this is off. Note that this will increase execution time and the size of the output. (The names of these point data arrays are: "Gradient", "Gradient Magnitude", and "Classification")
- locator : (vtkPointLocator)
can be assigned from a previous call for speed (access it via
object.point_locator
).
Examples:
4875 def densify(self, target_distance=0.1, nclosest=6, radius=None, niter=1, nmax=None): 4876 """ 4877 Return a copy of the cloud with new added points. 4878 The new points are created in such a way that all points in any local neighborhood are 4879 within a target distance of one another. 4880 4881 For each input point, the distance to all points in its neighborhood is computed. 4882 If any of its neighbors is further than the target distance, 4883 the edge connecting the point and its neighbor is bisected and 4884 a new point is inserted at the bisection point. 4885 A single pass is completed once all the input points are visited. 4886 Then the process repeats to the number of iterations. 4887 4888 Examples: 4889 - [densifycloud.py](https://github.com/marcomusy/vedo/blob/master/examples/volumetric/densifycloud.py) 4890 4891 ![](https://vedo.embl.es/images/volumetric/densifycloud.png) 4892 4893 .. note:: 4894 Points will be created in an iterative fashion until all points in their 4895 local neighborhood are the target distance apart or less. 4896 Note that the process may terminate early due to the 4897 number of iterations. By default the target distance is set to 0.5. 4898 Note that the target_distance should be less than the radius 4899 or nothing will change on output. 4900 4901 .. warning:: 4902 This class can generate a lot of points very quickly. 4903 The maximum number of iterations is by default set to =1.0 for this reason. 4904 Increase the number of iterations very carefully. 4905 Also, `nmax` can be set to limit the explosion of points. 4906 It is also recommended that a N closest neighborhood is used. 4907 4908 """ 4909 src = vtk.vtkProgrammableSource() 4910 opts = self.points() 4911 4912 def _readPoints(): 4913 output = src.GetPolyDataOutput() 4914 points = vtk.vtkPoints() 4915 for p in opts: 4916 points.InsertNextPoint(p) 4917 output.SetPoints(points) 4918 4919 src.SetExecuteMethod(_readPoints) 4920 4921 dens = vtk.vtkDensifyPointCloudFilter() 4922 dens.SetInputConnection(src.GetOutputPort()) 4923 dens.InterpolateAttributeDataOn() 4924 dens.SetTargetDistance(target_distance) 4925 dens.SetMaximumNumberOfIterations(niter) 4926 if nmax: 4927 dens.SetMaximumNumberOfPoints(nmax) 4928 4929 if radius: 4930 dens.SetNeighborhoodTypeToRadius() 4931 dens.SetRadius(radius) 4932 elif nclosest: 4933 dens.SetNeighborhoodTypeToNClosest() 4934 dens.SetNumberOfClosestPoints(nclosest) 4935 else: 4936 vedo.logger.error("set either radius or nclosest") 4937 raise RuntimeError() 4938 dens.Update() 4939 pts = utils.vtk2numpy(dens.GetOutput().GetPoints().GetData()) 4940 cld = Points(pts, c=None).point_size(self.GetProperty().GetPointSize()) 4941 cld.interpolate_data_from(self, n=nclosest, radius=radius) 4942 cld.name = "densifiedCloud" 4943 4944 cld.pipeline = utils.OperationNode( 4945 "densify", parents=[self], c="#e9c46a:", 4946 comment=f"#pts {cld.inputdata().GetNumberOfPoints()}" 4947 ) 4948 return cld
Return a copy of the cloud with new added points. The new points are created in such a way that all points in any local neighborhood are within a target distance of one another.
For each input point, the distance to all points in its neighborhood is computed. If any of its neighbors is further than the target distance, the edge connecting the point and its neighbor is bisected and a new point is inserted at the bisection point. A single pass is completed once all the input points are visited. Then the process repeats to the number of iterations.
Examples:
Points will be created in an iterative fashion until all points in their local neighborhood are the target distance apart or less. Note that the process may terminate early due to the number of iterations. By default the target distance is set to 0.5. Note that the target_distance should be less than the radius or nothing will change on output.
This class can generate a lot of points very quickly.
The maximum number of iterations is by default set to =1.0 for this reason.
Increase the number of iterations very carefully.
Also, nmax
can be set to limit the explosion of points.
It is also recommended that a N closest neighborhood is used.
4954 def signed_distance(self, bounds=None, dims=(20, 20, 20), invert=False, maxradius=None): 4955 """ 4956 Compute the `Volume` object whose voxels contains the signed distance from 4957 the point cloud. The point cloud must have Normals. 4958 4959 Arguments: 4960 bounds : (list, actor) 4961 bounding box sizes 4962 dims : (list) 4963 dimensions (nr. of voxels) of the output volume. 4964 invert : (bool) 4965 flip the sign 4966 maxradius : (float) 4967 specify how far out to propagate distance calculation 4968 4969 Examples: 4970 - [distance2mesh.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/distance2mesh.py) 4971 4972 ![](https://vedo.embl.es/images/basic/distance2mesh.png) 4973 """ 4974 if bounds is None: 4975 bounds = self.bounds() 4976 if maxradius is None: 4977 maxradius = self.diagonal_size() / 2 4978 dist = vtk.vtkSignedDistance() 4979 dist.SetInputData(self.polydata()) 4980 dist.SetRadius(maxradius) 4981 dist.SetBounds(bounds) 4982 dist.SetDimensions(dims) 4983 dist.Update() 4984 img = dist.GetOutput() 4985 if invert: 4986 mat = vtk.vtkImageMathematics() 4987 mat.SetInput1Data(img) 4988 mat.SetOperationToMultiplyByK() 4989 mat.SetConstantK(-1) 4990 mat.Update() 4991 img = mat.GetOutput() 4992 4993 vol = vedo.Volume(img) 4994 vol.name = "SignedDistanceVolume" 4995 4996 vol.pipeline = utils.OperationNode( 4997 "signed_distance", 4998 parents=[self], 4999 comment=f"dim = {tuple(vol.dimensions())}", 5000 c="#e9c46a:#0096c7", 5001 ) 5002 return vol
Compute the Volume
object whose voxels contains the signed distance from
the point cloud. The point cloud must have Normals.
Arguments:
- bounds : (list, actor) bounding box sizes
- dims : (list) dimensions (nr. of voxels) of the output volume.
- invert : (bool) flip the sign
- maxradius : (float) specify how far out to propagate distance calculation
Examples:
5004 def tovolume( 5005 self, kernel="shepard", radius=None, n=None, bounds=None, null_value=None, dims=(25, 25, 25) 5006 ): 5007 """ 5008 Generate a `Volume` by interpolating a scalar 5009 or vector field which is only known on a scattered set of points or mesh. 5010 Available interpolation kernels are: shepard, gaussian, or linear. 5011 5012 Arguments: 5013 kernel : (str) 5014 interpolation kernel type [shepard] 5015 radius : (float) 5016 radius of the local search 5017 n : (int) 5018 number of point to use for interpolation 5019 bounds : (list) 5020 bounding box of the output Volume object 5021 dims : (list) 5022 dimensions of the output Volume object 5023 null_value : (float) 5024 value to be assigned to invalid points 5025 5026 Examples: 5027 - [interpolate_volume.py](https://github.com/marcomusy/vedo/blob/master/examples/volumetric/interpolate_volume.py) 5028 5029 ![](https://vedo.embl.es/images/volumetric/59095175-1ec5a300-8918-11e9-8bc0-fd35c8981e2b.jpg) 5030 """ 5031 if radius is None and not n: 5032 vedo.logger.error("please set either radius or n") 5033 raise RuntimeError 5034 5035 poly = self.polydata() 5036 5037 # Create a probe volume 5038 probe = vtk.vtkImageData() 5039 probe.SetDimensions(dims) 5040 if bounds is None: 5041 bounds = self.bounds() 5042 probe.SetOrigin(bounds[0], bounds[2], bounds[4]) 5043 probe.SetSpacing( 5044 (bounds[1] - bounds[0]) / dims[0], 5045 (bounds[3] - bounds[2]) / dims[1], 5046 (bounds[5] - bounds[4]) / dims[2], 5047 ) 5048 5049 if not self.point_locator: 5050 self.point_locator = vtk.vtkPointLocator() 5051 self.point_locator.SetDataSet(poly) 5052 self.point_locator.BuildLocator() 5053 5054 if kernel == "shepard": 5055 kern = vtk.vtkShepardKernel() 5056 kern.SetPowerParameter(2) 5057 elif kernel == "gaussian": 5058 kern = vtk.vtkGaussianKernel() 5059 elif kernel == "linear": 5060 kern = vtk.vtkLinearKernel() 5061 else: 5062 vedo.logger.error("Error in tovolume(), available kernels are:") 5063 vedo.logger.error(" [shepard, gaussian, linear]") 5064 raise RuntimeError() 5065 5066 if radius: 5067 kern.SetRadius(radius) 5068 5069 interpolator = vtk.vtkPointInterpolator() 5070 interpolator.SetInputData(probe) 5071 interpolator.SetSourceData(poly) 5072 interpolator.SetKernel(kern) 5073 interpolator.SetLocator(self.point_locator) 5074 5075 if n: 5076 kern.SetNumberOfPoints(n) 5077 kern.SetKernelFootprintToNClosest() 5078 else: 5079 kern.SetRadius(radius) 5080 5081 if null_value is not None: 5082 interpolator.SetNullValue(null_value) 5083 else: 5084 interpolator.SetNullPointsStrategyToClosestPoint() 5085 interpolator.Update() 5086 5087 vol = vedo.Volume(interpolator.GetOutput()) 5088 5089 vol.pipeline = utils.OperationNode( 5090 "signed_distance", 5091 parents=[self], 5092 comment=f"dim = {tuple(vol.dimensions())}", 5093 c="#e9c46a:#0096c7", 5094 ) 5095 return vol
Generate a Volume
by interpolating a scalar
or vector field which is only known on a scattered set of points or mesh.
Available interpolation kernels are: shepard, gaussian, or linear.
Arguments:
- kernel : (str) interpolation kernel type [shepard]
- radius : (float) radius of the local search
- n : (int) number of point to use for interpolation
- bounds : (list) bounding box of the output Volume object
- dims : (list) dimensions of the output Volume object
- null_value : (float) value to be assigned to invalid points
Examples:
5097 def generate_random_data(self): 5098 """Fill a dataset with random attributes""" 5099 gen = vtk.vtkRandomAttributeGenerator() 5100 gen.SetInputData(self._data) 5101 gen.GenerateAllDataOn() 5102 gen.SetDataTypeToFloat() 5103 gen.GeneratePointNormalsOff() 5104 gen.GeneratePointTensorsOn() 5105 gen.GenerateCellScalarsOn() 5106 gen.Update() 5107 5108 m = self._update(gen.GetOutput()) 5109 5110 m.pipeline = utils.OperationNode("generate\nrandom data", parents=[self]) 5111 return m
Fill a dataset with random attributes
Inherited Members
- vedo.base.BaseActor
- mapper
- inputdata
- modified
- npoints
- ncells
- points
- cell_centers
- delete_cells
- mark_boundaries
- find_cells_in
- count_vertices
- lighting
- print_histogram
- c
- pointdata
- celldata
- metadata
- map_cells_to_points
- map_points_to_cells
- resample_data_from
- add_ids
- gradient
- divergence
- vorticity
- add_scalarbar
- add_scalarbar3d
- write
698def Point(pos=(0, 0, 0), r=12, c="red", alpha=1.0): 699 """ 700 Create a simple point in space. 701 702 .. note:: if you are creating many points you should definitely use class `Points` instead! 703 """ 704 if isinstance(pos, vtk.vtkActor): 705 pos = pos.GetPosition() 706 pd = utils.buildPolyData([[0, 0, 0]]) 707 if len(pos) == 2: 708 pos = (pos[0], pos[1], 0.0) 709 pt = Points(pd, r, c, alpha) 710 pt.SetPosition(pos) 711 pt.name = "Point" 712 return pt
Create a simple point in space.
if you are creating many points you should definitely use class Points
instead!
41def merge(*meshs, flag=False): 42 """ 43 Build a new Mesh (or Points) formed by the fusion of the inputs. 44 45 Similar to Assembly, but in this case the input objects become a single entity. 46 47 To keep track of the original identities of the inputs you can use `flag`. 48 In this case a point array of IDs is added to the output. 49 50 Examples: 51 - [warp1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/warp1.py) 52 53 ![](https://vedo.embl.es/images/advanced/warp1.png) 54 55 - [value_iteration.py](https://github.com/marcomusy/vedo/tree/master/examples/simulations/value_iteration.py) 56 57 """ 58 acts = [a for a in utils.flatten(meshs) if a] 59 60 if not acts: 61 return None 62 63 idarr = [] 64 polyapp = vtk.vtkAppendPolyData() 65 for i, a in enumerate(acts): 66 try: 67 poly = a.polydata() 68 except AttributeError: 69 # so a vtkPolydata can also be passed 70 poly = a 71 polyapp.AddInputData(poly) 72 if flag: 73 idarr += [i] * poly.GetNumberOfPoints() 74 polyapp.Update() 75 mpoly = polyapp.GetOutput() 76 77 if flag: 78 varr = utils.numpy2vtk(idarr, dtype=np.uint16, name="OriginalMeshID") 79 mpoly.GetPointData().AddArray(varr) 80 81 if isinstance(acts[0], vedo.Mesh): 82 msh = vedo.Mesh(mpoly) 83 else: 84 msh = Points(mpoly) 85 86 if isinstance(acts[0], vtk.vtkActor): 87 cprp = vtk.vtkProperty() 88 cprp.DeepCopy(acts[0].GetProperty()) 89 msh.SetProperty(cprp) 90 msh.property = cprp 91 92 msh.pipeline = utils.OperationNode( 93 "merge", parents=acts, 94 comment=f"#pts {msh.inputdata().GetNumberOfPoints()}", 95 ) 96 return msh
Build a new Mesh (or Points) formed by the fusion of the inputs.
Similar to Assembly, but in this case the input objects become a single entity.
To keep track of the original identities of the inputs you can use flag
.
In this case a point array of IDs is added to the output.
Examples:
100def visible_points(mesh, area=(), tol=None, invert=False): 101 """ 102 Extract points based on whether they are visible or not. 103 Visibility is determined by accessing the z-buffer of a rendering window. 104 The position of each input point is converted into display coordinates, 105 and then the z-value at that point is obtained. 106 If within the user-specified tolerance, the point is considered visible. 107 Associated data attributes are passed to the output as well. 108 109 This filter also allows you to specify a rectangular window in display (pixel) 110 coordinates in which the visible points must lie. 111 112 Arguments: 113 area : (list) 114 specify a rectangular region as (xmin,xmax,ymin,ymax) 115 tol : (float) 116 a tolerance in normalized display coordinate system 117 invert : (bool) 118 select invisible points instead. 119 120 Example: 121 ```python 122 from vedo import Ellipsoid, show, visible_points 123 s = Ellipsoid().rotate_y(30) 124 125 #Camera options: pos, focal_point, viewup, distance, 126 camopts = dict(pos=(0,0,25), focal_point=(0,0,0)) 127 show(s, camera=camopts, offscreen=True) 128 129 m = visible_points(s) 130 #print('visible pts:', m.points()) # numpy array 131 show(m, new=True, axes=1) # optionally draw result on a new window 132 ``` 133 ![](https://vedo.embl.es/images/feats/visible_points.png) 134 """ 135 # specify a rectangular region 136 svp = vtk.vtkSelectVisiblePoints() 137 svp.SetInputData(mesh.polydata()) 138 svp.SetRenderer(vedo.plotter_instance.renderer) 139 140 if len(area) == 4: 141 svp.SetSelection(area[0], area[1], area[2], area[3]) 142 if tol is not None: 143 svp.SetTolerance(tol) 144 if invert: 145 svp.SelectInvisibleOn() 146 svp.Update() 147 148 m = Points(svp.GetOutput()).point_size(5) 149 m.name = "VisiblePoints" 150 return m
Extract points based on whether they are visible or not. Visibility is determined by accessing the z-buffer of a rendering window. The position of each input point is converted into display coordinates, and then the z-value at that point is obtained. If within the user-specified tolerance, the point is considered visible. Associated data attributes are passed to the output as well.
This filter also allows you to specify a rectangular window in display (pixel) coordinates in which the visible points must lie.
Arguments:
- area : (list) specify a rectangular region as (xmin,xmax,ymin,ymax)
- tol : (float) a tolerance in normalized display coordinate system
- invert : (bool) select invisible points instead.
Example:
from vedo import Ellipsoid, show, visible_points s = Ellipsoid().rotate_y(30) #Camera options: pos, focal_point, viewup, distance, camopts = dict(pos=(0,0,25), focal_point=(0,0,0)) show(s, camera=camopts, offscreen=True) m = visible_points(s) #print('visible pts:', m.points()) # numpy array show(m, new=True, axes=1) # optionally draw result on a new window
153def delaunay2d(plist, mode="scipy", boundaries=(), tol=None, alpha=0.0, offset=0.0, transform=None): 154 """ 155 Create a mesh from points in the XY plane. 156 If `mode='fit'` then the filter computes a best fitting 157 plane and projects the points onto it. 158 159 Arguments: 160 tol : (float) 161 specify a tolerance to control discarding of closely spaced points. 162 This tolerance is specified as a fraction of the diagonal length of the bounding box of the points. 163 alpha : (float) 164 for a non-zero alpha value, only edges or triangles contained 165 within a sphere centered at mesh vertices will be output. 166 Otherwise, only triangles will be output. 167 offset : (float) 168 multiplier to control the size of the initial, bounding Delaunay triangulation. 169 transform: vtkTransform 170 a VTK transformation (eg. a thinplate spline) 171 which is applied to points to generate a 2D problem. 172 This maps a 3D dataset into a 2D dataset where triangulation can be done on the XY plane. 173 The points are transformed and triangulated. 174 The topology of triangulated points is used as the output topology. 175 176 Examples: 177 - [delaunay2d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/delaunay2d.py) 178 179 ![](https://vedo.embl.es/images/basic/delaunay2d.png) 180 """ 181 if isinstance(plist, Points): 182 parents = [plist] 183 plist = plist.points() 184 else: 185 parents = [] 186 plist = np.ascontiguousarray(plist) 187 plist = utils.make3d(plist) 188 189 ############################################# 190 if mode == "scipy": 191 from scipy.spatial import Delaunay as scipy_delaunay 192 193 tri = scipy_delaunay(plist[:, 0:2]) 194 return vedo.mesh.Mesh([plist, tri.simplices]) 195 ############################################# 196 197 pd = vtk.vtkPolyData() 198 vpts = vtk.vtkPoints() 199 vpts.SetData(utils.numpy2vtk(plist, dtype=np.float32)) 200 pd.SetPoints(vpts) 201 202 delny = vtk.vtkDelaunay2D() 203 delny.SetInputData(pd) 204 if tol: 205 delny.SetTolerance(tol) 206 delny.SetAlpha(alpha) 207 delny.SetOffset(offset) 208 if transform: 209 if hasattr(transform, "transform"): 210 transform = transform.transform 211 delny.SetTransform(transform) 212 213 if mode == "xy" and boundaries: 214 boundary = vtk.vtkPolyData() 215 boundary.SetPoints(vpts) 216 cell_array = vtk.vtkCellArray() 217 for b in boundaries: 218 cpolygon = vtk.vtkPolygon() 219 for idd in b: 220 cpolygon.GetPointIds().InsertNextId(idd) 221 cell_array.InsertNextCell(cpolygon) 222 boundary.SetPolys(cell_array) 223 delny.SetSourceData(boundary) 224 225 if mode == "fit": 226 delny.SetProjectionPlaneMode(vtk.VTK_BEST_FITTING_PLANE) 227 delny.Update() 228 msh = vedo.mesh.Mesh(delny.GetOutput()).clean().lighting("off") 229 230 msh.pipeline = utils.OperationNode( 231 "delaunay2d", parents=parents, 232 comment=f"#cells {msh.inputdata().GetNumberOfCells()}" 233 ) 234 return msh
Create a mesh from points in the XY plane.
If mode='fit'
then the filter computes a best fitting
plane and projects the points onto it.
Arguments:
- tol : (float) specify a tolerance to control discarding of closely spaced points. This tolerance is specified as a fraction of the diagonal length of the bounding box of the points.
- alpha : (float) for a non-zero alpha value, only edges or triangles contained within a sphere centered at mesh vertices will be output. Otherwise, only triangles will be output.
- offset : (float) multiplier to control the size of the initial, bounding Delaunay triangulation.
- transform: vtkTransform a VTK transformation (eg. a thinplate spline) which is applied to points to generate a 2D problem. This maps a 3D dataset into a 2D dataset where triangulation can be done on the XY plane. The points are transformed and triangulated. The topology of triangulated points is used as the output topology.
Examples:
237def voronoi(pts, padding=0.0, fit=False, method="vtk"): 238 """ 239 Generate the 2D Voronoi convex tiling of the input points (z is ignored). 240 The points are assumed to lie in a plane. The output is a Mesh. Each output cell is a convex polygon. 241 242 The 2D Voronoi tessellation is a tiling of space, where each Voronoi tile represents the region nearest 243 to one of the input points. Voronoi tessellations are important in computational geometry 244 (and many other fields), and are the dual of Delaunay triangulations. 245 246 Thus the triangulation is constructed in the x-y plane, and the z coordinate is ignored 247 (although carried through to the output). 248 If you desire to triangulate in a different plane, you can use fit=True. 249 250 A brief summary is as follows. Each (generating) input point is associated with 251 an initial Voronoi tile, which is simply the bounding box of the point set. 252 A locator is then used to identify nearby points: each neighbor in turn generates a 253 clipping line positioned halfway between the generating point and the neighboring point, 254 and orthogonal to the line connecting them. Clips are readily performed by evaluationg the 255 vertices of the convex Voronoi tile as being on either side (inside,outside) of the clip line. 256 If two intersections of the Voronoi tile are found, the portion of the tile "outside" the clip 257 line is discarded, resulting in a new convex, Voronoi tile. As each clip occurs, 258 the Voronoi "Flower" error metric (the union of error spheres) is compared to the extent of the region 259 containing the neighboring clip points. The clip region (along with the points contained in it) is grown 260 by careful expansion (e.g., outward spiraling iterator over all candidate clip points). 261 When the Voronoi Flower is contained within the clip region, the algorithm terminates and the Voronoi 262 tile is output. Once complete, it is possible to construct the Delaunay triangulation from the Voronoi 263 tessellation. Note that topological and geometric information is used to generate a valid triangulation 264 (e.g., merging points and validating topology). 265 266 Arguments: 267 pts : (list) 268 list of input points. 269 padding : (float) 270 padding distance. The default is 0. 271 fit : (bool) 272 detect automatically the best fitting plane. The default is False. 273 274 Examples: 275 - [voronoi1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/voronoi1.py) 276 277 ![](https://vedo.embl.es/images/basic/voronoi1.png) 278 279 - [voronoi2.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/voronoi2.py) 280 281 ![](https://vedo.embl.es/images/advanced/voronoi2.png) 282 """ 283 if method == "scipy": 284 from scipy.spatial import Voronoi as scipy_voronoi 285 286 pts = np.asarray(pts)[:, (0, 1)] 287 vor = scipy_voronoi(pts) 288 regs = [] # filter out invalid indices 289 for r in vor.regions: 290 flag = True 291 for x in r: 292 if x < 0: 293 flag = False 294 break 295 if flag and len(r) > 0: 296 regs.append(r) 297 298 m = vedo.Mesh([vor.vertices, regs], c="orange5") 299 m.celldata["VoronoiID"] = np.array(list(range(len(regs)))).astype(int) 300 m.locator = None 301 302 elif method == "vtk": 303 vor = vtk.vtkVoronoi2D() 304 if isinstance(pts, Points): 305 vor.SetInputData(pts.polydata()) 306 else: 307 pts = np.asarray(pts) 308 if pts.shape[1] == 2: 309 pts = np.c_[pts, np.zeros(len(pts))] 310 pd = vtk.vtkPolyData() 311 vpts = vtk.vtkPoints() 312 vpts.SetData(utils.numpy2vtk(pts, dtype=np.float32)) 313 pd.SetPoints(vpts) 314 vor.SetInputData(pd) 315 vor.SetPadding(padding) 316 vor.SetGenerateScalarsToPointIds() 317 if fit: 318 vor.SetProjectionPlaneModeToBestFittingPlane() 319 else: 320 vor.SetProjectionPlaneModeToXYPlane() 321 vor.Update() 322 poly = vor.GetOutput() 323 arr = poly.GetCellData().GetArray(0) 324 if arr: 325 arr.SetName("VoronoiID") 326 m = vedo.Mesh(poly, c="orange5") 327 m.locator = vor.GetLocator() 328 329 else: 330 vedo.logger.error(f"Unknown method {method} in voronoi()") 331 raise RuntimeError 332 333 m.lw(2).lighting("off").wireframe() 334 m.name = "Voronoi" 335 return m
Generate the 2D Voronoi convex tiling of the input points (z is ignored). The points are assumed to lie in a plane. The output is a Mesh. Each output cell is a convex polygon.
The 2D Voronoi tessellation is a tiling of space, where each Voronoi tile represents the region nearest to one of the input points. Voronoi tessellations are important in computational geometry (and many other fields), and are the dual of Delaunay triangulations.
Thus the triangulation is constructed in the x-y plane, and the z coordinate is ignored (although carried through to the output). If you desire to triangulate in a different plane, you can use fit=True.
A brief summary is as follows. Each (generating) input point is associated with an initial Voronoi tile, which is simply the bounding box of the point set. A locator is then used to identify nearby points: each neighbor in turn generates a clipping line positioned halfway between the generating point and the neighboring point, and orthogonal to the line connecting them. Clips are readily performed by evaluationg the vertices of the convex Voronoi tile as being on either side (inside,outside) of the clip line. If two intersections of the Voronoi tile are found, the portion of the tile "outside" the clip line is discarded, resulting in a new convex, Voronoi tile. As each clip occurs, the Voronoi "Flower" error metric (the union of error spheres) is compared to the extent of the region containing the neighboring clip points. The clip region (along with the points contained in it) is grown by careful expansion (e.g., outward spiraling iterator over all candidate clip points). When the Voronoi Flower is contained within the clip region, the algorithm terminates and the Voronoi tile is output. Once complete, it is possible to construct the Delaunay triangulation from the Voronoi tessellation. Note that topological and geometric information is used to generate a valid triangulation (e.g., merging points and validating topology).
Arguments:
- pts : (list) list of input points.
- padding : (float) padding distance. The default is 0.
- fit : (bool) detect automatically the best fitting plane. The default is False.
Examples:
378def fit_line(points): 379 """ 380 Fits a line through points. 381 382 Extra info is stored in `Line.slope`, `Line.center`, `Line.variances`. 383 384 Examples: 385 - [fitline.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitline.py) 386 387 ![](https://vedo.embl.es/images/advanced/fitline.png) 388 """ 389 if isinstance(points, Points): 390 points = points.points() 391 data = np.array(points) 392 datamean = data.mean(axis=0) 393 _, dd, vv = np.linalg.svd(data - datamean) 394 vv = vv[0] / np.linalg.norm(vv[0]) 395 # vv contains the first principal component, i.e. the direction 396 # vector of the best fit line in the least squares sense. 397 xyz_min = points.min(axis=0) 398 xyz_max = points.max(axis=0) 399 a = np.linalg.norm(xyz_min - datamean) 400 b = np.linalg.norm(xyz_max - datamean) 401 p1 = datamean - a * vv 402 p2 = datamean + b * vv 403 line = vedo.shapes.Line(p1, p2, lw=1) 404 line.slope = vv 405 line.center = datamean 406 line.variances = dd 407 return line
Fits a line through points.
Extra info is stored in Line.slope
, Line.center
, Line.variances
.
Examples:
410def fit_circle(points): 411 """ 412 Fits a circle through a set of 3D points, with a very fast non-iterative method. 413 414 Returns the tuple `(center, radius, normal_to_circle)`. 415 416 .. warning:: 417 trying to fit s-shaped points will inevitably lead to instabilities and 418 circles of small radius. 419 420 References: 421 *J.F. Crawford, Nucl. Instr. Meth. 211, 1983, 223-225.* 422 """ 423 data = np.asarray(points) 424 425 offs = data.mean(axis=0) 426 data, n0 = _rotate_points(data - offs) 427 428 xi = data[:, 0] 429 yi = data[:, 1] 430 431 x = sum(xi) 432 xi2 = xi * xi 433 xx = sum(xi2) 434 xxx = sum(xi2 * xi) 435 436 y = sum(yi) 437 yi2 = yi * yi 438 yy = sum(yi2) 439 yyy = sum(yi2 * yi) 440 441 xiyi = xi * yi 442 xy = sum(xiyi) 443 xyy = sum(xiyi * yi) 444 xxy = sum(xi * xiyi) 445 446 N = len(xi) 447 k = (xx + yy) / N 448 449 a1 = xx - x * x / N 450 b1 = xy - x * y / N 451 c1 = 0.5 * (xxx + xyy - x * k) 452 453 a2 = xy - x * y / N 454 b2 = yy - y * y / N 455 c2 = 0.5 * (xxy + yyy - y * k) 456 457 d = a2 * b1 - a1 * b2 458 if not d: 459 return offs, 0, n0 460 x0 = (b1 * c2 - b2 * c1) / d 461 y0 = (c1 - a1 * x0) / b1 462 463 R = np.sqrt(x0 * x0 + y0 * y0 - 1 / N * (2 * x0 * x + 2 * y0 * y - xx - yy)) 464 465 c, _ = _rotate_points([x0, y0, 0], (0, 0, 1), n0) 466 467 return c[0] + offs, R, n0
Fits a circle through a set of 3D points, with a very fast non-iterative method.
Returns the tuple (center, radius, normal_to_circle)
.
trying to fit s-shaped points will inevitably lead to instabilities and circles of small radius.
References:
J.F. Crawford, Nucl. Instr. Meth. 211, 1983, 223-225.
470def fit_plane(points, signed=False): 471 """ 472 Fits a plane to a set of points. 473 474 Extra info is stored in `Plane.normal`, `Plane.center`, `Plane.variance`. 475 476 Arguments: 477 signed : (bool) 478 if True flip sign of the normal based on the ordering of the points 479 480 Examples: 481 - [fitline.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/fitline.py) 482 483 ![](https://vedo.embl.es/images/advanced/fitline.png) 484 """ 485 if isinstance(points, Points): 486 points = points.points() 487 data = np.asarray(points) 488 datamean = data.mean(axis=0) 489 pts = data - datamean 490 res = np.linalg.svd(pts) 491 dd, vv = res[1], res[2] 492 n = np.cross(vv[0], vv[1]) 493 if signed: 494 v = np.zeros_like(pts) 495 for i in range(len(pts) - 1): 496 vi = np.cross(pts[i], pts[i + 1]) 497 v[i] = vi / np.linalg.norm(vi) 498 ns = np.mean(v, axis=0) # normal to the points plane 499 if np.dot(n, ns) < 0: 500 n = -n 501 xyz_min = data.min(axis=0) 502 xyz_max = data.max(axis=0) 503 s = np.linalg.norm(xyz_max - xyz_min) 504 pla = vedo.shapes.Plane(datamean, n, s=[s, s]) 505 pla.normal = n 506 pla.center = datamean 507 pla.variance = dd[2] 508 pla.name = "FitPlane" 509 pla.top = n 510 return pla
Fits a plane to a set of points.
Extra info is stored in Plane.normal
, Plane.center
, Plane.variance
.
Arguments: signed : (bool) if True flip sign of the normal based on the ordering of the points
Examples:
513def fit_sphere(coords): 514 """ 515 Fits a sphere to a set of points. 516 517 Extra info is stored in `Sphere.radius`, `Sphere.center`, `Sphere.residue`. 518 519 Examples: 520 - [fitspheres1.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/fitspheres1.py) 521 522 ![](https://vedo.embl.es/images/advanced/fitspheres1.jpg) 523 """ 524 if isinstance(coords, Points): 525 coords = coords.points() 526 coords = np.array(coords) 527 n = len(coords) 528 A = np.zeros((n, 4)) 529 A[:, :-1] = coords * 2 530 A[:, 3] = 1 531 f = np.zeros((n, 1)) 532 x = coords[:, 0] 533 y = coords[:, 1] 534 z = coords[:, 2] 535 f[:, 0] = x * x + y * y + z * z 536 try: 537 C, residue, rank, _ = np.linalg.lstsq(A, f, rcond=-1) # solve AC=f 538 except: 539 C, residue, rank, _ = np.linalg.lstsq(A, f) # solve AC=f 540 if rank < 4: 541 return None 542 t = (C[0] * C[0]) + (C[1] * C[1]) + (C[2] * C[2]) + C[3] 543 radius = np.sqrt(t)[0] 544 center = np.array([C[0][0], C[1][0], C[2][0]]) 545 if len(residue) > 0: 546 residue = np.sqrt(residue[0]) / n 547 else: 548 residue = 0 549 sph = vedo.shapes.Sphere(center, radius, c=(1, 0, 0)).wireframe(1) 550 sph.radius = radius 551 sph.center = center 552 sph.residue = residue 553 sph.name = "FitSphere" 554 return sph
Fits a sphere to a set of points.
Extra info is stored in Sphere.radius
, Sphere.center
, Sphere.residue
.
Examples:
557def pca_ellipse(points, pvalue=0.673, res=60): 558 """ 559 Show the oriented PCA 2D ellipse that contains the fraction `pvalue` of points. 560 561 Parameter `pvalue` sets the specified fraction of points inside the ellipse. 562 Normalized directions are stored in `ellipse.axis1`, `ellipse.axis12` 563 axes sizes are stored in `ellipse.va`, `ellipse.vb` 564 565 Arguments: 566 pvalue : (float) 567 ellipse will include this fraction of points 568 res : (int) 569 resolution of the ellipse 570 571 Examples: 572 - [histo_pca.py](https://github.com/marcomusy/vedo/tree/master/examples/pyplot/histo_pca.py) 573 574 ![](https://vedo.embl.es/images/pyplot/histo_pca.png) 575 """ 576 from scipy.stats import f 577 578 if isinstance(points, Points): 579 coords = points.points() 580 else: 581 coords = points 582 if len(coords) < 4: 583 vedo.logger.warning("in pca_ellipse(), there are not enough points!") 584 return None 585 586 P = np.array(coords, dtype=float)[:,(0,1)] 587 cov = np.cov(P, rowvar=0) # covariance matrix 588 _, s, R = np.linalg.svd(cov) # singular value decomposition 589 p, n = s.size, P.shape[0] 590 fppf = f.ppf(pvalue, p, n-p) # f % point function 591 ua, ub = np.sqrt(s*fppf/2)*2 # semi-axes (largest first) 592 center = np.mean(P, axis=0) # centroid of the ellipse 593 594 matri = vtk.vtkMatrix4x4() 595 matri.DeepCopy(( 596 R[0][0] * ua, R[1][0] * ub, 0, center[0], 597 R[0][1] * ua, R[1][1] * ub, 0, center[1], 598 0, 0, 1, 0, 599 0, 0, 0, 1) 600 ) 601 vtra = vtk.vtkTransform() 602 vtra.SetMatrix(matri) 603 604 elli = vedo.shapes.Circle(alpha=0.75, res=res) 605 606 # assign the transformation 607 elli.SetScale(vtra.GetScale()) 608 elli.SetOrientation(vtra.GetOrientation()) 609 elli.SetPosition(vtra.GetPosition()) 610 611 elli.center = np.array(vtra.GetPosition()) 612 elli.nr_of_points = n 613 elli.va = ua 614 elli.vb = ub 615 elli.axis1 = np.array(vtra.TransformPoint([1, 0, 0])) - elli.center 616 elli.axis2 = np.array(vtra.TransformPoint([0, 1, 0])) - elli.center 617 elli.axis1 /= np.linalg.norm(elli.axis1) 618 elli.axis2 /= np.linalg.norm(elli.axis2) 619 elli.transformation = vtra 620 elli.name = "PCAEllipse" 621 return elli
Show the oriented PCA 2D ellipse that contains the fraction pvalue
of points.
Parameter pvalue
sets the specified fraction of points inside the ellipse.
Normalized directions are stored in ellipse.axis1
, ellipse.axis12
axes sizes are stored in ellipse.va
, ellipse.vb
Arguments:
- pvalue : (float) ellipse will include this fraction of points
- res : (int) resolution of the ellipse
Examples:
624def pca_ellipsoid(points, pvalue=0.673): 625 """ 626 Show the oriented PCA ellipsoid that contains fraction `pvalue` of points. 627 628 Parameter `pvalue` sets the specified fraction of points inside the ellipsoid. 629 630 Extra can be calculated with `mesh.asphericity()`, `mesh.asphericity_error()` 631 (asphericity is equal to 0 for a perfect sphere). 632 633 Axes sizes can be accessed in `ellips.va`, `ellips.vb`, `ellips.vc`, 634 normalized directions are stored in `ellips.axis1`, `ellips.axis12` 635 and `ellips.axis3`. 636 637 .. warning:: the meaning of `ellips.axis1`, has changed wrt `vedo==2022.1.0` 638 in that it's now the direction wrt the origin (e.i. the center is subtracted) 639 640 Examples: 641 - [pca_ellipsoid.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/pca_ellipsoid.py) 642 643 ![](https://vedo.embl.es/images/basic/pca.png) 644 """ 645 from scipy.stats import f 646 647 if isinstance(points, Points): 648 coords = points.points() 649 else: 650 coords = points 651 if len(coords) < 4: 652 vedo.logger.warning("in pcaEllipsoid(), there are not enough points!") 653 return None 654 655 P = np.array(coords, ndmin=2, dtype=float) 656 cov = np.cov(P, rowvar=0) # covariance matrix 657 _, s, R = np.linalg.svd(cov) # singular value decomposition 658 p, n = s.size, P.shape[0] 659 fppf = f.ppf(pvalue, p, n-p)*(n-1)*p*(n+1)/n/(n-p) # f % point function 660 cfac = 1 + 6/(n-1) # correction factor for low statistics 661 ua, ub, uc = np.sqrt(s*fppf)/cfac # semi-axes (largest first) 662 center = np.mean(P, axis=0) # centroid of the hyperellipsoid 663 664 elli = vedo.shapes.Ellipsoid((0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1), alpha=0.25) 665 666 matri = vtk.vtkMatrix4x4() 667 matri.DeepCopy(( 668 R[0][0] * ua*2, R[1][0] * ub*2, R[2][0] * uc*2, center[0], 669 R[0][1] * ua*2, R[1][1] * ub*2, R[2][1] * uc*2, center[1], 670 R[0][2] * ua*2, R[1][2] * ub*2, R[2][2] * uc*2, center[2], 671 0, 0, 0, 1) 672 ) 673 vtra = vtk.vtkTransform() 674 vtra.SetMatrix(matri) 675 676 # assign the transformation 677 elli.SetScale(vtra.GetScale()) 678 elli.SetOrientation(vtra.GetOrientation()) 679 elli.SetPosition(vtra.GetPosition()) 680 681 elli.center = np.array(vtra.GetPosition()) 682 elli.nr_of_points = n 683 elli.va = ua 684 elli.vb = ub 685 elli.vc = uc 686 elli.axis1 = np.array(vtra.TransformPoint([1, 0, 0])) - elli.center 687 elli.axis2 = np.array(vtra.TransformPoint([0, 1, 0])) - elli.center 688 elli.axis3 = np.array(vtra.TransformPoint([0, 0, 1])) - elli.center 689 elli.axis1 /= np.linalg.norm(elli.axis1) 690 elli.axis2 /= np.linalg.norm(elli.axis2) 691 elli.axis3 /= np.linalg.norm(elli.axis3) 692 elli.transformation = vtra 693 elli.name = "PCAEllipsoid" 694 return elli
Show the oriented PCA ellipsoid that contains fraction pvalue
of points.
Parameter pvalue
sets the specified fraction of points inside the ellipsoid.
Extra can be calculated with mesh.asphericity()
, mesh.asphericity_error()
(asphericity is equal to 0 for a perfect sphere).
Axes sizes can be accessed in ellips.va
, ellips.vb
, ellips.vc
,
normalized directions are stored in ellips.axis1
, ellips.axis12
and ellips.axis3
.
the meaning of ellips.axis1
, has changed wrt vedo==2022.1.0
in that it's now the direction wrt the origin (e.i. the center is subtracted)