vedo.core
Base classes providing functionality to different vedo objects.
1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3import numpy as np 4from typing import List, Union, Any 5from typing_extensions import Self 6 7import vedo.vtkclasses as vtki 8 9import vedo 10from vedo import colors 11from vedo import utils 12from vedo.transformations import LinearTransform, NonLinearTransform 13 14 15__docformat__ = "google" 16 17__doc__ = """ 18Base classes providing functionality to different vedo objects. 19 20![](https://vedo.embl.es/images/feats/algorithms_illustration.png) 21""" 22 23__all__ = [ 24 "DataArrayHelper", 25 "CommonAlgorithms", 26 "PointAlgorithms", 27 "VolumeAlgorithms", 28] 29 30warnings = dict( 31 points_getter=( 32 "WARNING: points() is deprecated, use vertices instead. Change:\n" 33 " mesh.points() -> mesh.vertices\n" 34 " (silence this with vedo.core.warnings['points_getter']=False)" 35 ), 36 points_setter=( 37 "WARNING: points() is deprecated, use vertices instead. Change:\n" 38 " mesh.points([[x,y,z], ...]) -> mesh.vertices = [[x,y,z], ...]\n" 39 " (silence this with vedo.core.warnings['points_getter']=False)" 40 ), 41) 42 43############################################################################### 44class DataArrayHelper: 45 # Internal use only. 46 # Helper class to manage data associated to either 47 # points (or vertices) and cells (or faces). 48 def __init__(self, obj, association): 49 50 self.obj = obj 51 self.association = association 52 53 def __getitem__(self, key): 54 55 if self.association == 0: 56 data = self.obj.dataset.GetPointData() 57 58 elif self.association == 1: 59 data = self.obj.dataset.GetCellData() 60 61 elif self.association == 2: 62 data = self.obj.dataset.GetFieldData() 63 64 varr = data.GetAbstractArray(key) 65 if isinstance(varr, vtki.vtkStringArray): 66 if isinstance(key, int): 67 key = data.GetArrayName(key) 68 n = varr.GetNumberOfValues() 69 narr = [varr.GetValue(i) for i in range(n)] 70 return narr 71 ########### 72 73 else: 74 raise RuntimeError() 75 76 if isinstance(key, int): 77 key = data.GetArrayName(key) 78 79 arr = data.GetArray(key) 80 if not arr: 81 return None 82 return utils.vtk2numpy(arr) 83 84 def __setitem__(self, key, input_array): 85 86 if self.association == 0: 87 data = self.obj.dataset.GetPointData() 88 n = self.obj.dataset.GetNumberOfPoints() 89 self.obj.mapper.SetScalarModeToUsePointData() 90 91 elif self.association == 1: 92 data = self.obj.dataset.GetCellData() 93 n = self.obj.dataset.GetNumberOfCells() 94 self.obj.mapper.SetScalarModeToUseCellData() 95 96 elif self.association == 2: 97 data = self.obj.dataset.GetFieldData() 98 if not utils.is_sequence(input_array): 99 input_array = [input_array] 100 101 if isinstance(input_array[0], str): 102 varr = vtki.vtkStringArray() 103 varr.SetName(key) 104 varr.SetNumberOfComponents(1) 105 varr.SetNumberOfTuples(len(input_array)) 106 for i, iarr in enumerate(input_array): 107 if isinstance(iarr, np.ndarray): 108 iarr = iarr.tolist() # better format 109 # Note: a string k can be converted to numpy with 110 # import json; k = np.array(json.loads(k)) 111 varr.InsertValue(i, str(iarr)) 112 else: 113 try: 114 varr = utils.numpy2vtk(input_array, name=key) 115 except TypeError as e: 116 vedo.logger.error( 117 f"cannot create metadata with input object:\n" 118 f"{input_array}" 119 f"\n\nAllowed content examples are:\n" 120 f"- flat list of strings ['a','b', 1, [1,2,3], ...]" 121 f" (first item must be a string in this case)\n" 122 f" hint: use k = np.array(json.loads(k)) to convert strings\n" 123 f"- numpy arrays of any shape" 124 ) 125 raise e 126 127 data.AddArray(varr) 128 return ############ 129 130 else: 131 raise RuntimeError() 132 133 if len(input_array) != n: 134 vedo.logger.error( 135 f"Error in point/cell data: length of input {len(input_array)}" 136 f" != {n} nr. of elements" 137 ) 138 raise RuntimeError() 139 140 input_array = np.asarray(input_array) 141 varr = utils.numpy2vtk(input_array, name=key) 142 data.AddArray(varr) 143 144 if len(input_array.shape) == 1: # scalars 145 data.SetActiveScalars(key) 146 try: # could be a volume mapper 147 self.obj.mapper.SetScalarRange(data.GetScalars().GetRange()) 148 except AttributeError: 149 pass 150 elif len(input_array.shape) == 2 and input_array.shape[1] == 3: # vectors 151 if key.lower() == "normals": 152 data.SetActiveNormals(key) 153 else: 154 data.SetActiveVectors(key) 155 156 def keys(self) -> List[str]: 157 """Return the list of available data array names""" 158 if self.association == 0: 159 data = self.obj.dataset.GetPointData() 160 elif self.association == 1: 161 data = self.obj.dataset.GetCellData() 162 elif self.association == 2: 163 data = self.obj.dataset.GetFieldData() 164 arrnames = [] 165 for i in range(data.GetNumberOfArrays()): 166 name = "" 167 if self.association == 2: 168 name = data.GetAbstractArray(i).GetName() 169 else: 170 iarr = data.GetArray(i) 171 if iarr: 172 name = iarr.GetName() 173 if name: 174 arrnames.append(name) 175 return arrnames 176 177 def items(self) -> List: 178 """Return the list of available data array `(names, values)`.""" 179 if self.association == 0: 180 data = self.obj.dataset.GetPointData() 181 elif self.association == 1: 182 data = self.obj.dataset.GetCellData() 183 elif self.association == 2: 184 data = self.obj.dataset.GetFieldData() 185 arrnames = [] 186 for i in range(data.GetNumberOfArrays()): 187 if self.association == 2: 188 name = data.GetAbstractArray(i).GetName() 189 else: 190 name = data.GetArray(i).GetName() 191 if name: 192 arrnames.append((name, self[name])) 193 return arrnames 194 195 def todict(self) -> dict: 196 """Return a dictionary of the available data arrays.""" 197 return dict(self.items()) 198 199 def rename(self, oldname: str, newname: str) -> None: 200 """Rename an array""" 201 if self.association == 0: 202 varr = self.obj.dataset.GetPointData().GetArray(oldname) 203 elif self.association == 1: 204 varr = self.obj.dataset.GetCellData().GetArray(oldname) 205 elif self.association == 2: 206 varr = self.obj.dataset.GetFieldData().GetAbstractArray(oldname) 207 if varr: 208 varr.SetName(newname) 209 else: 210 vedo.logger.warning( 211 f"Cannot rename non existing array {oldname} to {newname}" 212 ) 213 214 def remove(self, key: Union[int, str]) -> None: 215 """Remove a data array by name or number""" 216 if self.association == 0: 217 self.obj.dataset.GetPointData().RemoveArray(key) 218 elif self.association == 1: 219 self.obj.dataset.GetCellData().RemoveArray(key) 220 elif self.association == 2: 221 self.obj.dataset.GetFieldData().RemoveArray(key) 222 223 def clear(self) -> None: 224 """Remove all data associated to this object""" 225 if self.association == 0: 226 data = self.obj.dataset.GetPointData() 227 elif self.association == 1: 228 data = self.obj.dataset.GetCellData() 229 elif self.association == 2: 230 data = self.obj.dataset.GetFieldData() 231 for i in range(data.GetNumberOfArrays()): 232 if self.association == 2: 233 name = data.GetAbstractArray(i).GetName() 234 else: 235 name = data.GetArray(i).GetName() 236 data.RemoveArray(name) 237 238 def select(self, key: Union[int, str]) -> Any: 239 """Select one specific array by its name to make it the `active` one.""" 240 # Default (ColorModeToDefault): unsigned char scalars are treated as colors, 241 # and NOT mapped through the lookup table, while everything else is. 242 # ColorModeToDirectScalar extends ColorModeToDefault such that all integer 243 # types are treated as colors with values in the range 0-255 244 # and floating types are treated as colors with values in the range 0.0-1.0. 245 # Setting ColorModeToMapScalars means that all scalar data will be mapped 246 # through the lookup table. 247 # (Note that for multi-component scalars, the particular component 248 # to use for mapping can be specified using the SelectColorArray() method.) 249 if self.association == 0: 250 data = self.obj.dataset.GetPointData() 251 self.obj.mapper.SetScalarModeToUsePointData() 252 else: 253 data = self.obj.dataset.GetCellData() 254 self.obj.mapper.SetScalarModeToUseCellData() 255 256 if isinstance(key, int): 257 key = data.GetArrayName(key) 258 259 arr = data.GetArray(key) 260 if not arr: 261 return self.obj 262 263 nc = arr.GetNumberOfComponents() 264 # print("GetNumberOfComponents", nc) 265 if nc == 1: 266 data.SetActiveScalars(key) 267 elif nc == 2: 268 data.SetTCoords(arr) 269 elif nc in (3, 4): 270 if "rgb" in key.lower(): # type: ignore 271 data.SetActiveScalars(key) 272 try: 273 # could be a volume mapper 274 self.obj.mapper.SetColorModeToDirectScalars() 275 data.SetActiveVectors(None) # need this to fix bug in #1066 276 # print("SetColorModeToDirectScalars for", key) 277 except AttributeError: 278 pass 279 else: 280 data.SetActiveVectors(key) 281 elif nc == 9: 282 data.SetActiveTensors(key) 283 else: 284 vedo.logger.error(f"Cannot select array {key} with {nc} components") 285 return self.obj 286 287 try: 288 # could be a volume mapper 289 self.obj.mapper.SetArrayName(key) 290 self.obj.mapper.ScalarVisibilityOn() 291 except AttributeError: 292 pass 293 294 return self.obj 295 296 def select_texture_coords(self, key: Union[int,str]) -> Any: 297 """Select one specific array to be used as texture coordinates.""" 298 if self.association == 0: 299 data = self.obj.dataset.GetPointData() 300 else: 301 vedo.logger.warning("texture coordinates are only available for point data") 302 return 303 304 if isinstance(key, int): 305 key = data.GetArrayName(key) 306 data.SetTCoords(data.GetArray(key)) 307 return self.obj 308 309 def select_normals(self, key: Union[int,str]) -> Any: 310 """Select one specific normal array by its name to make it the "active" one.""" 311 if self.association == 0: 312 data = self.obj.dataset.GetPointData() 313 self.obj.mapper.SetScalarModeToUsePointData() 314 else: 315 data = self.obj.dataset.GetCellData() 316 self.obj.mapper.SetScalarModeToUseCellData() 317 318 if isinstance(key, int): 319 key = data.GetArrayName(key) 320 data.SetActiveNormals(key) 321 return self.obj 322 323 def print(self, **kwargs) -> None: 324 """Print the array names available to terminal""" 325 colors.printc(self.keys(), **kwargs) 326 327 def __repr__(self) -> str: 328 """Representation""" 329 330 def _get_str(pd, header): 331 out = f"\x1b[2m\x1b[1m\x1b[7m{header}" 332 if pd.GetNumberOfArrays(): 333 if self.obj.name: 334 out += f" in {self.obj.name}" 335 out += f" contains {pd.GetNumberOfArrays()} array(s)\x1b[0m" 336 for i in range(pd.GetNumberOfArrays()): 337 varr = pd.GetArray(i) 338 out += f"\n\x1b[1m\x1b[4mArray name : {varr.GetName()}\x1b[0m" 339 out += "\nindex".ljust(15) + f": {i}" 340 t = varr.GetDataType() 341 if t in vtki.array_types: 342 out += "\ntype".ljust(15) 343 out += f": {vtki.array_types[t]}" 344 shape = (varr.GetNumberOfTuples(), varr.GetNumberOfComponents()) 345 out += "\nshape".ljust(15) + f": {shape}" 346 out += "\nrange".ljust(15) + f": {np.array(varr.GetRange())}" 347 out += "\nmax id".ljust(15) + f": {varr.GetMaxId()}" 348 out += "\nlook up table".ljust(15) + f": {bool(varr.GetLookupTable())}" 349 out += "\nin-memory size".ljust(15) + f": {varr.GetActualMemorySize()} KB" 350 else: 351 out += " is empty.\x1b[0m" 352 return out 353 354 if self.association == 0: 355 out = _get_str(self.obj.dataset.GetPointData(), "Point Data") 356 elif self.association == 1: 357 out = _get_str(self.obj.dataset.GetCellData(), "Cell Data") 358 elif self.association == 2: 359 pd = self.obj.dataset.GetFieldData() 360 if pd.GetNumberOfArrays(): 361 out = "\x1b[2m\x1b[1m\x1b[7mMeta Data" 362 if self.obj.name: 363 out += f" in {self.obj.name}" 364 out += f" contains {pd.GetNumberOfArrays()} entries\x1b[0m" 365 for i in range(pd.GetNumberOfArrays()): 366 varr = pd.GetAbstractArray(i) 367 out += f"\n\x1b[1m\x1b[4mEntry name : {varr.GetName()}\x1b[0m" 368 out += "\nindex".ljust(15) + f": {i}" 369 shape = (varr.GetNumberOfTuples(), varr.GetNumberOfComponents()) 370 out += "\nshape".ljust(15) + f": {shape}" 371 372 return out 373 374 375############################################################################### 376class CommonAlgorithms: 377 """Common algorithms.""" 378 379 @property 380 def pointdata(self): 381 """ 382 Create and/or return a `numpy.array` associated to points (vertices). 383 A data array can be indexed either as a string or by an integer number. 384 E.g.: `myobj.pointdata["arrayname"]` 385 386 Usage: 387 388 `myobj.pointdata.keys()` to return the available data array names 389 390 `myobj.pointdata.select(name)` to make this array the active one 391 392 `myobj.pointdata.remove(name)` to remove this array 393 """ 394 return DataArrayHelper(self, 0) 395 396 @property 397 def celldata(self): 398 """ 399 Create and/or return a `numpy.array` associated to cells (faces). 400 A data array can be indexed either as a string or by an integer number. 401 E.g.: `myobj.celldata["arrayname"]` 402 403 Usage: 404 405 `myobj.celldata.keys()` to return the available data array names 406 407 `myobj.celldata.select(name)` to make this array the active one 408 409 `myobj.celldata.remove(name)` to remove this array 410 """ 411 return DataArrayHelper(self, 1) 412 413 @property 414 def metadata(self): 415 """ 416 Create and/or return a `numpy.array` associated to neither cells nor faces. 417 A data array can be indexed either as a string or by an integer number. 418 E.g.: `myobj.metadata["arrayname"]` 419 420 Usage: 421 422 `myobj.metadata.keys()` to return the available data array names 423 424 `myobj.metadata.select(name)` to make this array the active one 425 426 `myobj.metadata.remove(name)` to remove this array 427 """ 428 return DataArrayHelper(self, 2) 429 430 def memory_address(self) -> int: 431 """ 432 Return a unique memory address integer which may serve as the ID of the 433 object, or passed to c++ code. 434 """ 435 # https://www.linkedin.com/pulse/speedup-your-code-accessing-python-vtk-objects-from-c-pletzer/ 436 # https://github.com/tfmoraes/polydata_connectivity 437 return int(self.dataset.GetAddressAsString("")[5:], 16) 438 439 def memory_size(self) -> int: 440 """Return the size in bytes of the object in memory.""" 441 return self.dataset.GetActualMemorySize() 442 443 def modified(self) -> Self: 444 """Use in conjunction with `tonumpy()` to update any modifications to the image array.""" 445 self.dataset.GetPointData().Modified() 446 scals = self.dataset.GetPointData().GetScalars() 447 if scals: 448 scals.Modified() 449 return self 450 451 def box(self, scale=1, padding=0) -> "vedo.Mesh": 452 """ 453 Return the bounding box as a new `Mesh` object. 454 455 Arguments: 456 scale : (float) 457 box size can be scaled by a factor 458 padding : (float, list) 459 a constant padding can be added (can be a list `[padx,pady,padz]`) 460 """ 461 b = self.bounds() 462 if not utils.is_sequence(padding): 463 padding = [padding, padding, padding] 464 length, width, height = b[1] - b[0], b[3] - b[2], b[5] - b[4] 465 tol = (length + width + height) / 30000 # useful for boxing text 466 pos = [(b[0] + b[1]) / 2, (b[3] + b[2]) / 2, (b[5] + b[4]) / 2 - tol] 467 bx = vedo.shapes.Box( 468 pos, 469 length * scale + padding[0], 470 width * scale + padding[1], 471 height * scale + padding[2], 472 c="gray", 473 ) 474 try: 475 pr = vtki.vtkProperty() 476 pr.DeepCopy(self.properties) 477 bx.actor.SetProperty(pr) 478 bx.properties = pr 479 except (AttributeError, TypeError): 480 pass 481 bx.flat().lighting("off").wireframe(True) 482 return bx 483 484 def update_dataset(self, dataset, **kwargs) -> Self: 485 """Update the dataset of the object with the provided VTK dataset.""" 486 self._update(dataset, **kwargs) 487 return self 488 489 def bounds(self) -> np.ndarray: 490 """ 491 Get the object bounds. 492 Returns a list in format `[xmin,xmax, ymin,ymax, zmin,zmax]`. 493 """ 494 try: # this is very slow for large meshes 495 pts = self.vertices 496 xmin, ymin, zmin = np.min(pts, axis=0) 497 xmax, ymax, zmax = np.max(pts, axis=0) 498 return np.array([xmin, xmax, ymin, ymax, zmin, zmax]) 499 except (AttributeError, ValueError): 500 return np.array(self.dataset.GetBounds()) 501 502 def xbounds(self, i=None) -> np.ndarray: 503 """Get the bounds `[xmin,xmax]`. Can specify upper or lower with i (0,1).""" 504 b = self.bounds() 505 if i is not None: 506 return b[i] 507 return np.array([b[0], b[1]]) 508 509 def ybounds(self, i=None) -> np.ndarray: 510 """Get the bounds `[ymin,ymax]`. Can specify upper or lower with i (0,1).""" 511 b = self.bounds() 512 if i == 0: 513 return b[2] 514 if i == 1: 515 return b[3] 516 return np.array([b[2], b[3]]) 517 518 def zbounds(self, i=None) -> np.ndarray: 519 """Get the bounds `[zmin,zmax]`. Can specify upper or lower with i (0,1).""" 520 b = self.bounds() 521 if i == 0: 522 return b[4] 523 if i == 1: 524 return b[5] 525 return np.array([b[4], b[5]]) 526 527 def diagonal_size(self) -> float: 528 """Get the length of the diagonal of the bounding box.""" 529 b = self.bounds() 530 return np.sqrt((b[1] - b[0])**2 + (b[3] - b[2])**2 + (b[5] - b[4])**2) 531 532 def average_size(self) -> float: 533 """ 534 Calculate and return the average size of the object. 535 This is the mean of the vertex distances from the center of mass. 536 """ 537 coords = self.vertices 538 cm = np.mean(coords, axis=0) 539 if coords.shape[0] == 0: 540 return 0.0 541 cc = coords - cm 542 return np.mean(np.linalg.norm(cc, axis=1)) 543 544 def center_of_mass(self) -> np.ndarray: 545 """Get the center of mass of the object.""" 546 if isinstance(self, (vedo.RectilinearGrid, vedo.Volume)): 547 return np.array(self.dataset.GetCenter()) 548 cmf = vtki.new("CenterOfMass") 549 cmf.SetInputData(self.dataset) 550 cmf.Update() 551 c = cmf.GetCenter() 552 return np.array(c) 553 554 def copy_data_from(self, obj: Any) -> Self: 555 """Copy all data (point and cell data) from this input object""" 556 self.dataset.GetPointData().PassData(obj.dataset.GetPointData()) 557 self.dataset.GetCellData().PassData(obj.dataset.GetCellData()) 558 self.pipeline = utils.OperationNode( 559 "copy_data_from", 560 parents=[self, obj], 561 comment=f"{obj.__class__.__name__}", 562 shape="note", 563 c="#ccc5b9", 564 ) 565 return self 566 567 def inputdata(self): 568 """Obsolete, use `.dataset` instead.""" 569 colors.printc("WARNING: 'inputdata()' is obsolete, use '.dataset' instead.", c="y") 570 return self.dataset 571 572 @property 573 def npoints(self): 574 """Retrieve the number of points (or vertices).""" 575 return self.dataset.GetNumberOfPoints() 576 577 @property 578 def nvertices(self): 579 """Retrieve the number of vertices (or points).""" 580 return self.dataset.GetNumberOfPoints() 581 582 @property 583 def ncells(self): 584 """Retrieve the number of cells.""" 585 return self.dataset.GetNumberOfCells() 586 587 def points(self, pts=None): 588 """Obsolete, use `self.vertices` or `self.coordinates` instead.""" 589 if pts is None: ### getter 590 591 if warnings["points_getter"]: 592 colors.printc(warnings["points_getter"], c="y") 593 warnings["points_getter"] = "" 594 return self.vertices 595 596 else: ### setter 597 598 if warnings["points_setter"]: 599 colors.printc(warnings["points_setter"], c="y") 600 warnings["points_setter"] = "" 601 602 pts = np.asarray(pts, dtype=np.float32) 603 604 if pts.ndim == 1: 605 ### getter by point index ################### 606 indices = pts.astype(int) 607 vpts = self.dataset.GetPoints() 608 arr = utils.vtk2numpy(vpts.GetData()) 609 return arr[indices] ########### 610 611 ### setter #################################### 612 if pts.shape[1] == 2: 613 pts = np.c_[pts, np.zeros(pts.shape[0], dtype=np.float32)] 614 arr = utils.numpy2vtk(pts, dtype=np.float32) 615 616 vpts = self.dataset.GetPoints() 617 vpts.SetData(arr) 618 vpts.Modified() 619 # reset mesh to identity matrix position/rotation: 620 self.point_locator = None 621 self.cell_locator = None 622 self.line_locator = None 623 self.transform = LinearTransform() 624 return self 625 626 @property 627 def cell_centers(self): 628 """ 629 Get the coordinates of the cell centers. 630 631 Examples: 632 - [delaunay2d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/delaunay2d.py) 633 634 See also: `CellCenters()`. 635 """ 636 vcen = vtki.new("CellCenters") 637 vcen.CopyArraysOff() 638 vcen.SetInputData(self.dataset) 639 vcen.Update() 640 return utils.vtk2numpy(vcen.GetOutput().GetPoints().GetData()) 641 642 @property 643 def lines(self): 644 """ 645 Get lines connectivity ids as a python array 646 formatted as `[[id0,id1], [id3,id4], ...]` 647 648 See also: `lines_as_flat_array()`. 649 """ 650 # Get cell connettivity ids as a 1D array. The vtk format is: 651 # [nids1, id0 ... idn, niids2, id0 ... idm, etc]. 652 try: 653 arr1d = utils.vtk2numpy(self.dataset.GetLines().GetData()) 654 except AttributeError: 655 return np.array([]) 656 i = 0 657 conn = [] 658 n = len(arr1d) 659 for _ in range(n): 660 cell = [arr1d[i + k + 1] for k in range(arr1d[i])] 661 conn.append(cell) 662 i += arr1d[i] + 1 663 if i >= n: 664 break 665 666 return conn # cannot always make a numpy array of it! 667 668 @property 669 def lines_as_flat_array(self): 670 """ 671 Get lines connectivity ids as a 1D numpy array. 672 Format is e.g. [2, 10,20, 3, 10,11,12, 2, 70,80, ...] 673 674 See also: `lines()`. 675 """ 676 try: 677 return utils.vtk2numpy(self.dataset.GetLines().GetData()) 678 except AttributeError: 679 return np.array([]) 680 681 def mark_boundaries(self) -> Self: 682 """ 683 Mark cells and vertices if they lie on a boundary. 684 A new array called `BoundaryCells` is added to the object. 685 """ 686 mb = vtki.new("MarkBoundaryFilter") 687 mb.SetInputData(self.dataset) 688 mb.Update() 689 self.dataset.DeepCopy(mb.GetOutput()) 690 self.pipeline = utils.OperationNode("mark_boundaries", parents=[self]) 691 return self 692 693 def find_cells_in_bounds(self, xbounds=(), ybounds=(), zbounds=()) -> np.ndarray: 694 """ 695 Find cells that are within the specified bounds. 696 """ 697 try: 698 xbounds = list(xbounds.bounds()) 699 except AttributeError: 700 pass 701 702 if len(xbounds) == 6: 703 bnds = xbounds 704 else: 705 bnds = list(self.bounds()) 706 if len(xbounds) == 2: 707 bnds[0] = xbounds[0] 708 bnds[1] = xbounds[1] 709 if len(ybounds) == 2: 710 bnds[2] = ybounds[0] 711 bnds[3] = ybounds[1] 712 if len(zbounds) == 2: 713 bnds[4] = zbounds[0] 714 bnds[5] = zbounds[1] 715 716 cell_ids = vtki.vtkIdList() 717 if not self.cell_locator: 718 self.cell_locator = vtki.new("CellTreeLocator") 719 self.cell_locator.SetDataSet(self.dataset) 720 self.cell_locator.BuildLocator() 721 self.cell_locator.FindCellsWithinBounds(bnds, cell_ids) 722 cids = [] 723 for i in range(cell_ids.GetNumberOfIds()): 724 cid = cell_ids.GetId(i) 725 cids.append(cid) 726 return np.array(cids) 727 728 def find_cells_along_line(self, p0, p1, tol=0.001) -> np.ndarray: 729 """ 730 Find cells that are intersected by a line segment. 731 """ 732 cell_ids = vtki.vtkIdList() 733 if not self.cell_locator: 734 self.cell_locator = vtki.new("CellTreeLocator") 735 self.cell_locator.SetDataSet(self.dataset) 736 self.cell_locator.BuildLocator() 737 self.cell_locator.FindCellsAlongLine(p0, p1, tol, cell_ids) 738 cids = [] 739 for i in range(cell_ids.GetNumberOfIds()): 740 cid = cell_ids.GetId(i) 741 cids.append(cid) 742 return np.array(cids) 743 744 def find_cells_along_plane(self, origin, normal, tol=0.001) -> np.ndarray: 745 """ 746 Find cells that are intersected by a plane. 747 """ 748 cell_ids = vtki.vtkIdList() 749 if not self.cell_locator: 750 self.cell_locator = vtki.new("CellTreeLocator") 751 self.cell_locator.SetDataSet(self.dataset) 752 self.cell_locator.BuildLocator() 753 self.cell_locator.FindCellsAlongPlane(origin, normal, tol, cell_ids) 754 cids = [] 755 for i in range(cell_ids.GetNumberOfIds()): 756 cid = cell_ids.GetId(i) 757 cids.append(cid) 758 return np.array(cids) 759 760 def keep_cell_types(self, types=()): 761 """ 762 Extract cells of a specific type. 763 764 Check the VTK cell types here: 765 https://vtk.org/doc/nightly/html/vtkCellType_8h.html 766 """ 767 fe = vtki.new("ExtractCellsByType") 768 fe.SetInputData(self.dataset) 769 for t in types: 770 try: 771 if utils.is_integer(t): 772 it = t 773 else: 774 it = vtki.cell_types[t.upper()] 775 except KeyError: 776 vedo.logger.error(f"Cell type '{t}' not recognized") 777 continue 778 fe.AddCellType(it) 779 fe.Update() 780 self._update(fe.GetOutput()) 781 return self 782 783 def map_cells_to_points(self, arrays=(), move=False) -> Self: 784 """ 785 Interpolate cell data (i.e., data specified per cell or face) 786 into point data (i.e., data specified at each vertex). 787 The method of transformation is based on averaging the data values 788 of all cells using a particular point. 789 790 A custom list of arrays to be mapped can be passed in input. 791 792 Set `move=True` to delete the original `celldata` array. 793 """ 794 c2p = vtki.new("CellDataToPointData") 795 c2p.SetInputData(self.dataset) 796 if not move: 797 c2p.PassCellDataOn() 798 if arrays: 799 c2p.ClearCellDataArrays() 800 c2p.ProcessAllArraysOff() 801 for arr in arrays: 802 c2p.AddCellDataArray(arr) 803 else: 804 c2p.ProcessAllArraysOn() 805 c2p.Update() 806 self._update(c2p.GetOutput(), reset_locators=False) 807 self.mapper.SetScalarModeToUsePointData() 808 self.pipeline = utils.OperationNode("map_cells_to_points", parents=[self]) 809 return self 810 811 @property 812 def vertices(self): 813 """Return the vertices (points) coordinates.""" 814 try: 815 # for polydata and unstructured grid 816 varr = self.dataset.GetPoints().GetData() 817 except (AttributeError, TypeError): 818 try: 819 # for RectilinearGrid, StructuredGrid 820 vpts = vtki.vtkPoints() 821 self.dataset.GetPoints(vpts) 822 varr = vpts.GetData() 823 except (AttributeError, TypeError): 824 try: 825 # for ImageData 826 v2p = vtki.new("ImageToPoints") 827 v2p.SetInputData(self.dataset) 828 v2p.Update() 829 varr = v2p.GetOutput().GetPoints().GetData() 830 except AttributeError: 831 return np.array([]) 832 833 return utils.vtk2numpy(varr) 834 835 # setter 836 @vertices.setter 837 def vertices(self, pts): 838 """Set vertices (points) coordinates.""" 839 pts = utils.make3d(pts) 840 arr = utils.numpy2vtk(pts, dtype=np.float32) 841 try: 842 vpts = self.dataset.GetPoints() 843 vpts.SetData(arr) 844 vpts.Modified() 845 except (AttributeError, TypeError): 846 vedo.logger.error(f"Cannot set vertices for {type(self)}") 847 return self 848 # reset mesh to identity matrix position/rotation: 849 self.point_locator = None 850 self.cell_locator = None 851 self.line_locator = None 852 self.transform = LinearTransform() 853 854 @property 855 def coordinates(self): 856 """Return the vertices (points) coordinates. Same as `vertices`.""" 857 return self.vertices 858 859 @coordinates.setter 860 def coordinates(self, pts): 861 """Set vertices (points) coordinates. Same as `vertices`.""" 862 self.vertices = pts 863 864 @property 865 def cells_as_flat_array(self): 866 """ 867 Get cell connectivity ids as a 1D numpy array. 868 Format is e.g. [3, 10,20,30 4, 10,11,12,13 ...] 869 """ 870 try: 871 # valid for unstructured grid 872 arr1d = utils.vtk2numpy(self.dataset.GetCells().GetData()) 873 except AttributeError: 874 # valid for polydata 875 arr1d = utils.vtk2numpy(self.dataset.GetPolys().GetData()) 876 return arr1d 877 878 @property 879 def cells(self): 880 """ 881 Get the cells connectivity ids as a numpy array. 882 883 The output format is: `[[id0 ... idn], [id0 ... idm], etc]`. 884 """ 885 try: 886 # valid for unstructured grid 887 arr1d = utils.vtk2numpy(self.dataset.GetCells().GetData()) 888 except AttributeError: 889 try: 890 # valid for polydata 891 arr1d = utils.vtk2numpy(self.dataset.GetPolys().GetData()) 892 except AttributeError: 893 vedo.logger.warning(f"Cannot get cells for {type(self)}") 894 return np.array([]) 895 896 # Get cell connettivity ids as a 1D array. vtk format is: 897 # [nids1, id0 ... idn, niids2, id0 ... idm, etc]. 898 i = 0 899 conn = [] 900 n = len(arr1d) 901 if n: 902 while True: 903 cell = [arr1d[i + k] for k in range(1, arr1d[i] + 1)] 904 conn.append(cell) 905 i += arr1d[i] + 1 906 if i >= n: 907 break 908 return conn 909 910 def cell_edge_neighbors(self): 911 """ 912 Get the cell neighbor indices of each cell. 913 914 Returns a python list of lists. 915 """ 916 917 def face_to_edges(face): 918 edges = [] 919 size = len(face) 920 for i in range(1, size + 1): 921 if i == size: 922 edges.append([face[i - 1], face[0]]) 923 else: 924 edges.append([face[i - 1], face[i]]) 925 return edges 926 927 pd = self.dataset 928 pd.BuildLinks() 929 930 neicells = [] 931 for i, cell in enumerate(self.cells): 932 nn = [] 933 for edge in face_to_edges(cell): 934 neighbors = vtki.vtkIdList() 935 pd.GetCellEdgeNeighbors(i, edge[0], edge[1], neighbors) 936 if neighbors.GetNumberOfIds() > 0: 937 neighbor = neighbors.GetId(0) 938 nn.append(neighbor) 939 neicells.append(nn) 940 941 return neicells 942 943 944 def map_points_to_cells(self, arrays=(), move=False) -> Self: 945 """ 946 Interpolate point data (i.e., data specified per point or vertex) 947 into cell data (i.e., data specified per cell). 948 The method of transformation is based on averaging the data values 949 of all points defining a particular cell. 950 951 A custom list of arrays to be mapped can be passed in input. 952 953 Set `move=True` to delete the original `pointdata` array. 954 955 Examples: 956 - [mesh_map2cell.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_map2cell.py) 957 """ 958 p2c = vtki.new("PointDataToCellData") 959 p2c.SetInputData(self.dataset) 960 if not move: 961 p2c.PassPointDataOn() 962 if arrays: 963 p2c.ClearPointDataArrays() 964 p2c.ProcessAllArraysOff() 965 for arr in arrays: 966 p2c.AddPointDataArray(arr) 967 else: 968 p2c.ProcessAllArraysOn() 969 p2c.Update() 970 self._update(p2c.GetOutput(), reset_locators=False) 971 self.mapper.SetScalarModeToUseCellData() 972 self.pipeline = utils.OperationNode("map_points_to_cells", parents=[self]) 973 return self 974 975 def resample_data_from(self, source, tol=None, categorical=False) -> Self: 976 """ 977 Resample point and cell data from another dataset. 978 The output has the same structure but its point data have 979 the resampled values from target. 980 981 Use `tol` to set the tolerance used to compute whether 982 a point in the source is in a cell of the current object. 983 Points without resampled values, and their cells, are marked as blank. 984 If the data is categorical, then the resulting data will be determined 985 by a nearest neighbor interpolation scheme. 986 987 Example: 988 ```python 989 from vedo import * 990 m1 = Mesh(dataurl+'bunny.obj')#.add_gaussian_noise(0.1) 991 pts = m1.vertices 992 ces = m1.cell_centers 993 m1.pointdata["xvalues"] = np.power(pts[:,0], 3) 994 m1.celldata["yvalues"] = np.power(ces[:,1], 3) 995 m2 = Mesh(dataurl+'bunny.obj') 996 m2.resample_data_from(m1) 997 # print(m2.pointdata["xvalues"]) 998 show(m1, m2 , N=2, axes=1) 999 ``` 1000 """ 1001 rs = vtki.new("ResampleWithDataSet") 1002 rs.SetInputData(self.dataset) 1003 rs.SetSourceData(source.dataset) 1004 1005 rs.SetPassPointArrays(True) 1006 rs.SetPassCellArrays(True) 1007 rs.SetPassFieldArrays(True) 1008 rs.SetCategoricalData(categorical) 1009 1010 rs.SetComputeTolerance(True) 1011 if tol: 1012 rs.SetComputeTolerance(False) 1013 rs.SetTolerance(tol) 1014 rs.Update() 1015 self._update(rs.GetOutput(), reset_locators=False) 1016 self.pipeline = utils.OperationNode( 1017 "resample_data_from", 1018 comment=f"{source.__class__.__name__}", 1019 parents=[self, source], 1020 ) 1021 return self 1022 1023 def interpolate_data_from( 1024 self, 1025 source, 1026 radius=None, 1027 n=None, 1028 kernel="shepard", 1029 exclude=("Normals",), 1030 on="points", 1031 null_strategy=1, 1032 null_value=0, 1033 ) -> Self: 1034 """ 1035 Interpolate over source to port its data onto the current object using various kernels. 1036 1037 If n (number of closest points to use) is set then radius value is ignored. 1038 1039 Check out also: 1040 `probe()` which in many cases can be faster. 1041 1042 Arguments: 1043 kernel : (str) 1044 available kernels are [shepard, gaussian, linear] 1045 null_strategy : (int) 1046 specify a strategy to use when encountering a "null" point 1047 during the interpolation process. Null points occur when the local neighborhood 1048 (of nearby points to interpolate from) is empty. 1049 1050 - Case 0: an output array is created that marks points 1051 as being valid (=1) or null (invalid =0), and the null_value is set as well 1052 - Case 1: the output data value(s) are set to the provided null_value 1053 - Case 2: simply use the closest point to perform the interpolation. 1054 null_value : (float) 1055 see above. 1056 1057 Examples: 1058 - [interpolate_scalar1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolate_scalar1.py) 1059 - [interpolate_scalar3.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolate_scalar3.py) 1060 - [interpolate_scalar4.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolate_scalar4.py) 1061 - [image_probe.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/image_probe.py) 1062 1063 ![](https://vedo.embl.es/images/advanced/interpolateMeshArray.png) 1064 """ 1065 if radius is None and not n: 1066 vedo.logger.error("in interpolate_data_from(): please set either radius or n") 1067 raise RuntimeError 1068 1069 if on == "points": 1070 points = source.dataset 1071 elif on == "cells": 1072 c2p = vtki.new("CellDataToPointData") 1073 c2p.SetInputData(source.dataset) 1074 c2p.Update() 1075 points = c2p.GetOutput() 1076 else: 1077 vedo.logger.error("in interpolate_data_from(), on must be on points or cells") 1078 raise RuntimeError() 1079 1080 locator = vtki.new("PointLocator") 1081 locator.SetDataSet(points) 1082 locator.BuildLocator() 1083 1084 if kernel.lower() == "shepard": 1085 kern = vtki.new("ShepardKernel") 1086 kern.SetPowerParameter(2) 1087 elif kernel.lower() == "gaussian": 1088 kern = vtki.new("GaussianKernel") 1089 kern.SetSharpness(2) 1090 elif kernel.lower() == "linear": 1091 kern = vtki.new("LinearKernel") 1092 else: 1093 vedo.logger.error("available kernels are: [shepard, gaussian, linear]") 1094 raise RuntimeError() 1095 1096 if n: 1097 kern.SetNumberOfPoints(n) 1098 kern.SetKernelFootprintToNClosest() 1099 else: 1100 kern.SetRadius(radius) 1101 kern.SetKernelFootprintToRadius() 1102 1103 interpolator = vtki.new("PointInterpolator") 1104 interpolator.SetInputData(self.dataset) 1105 interpolator.SetSourceData(points) 1106 interpolator.SetKernel(kern) 1107 interpolator.SetLocator(locator) 1108 interpolator.PassFieldArraysOn() 1109 interpolator.SetNullPointsStrategy(null_strategy) 1110 interpolator.SetNullValue(null_value) 1111 interpolator.SetValidPointsMaskArrayName("ValidPointMask") 1112 for ex in exclude: 1113 interpolator.AddExcludedArray(ex) 1114 1115 # remove arrays that are already present in the source 1116 # this is because the interpolator will ignore them otherwise 1117 for i in range(self.dataset.GetPointData().GetNumberOfArrays()): 1118 name = self.dataset.GetPointData().GetArrayName(i) 1119 if name not in exclude: 1120 self.dataset.GetPointData().RemoveArray(name) 1121 1122 interpolator.Update() 1123 1124 if on == "cells": 1125 p2c = vtki.new("PointDataToCellData") 1126 p2c.SetInputData(interpolator.GetOutput()) 1127 p2c.Update() 1128 cpoly = p2c.GetOutput() 1129 else: 1130 cpoly = interpolator.GetOutput() 1131 1132 self._update(cpoly, reset_locators=False) 1133 1134 self.pipeline = utils.OperationNode("interpolate_data_from", parents=[self, source]) 1135 return self 1136 1137 def add_ids(self) -> Self: 1138 """ 1139 Generate point and cell ids arrays. 1140 1141 Two new arrays are added to the mesh: `PointID` and `CellID`. 1142 """ 1143 ids = vtki.new("IdFilter") 1144 ids.SetInputData(self.dataset) 1145 ids.PointIdsOn() 1146 ids.CellIdsOn() 1147 ids.FieldDataOff() 1148 ids.SetPointIdsArrayName("PointID") 1149 ids.SetCellIdsArrayName("CellID") 1150 ids.Update() 1151 self._update(ids.GetOutput(), reset_locators=False) 1152 self.pipeline = utils.OperationNode("add_ids", parents=[self]) 1153 return self 1154 1155 def gradient(self, input_array=None, on="points", fast=False) -> np.ndarray: 1156 """ 1157 Compute and return the gradiend of the active scalar field as a numpy array. 1158 1159 Arguments: 1160 input_array : (str) 1161 array of the scalars to compute the gradient, 1162 if None the current active array is selected 1163 on : (str) 1164 compute either on 'points' or 'cells' data 1165 fast : (bool) 1166 if True, will use a less accurate algorithm 1167 that performs fewer derivative calculations (and is therefore faster). 1168 1169 Examples: 1170 - [isolines.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/isolines.py) 1171 1172 ![](https://user-images.githubusercontent.com/32848391/72433087-f00a8780-3798-11ea-9778-991f0abeca70.png) 1173 """ 1174 gra = vtki.new("GradientFilter") 1175 if on.startswith("p"): 1176 varr = self.dataset.GetPointData() 1177 tp = vtki.vtkDataObject.FIELD_ASSOCIATION_POINTS 1178 elif on.startswith("c"): 1179 varr = self.dataset.GetCellData() 1180 tp = vtki.vtkDataObject.FIELD_ASSOCIATION_CELLS 1181 else: 1182 vedo.logger.error(f"in gradient: unknown option {on}") 1183 raise RuntimeError 1184 1185 if input_array is None: 1186 if varr.GetScalars(): 1187 input_array = varr.GetScalars().GetName() 1188 else: 1189 vedo.logger.error(f"in gradient: no scalars found for {on}") 1190 raise RuntimeError 1191 1192 gra.SetInputData(self.dataset) 1193 gra.SetInputScalars(tp, input_array) 1194 gra.SetResultArrayName("Gradient") 1195 gra.SetFasterApproximation(fast) 1196 gra.ComputeDivergenceOff() 1197 gra.ComputeVorticityOff() 1198 gra.ComputeGradientOn() 1199 gra.Update() 1200 # self._update(gra.GetOutput(), reset_locators=False) 1201 if on.startswith("p"): 1202 gvecs = utils.vtk2numpy(gra.GetOutput().GetPointData().GetArray("Gradient")) 1203 else: 1204 gvecs = utils.vtk2numpy(gra.GetOutput().GetCellData().GetArray("Gradient")) 1205 return gvecs 1206 1207 def divergence(self, array_name=None, on="points", fast=False) -> np.ndarray: 1208 """ 1209 Compute and return the divergence of a vector field as a numpy array. 1210 1211 Arguments: 1212 array_name : (str) 1213 name of the array of vectors to compute the divergence, 1214 if None the current active array is selected 1215 on : (str) 1216 compute either on 'points' or 'cells' data 1217 fast : (bool) 1218 if True, will use a less accurate algorithm 1219 that performs fewer derivative calculations (and is therefore faster). 1220 """ 1221 div = vtki.new("GradientFilter") 1222 if on.startswith("p"): 1223 varr = self.dataset.GetPointData() 1224 tp = vtki.vtkDataObject.FIELD_ASSOCIATION_POINTS 1225 elif on.startswith("c"): 1226 varr = self.dataset.GetCellData() 1227 tp = vtki.vtkDataObject.FIELD_ASSOCIATION_CELLS 1228 else: 1229 vedo.logger.error(f"in divergence(): unknown option {on}") 1230 raise RuntimeError 1231 1232 if array_name is None: 1233 if varr.GetVectors(): 1234 array_name = varr.GetVectors().GetName() 1235 else: 1236 vedo.logger.error(f"in divergence(): no vectors found for {on}") 1237 raise RuntimeError 1238 1239 div.SetInputData(self.dataset) 1240 div.SetInputScalars(tp, array_name) 1241 div.ComputeDivergenceOn() 1242 div.ComputeGradientOff() 1243 div.ComputeVorticityOff() 1244 div.SetDivergenceArrayName("Divergence") 1245 div.SetFasterApproximation(fast) 1246 div.Update() 1247 # self._update(div.GetOutput(), reset_locators=False) 1248 if on.startswith("p"): 1249 dvecs = utils.vtk2numpy(div.GetOutput().GetPointData().GetArray("Divergence")) 1250 else: 1251 dvecs = utils.vtk2numpy(div.GetOutput().GetCellData().GetArray("Divergence")) 1252 return dvecs 1253 1254 def vorticity(self, array_name=None, on="points", fast=False) -> np.ndarray: 1255 """ 1256 Compute and return the vorticity of a vector field as a numpy array. 1257 1258 Arguments: 1259 array_name : (str) 1260 name of the array to compute the vorticity, 1261 if None the current active array is selected 1262 on : (str) 1263 compute either on 'points' or 'cells' data 1264 fast : (bool) 1265 if True, will use a less accurate algorithm 1266 that performs fewer derivative calculations (and is therefore faster). 1267 """ 1268 vort = vtki.new("GradientFilter") 1269 if on.startswith("p"): 1270 varr = self.dataset.GetPointData() 1271 tp = vtki.vtkDataObject.FIELD_ASSOCIATION_POINTS 1272 elif on.startswith("c"): 1273 varr = self.dataset.GetCellData() 1274 tp = vtki.vtkDataObject.FIELD_ASSOCIATION_CELLS 1275 else: 1276 vedo.logger.error(f"in vorticity(): unknown option {on}") 1277 raise RuntimeError 1278 1279 if array_name is None: 1280 if varr.GetVectors(): 1281 array_name = varr.GetVectors().GetName() 1282 else: 1283 vedo.logger.error(f"in vorticity(): no vectors found for {on}") 1284 raise RuntimeError 1285 1286 vort.SetInputData(self.dataset) 1287 vort.SetInputScalars(tp, array_name) 1288 vort.ComputeDivergenceOff() 1289 vort.ComputeGradientOff() 1290 vort.ComputeVorticityOn() 1291 vort.SetVorticityArrayName("Vorticity") 1292 vort.SetFasterApproximation(fast) 1293 vort.Update() 1294 if on.startswith("p"): 1295 vvecs = utils.vtk2numpy(vort.GetOutput().GetPointData().GetArray("Vorticity")) 1296 else: 1297 vvecs = utils.vtk2numpy(vort.GetOutput().GetCellData().GetArray("Vorticity")) 1298 return vvecs 1299 1300 def probe( 1301 self, 1302 source, 1303 categorical=False, 1304 snap=False, 1305 tol=0, 1306 ) -> Self: 1307 """ 1308 Takes a data set and probes its scalars at the specified points in space. 1309 1310 Note that a mask is also output with valid/invalid points which can be accessed 1311 with `mesh.pointdata['ValidPointMask']`. 1312 1313 Arguments: 1314 source : any dataset 1315 the data set to probe. 1316 categorical : bool 1317 control whether the source pointdata is to be treated as categorical. 1318 snap : bool 1319 snap to the cell with the closest point if no cell was found 1320 tol : float 1321 the tolerance to use when performing the probe. 1322 1323 Check out also: 1324 `interpolate_data_from()` and `tovolume()` 1325 1326 Examples: 1327 - [probe_points.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/probe_points.py) 1328 1329 ![](https://vedo.embl.es/images/volumetric/probePoints.png) 1330 """ 1331 probe_filter = vtki.new("ProbeFilter") 1332 probe_filter.SetSourceData(source.dataset) 1333 probe_filter.SetInputData(self.dataset) 1334 probe_filter.PassCellArraysOn() 1335 probe_filter.PassFieldArraysOn() 1336 probe_filter.PassPointArraysOn() 1337 probe_filter.SetCategoricalData(categorical) 1338 probe_filter.ComputeToleranceOff() 1339 if tol: 1340 probe_filter.ComputeToleranceOn() 1341 probe_filter.SetTolerance(tol) 1342 probe_filter.SetSnapToCellWithClosestPoint(snap) 1343 probe_filter.Update() 1344 self._update(probe_filter.GetOutput(), reset_locators=False) 1345 self.pipeline = utils.OperationNode("probe", parents=[self, source]) 1346 self.pointdata.rename("vtkValidPointMask", "ValidPointMask") 1347 return self 1348 1349 def compute_cell_size(self) -> Self: 1350 """ 1351 Add to this object a cell data array 1352 containing the area, volume and edge length 1353 of the cells (when applicable to the object type). 1354 1355 Array names are: `Area`, `Volume`, `Length`. 1356 """ 1357 csf = vtki.new("CellSizeFilter") 1358 csf.SetInputData(self.dataset) 1359 csf.SetComputeArea(1) 1360 csf.SetComputeVolume(1) 1361 csf.SetComputeLength(1) 1362 csf.SetComputeVertexCount(0) 1363 csf.SetAreaArrayName("Area") 1364 csf.SetVolumeArrayName("Volume") 1365 csf.SetLengthArrayName("Length") 1366 csf.Update() 1367 self._update(csf.GetOutput(), reset_locators=False) 1368 return self 1369 1370 def generate_random_data(self) -> Self: 1371 """Fill a dataset with random attributes""" 1372 gen = vtki.new("RandomAttributeGenerator") 1373 gen.SetInputData(self.dataset) 1374 gen.GenerateAllDataOn() 1375 gen.SetDataTypeToFloat() 1376 gen.GeneratePointNormalsOff() 1377 gen.GeneratePointTensorsOn() 1378 gen.GenerateCellScalarsOn() 1379 gen.Update() 1380 self._update(gen.GetOutput(), reset_locators=False) 1381 self.pipeline = utils.OperationNode("generate_random_data", parents=[self]) 1382 return self 1383 1384 def integrate_data(self) -> dict: 1385 """ 1386 Integrate point and cell data arrays while computing length, 1387 area or volume of the domain. It works for 1D, 2D or 3D cells. 1388 1389 For volumetric datasets, this filter ignores all but 3D cells. 1390 It will not compute the volume contained in a closed surface. 1391 1392 Returns a dictionary with keys: `pointdata`, `celldata`, `metadata`, 1393 which contain the integration result for the corresponding attributes. 1394 1395 Examples: 1396 ```python 1397 from vedo import * 1398 surf = Sphere(res=100) 1399 surf.pointdata['scalars'] = np.ones(surf.npoints) 1400 data = surf.integrate_data() 1401 print(data['pointdata']['scalars'], "is equal to 4pi", 4*np.pi) 1402 ``` 1403 1404 ```python 1405 from vedo import * 1406 1407 xcoords1 = np.arange(0, 2.2, 0.2) 1408 xcoords2 = sqrt(np.arange(0, 4.2, 0.2)) 1409 1410 ycoords = np.arange(0, 1.2, 0.2) 1411 1412 surf1 = Grid(s=(xcoords1, ycoords)).rotate_y(-45).lw(2) 1413 surf2 = Grid(s=(xcoords2, ycoords)).rotate_y(-45).lw(2) 1414 1415 surf1.pointdata['scalars'] = surf1.vertices[:,2] 1416 surf2.pointdata['scalars'] = surf2.vertices[:,2] 1417 1418 data1 = surf1.integrate_data() 1419 data2 = surf2.integrate_data() 1420 1421 print(data1['pointdata']['scalars'], 1422 "is equal to", 1423 data2['pointdata']['scalars'], 1424 "even if the grids are different!", 1425 "(= the volume under the surface)" 1426 ) 1427 show(surf1, surf2, N=2, axes=1).close() 1428 ``` 1429 """ 1430 vinteg = vtki.new("IntegrateAttributes") 1431 vinteg.SetInputData(self.dataset) 1432 vinteg.Update() 1433 ugrid = vedo.UnstructuredGrid(vinteg.GetOutput()) 1434 data = dict( 1435 pointdata=ugrid.pointdata.todict(), 1436 celldata=ugrid.celldata.todict(), 1437 metadata=ugrid.metadata.todict(), 1438 ) 1439 return data 1440 1441 def write(self, filename, binary=True) -> None: 1442 """Write object to file.""" 1443 out = vedo.file_io.write(self, filename, binary) 1444 out.pipeline = utils.OperationNode( 1445 "write", parents=[self], comment=filename[:15], shape="folder", c="#8a817c" 1446 ) 1447 1448 def tomesh(self, bounds=(), shrink=0) -> "vedo.Mesh": 1449 """ 1450 Extract boundary geometry from dataset (or convert data to polygonal type). 1451 1452 Two new arrays are added to the mesh: `OriginalCellIds` and `OriginalPointIds` 1453 to keep track of the original mesh elements. 1454 1455 Arguments: 1456 bounds : (list) 1457 specify a sub-region to extract 1458 shrink : (float) 1459 shrink the cells to a fraction of their original size 1460 """ 1461 geo = vtki.new("GeometryFilter") 1462 1463 if shrink: 1464 sf = vtki.new("ShrinkFilter") 1465 sf.SetInputData(self.dataset) 1466 sf.SetShrinkFactor(shrink) 1467 sf.Update() 1468 geo.SetInputData(sf.GetOutput()) 1469 else: 1470 geo.SetInputData(self.dataset) 1471 1472 geo.SetPassThroughCellIds(1) 1473 geo.SetPassThroughPointIds(1) 1474 geo.SetOriginalCellIdsName("OriginalCellIds") 1475 geo.SetOriginalPointIdsName("OriginalPointIds") 1476 geo.SetNonlinearSubdivisionLevel(1) 1477 # geo.MergingOff() # crashes on StructuredGrids 1478 if bounds: 1479 geo.SetExtent(bounds) 1480 geo.ExtentClippingOn() 1481 geo.Update() 1482 msh = vedo.mesh.Mesh(geo.GetOutput()) 1483 msh.pipeline = utils.OperationNode("tomesh", parents=[self], c="#9e2a2b") 1484 return msh 1485 1486 def signed_distance(self, dims=(20, 20, 20), bounds=None, invert=False, max_radius=None) -> "vedo.Volume": 1487 """ 1488 Compute the `Volume` object whose voxels contains the signed distance from 1489 the object. The calling object must have "Normals" defined. 1490 1491 Arguments: 1492 bounds : (list, actor) 1493 bounding box sizes 1494 dims : (list) 1495 dimensions (nr. of voxels) of the output volume. 1496 invert : (bool) 1497 flip the sign 1498 max_radius : (float) 1499 specify how far out to propagate distance calculation 1500 1501 Examples: 1502 - [distance2mesh.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/distance2mesh.py) 1503 1504 ![](https://vedo.embl.es/images/basic/distance2mesh.png) 1505 """ 1506 if bounds is None: 1507 bounds = self.bounds() 1508 if max_radius is None: 1509 max_radius = self.diagonal_size() / 2 1510 dist = vtki.new("SignedDistance") 1511 dist.SetInputData(self.dataset) 1512 dist.SetRadius(max_radius) 1513 dist.SetBounds(bounds) 1514 dist.SetDimensions(dims) 1515 dist.Update() 1516 img = dist.GetOutput() 1517 if invert: 1518 mat = vtki.new("ImageMathematics") 1519 mat.SetInput1Data(img) 1520 mat.SetOperationToMultiplyByK() 1521 mat.SetConstantK(-1) 1522 mat.Update() 1523 img = mat.GetOutput() 1524 1525 vol = vedo.Volume(img) 1526 vol.name = "SignedDistanceVolume" 1527 vol.pipeline = utils.OperationNode( 1528 "signed_distance", 1529 parents=[self], 1530 comment=f"dims={tuple(vol.dimensions())}", 1531 c="#e9c46a:#0096c7", 1532 ) 1533 return vol 1534 1535 def unsigned_distance( 1536 self, dims=(25,25,25), bounds=(), max_radius=0, cap_value=0) -> "vedo.Volume": 1537 """ 1538 Compute the `Volume` object whose voxels contains the unsigned distance. 1539 """ 1540 dist = vtki.new("UnsignedDistance") 1541 dist.SetInputData(self.dataset) 1542 dist.SetDimensions(dims) 1543 1544 if len(bounds) == 6: 1545 dist.SetBounds(bounds) 1546 # elif bounds == "auto": 1547 # dist.AdjustBoundsOn() 1548 else: 1549 dist.SetBounds(self.bounds()) 1550 if not max_radius: 1551 max_radius = self.diagonal_size() / 10 1552 dist.SetRadius(max_radius) 1553 1554 if self.point_locator: 1555 dist.SetLocator(self.point_locator) 1556 1557 if cap_value is not None: 1558 dist.CappingOn() 1559 dist.SetCapValue(cap_value) 1560 dist.SetOutputScalarTypeToFloat() 1561 dist.Update() 1562 vol = vedo.Volume(dist.GetOutput()) 1563 vol.name = "UnsignedDistanceVolume" 1564 vol.pipeline = utils.OperationNode( 1565 "unsigned_distance", parents=[self], c="#e9c46a:#0096c7") 1566 return vol 1567 1568 def smooth_data(self, 1569 niter=10, relaxation_factor=0.1, strategy=0, mask=None, 1570 mode="distance2", 1571 exclude=("Normals", "TextureCoordinates"), 1572 ) -> Self: 1573 """ 1574 Smooth point attribute data using distance weighted Laplacian kernel. 1575 The effect is to blur regions of high variation and emphasize low variation regions. 1576 1577 A central concept of this method is the point smoothing stencil. 1578 A smoothing stencil for a point p(i) is the list of points p(j) which connect to p(i) via an edge. 1579 To smooth the attributes of point p(i), p(i)'s attribute data a(i) are iteratively averaged using 1580 the distance weighted average of the attributes of a(j) (the weights w[j] sum to 1). 1581 This averaging process is repeated until the maximum number of iterations is reached. 1582 1583 The relaxation factor (R) is also important as the smoothing process proceeds in an iterative fashion. 1584 The a(i+1) attributes are determined from the a(i) attributes as follows: 1585 a(i+1) = (1-R)*a(i) + R*sum(w(j)*a(j)) 1586 1587 Convergence occurs faster for larger relaxation factors. 1588 Typically a small number of iterations is required for large relaxation factors, 1589 and in cases where only points adjacent to the boundary are being smoothed, a single iteration with R=1 may be 1590 adequate (i.e., just a distance weighted average is computed). 1591 1592 Warning: 1593 Certain data attributes cannot be correctly interpolated. For example, surface normals are expected to be |n|=1; 1594 after attribute smoothing this constraint is likely to be violated. 1595 Other vectors and tensors may suffer from similar issues. 1596 In such a situation, specify `exclude=...` which will not be smoothed (and simply passed through to the output). 1597 Distance weighting function is based on averaging, 1/r, or 1/r**2 weights, where r is the distance 1598 between the point to be smoothed and an edge connected neighbor (defined by the smoothing stencil). 1599 The weights are normalized so that sum(w(i))==1. When smoothing based on averaging, the weights are simply 1/n, 1600 where n is the number of connected points in the stencil. 1601 The smoothing process reduces high frequency information in the data attributes. 1602 With excessive smoothing (large numbers of iterations, and/or a large relaxation factor) important details may be lost, 1603 and the attributes will move towards an "average" value. 1604 While this filter will process any dataset type, if the input data is a 3D image volume, it's likely much faster to use 1605 an image-based algorithm to perform data smoothing. 1606 To determine boundary points in polygonal data, edges used by only one cell are considered boundary 1607 (and hence the associated points defining the edge). 1608 1609 Arguments: 1610 niter : (int) 1611 number of iterations 1612 relaxation_factor : (float) 1613 relaxation factor controlling the amount of Laplacian smoothing applied 1614 strategy : (int) 1615 strategy to use for Laplacian smoothing 1616 - 0: use all points, all point data attributes are smoothed 1617 - 1: smooth all point attribute data except those on the boundary 1618 - 2: only point data connected to a boundary point are smoothed 1619 mask : (str, np.ndarray) 1620 array to be used as a mask (ignore then the strategy keyword) 1621 mode : (str) 1622 smoothing mode, either "distance2", "distance" or "average" 1623 - distance**2 weighted (i.e., 1/r**2 interpolation weights) 1624 - distance weighted (i.e., 1/r) approach; 1625 - simple average of all connected points in the stencil 1626 exclude : (list) 1627 list of arrays to be excluded from smoothing 1628 """ 1629 try: 1630 saf = vtki.new("AttributeSmoothingFilter") 1631 except: 1632 vedo.logger.error("smooth_data() only avaialble in VTK>=9.3.0") 1633 return self 1634 saf.SetInputData(self.dataset) 1635 saf.SetRelaxationFactor(relaxation_factor) 1636 saf.SetNumberOfIterations(niter) 1637 1638 for ex in exclude: 1639 saf.AddExcludedArray(ex) 1640 1641 if mode == "distance": 1642 saf.SetWeightsTypeToDistance() 1643 elif mode == "distance2": 1644 saf.SetWeightsTypeToDistance2() 1645 elif mode == "average": 1646 saf.SetWeightsTypeToAverage() 1647 else: 1648 vedo.logger.error(f"smooth_data(): unknown mode {mode}") 1649 raise TypeError 1650 1651 saf.SetSmoothingStrategy(strategy) 1652 if mask is not None: 1653 saf.SetSmoothingStrategyToSmoothingMask() 1654 if isinstance(mask, str): 1655 mask_ = self.dataset.GetPointData().GetArray(mask) 1656 if not mask_: 1657 vedo.logger.error(f"smooth_data(): mask array {mask} not found") 1658 return self 1659 mask_array = vtki.vtkUnsignedCharArray() 1660 mask_array.ShallowCopy(mask_) 1661 mask_array.SetName(mask_.GetName()) 1662 else: 1663 mask_array = utils.numpy2vtk(mask, dtype=np.uint8) 1664 saf.SetSmoothingMask(mask_array) 1665 1666 saf.Update() 1667 1668 self._update(saf.GetOutput()) 1669 self.pipeline = utils.OperationNode( 1670 "smooth_data", comment=f"strategy {strategy}", parents=[self], c="#9e2a2b" 1671 ) 1672 return self 1673 1674 def compute_streamlines( 1675 self, 1676 seeds: Any, 1677 integrator="rk4", 1678 direction="forward", 1679 initial_step_size=None, 1680 max_propagation=None, 1681 max_steps=10000, 1682 step_length=0, 1683 surface_constrained=False, 1684 compute_vorticity=False, 1685 ) -> Union["vedo.Lines", None]: 1686 """ 1687 Integrate a vector field to generate streamlines. 1688 1689 Arguments: 1690 seeds : (Mesh, Points, list) 1691 starting points of the streamlines 1692 integrator : (str) 1693 type of integration method to be used: 1694 - "rk2" (Runge-Kutta 2) 1695 - "rk4" (Runge-Kutta 4) 1696 - "rk45" (Runge-Kutta 45) 1697 direction : (str) 1698 direction of integration, either "forward", "backward" or "both" 1699 initial_step_size : (float) 1700 initial step size used for line integration 1701 max_propagation : (float) 1702 maximum length of a streamline expressed in absolute units 1703 max_steps : (int) 1704 maximum number of steps for a streamline 1705 step_length : (float) 1706 maximum length of a step expressed in absolute units 1707 surface_constrained : (bool) 1708 whether to stop integrating when the streamline leaves the surface 1709 compute_vorticity : (bool) 1710 whether to compute the vorticity at each streamline point 1711 """ 1712 b = self.dataset.GetBounds() 1713 size = (b[5]-b[4] + b[3]-b[2] + b[1]-b[0]) / 3 1714 if initial_step_size is None: 1715 initial_step_size = size / 1000.0 1716 1717 if max_propagation is None: 1718 max_propagation = size * 2 1719 1720 if utils.is_sequence(seeds): 1721 seeds = vedo.Points(seeds) 1722 1723 sti = vtki.new("StreamTracer") 1724 sti.SetSourceData(seeds.dataset) 1725 if isinstance(self, vedo.RectilinearGrid): 1726 sti.SetInputData(vedo.UnstructuredGrid(self.dataset).dataset) 1727 else: 1728 sti.SetInputDataObject(self.dataset) 1729 1730 sti.SetInitialIntegrationStep(initial_step_size) 1731 sti.SetComputeVorticity(compute_vorticity) 1732 sti.SetMaximumNumberOfSteps(max_steps) 1733 sti.SetMaximumPropagation(max_propagation) 1734 sti.SetSurfaceStreamlines(surface_constrained) 1735 if step_length: 1736 sti.SetMaximumIntegrationStep(step_length) 1737 1738 if "for" in direction: 1739 sti.SetIntegrationDirectionToForward() 1740 elif "back" in direction: 1741 sti.SetIntegrationDirectionToBackward() 1742 elif "both" in direction: 1743 sti.SetIntegrationDirectionToBoth() 1744 else: 1745 vedo.logger.error(f"in compute_streamlines(), unknown direction {direction}") 1746 return None 1747 1748 if integrator == "rk2": 1749 sti.SetIntegratorTypeToRungeKutta2() 1750 elif integrator == "rk4": 1751 sti.SetIntegratorTypeToRungeKutta4() 1752 elif integrator == "rk45": 1753 sti.SetIntegratorTypeToRungeKutta45() 1754 else: 1755 vedo.logger.error(f"in compute_streamlines(), unknown integrator {integrator}") 1756 return None 1757 1758 sti.Update() 1759 1760 stlines = vedo.shapes.Lines(sti.GetOutput(), lw=4) 1761 stlines.name = "StreamLines" 1762 self.pipeline = utils.OperationNode( 1763 "compute_streamlines", comment=f"{integrator}", parents=[self, seeds], c="#9e2a2b" 1764 ) 1765 return stlines 1766 1767############################################################################### 1768class PointAlgorithms(CommonAlgorithms): 1769 """Methods for point clouds.""" 1770 1771 def apply_transform(self, LT: Any, deep_copy=True) -> Self: 1772 """ 1773 Apply a linear or non-linear transformation to the mesh polygonal data. 1774 1775 Example: 1776 ```python 1777 from vedo import Cube, show, settings 1778 settings.use_parallel_projection = True 1779 c1 = Cube().rotate_z(25).pos(2,1).mirror().alpha(0.5) 1780 T = c1.transform # rotate by 5 degrees, place at (2,1) 1781 c2 = Cube().c('red4').wireframe().lw(10).lighting('off') 1782 c2.apply_transform(T) 1783 show(c1, c2, "The 2 cubes should overlap!", axes=1).close() 1784 ``` 1785 1786 ![](https://vedo.embl.es/images/feats/apply_transform.png) 1787 """ 1788 if self.dataset.GetNumberOfPoints() == 0: 1789 return self 1790 1791 if isinstance(LT, LinearTransform): 1792 LT_is_linear = True 1793 tr = LT.T 1794 if LT.is_identity(): 1795 return self 1796 1797 elif isinstance(LT, (vtki.vtkMatrix4x4, vtki.vtkLinearTransform)) or utils.is_sequence(LT): 1798 LT_is_linear = True 1799 LT = LinearTransform(LT) 1800 tr = LT.T 1801 if LT.is_identity(): 1802 return self 1803 1804 elif isinstance(LT, NonLinearTransform): 1805 LT_is_linear = False 1806 tr = LT.T 1807 self.transform = LT # reset 1808 1809 elif isinstance(LT, vtki.vtkThinPlateSplineTransform): 1810 LT_is_linear = False 1811 tr = LT 1812 self.transform = NonLinearTransform(LT) # reset 1813 1814 else: 1815 vedo.logger.error(f"apply_transform(), unknown input type:\n{LT}") 1816 return self 1817 1818 ################ 1819 if LT_is_linear: 1820 try: 1821 # self.transform might still not be linear 1822 self.transform.concatenate(LT) 1823 except AttributeError: 1824 # in that case reset it 1825 self.transform = LinearTransform() 1826 1827 ################ 1828 if isinstance(self.dataset, vtki.vtkPolyData): 1829 tp = vtki.new("TransformPolyDataFilter") 1830 elif isinstance(self.dataset, vtki.vtkUnstructuredGrid): 1831 tp = vtki.new("TransformFilter") 1832 tp.TransformAllInputVectorsOn() 1833 # elif isinstance(self.dataset, vtki.vtkImageData): 1834 # tp = vtki.new("ImageReslice") 1835 # tp.SetInterpolationModeToCubic() 1836 # tp.SetResliceTransform(tr) 1837 else: 1838 vedo.logger.error(f"apply_transform(), unknown input type: {[self.dataset]}") 1839 return self 1840 1841 tp.SetTransform(tr) 1842 tp.SetInputData(self.dataset) 1843 tp.Update() 1844 out = tp.GetOutput() 1845 1846 if deep_copy: 1847 self.dataset.DeepCopy(out) 1848 else: 1849 self.dataset.ShallowCopy(out) 1850 1851 # reset the locators 1852 self.point_locator = None 1853 self.cell_locator = None 1854 self.line_locator = None 1855 return self 1856 1857 def apply_transform_from_actor(self) -> LinearTransform: 1858 """ 1859 Apply the current transformation of the actor to the data. 1860 Useful when manually moving an actor (eg. when pressing "a"). 1861 Returns the `LinearTransform` object. 1862 1863 Note that this method is automatically called when the window is closed, 1864 or the interactor style is changed. 1865 """ 1866 M = self.actor.GetMatrix() 1867 self.apply_transform(M) 1868 iden = vtki.vtkMatrix4x4() 1869 self.actor.PokeMatrix(iden) 1870 return LinearTransform(M) 1871 1872 def pos(self, x=None, y=None, z=None) -> Self: 1873 """Set/Get object position.""" 1874 if x is None: # get functionality 1875 return self.transform.position 1876 1877 if z is None and y is None: # assume x is of the form (x,y,z) 1878 if len(x) == 3: 1879 x, y, z = x 1880 else: 1881 x, y = x 1882 z = 0 1883 elif z is None: # assume x,y is of the form x, y 1884 z = 0 1885 1886 q = self.transform.position 1887 delta = [x, y, z] - q 1888 if delta[0] == delta[1] == delta[2] == 0: 1889 return self 1890 LT = LinearTransform().translate(delta) 1891 return self.apply_transform(LT) 1892 1893 def shift(self, dx=0, dy=0, dz=0) -> Self: 1894 """Add a vector to the current object position.""" 1895 if utils.is_sequence(dx): 1896 dx, dy, dz = utils.make3d(dx) 1897 if dx == dy == dz == 0: 1898 return self 1899 LT = LinearTransform().translate([dx, dy, dz]) 1900 return self.apply_transform(LT) 1901 1902 def x(self, val=None) -> Self: 1903 """Set/Get object position along x axis.""" 1904 p = self.transform.position 1905 if val is None: 1906 return p[0] 1907 self.pos(val, p[1], p[2]) 1908 return self 1909 1910 def y(self, val=None)-> Self: 1911 """Set/Get object position along y axis.""" 1912 p = self.transform.position 1913 if val is None: 1914 return p[1] 1915 self.pos(p[0], val, p[2]) 1916 return self 1917 1918 def z(self, val=None) -> Self: 1919 """Set/Get object position along z axis.""" 1920 p = self.transform.position 1921 if val is None: 1922 return p[2] 1923 self.pos(p[0], p[1], val) 1924 return self 1925 1926 def rotate(self, angle: float, axis=(1, 0, 0), point=(0, 0, 0), rad=False) -> Self: 1927 """ 1928 Rotate around an arbitrary `axis` passing through `point`. 1929 1930 Example: 1931 ```python 1932 from vedo import * 1933 c1 = Cube() 1934 c2 = c1.clone().c('violet').alpha(0.5) # copy of c1 1935 v = vector(0.2,1,0) 1936 p = vector(1,0,0) # axis passes through this point 1937 c2.rotate(90, axis=v, point=p) 1938 l = Line(-v+p, v+p).lw(3).c('red') 1939 show(c1, l, c2, axes=1).close() 1940 ``` 1941 1942 ![](https://vedo.embl.es/images/feats/rotate_axis.png) 1943 """ 1944 LT = LinearTransform() 1945 LT.rotate(angle, axis, point, rad) 1946 return self.apply_transform(LT) 1947 1948 def rotate_x(self, angle: float, rad=False, around=None) -> Self: 1949 """ 1950 Rotate around x-axis. If angle is in radians set `rad=True`. 1951 1952 Use `around` to define a pivoting point. 1953 """ 1954 if angle == 0: 1955 return self 1956 LT = LinearTransform().rotate_x(angle, rad, around) 1957 return self.apply_transform(LT) 1958 1959 def rotate_y(self, angle: float, rad=False, around=None) -> Self: 1960 """ 1961 Rotate around y-axis. If angle is in radians set `rad=True`. 1962 1963 Use `around` to define a pivoting point. 1964 """ 1965 if angle == 0: 1966 return self 1967 LT = LinearTransform().rotate_y(angle, rad, around) 1968 return self.apply_transform(LT) 1969 1970 def rotate_z(self, angle: float, rad=False, around=None) -> Self: 1971 """ 1972 Rotate around z-axis. If angle is in radians set `rad=True`. 1973 1974 Use `around` to define a pivoting point. 1975 """ 1976 if angle == 0: 1977 return self 1978 LT = LinearTransform().rotate_z(angle, rad, around) 1979 return self.apply_transform(LT) 1980 1981 def reorient(self, initaxis, newaxis, rotation=0, rad=False, xyplane=False) -> Self: 1982 """ 1983 Reorient the object to point to a new direction from an initial one. 1984 If `initaxis` is None, the object will be assumed in its "default" orientation. 1985 If `xyplane` is True, the object will be rotated to lie on the xy plane. 1986 1987 Use `rotation` to first rotate the object around its `initaxis`. 1988 """ 1989 q = self.transform.position 1990 LT = LinearTransform() 1991 LT.reorient(initaxis, newaxis, q, rotation, rad, xyplane) 1992 return self.apply_transform(LT) 1993 1994 def scale(self, s=None, reset=False, origin=True) -> Union[Self, np.array]: 1995 """ 1996 Set/get object's scaling factor. 1997 1998 Arguments: 1999 s : (list, float) 2000 scaling factor(s). 2001 reset : (bool) 2002 if True previous scaling factors are ignored. 2003 origin : (bool) 2004 if True scaling is applied with respect to object's position, 2005 otherwise is applied respect to (0,0,0). 2006 2007 Note: 2008 use `s=(sx,sy,sz)` to scale differently in the three coordinates. 2009 """ 2010 if s is None: 2011 return np.array(self.transform.T.GetScale()) 2012 2013 if not utils.is_sequence(s): 2014 s = [s, s, s] 2015 2016 LT = LinearTransform() 2017 if reset: 2018 old_s = np.array(self.transform.T.GetScale()) 2019 LT.scale(s / old_s) 2020 else: 2021 if origin is True: 2022 LT.scale(s, origin=self.transform.position) 2023 elif origin is False: 2024 LT.scale(s, origin=False) 2025 else: 2026 LT.scale(s, origin=origin) 2027 2028 return self.apply_transform(LT) 2029 2030 2031############################################################################### 2032class VolumeAlgorithms(CommonAlgorithms): 2033 """Methods for Volume objects.""" 2034 2035 def bounds(self) -> np.ndarray: 2036 """ 2037 Get the object bounds. 2038 Returns a list in format `[xmin,xmax, ymin,ymax, zmin,zmax]`. 2039 """ 2040 # OVERRIDE CommonAlgorithms.bounds() which is too slow 2041 return np.array(self.dataset.GetBounds()) 2042 2043 def isosurface(self, value=None, flying_edges=False) -> "vedo.mesh.Mesh": 2044 """ 2045 Return an `Mesh` isosurface extracted from the `Volume` object. 2046 2047 Set `value` as single float or list of values to draw the isosurface(s). 2048 Use flying_edges for faster results (but sometimes can interfere with `smooth()`). 2049 2050 Examples: 2051 - [isosurfaces1.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/isosurfaces1.py) 2052 2053 ![](https://vedo.embl.es/images/volumetric/isosurfaces.png) 2054 """ 2055 scrange = self.dataset.GetScalarRange() 2056 2057 if flying_edges: 2058 cf = vtki.new("FlyingEdges3D") 2059 cf.InterpolateAttributesOn() 2060 else: 2061 cf = vtki.new("ContourFilter") 2062 cf.UseScalarTreeOn() 2063 2064 cf.SetInputData(self.dataset) 2065 cf.ComputeNormalsOn() 2066 2067 if utils.is_sequence(value): 2068 cf.SetNumberOfContours(len(value)) 2069 for i, t in enumerate(value): 2070 cf.SetValue(i, t) 2071 else: 2072 if value is None: 2073 value = (2 * scrange[0] + scrange[1]) / 3.0 2074 # print("automatic isosurface value =", value) 2075 cf.SetValue(0, value) 2076 2077 cf.Update() 2078 poly = cf.GetOutput() 2079 2080 out = vedo.mesh.Mesh(poly, c=None).phong() 2081 out.mapper.SetScalarRange(scrange[0], scrange[1]) 2082 2083 out.pipeline = utils.OperationNode( 2084 "isosurface", 2085 parents=[self], 2086 comment=f"#pts {out.dataset.GetNumberOfPoints()}", 2087 c="#4cc9f0:#e9c46a", 2088 ) 2089 return out 2090 2091 def isosurface_discrete( 2092 self, values, background_label=None, internal_boundaries=True, use_quads=False, nsmooth=0, 2093 ) -> "vedo.mesh.Mesh": 2094 """ 2095 Create boundary/isocontour surfaces from a label map (e.g., a segmented image) using a threaded, 2096 3D version of the multiple objects/labels Surface Nets algorithm. 2097 The input is a 3D image (i.e., volume) where each voxel is labeled 2098 (integer labels are preferred to real values), and the output data is a polygonal mesh separating 2099 labeled regions / objects. 2100 (Note that on output each region [corresponding to a different segmented object] will share 2101 points/edges on a common boundary, i.e., two neighboring objects will share the boundary that separates them). 2102 2103 Besides output geometry defining the surface net, the filter outputs a two-component celldata array indicating 2104 the labels on either side of the polygons composing the output Mesh. 2105 (This can be used for advanced operations like extracting shared/contacting boundaries between two objects. 2106 The name of this celldata array is "BoundaryLabels"). 2107 2108 Arguments: 2109 value : (float, list) 2110 single value or list of values to draw the isosurface(s). 2111 background_label : (float) 2112 this value specifies the label value to use when referencing the background 2113 region outside of any of the specified regions. 2114 boundaries : (bool, list) 2115 if True, the output will only contain the boundary surface. Internal surfaces will be removed. 2116 If a list of integers is provided, only the boundaries between the specified labels will be extracted. 2117 use_quads : (bool) 2118 if True, the output polygons will be quads. If False, the output polygons will be triangles. 2119 nsmooth : (int) 2120 number of iterations of smoothing (0 means no smoothing). 2121 2122 Examples: 2123 - [isosurfaces2.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/isosurfaces2.py) 2124 """ 2125 logger = vtki.get_class("Logger") 2126 logger.SetStderrVerbosity(logger.VERBOSITY_ERROR) 2127 2128 snets = vtki.new("SurfaceNets3D") 2129 snets.SetInputData(self.dataset) 2130 2131 if nsmooth: 2132 snets.SmoothingOn() 2133 snets.AutomaticSmoothingConstraintsOn() 2134 snets.GetSmoother().SetNumberOfIterations(nsmooth) 2135 # snets.GetSmoother().SetRelaxationFactor(relaxation_factor) 2136 # snets.GetSmoother().SetConstraintDistance(constraint_distance) 2137 else: 2138 snets.SmoothingOff() 2139 2140 if internal_boundaries is False: 2141 snets.SetOutputStyleToBoundary() 2142 elif internal_boundaries is True: 2143 snets.SetOutputStyleToDefault() 2144 elif utils.is_sequence(internal_boundaries): 2145 snets.SetOutputStyleToSelected() 2146 snets.InitializeSelectedLabelsList() 2147 for val in internal_boundaries: 2148 snets.AddSelectedLabel(val) 2149 else: 2150 vedo.logger.error("isosurface_discrete(): unknown boundaries option") 2151 2152 n = len(values) 2153 snets.SetNumberOfContours(n) 2154 snets.SetNumberOfLabels(n) 2155 2156 if background_label is not None: 2157 snets.SetBackgroundLabel(background_label) 2158 2159 for i, val in enumerate(values): 2160 snets.SetValue(i, val) 2161 2162 if use_quads: 2163 snets.SetOutputMeshTypeToQuads() 2164 else: 2165 snets.SetOutputMeshTypeToTriangles() 2166 snets.Update() 2167 2168 out = vedo.mesh.Mesh(snets.GetOutput()) 2169 out.pipeline = utils.OperationNode( 2170 "isosurface_discrete", 2171 parents=[self], 2172 comment=f"#pts {out.dataset.GetNumberOfPoints()}", 2173 c="#4cc9f0:#e9c46a", 2174 ) 2175 2176 logger.SetStderrVerbosity(logger.VERBOSITY_INFO) 2177 return out 2178 2179 2180 def legosurface( 2181 self, 2182 vmin=None, 2183 vmax=None, 2184 invert=False, 2185 boundary=True, 2186 array_name="input_scalars", 2187 ) -> "vedo.mesh.Mesh": 2188 """ 2189 Represent an object - typically a `Volume` - as lego blocks (voxels). 2190 By default colors correspond to the volume's scalar. 2191 Returns an `Mesh` object. 2192 2193 Arguments: 2194 vmin : (float) 2195 the lower threshold, voxels below this value are not shown. 2196 vmax : (float) 2197 the upper threshold, voxels above this value are not shown. 2198 boundary : (bool) 2199 controls whether to include cells that are partially inside 2200 array_name : (int, str) 2201 name or index of the scalar array to be considered 2202 2203 Examples: 2204 - [legosurface.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/legosurface.py) 2205 2206 ![](https://vedo.embl.es/images/volumetric/56820682-da40e500-684c-11e9-8ea3-91cbcba24b3a.png) 2207 """ 2208 imp_dataset = vtki.new("ImplicitDataSet") 2209 imp_dataset.SetDataSet(self.dataset) 2210 window = vtki.new("ImplicitWindowFunction") 2211 window.SetImplicitFunction(imp_dataset) 2212 2213 srng = list(self.dataset.GetScalarRange()) 2214 if vmin is not None: 2215 srng[0] = vmin 2216 if vmax is not None: 2217 srng[1] = vmax 2218 if not boundary: 2219 tol = 0.00001 * (srng[1] - srng[0]) 2220 srng[0] -= tol 2221 srng[1] += tol 2222 window.SetWindowRange(srng) 2223 # print("legosurface window range:", srng) 2224 2225 extract = vtki.new("ExtractGeometry") 2226 extract.SetInputData(self.dataset) 2227 extract.SetImplicitFunction(window) 2228 extract.SetExtractInside(invert) 2229 extract.SetExtractBoundaryCells(boundary) 2230 extract.Update() 2231 2232 gf = vtki.new("GeometryFilter") 2233 gf.SetInputData(extract.GetOutput()) 2234 gf.Update() 2235 2236 m = vedo.mesh.Mesh(gf.GetOutput()).lw(0.1).flat() 2237 m.map_points_to_cells() 2238 m.celldata.select(array_name) 2239 2240 m.pipeline = utils.OperationNode( 2241 "legosurface", 2242 parents=[self], 2243 comment=f"array: {array_name}", 2244 c="#4cc9f0:#e9c46a", 2245 ) 2246 return m 2247 2248 def tomesh(self, fill=True, shrink=1.0) -> "vedo.mesh.Mesh": 2249 """ 2250 Build a polygonal Mesh from the current object. 2251 2252 If `fill=True`, the interior faces of all the cells are created. 2253 (setting a `shrink` value slightly smaller than the default 1.0 2254 can avoid flickering due to internal adjacent faces). 2255 2256 If `fill=False`, only the boundary faces will be generated. 2257 """ 2258 gf = vtki.new("GeometryFilter") 2259 if fill: 2260 sf = vtki.new("ShrinkFilter") 2261 sf.SetInputData(self.dataset) 2262 sf.SetShrinkFactor(shrink) 2263 sf.Update() 2264 gf.SetInputData(sf.GetOutput()) 2265 gf.Update() 2266 poly = gf.GetOutput() 2267 if shrink == 1.0: 2268 clean_poly = vtki.new("CleanPolyData") 2269 clean_poly.PointMergingOn() 2270 clean_poly.ConvertLinesToPointsOn() 2271 clean_poly.ConvertPolysToLinesOn() 2272 clean_poly.ConvertStripsToPolysOn() 2273 clean_poly.SetInputData(poly) 2274 clean_poly.Update() 2275 poly = clean_poly.GetOutput() 2276 else: 2277 gf.SetInputData(self.dataset) 2278 gf.Update() 2279 poly = gf.GetOutput() 2280 2281 msh = vedo.mesh.Mesh(poly).flat() 2282 msh.scalarbar = self.scalarbar 2283 lut = utils.ctf2lut(self) 2284 if lut: 2285 msh.mapper.SetLookupTable(lut) 2286 2287 msh.pipeline = utils.OperationNode( 2288 "tomesh", parents=[self], comment=f"fill={fill}", c="#9e2a2b:#e9c46a" 2289 ) 2290 return msh
class
DataArrayHelper:
45class DataArrayHelper: 46 # Internal use only. 47 # Helper class to manage data associated to either 48 # points (or vertices) and cells (or faces). 49 def __init__(self, obj, association): 50 51 self.obj = obj 52 self.association = association 53 54 def __getitem__(self, key): 55 56 if self.association == 0: 57 data = self.obj.dataset.GetPointData() 58 59 elif self.association == 1: 60 data = self.obj.dataset.GetCellData() 61 62 elif self.association == 2: 63 data = self.obj.dataset.GetFieldData() 64 65 varr = data.GetAbstractArray(key) 66 if isinstance(varr, vtki.vtkStringArray): 67 if isinstance(key, int): 68 key = data.GetArrayName(key) 69 n = varr.GetNumberOfValues() 70 narr = [varr.GetValue(i) for i in range(n)] 71 return narr 72 ########### 73 74 else: 75 raise RuntimeError() 76 77 if isinstance(key, int): 78 key = data.GetArrayName(key) 79 80 arr = data.GetArray(key) 81 if not arr: 82 return None 83 return utils.vtk2numpy(arr) 84 85 def __setitem__(self, key, input_array): 86 87 if self.association == 0: 88 data = self.obj.dataset.GetPointData() 89 n = self.obj.dataset.GetNumberOfPoints() 90 self.obj.mapper.SetScalarModeToUsePointData() 91 92 elif self.association == 1: 93 data = self.obj.dataset.GetCellData() 94 n = self.obj.dataset.GetNumberOfCells() 95 self.obj.mapper.SetScalarModeToUseCellData() 96 97 elif self.association == 2: 98 data = self.obj.dataset.GetFieldData() 99 if not utils.is_sequence(input_array): 100 input_array = [input_array] 101 102 if isinstance(input_array[0], str): 103 varr = vtki.vtkStringArray() 104 varr.SetName(key) 105 varr.SetNumberOfComponents(1) 106 varr.SetNumberOfTuples(len(input_array)) 107 for i, iarr in enumerate(input_array): 108 if isinstance(iarr, np.ndarray): 109 iarr = iarr.tolist() # better format 110 # Note: a string k can be converted to numpy with 111 # import json; k = np.array(json.loads(k)) 112 varr.InsertValue(i, str(iarr)) 113 else: 114 try: 115 varr = utils.numpy2vtk(input_array, name=key) 116 except TypeError as e: 117 vedo.logger.error( 118 f"cannot create metadata with input object:\n" 119 f"{input_array}" 120 f"\n\nAllowed content examples are:\n" 121 f"- flat list of strings ['a','b', 1, [1,2,3], ...]" 122 f" (first item must be a string in this case)\n" 123 f" hint: use k = np.array(json.loads(k)) to convert strings\n" 124 f"- numpy arrays of any shape" 125 ) 126 raise e 127 128 data.AddArray(varr) 129 return ############ 130 131 else: 132 raise RuntimeError() 133 134 if len(input_array) != n: 135 vedo.logger.error( 136 f"Error in point/cell data: length of input {len(input_array)}" 137 f" != {n} nr. of elements" 138 ) 139 raise RuntimeError() 140 141 input_array = np.asarray(input_array) 142 varr = utils.numpy2vtk(input_array, name=key) 143 data.AddArray(varr) 144 145 if len(input_array.shape) == 1: # scalars 146 data.SetActiveScalars(key) 147 try: # could be a volume mapper 148 self.obj.mapper.SetScalarRange(data.GetScalars().GetRange()) 149 except AttributeError: 150 pass 151 elif len(input_array.shape) == 2 and input_array.shape[1] == 3: # vectors 152 if key.lower() == "normals": 153 data.SetActiveNormals(key) 154 else: 155 data.SetActiveVectors(key) 156 157 def keys(self) -> List[str]: 158 """Return the list of available data array names""" 159 if self.association == 0: 160 data = self.obj.dataset.GetPointData() 161 elif self.association == 1: 162 data = self.obj.dataset.GetCellData() 163 elif self.association == 2: 164 data = self.obj.dataset.GetFieldData() 165 arrnames = [] 166 for i in range(data.GetNumberOfArrays()): 167 name = "" 168 if self.association == 2: 169 name = data.GetAbstractArray(i).GetName() 170 else: 171 iarr = data.GetArray(i) 172 if iarr: 173 name = iarr.GetName() 174 if name: 175 arrnames.append(name) 176 return arrnames 177 178 def items(self) -> List: 179 """Return the list of available data array `(names, values)`.""" 180 if self.association == 0: 181 data = self.obj.dataset.GetPointData() 182 elif self.association == 1: 183 data = self.obj.dataset.GetCellData() 184 elif self.association == 2: 185 data = self.obj.dataset.GetFieldData() 186 arrnames = [] 187 for i in range(data.GetNumberOfArrays()): 188 if self.association == 2: 189 name = data.GetAbstractArray(i).GetName() 190 else: 191 name = data.GetArray(i).GetName() 192 if name: 193 arrnames.append((name, self[name])) 194 return arrnames 195 196 def todict(self) -> dict: 197 """Return a dictionary of the available data arrays.""" 198 return dict(self.items()) 199 200 def rename(self, oldname: str, newname: str) -> None: 201 """Rename an array""" 202 if self.association == 0: 203 varr = self.obj.dataset.GetPointData().GetArray(oldname) 204 elif self.association == 1: 205 varr = self.obj.dataset.GetCellData().GetArray(oldname) 206 elif self.association == 2: 207 varr = self.obj.dataset.GetFieldData().GetAbstractArray(oldname) 208 if varr: 209 varr.SetName(newname) 210 else: 211 vedo.logger.warning( 212 f"Cannot rename non existing array {oldname} to {newname}" 213 ) 214 215 def remove(self, key: Union[int, str]) -> None: 216 """Remove a data array by name or number""" 217 if self.association == 0: 218 self.obj.dataset.GetPointData().RemoveArray(key) 219 elif self.association == 1: 220 self.obj.dataset.GetCellData().RemoveArray(key) 221 elif self.association == 2: 222 self.obj.dataset.GetFieldData().RemoveArray(key) 223 224 def clear(self) -> None: 225 """Remove all data associated to this object""" 226 if self.association == 0: 227 data = self.obj.dataset.GetPointData() 228 elif self.association == 1: 229 data = self.obj.dataset.GetCellData() 230 elif self.association == 2: 231 data = self.obj.dataset.GetFieldData() 232 for i in range(data.GetNumberOfArrays()): 233 if self.association == 2: 234 name = data.GetAbstractArray(i).GetName() 235 else: 236 name = data.GetArray(i).GetName() 237 data.RemoveArray(name) 238 239 def select(self, key: Union[int, str]) -> Any: 240 """Select one specific array by its name to make it the `active` one.""" 241 # Default (ColorModeToDefault): unsigned char scalars are treated as colors, 242 # and NOT mapped through the lookup table, while everything else is. 243 # ColorModeToDirectScalar extends ColorModeToDefault such that all integer 244 # types are treated as colors with values in the range 0-255 245 # and floating types are treated as colors with values in the range 0.0-1.0. 246 # Setting ColorModeToMapScalars means that all scalar data will be mapped 247 # through the lookup table. 248 # (Note that for multi-component scalars, the particular component 249 # to use for mapping can be specified using the SelectColorArray() method.) 250 if self.association == 0: 251 data = self.obj.dataset.GetPointData() 252 self.obj.mapper.SetScalarModeToUsePointData() 253 else: 254 data = self.obj.dataset.GetCellData() 255 self.obj.mapper.SetScalarModeToUseCellData() 256 257 if isinstance(key, int): 258 key = data.GetArrayName(key) 259 260 arr = data.GetArray(key) 261 if not arr: 262 return self.obj 263 264 nc = arr.GetNumberOfComponents() 265 # print("GetNumberOfComponents", nc) 266 if nc == 1: 267 data.SetActiveScalars(key) 268 elif nc == 2: 269 data.SetTCoords(arr) 270 elif nc in (3, 4): 271 if "rgb" in key.lower(): # type: ignore 272 data.SetActiveScalars(key) 273 try: 274 # could be a volume mapper 275 self.obj.mapper.SetColorModeToDirectScalars() 276 data.SetActiveVectors(None) # need this to fix bug in #1066 277 # print("SetColorModeToDirectScalars for", key) 278 except AttributeError: 279 pass 280 else: 281 data.SetActiveVectors(key) 282 elif nc == 9: 283 data.SetActiveTensors(key) 284 else: 285 vedo.logger.error(f"Cannot select array {key} with {nc} components") 286 return self.obj 287 288 try: 289 # could be a volume mapper 290 self.obj.mapper.SetArrayName(key) 291 self.obj.mapper.ScalarVisibilityOn() 292 except AttributeError: 293 pass 294 295 return self.obj 296 297 def select_texture_coords(self, key: Union[int,str]) -> Any: 298 """Select one specific array to be used as texture coordinates.""" 299 if self.association == 0: 300 data = self.obj.dataset.GetPointData() 301 else: 302 vedo.logger.warning("texture coordinates are only available for point data") 303 return 304 305 if isinstance(key, int): 306 key = data.GetArrayName(key) 307 data.SetTCoords(data.GetArray(key)) 308 return self.obj 309 310 def select_normals(self, key: Union[int,str]) -> Any: 311 """Select one specific normal array by its name to make it the "active" one.""" 312 if self.association == 0: 313 data = self.obj.dataset.GetPointData() 314 self.obj.mapper.SetScalarModeToUsePointData() 315 else: 316 data = self.obj.dataset.GetCellData() 317 self.obj.mapper.SetScalarModeToUseCellData() 318 319 if isinstance(key, int): 320 key = data.GetArrayName(key) 321 data.SetActiveNormals(key) 322 return self.obj 323 324 def print(self, **kwargs) -> None: 325 """Print the array names available to terminal""" 326 colors.printc(self.keys(), **kwargs) 327 328 def __repr__(self) -> str: 329 """Representation""" 330 331 def _get_str(pd, header): 332 out = f"\x1b[2m\x1b[1m\x1b[7m{header}" 333 if pd.GetNumberOfArrays(): 334 if self.obj.name: 335 out += f" in {self.obj.name}" 336 out += f" contains {pd.GetNumberOfArrays()} array(s)\x1b[0m" 337 for i in range(pd.GetNumberOfArrays()): 338 varr = pd.GetArray(i) 339 out += f"\n\x1b[1m\x1b[4mArray name : {varr.GetName()}\x1b[0m" 340 out += "\nindex".ljust(15) + f": {i}" 341 t = varr.GetDataType() 342 if t in vtki.array_types: 343 out += "\ntype".ljust(15) 344 out += f": {vtki.array_types[t]}" 345 shape = (varr.GetNumberOfTuples(), varr.GetNumberOfComponents()) 346 out += "\nshape".ljust(15) + f": {shape}" 347 out += "\nrange".ljust(15) + f": {np.array(varr.GetRange())}" 348 out += "\nmax id".ljust(15) + f": {varr.GetMaxId()}" 349 out += "\nlook up table".ljust(15) + f": {bool(varr.GetLookupTable())}" 350 out += "\nin-memory size".ljust(15) + f": {varr.GetActualMemorySize()} KB" 351 else: 352 out += " is empty.\x1b[0m" 353 return out 354 355 if self.association == 0: 356 out = _get_str(self.obj.dataset.GetPointData(), "Point Data") 357 elif self.association == 1: 358 out = _get_str(self.obj.dataset.GetCellData(), "Cell Data") 359 elif self.association == 2: 360 pd = self.obj.dataset.GetFieldData() 361 if pd.GetNumberOfArrays(): 362 out = "\x1b[2m\x1b[1m\x1b[7mMeta Data" 363 if self.obj.name: 364 out += f" in {self.obj.name}" 365 out += f" contains {pd.GetNumberOfArrays()} entries\x1b[0m" 366 for i in range(pd.GetNumberOfArrays()): 367 varr = pd.GetAbstractArray(i) 368 out += f"\n\x1b[1m\x1b[4mEntry name : {varr.GetName()}\x1b[0m" 369 out += "\nindex".ljust(15) + f": {i}" 370 shape = (varr.GetNumberOfTuples(), varr.GetNumberOfComponents()) 371 out += "\nshape".ljust(15) + f": {shape}" 372 373 return out
def
keys(self) -> List[str]:
157 def keys(self) -> List[str]: 158 """Return the list of available data array names""" 159 if self.association == 0: 160 data = self.obj.dataset.GetPointData() 161 elif self.association == 1: 162 data = self.obj.dataset.GetCellData() 163 elif self.association == 2: 164 data = self.obj.dataset.GetFieldData() 165 arrnames = [] 166 for i in range(data.GetNumberOfArrays()): 167 name = "" 168 if self.association == 2: 169 name = data.GetAbstractArray(i).GetName() 170 else: 171 iarr = data.GetArray(i) 172 if iarr: 173 name = iarr.GetName() 174 if name: 175 arrnames.append(name) 176 return arrnames
Return the list of available data array names
def
items(self) -> List:
178 def items(self) -> List: 179 """Return the list of available data array `(names, values)`.""" 180 if self.association == 0: 181 data = self.obj.dataset.GetPointData() 182 elif self.association == 1: 183 data = self.obj.dataset.GetCellData() 184 elif self.association == 2: 185 data = self.obj.dataset.GetFieldData() 186 arrnames = [] 187 for i in range(data.GetNumberOfArrays()): 188 if self.association == 2: 189 name = data.GetAbstractArray(i).GetName() 190 else: 191 name = data.GetArray(i).GetName() 192 if name: 193 arrnames.append((name, self[name])) 194 return arrnames
Return the list of available data array (names, values)
.
def
todict(self) -> dict:
196 def todict(self) -> dict: 197 """Return a dictionary of the available data arrays.""" 198 return dict(self.items())
Return a dictionary of the available data arrays.
def
rename(self, oldname: str, newname: str) -> None:
200 def rename(self, oldname: str, newname: str) -> None: 201 """Rename an array""" 202 if self.association == 0: 203 varr = self.obj.dataset.GetPointData().GetArray(oldname) 204 elif self.association == 1: 205 varr = self.obj.dataset.GetCellData().GetArray(oldname) 206 elif self.association == 2: 207 varr = self.obj.dataset.GetFieldData().GetAbstractArray(oldname) 208 if varr: 209 varr.SetName(newname) 210 else: 211 vedo.logger.warning( 212 f"Cannot rename non existing array {oldname} to {newname}" 213 )
Rename an array
def
remove(self, key: Union[int, str]) -> None:
215 def remove(self, key: Union[int, str]) -> None: 216 """Remove a data array by name or number""" 217 if self.association == 0: 218 self.obj.dataset.GetPointData().RemoveArray(key) 219 elif self.association == 1: 220 self.obj.dataset.GetCellData().RemoveArray(key) 221 elif self.association == 2: 222 self.obj.dataset.GetFieldData().RemoveArray(key)
Remove a data array by name or number
def
clear(self) -> None:
224 def clear(self) -> None: 225 """Remove all data associated to this object""" 226 if self.association == 0: 227 data = self.obj.dataset.GetPointData() 228 elif self.association == 1: 229 data = self.obj.dataset.GetCellData() 230 elif self.association == 2: 231 data = self.obj.dataset.GetFieldData() 232 for i in range(data.GetNumberOfArrays()): 233 if self.association == 2: 234 name = data.GetAbstractArray(i).GetName() 235 else: 236 name = data.GetArray(i).GetName() 237 data.RemoveArray(name)
Remove all data associated to this object
def
select(self, key: Union[int, str]) -> Any:
239 def select(self, key: Union[int, str]) -> Any: 240 """Select one specific array by its name to make it the `active` one.""" 241 # Default (ColorModeToDefault): unsigned char scalars are treated as colors, 242 # and NOT mapped through the lookup table, while everything else is. 243 # ColorModeToDirectScalar extends ColorModeToDefault such that all integer 244 # types are treated as colors with values in the range 0-255 245 # and floating types are treated as colors with values in the range 0.0-1.0. 246 # Setting ColorModeToMapScalars means that all scalar data will be mapped 247 # through the lookup table. 248 # (Note that for multi-component scalars, the particular component 249 # to use for mapping can be specified using the SelectColorArray() method.) 250 if self.association == 0: 251 data = self.obj.dataset.GetPointData() 252 self.obj.mapper.SetScalarModeToUsePointData() 253 else: 254 data = self.obj.dataset.GetCellData() 255 self.obj.mapper.SetScalarModeToUseCellData() 256 257 if isinstance(key, int): 258 key = data.GetArrayName(key) 259 260 arr = data.GetArray(key) 261 if not arr: 262 return self.obj 263 264 nc = arr.GetNumberOfComponents() 265 # print("GetNumberOfComponents", nc) 266 if nc == 1: 267 data.SetActiveScalars(key) 268 elif nc == 2: 269 data.SetTCoords(arr) 270 elif nc in (3, 4): 271 if "rgb" in key.lower(): # type: ignore 272 data.SetActiveScalars(key) 273 try: 274 # could be a volume mapper 275 self.obj.mapper.SetColorModeToDirectScalars() 276 data.SetActiveVectors(None) # need this to fix bug in #1066 277 # print("SetColorModeToDirectScalars for", key) 278 except AttributeError: 279 pass 280 else: 281 data.SetActiveVectors(key) 282 elif nc == 9: 283 data.SetActiveTensors(key) 284 else: 285 vedo.logger.error(f"Cannot select array {key} with {nc} components") 286 return self.obj 287 288 try: 289 # could be a volume mapper 290 self.obj.mapper.SetArrayName(key) 291 self.obj.mapper.ScalarVisibilityOn() 292 except AttributeError: 293 pass 294 295 return self.obj
Select one specific array by its name to make it the active
one.
def
select_texture_coords(self, key: Union[int, str]) -> Any:
297 def select_texture_coords(self, key: Union[int,str]) -> Any: 298 """Select one specific array to be used as texture coordinates.""" 299 if self.association == 0: 300 data = self.obj.dataset.GetPointData() 301 else: 302 vedo.logger.warning("texture coordinates are only available for point data") 303 return 304 305 if isinstance(key, int): 306 key = data.GetArrayName(key) 307 data.SetTCoords(data.GetArray(key)) 308 return self.obj
Select one specific array to be used as texture coordinates.
def
select_normals(self, key: Union[int, str]) -> Any:
310 def select_normals(self, key: Union[int,str]) -> Any: 311 """Select one specific normal array by its name to make it the "active" one.""" 312 if self.association == 0: 313 data = self.obj.dataset.GetPointData() 314 self.obj.mapper.SetScalarModeToUsePointData() 315 else: 316 data = self.obj.dataset.GetCellData() 317 self.obj.mapper.SetScalarModeToUseCellData() 318 319 if isinstance(key, int): 320 key = data.GetArrayName(key) 321 data.SetActiveNormals(key) 322 return self.obj
Select one specific normal array by its name to make it the "active" one.
class
CommonAlgorithms:
377class CommonAlgorithms: 378 """Common algorithms.""" 379 380 @property 381 def pointdata(self): 382 """ 383 Create and/or return a `numpy.array` associated to points (vertices). 384 A data array can be indexed either as a string or by an integer number. 385 E.g.: `myobj.pointdata["arrayname"]` 386 387 Usage: 388 389 `myobj.pointdata.keys()` to return the available data array names 390 391 `myobj.pointdata.select(name)` to make this array the active one 392 393 `myobj.pointdata.remove(name)` to remove this array 394 """ 395 return DataArrayHelper(self, 0) 396 397 @property 398 def celldata(self): 399 """ 400 Create and/or return a `numpy.array` associated to cells (faces). 401 A data array can be indexed either as a string or by an integer number. 402 E.g.: `myobj.celldata["arrayname"]` 403 404 Usage: 405 406 `myobj.celldata.keys()` to return the available data array names 407 408 `myobj.celldata.select(name)` to make this array the active one 409 410 `myobj.celldata.remove(name)` to remove this array 411 """ 412 return DataArrayHelper(self, 1) 413 414 @property 415 def metadata(self): 416 """ 417 Create and/or return a `numpy.array` associated to neither cells nor faces. 418 A data array can be indexed either as a string or by an integer number. 419 E.g.: `myobj.metadata["arrayname"]` 420 421 Usage: 422 423 `myobj.metadata.keys()` to return the available data array names 424 425 `myobj.metadata.select(name)` to make this array the active one 426 427 `myobj.metadata.remove(name)` to remove this array 428 """ 429 return DataArrayHelper(self, 2) 430 431 def memory_address(self) -> int: 432 """ 433 Return a unique memory address integer which may serve as the ID of the 434 object, or passed to c++ code. 435 """ 436 # https://www.linkedin.com/pulse/speedup-your-code-accessing-python-vtk-objects-from-c-pletzer/ 437 # https://github.com/tfmoraes/polydata_connectivity 438 return int(self.dataset.GetAddressAsString("")[5:], 16) 439 440 def memory_size(self) -> int: 441 """Return the size in bytes of the object in memory.""" 442 return self.dataset.GetActualMemorySize() 443 444 def modified(self) -> Self: 445 """Use in conjunction with `tonumpy()` to update any modifications to the image array.""" 446 self.dataset.GetPointData().Modified() 447 scals = self.dataset.GetPointData().GetScalars() 448 if scals: 449 scals.Modified() 450 return self 451 452 def box(self, scale=1, padding=0) -> "vedo.Mesh": 453 """ 454 Return the bounding box as a new `Mesh` object. 455 456 Arguments: 457 scale : (float) 458 box size can be scaled by a factor 459 padding : (float, list) 460 a constant padding can be added (can be a list `[padx,pady,padz]`) 461 """ 462 b = self.bounds() 463 if not utils.is_sequence(padding): 464 padding = [padding, padding, padding] 465 length, width, height = b[1] - b[0], b[3] - b[2], b[5] - b[4] 466 tol = (length + width + height) / 30000 # useful for boxing text 467 pos = [(b[0] + b[1]) / 2, (b[3] + b[2]) / 2, (b[5] + b[4]) / 2 - tol] 468 bx = vedo.shapes.Box( 469 pos, 470 length * scale + padding[0], 471 width * scale + padding[1], 472 height * scale + padding[2], 473 c="gray", 474 ) 475 try: 476 pr = vtki.vtkProperty() 477 pr.DeepCopy(self.properties) 478 bx.actor.SetProperty(pr) 479 bx.properties = pr 480 except (AttributeError, TypeError): 481 pass 482 bx.flat().lighting("off").wireframe(True) 483 return bx 484 485 def update_dataset(self, dataset, **kwargs) -> Self: 486 """Update the dataset of the object with the provided VTK dataset.""" 487 self._update(dataset, **kwargs) 488 return self 489 490 def bounds(self) -> np.ndarray: 491 """ 492 Get the object bounds. 493 Returns a list in format `[xmin,xmax, ymin,ymax, zmin,zmax]`. 494 """ 495 try: # this is very slow for large meshes 496 pts = self.vertices 497 xmin, ymin, zmin = np.min(pts, axis=0) 498 xmax, ymax, zmax = np.max(pts, axis=0) 499 return np.array([xmin, xmax, ymin, ymax, zmin, zmax]) 500 except (AttributeError, ValueError): 501 return np.array(self.dataset.GetBounds()) 502 503 def xbounds(self, i=None) -> np.ndarray: 504 """Get the bounds `[xmin,xmax]`. Can specify upper or lower with i (0,1).""" 505 b = self.bounds() 506 if i is not None: 507 return b[i] 508 return np.array([b[0], b[1]]) 509 510 def ybounds(self, i=None) -> np.ndarray: 511 """Get the bounds `[ymin,ymax]`. Can specify upper or lower with i (0,1).""" 512 b = self.bounds() 513 if i == 0: 514 return b[2] 515 if i == 1: 516 return b[3] 517 return np.array([b[2], b[3]]) 518 519 def zbounds(self, i=None) -> np.ndarray: 520 """Get the bounds `[zmin,zmax]`. Can specify upper or lower with i (0,1).""" 521 b = self.bounds() 522 if i == 0: 523 return b[4] 524 if i == 1: 525 return b[5] 526 return np.array([b[4], b[5]]) 527 528 def diagonal_size(self) -> float: 529 """Get the length of the diagonal of the bounding box.""" 530 b = self.bounds() 531 return np.sqrt((b[1] - b[0])**2 + (b[3] - b[2])**2 + (b[5] - b[4])**2) 532 533 def average_size(self) -> float: 534 """ 535 Calculate and return the average size of the object. 536 This is the mean of the vertex distances from the center of mass. 537 """ 538 coords = self.vertices 539 cm = np.mean(coords, axis=0) 540 if coords.shape[0] == 0: 541 return 0.0 542 cc = coords - cm 543 return np.mean(np.linalg.norm(cc, axis=1)) 544 545 def center_of_mass(self) -> np.ndarray: 546 """Get the center of mass of the object.""" 547 if isinstance(self, (vedo.RectilinearGrid, vedo.Volume)): 548 return np.array(self.dataset.GetCenter()) 549 cmf = vtki.new("CenterOfMass") 550 cmf.SetInputData(self.dataset) 551 cmf.Update() 552 c = cmf.GetCenter() 553 return np.array(c) 554 555 def copy_data_from(self, obj: Any) -> Self: 556 """Copy all data (point and cell data) from this input object""" 557 self.dataset.GetPointData().PassData(obj.dataset.GetPointData()) 558 self.dataset.GetCellData().PassData(obj.dataset.GetCellData()) 559 self.pipeline = utils.OperationNode( 560 "copy_data_from", 561 parents=[self, obj], 562 comment=f"{obj.__class__.__name__}", 563 shape="note", 564 c="#ccc5b9", 565 ) 566 return self 567 568 def inputdata(self): 569 """Obsolete, use `.dataset` instead.""" 570 colors.printc("WARNING: 'inputdata()' is obsolete, use '.dataset' instead.", c="y") 571 return self.dataset 572 573 @property 574 def npoints(self): 575 """Retrieve the number of points (or vertices).""" 576 return self.dataset.GetNumberOfPoints() 577 578 @property 579 def nvertices(self): 580 """Retrieve the number of vertices (or points).""" 581 return self.dataset.GetNumberOfPoints() 582 583 @property 584 def ncells(self): 585 """Retrieve the number of cells.""" 586 return self.dataset.GetNumberOfCells() 587 588 def points(self, pts=None): 589 """Obsolete, use `self.vertices` or `self.coordinates` instead.""" 590 if pts is None: ### getter 591 592 if warnings["points_getter"]: 593 colors.printc(warnings["points_getter"], c="y") 594 warnings["points_getter"] = "" 595 return self.vertices 596 597 else: ### setter 598 599 if warnings["points_setter"]: 600 colors.printc(warnings["points_setter"], c="y") 601 warnings["points_setter"] = "" 602 603 pts = np.asarray(pts, dtype=np.float32) 604 605 if pts.ndim == 1: 606 ### getter by point index ################### 607 indices = pts.astype(int) 608 vpts = self.dataset.GetPoints() 609 arr = utils.vtk2numpy(vpts.GetData()) 610 return arr[indices] ########### 611 612 ### setter #################################### 613 if pts.shape[1] == 2: 614 pts = np.c_[pts, np.zeros(pts.shape[0], dtype=np.float32)] 615 arr = utils.numpy2vtk(pts, dtype=np.float32) 616 617 vpts = self.dataset.GetPoints() 618 vpts.SetData(arr) 619 vpts.Modified() 620 # reset mesh to identity matrix position/rotation: 621 self.point_locator = None 622 self.cell_locator = None 623 self.line_locator = None 624 self.transform = LinearTransform() 625 return self 626 627 @property 628 def cell_centers(self): 629 """ 630 Get the coordinates of the cell centers. 631 632 Examples: 633 - [delaunay2d.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/delaunay2d.py) 634 635 See also: `CellCenters()`. 636 """ 637 vcen = vtki.new("CellCenters") 638 vcen.CopyArraysOff() 639 vcen.SetInputData(self.dataset) 640 vcen.Update() 641 return utils.vtk2numpy(vcen.GetOutput().GetPoints().GetData()) 642 643 @property 644 def lines(self): 645 """ 646 Get lines connectivity ids as a python array 647 formatted as `[[id0,id1], [id3,id4], ...]` 648 649 See also: `lines_as_flat_array()`. 650 """ 651 # Get cell connettivity ids as a 1D array. The vtk format is: 652 # [nids1, id0 ... idn, niids2, id0 ... idm, etc]. 653 try: 654 arr1d = utils.vtk2numpy(self.dataset.GetLines().GetData()) 655 except AttributeError: 656 return np.array([]) 657 i = 0 658 conn = [] 659 n = len(arr1d) 660 for _ in range(n): 661 cell = [arr1d[i + k + 1] for k in range(arr1d[i])] 662 conn.append(cell) 663 i += arr1d[i] + 1 664 if i >= n: 665 break 666 667 return conn # cannot always make a numpy array of it! 668 669 @property 670 def lines_as_flat_array(self): 671 """ 672 Get lines connectivity ids as a 1D numpy array. 673 Format is e.g. [2, 10,20, 3, 10,11,12, 2, 70,80, ...] 674 675 See also: `lines()`. 676 """ 677 try: 678 return utils.vtk2numpy(self.dataset.GetLines().GetData()) 679 except AttributeError: 680 return np.array([]) 681 682 def mark_boundaries(self) -> Self: 683 """ 684 Mark cells and vertices if they lie on a boundary. 685 A new array called `BoundaryCells` is added to the object. 686 """ 687 mb = vtki.new("MarkBoundaryFilter") 688 mb.SetInputData(self.dataset) 689 mb.Update() 690 self.dataset.DeepCopy(mb.GetOutput()) 691 self.pipeline = utils.OperationNode("mark_boundaries", parents=[self]) 692 return self 693 694 def find_cells_in_bounds(self, xbounds=(), ybounds=(), zbounds=()) -> np.ndarray: 695 """ 696 Find cells that are within the specified bounds. 697 """ 698 try: 699 xbounds = list(xbounds.bounds()) 700 except AttributeError: 701 pass 702 703 if len(xbounds) == 6: 704 bnds = xbounds 705 else: 706 bnds = list(self.bounds()) 707 if len(xbounds) == 2: 708 bnds[0] = xbounds[0] 709 bnds[1] = xbounds[1] 710 if len(ybounds) == 2: 711 bnds[2] = ybounds[0] 712 bnds[3] = ybounds[1] 713 if len(zbounds) == 2: 714 bnds[4] = zbounds[0] 715 bnds[5] = zbounds[1] 716 717 cell_ids = vtki.vtkIdList() 718 if not self.cell_locator: 719 self.cell_locator = vtki.new("CellTreeLocator") 720 self.cell_locator.SetDataSet(self.dataset) 721 self.cell_locator.BuildLocator() 722 self.cell_locator.FindCellsWithinBounds(bnds, cell_ids) 723 cids = [] 724 for i in range(cell_ids.GetNumberOfIds()): 725 cid = cell_ids.GetId(i) 726 cids.append(cid) 727 return np.array(cids) 728 729 def find_cells_along_line(self, p0, p1, tol=0.001) -> np.ndarray: 730 """ 731 Find cells that are intersected by a line segment. 732 """ 733 cell_ids = vtki.vtkIdList() 734 if not self.cell_locator: 735 self.cell_locator = vtki.new("CellTreeLocator") 736 self.cell_locator.SetDataSet(self.dataset) 737 self.cell_locator.BuildLocator() 738 self.cell_locator.FindCellsAlongLine(p0, p1, tol, cell_ids) 739 cids = [] 740 for i in range(cell_ids.GetNumberOfIds()): 741 cid = cell_ids.GetId(i) 742 cids.append(cid) 743 return np.array(cids) 744 745 def find_cells_along_plane(self, origin, normal, tol=0.001) -> np.ndarray: 746 """ 747 Find cells that are intersected by a plane. 748 """ 749 cell_ids = vtki.vtkIdList() 750 if not self.cell_locator: 751 self.cell_locator = vtki.new("CellTreeLocator") 752 self.cell_locator.SetDataSet(self.dataset) 753 self.cell_locator.BuildLocator() 754 self.cell_locator.FindCellsAlongPlane(origin, normal, tol, cell_ids) 755 cids = [] 756 for i in range(cell_ids.GetNumberOfIds()): 757 cid = cell_ids.GetId(i) 758 cids.append(cid) 759 return np.array(cids) 760 761 def keep_cell_types(self, types=()): 762 """ 763 Extract cells of a specific type. 764 765 Check the VTK cell types here: 766 https://vtk.org/doc/nightly/html/vtkCellType_8h.html 767 """ 768 fe = vtki.new("ExtractCellsByType") 769 fe.SetInputData(self.dataset) 770 for t in types: 771 try: 772 if utils.is_integer(t): 773 it = t 774 else: 775 it = vtki.cell_types[t.upper()] 776 except KeyError: 777 vedo.logger.error(f"Cell type '{t}' not recognized") 778 continue 779 fe.AddCellType(it) 780 fe.Update() 781 self._update(fe.GetOutput()) 782 return self 783 784 def map_cells_to_points(self, arrays=(), move=False) -> Self: 785 """ 786 Interpolate cell data (i.e., data specified per cell or face) 787 into point data (i.e., data specified at each vertex). 788 The method of transformation is based on averaging the data values 789 of all cells using a particular point. 790 791 A custom list of arrays to be mapped can be passed in input. 792 793 Set `move=True` to delete the original `celldata` array. 794 """ 795 c2p = vtki.new("CellDataToPointData") 796 c2p.SetInputData(self.dataset) 797 if not move: 798 c2p.PassCellDataOn() 799 if arrays: 800 c2p.ClearCellDataArrays() 801 c2p.ProcessAllArraysOff() 802 for arr in arrays: 803 c2p.AddCellDataArray(arr) 804 else: 805 c2p.ProcessAllArraysOn() 806 c2p.Update() 807 self._update(c2p.GetOutput(), reset_locators=False) 808 self.mapper.SetScalarModeToUsePointData() 809 self.pipeline = utils.OperationNode("map_cells_to_points", parents=[self]) 810 return self 811 812 @property 813 def vertices(self): 814 """Return the vertices (points) coordinates.""" 815 try: 816 # for polydata and unstructured grid 817 varr = self.dataset.GetPoints().GetData() 818 except (AttributeError, TypeError): 819 try: 820 # for RectilinearGrid, StructuredGrid 821 vpts = vtki.vtkPoints() 822 self.dataset.GetPoints(vpts) 823 varr = vpts.GetData() 824 except (AttributeError, TypeError): 825 try: 826 # for ImageData 827 v2p = vtki.new("ImageToPoints") 828 v2p.SetInputData(self.dataset) 829 v2p.Update() 830 varr = v2p.GetOutput().GetPoints().GetData() 831 except AttributeError: 832 return np.array([]) 833 834 return utils.vtk2numpy(varr) 835 836 # setter 837 @vertices.setter 838 def vertices(self, pts): 839 """Set vertices (points) coordinates.""" 840 pts = utils.make3d(pts) 841 arr = utils.numpy2vtk(pts, dtype=np.float32) 842 try: 843 vpts = self.dataset.GetPoints() 844 vpts.SetData(arr) 845 vpts.Modified() 846 except (AttributeError, TypeError): 847 vedo.logger.error(f"Cannot set vertices for {type(self)}") 848 return self 849 # reset mesh to identity matrix position/rotation: 850 self.point_locator = None 851 self.cell_locator = None 852 self.line_locator = None 853 self.transform = LinearTransform() 854 855 @property 856 def coordinates(self): 857 """Return the vertices (points) coordinates. Same as `vertices`.""" 858 return self.vertices 859 860 @coordinates.setter 861 def coordinates(self, pts): 862 """Set vertices (points) coordinates. Same as `vertices`.""" 863 self.vertices = pts 864 865 @property 866 def cells_as_flat_array(self): 867 """ 868 Get cell connectivity ids as a 1D numpy array. 869 Format is e.g. [3, 10,20,30 4, 10,11,12,13 ...] 870 """ 871 try: 872 # valid for unstructured grid 873 arr1d = utils.vtk2numpy(self.dataset.GetCells().GetData()) 874 except AttributeError: 875 # valid for polydata 876 arr1d = utils.vtk2numpy(self.dataset.GetPolys().GetData()) 877 return arr1d 878 879 @property 880 def cells(self): 881 """ 882 Get the cells connectivity ids as a numpy array. 883 884 The output format is: `[[id0 ... idn], [id0 ... idm], etc]`. 885 """ 886 try: 887 # valid for unstructured grid 888 arr1d = utils.vtk2numpy(self.dataset.GetCells().GetData()) 889 except AttributeError: 890 try: 891 # valid for polydata 892 arr1d = utils.vtk2numpy(self.dataset.GetPolys().GetData()) 893 except AttributeError: 894 vedo.logger.warning(f"Cannot get cells for {type(self)}") 895 return np.array([]) 896 897 # Get cell connettivity ids as a 1D array. vtk format is: 898 # [nids1, id0 ... idn, niids2, id0 ... idm, etc]. 899 i = 0 900 conn = [] 901 n = len(arr1d) 902 if n: 903 while True: 904 cell = [arr1d[i + k] for k in range(1, arr1d[i] + 1)] 905 conn.append(cell) 906 i += arr1d[i] + 1 907 if i >= n: 908 break 909 return conn 910 911 def cell_edge_neighbors(self): 912 """ 913 Get the cell neighbor indices of each cell. 914 915 Returns a python list of lists. 916 """ 917 918 def face_to_edges(face): 919 edges = [] 920 size = len(face) 921 for i in range(1, size + 1): 922 if i == size: 923 edges.append([face[i - 1], face[0]]) 924 else: 925 edges.append([face[i - 1], face[i]]) 926 return edges 927 928 pd = self.dataset 929 pd.BuildLinks() 930 931 neicells = [] 932 for i, cell in enumerate(self.cells): 933 nn = [] 934 for edge in face_to_edges(cell): 935 neighbors = vtki.vtkIdList() 936 pd.GetCellEdgeNeighbors(i, edge[0], edge[1], neighbors) 937 if neighbors.GetNumberOfIds() > 0: 938 neighbor = neighbors.GetId(0) 939 nn.append(neighbor) 940 neicells.append(nn) 941 942 return neicells 943 944 945 def map_points_to_cells(self, arrays=(), move=False) -> Self: 946 """ 947 Interpolate point data (i.e., data specified per point or vertex) 948 into cell data (i.e., data specified per cell). 949 The method of transformation is based on averaging the data values 950 of all points defining a particular cell. 951 952 A custom list of arrays to be mapped can be passed in input. 953 954 Set `move=True` to delete the original `pointdata` array. 955 956 Examples: 957 - [mesh_map2cell.py](https://github.com/marcomusy/vedo/tree/master/examples/basic/mesh_map2cell.py) 958 """ 959 p2c = vtki.new("PointDataToCellData") 960 p2c.SetInputData(self.dataset) 961 if not move: 962 p2c.PassPointDataOn() 963 if arrays: 964 p2c.ClearPointDataArrays() 965 p2c.ProcessAllArraysOff() 966 for arr in arrays: 967 p2c.AddPointDataArray(arr) 968 else: 969 p2c.ProcessAllArraysOn() 970 p2c.Update() 971 self._update(p2c.GetOutput(), reset_locators=False) 972 self.mapper.SetScalarModeToUseCellData() 973 self.pipeline = utils.OperationNode("map_points_to_cells", parents=[self]) 974 return self 975 976 def resample_data_from(self, source, tol=None, categorical=False) -> Self: 977 """ 978 Resample point and cell data from another dataset. 979 The output has the same structure but its point data have 980 the resampled values from target. 981 982 Use `tol` to set the tolerance used to compute whether 983 a point in the source is in a cell of the current object. 984 Points without resampled values, and their cells, are marked as blank. 985 If the data is categorical, then the resulting data will be determined 986 by a nearest neighbor interpolation scheme. 987 988 Example: 989 ```python 990 from vedo import * 991 m1 = Mesh(dataurl+'bunny.obj')#.add_gaussian_noise(0.1) 992 pts = m1.vertices 993 ces = m1.cell_centers 994 m1.pointdata["xvalues"] = np.power(pts[:,0], 3) 995 m1.celldata["yvalues"] = np.power(ces[:,1], 3) 996 m2 = Mesh(dataurl+'bunny.obj') 997 m2.resample_data_from(m1) 998 # print(m2.pointdata["xvalues"]) 999 show(m1, m2 , N=2, axes=1) 1000 ``` 1001 """ 1002 rs = vtki.new("ResampleWithDataSet") 1003 rs.SetInputData(self.dataset) 1004 rs.SetSourceData(source.dataset) 1005 1006 rs.SetPassPointArrays(True) 1007 rs.SetPassCellArrays(True) 1008 rs.SetPassFieldArrays(True) 1009 rs.SetCategoricalData(categorical) 1010 1011 rs.SetComputeTolerance(True) 1012 if tol: 1013 rs.SetComputeTolerance(False) 1014 rs.SetTolerance(tol) 1015 rs.Update() 1016 self._update(rs.GetOutput(), reset_locators=False) 1017 self.pipeline = utils.OperationNode( 1018 "resample_data_from", 1019 comment=f"{source.__class__.__name__}", 1020 parents=[self, source], 1021 ) 1022 return self 1023 1024 def interpolate_data_from( 1025 self, 1026 source, 1027 radius=None, 1028 n=None, 1029 kernel="shepard", 1030 exclude=("Normals",), 1031 on="points", 1032 null_strategy=1, 1033 null_value=0, 1034 ) -> Self: 1035 """ 1036 Interpolate over source to port its data onto the current object using various kernels. 1037 1038 If n (number of closest points to use) is set then radius value is ignored. 1039 1040 Check out also: 1041 `probe()` which in many cases can be faster. 1042 1043 Arguments: 1044 kernel : (str) 1045 available kernels are [shepard, gaussian, linear] 1046 null_strategy : (int) 1047 specify a strategy to use when encountering a "null" point 1048 during the interpolation process. Null points occur when the local neighborhood 1049 (of nearby points to interpolate from) is empty. 1050 1051 - Case 0: an output array is created that marks points 1052 as being valid (=1) or null (invalid =0), and the null_value is set as well 1053 - Case 1: the output data value(s) are set to the provided null_value 1054 - Case 2: simply use the closest point to perform the interpolation. 1055 null_value : (float) 1056 see above. 1057 1058 Examples: 1059 - [interpolate_scalar1.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolate_scalar1.py) 1060 - [interpolate_scalar3.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolate_scalar3.py) 1061 - [interpolate_scalar4.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/interpolate_scalar4.py) 1062 - [image_probe.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/image_probe.py) 1063 1064 ![](https://vedo.embl.es/images/advanced/interpolateMeshArray.png) 1065 """ 1066 if radius is None and not n: 1067 vedo.logger.error("in interpolate_data_from(): please set either radius or n") 1068 raise RuntimeError 1069 1070 if on == "points": 1071 points = source.dataset 1072 elif on == "cells": 1073 c2p = vtki.new("CellDataToPointData") 1074 c2p.SetInputData(source.dataset) 1075 c2p.Update() 1076 points = c2p.GetOutput() 1077 else: 1078 vedo.logger.error("in interpolate_data_from(), on must be on points or cells") 1079 raise RuntimeError() 1080 1081 locator = vtki.new("PointLocator") 1082 locator.SetDataSet(points) 1083 locator.BuildLocator() 1084 1085 if kernel.lower() == "shepard": 1086 kern = vtki.new("ShepardKernel") 1087 kern.SetPowerParameter(2) 1088 elif kernel.lower() == "gaussian": 1089 kern = vtki.new("GaussianKernel") 1090 kern.SetSharpness(2) 1091 elif kernel.lower() == "linear": 1092 kern = vtki.new("LinearKernel") 1093 else: 1094 vedo.logger.error("available kernels are: [shepard, gaussian, linear]") 1095 raise RuntimeError() 1096 1097 if n: 1098 kern.SetNumberOfPoints(n) 1099 kern.SetKernelFootprintToNClosest() 1100 else: 1101 kern.SetRadius(radius) 1102 kern.SetKernelFootprintToRadius() 1103 1104 interpolator = vtki.new("PointInterpolator") 1105 interpolator.SetInputData(self.dataset) 1106 interpolator.SetSourceData(points) 1107 interpolator.SetKernel(kern) 1108 interpolator.SetLocator(locator) 1109 interpolator.PassFieldArraysOn() 1110 interpolator.SetNullPointsStrategy(null_strategy) 1111 interpolator.SetNullValue(null_value) 1112 interpolator.SetValidPointsMaskArrayName("ValidPointMask") 1113 for ex in exclude: 1114 interpolator.AddExcludedArray(ex) 1115 1116 # remove arrays that are already present in the source 1117 # this is because the interpolator will ignore them otherwise 1118 for i in range(self.dataset.GetPointData().GetNumberOfArrays()): 1119 name = self.dataset.GetPointData().GetArrayName(i) 1120 if name not in exclude: 1121 self.dataset.GetPointData().RemoveArray(name) 1122 1123 interpolator.Update() 1124 1125 if on == "cells": 1126 p2c = vtki.new("PointDataToCellData") 1127 p2c.SetInputData(interpolator.GetOutput()) 1128 p2c.Update() 1129 cpoly = p2c.GetOutput() 1130 else: 1131 cpoly = interpolator.GetOutput() 1132 1133 self._update(cpoly, reset_locators=False) 1134 1135 self.pipeline = utils.OperationNode("interpolate_data_from", parents=[self, source]) 1136 return self 1137 1138 def add_ids(self) -> Self: 1139 """ 1140 Generate point and cell ids arrays. 1141 1142 Two new arrays are added to the mesh: `PointID` and `CellID`. 1143 """ 1144 ids = vtki.new("IdFilter") 1145 ids.SetInputData(self.dataset) 1146 ids.PointIdsOn() 1147 ids.CellIdsOn() 1148 ids.FieldDataOff() 1149 ids.SetPointIdsArrayName("PointID") 1150 ids.SetCellIdsArrayName("CellID") 1151 ids.Update() 1152 self._update(ids.GetOutput(), reset_locators=False) 1153 self.pipeline = utils.OperationNode("add_ids", parents=[self]) 1154 return self 1155 1156 def gradient(self, input_array=None, on="points", fast=False) -> np.ndarray: 1157 """ 1158 Compute and return the gradiend of the active scalar field as a numpy array. 1159 1160 Arguments: 1161 input_array : (str) 1162 array of the scalars to compute the gradient, 1163 if None the current active array is selected 1164 on : (str) 1165 compute either on 'points' or 'cells' data 1166 fast : (bool) 1167 if True, will use a less accurate algorithm 1168 that performs fewer derivative calculations (and is therefore faster). 1169 1170 Examples: 1171 - [isolines.py](https://github.com/marcomusy/vedo/tree/master/examples/advanced/isolines.py) 1172 1173 ![](https://user-images.githubusercontent.com/32848391/72433087-f00a8780-3798-11ea-9778-991f0abeca70.png) 1174 """ 1175 gra = vtki.new("GradientFilter") 1176 if on.startswith("p"): 1177 varr = self.dataset.GetPointData() 1178 tp = vtki.vtkDataObject.FIELD_ASSOCIATION_POINTS 1179 elif on.startswith("c"): 1180 varr = self.dataset.GetCellData() 1181 tp = vtki.vtkDataObject.FIELD_ASSOCIATION_CELLS 1182 else: 1183 vedo.logger.error(f"in gradient: unknown option {on}") 1184 raise RuntimeError 1185 1186 if input_array is None: 1187 if varr.GetScalars(): 1188 input_array = varr.GetScalars().GetName() 1189 else: 1190 vedo.logger.error(f"in gradient: no scalars found for {on}") 1191 raise RuntimeError 1192 1193 gra.SetInputData(self.dataset) 1194 gra.SetInputScalars(tp, input_array) 1195 gra.SetResultArrayName("Gradient") 1196 gra.SetFasterApproximation(fast) 1197 gra.ComputeDivergenceOff() 1198 gra.ComputeVorticityOff() 1199 gra.ComputeGradientOn() 1200 gra.Update() 1201 # self._update(gra.GetOutput(), reset_locators=False) 1202 if on.startswith("p"): 1203 gvecs = utils.vtk2numpy(gra.GetOutput().GetPointData().GetArray("Gradient")) 1204 else: 1205 gvecs = utils.vtk2numpy(gra.GetOutput().GetCellData().GetArray("Gradient")) 1206 return gvecs 1207 1208 def divergence(self, array_name=None, on="points", fast=False) -> np.ndarray: 1209 """ 1210 Compute and return the divergence of a vector field as a numpy array. 1211 1212 Arguments: 1213 array_name : (str) 1214 name of the array of vectors to compute the divergence, 1215 if None the current active array is selected 1216 on : (str) 1217 compute either on 'points' or 'cells' data 1218 fast : (bool) 1219 if True, will use a less accurate algorithm 1220 that performs fewer derivative calculations (and is therefore faster). 1221 """ 1222 div = vtki.new("GradientFilter") 1223 if on.startswith("p"): 1224 varr = self.dataset.GetPointData() 1225 tp = vtki.vtkDataObject.FIELD_ASSOCIATION_POINTS 1226 elif on.startswith("c"): 1227 varr = self.dataset.GetCellData() 1228 tp = vtki.vtkDataObject.FIELD_ASSOCIATION_CELLS 1229 else: 1230 vedo.logger.error(f"in divergence(): unknown option {on}") 1231 raise RuntimeError 1232 1233 if array_name is None: 1234 if varr.GetVectors(): 1235 array_name = varr.GetVectors().GetName() 1236 else: 1237 vedo.logger.error(f"in divergence(): no vectors found for {on}") 1238 raise RuntimeError 1239 1240 div.SetInputData(self.dataset) 1241 div.SetInputScalars(tp, array_name) 1242 div.ComputeDivergenceOn() 1243 div.ComputeGradientOff() 1244 div.ComputeVorticityOff() 1245 div.SetDivergenceArrayName("Divergence") 1246 div.SetFasterApproximation(fast) 1247 div.Update() 1248 # self._update(div.GetOutput(), reset_locators=False) 1249 if on.startswith("p"): 1250 dvecs = utils.vtk2numpy(div.GetOutput().GetPointData().GetArray("Divergence")) 1251 else: 1252 dvecs = utils.vtk2numpy(div.GetOutput().GetCellData().GetArray("Divergence")) 1253 return dvecs 1254 1255 def vorticity(self, array_name=None, on="points", fast=False) -> np.ndarray: 1256 """ 1257 Compute and return the vorticity of a vector field as a numpy array. 1258 1259 Arguments: 1260 array_name : (str) 1261 name of the array to compute the vorticity, 1262 if None the current active array is selected 1263 on : (str) 1264 compute either on 'points' or 'cells' data 1265 fast : (bool) 1266 if True, will use a less accurate algorithm 1267 that performs fewer derivative calculations (and is therefore faster). 1268 """ 1269 vort = vtki.new("GradientFilter") 1270 if on.startswith("p"): 1271 varr = self.dataset.GetPointData() 1272 tp = vtki.vtkDataObject.FIELD_ASSOCIATION_POINTS 1273 elif on.startswith("c"): 1274 varr = self.dataset.GetCellData() 1275 tp = vtki.vtkDataObject.FIELD_ASSOCIATION_CELLS 1276 else: 1277 vedo.logger.error(f"in vorticity(): unknown option {on}") 1278 raise RuntimeError 1279 1280 if array_name is None: 1281 if varr.GetVectors(): 1282 array_name = varr.GetVectors().GetName() 1283 else: 1284 vedo.logger.error(f"in vorticity(): no vectors found for {on}") 1285 raise RuntimeError 1286 1287 vort.SetInputData(self.dataset) 1288 vort.SetInputScalars(tp, array_name) 1289 vort.ComputeDivergenceOff() 1290 vort.ComputeGradientOff() 1291 vort.ComputeVorticityOn() 1292 vort.SetVorticityArrayName("Vorticity") 1293 vort.SetFasterApproximation(fast) 1294 vort.Update() 1295 if on.startswith("p"): 1296 vvecs = utils.vtk2numpy(vort.GetOutput().GetPointData().GetArray("Vorticity")) 1297 else: 1298 vvecs = utils.vtk2numpy(vort.GetOutput().GetCellData().GetArray("Vorticity")) 1299 return vvecs 1300 1301 def probe( 1302 self, 1303 source, 1304 categorical=False, 1305 snap=False, 1306 tol=0, 1307 ) -> Self: 1308 """ 1309 Takes a data set and probes its scalars at the specified points in space. 1310 1311 Note that a mask is also output with valid/invalid points which can be accessed 1312 with `mesh.pointdata['ValidPointMask']`. 1313 1314 Arguments: 1315 source : any dataset 1316 the data set to probe. 1317 categorical : bool 1318 control whether the source pointdata is to be treated as categorical. 1319 snap : bool 1320 snap to the cell with the closest point if no cell was found 1321 tol : float 1322 the tolerance to use when performing the probe. 1323 1324 Check out also: 1325 `interpolate_data_from()` and `tovolume()` 1326 1327 Examples: 1328 - [probe_points.py](https://github.com/marcomusy/vedo/tree/master/examples/volumetric/probe_points.py) 1329 1330 ![](https://vedo.embl.es/images/volumetric/probePoints.png) 1331 """ 1332 probe_filter = vtki.new("ProbeFilter") 1333 probe_filter.SetSourceData(source.dataset) 1334 probe_filter.SetInputData(self.dataset) 1335 probe_filter.PassCellArraysOn() 1336 probe_filter.PassFieldArraysOn() 1337 probe_filter.PassPointArraysOn() 1338 probe_filter.SetCategoricalData(categorical) 1339 probe_filter.ComputeToleranceOff() 1340 if tol: 1341 probe_filter.ComputeToleranceOn() 1342 probe_filter.SetTolerance(tol) 1343 probe_filter.SetSnapToCellWithClosestPoint(snap) 1344 probe_filter.Update() 1345 self._update(probe_filter.GetOutput(), reset_locators=False) 1346 self.pipeline = utils.OperationNode("probe", parents=[self, source]) 1347 self.pointdata.rename("vtkValidPointMask", "ValidPointMask") 1348 return self 1349 1350 def compute_cell_size(self) -> Self: 1351 """ 1352 Add to this object a cell data array 1353 containing the area, volume and edge length 1354 of the cells (when applicable to the object type). 1355 1356 Array names are: `Area`, `Volume`, `Length`. 1357 """ 1358 csf = vtki.new("CellSizeFilter") 1359 csf.SetInputData(self.dataset) 1360 csf.SetComputeArea(1) 1361 csf.SetComputeVolume(1) 1362 csf.SetComputeLength(1) 1363 csf.SetComputeVertexCount(0) 1364 csf.SetAreaArrayName("Area") 1365 csf.SetVolumeArrayName("Volume") 1366 csf.SetLengthArrayName("Length") 1367 csf.Update() 1368 self._update(csf.GetOutput(), reset_locators=False) 1369 return self 1370 1371 def generate_random_data(self) -> Self: 1372 """Fill a dataset with random attributes""" 1373 gen = vtki.new("RandomAttributeGenerator") 1374 gen.SetInputData(self.dataset) 1375 gen.GenerateAllDataOn() 1376 gen.SetDataTypeToFloat() 1377 gen.GeneratePointNormalsOff() 1378 gen.GeneratePointTensorsOn() 1379 gen.GenerateCellScalarsOn() 1380 gen.Update() 1381 self._update(gen.GetOutput(), reset_locators=False) 1382 self.pipeline = utils.OperationNode("generate_random_data", parents=[self]) 1383 return self 1384 1385 def integrate_data(self) -> dict: 1386 """ 1387 Integrate point and cell data arrays while computing length, 1388 area or volume of the domain. It works for 1D, 2D or 3D cells. 1389 1390 For volumetric datasets, this filter ignores all but 3D cells. 1391 It will not compute the volume contained in a closed surface. 1392 1393 Returns a dictionary with keys: `pointdata`, `celldata`, `metadata`, 1394 which contain the integration result for the corresponding attributes. 1395 1396 Examples: 1397 ```python 1398 from vedo import * 1399 surf = Sphere(res=100) 1400 surf.pointdata['scalars'] = np.ones(surf.npoints) 1401 data = surf.integrate_data() 1402 print(data['pointdata']['scalars'], "is equal to 4pi", 4*np.pi) 1403 ``` 1404 1405 ```python 1406 from vedo import * 1407 1408 xcoords1 = np.arange(0, 2.2, 0.2) 1409 xcoords2 = sqrt(np.arange(0, 4.2, 0.2)) 1410 1411 ycoords = np.arange(0, 1.2, 0.2) 1412 1413 surf1 = Grid(s=(xcoords1, ycoords)).rotate_y(-45).lw(2) 1414 surf2 = Grid(s=(xcoords2, ycoords)).rotate_y(-45).lw(2) 1415 1416 surf1.pointdata['scalars'] = surf1.vertices[:,2] 1417 surf2.pointdata['scalars'] = surf2.vertices[:,2] 1418 1419 data1 = surf1.integrate_data() 1420 data2 = surf2.integrate_data() 1421 1422 print(data1['pointdata']['scalars'], 1423 "is equal to", 1424 data2['pointdata']['scalars'], 1425 "even if the grids are different!", 1426 "(= the volume under the surface)" 1427 ) 1428 show(surf1, surf2, N=2, axes=1).close() 1429 ``` 1430 """ 1431 vinteg = vtki.new("IntegrateAttributes") 1432 vinteg.SetInputData(self.dataset) 1433 vinteg.Update() 1434 ugrid = vedo.UnstructuredGrid(vinteg.GetOutput()) 1435 data = dict( 1436 pointdata=ugrid.pointdata.todict(), 1437 celldata=ugrid.celldata.todict(), 1438 metadata=ugrid.metadata.todict(), 1439 ) 1440 return data 1441 1442 def write(self, filename, binary=True) -> None: 1443 """Write object to file.""" 1444 out = vedo.file_io.write(self, filename, binary) 1445 out.pipeline = utils.OperationNode( 1446 "write", parents=[self], comment=filename[:15], shape="folder", c="#8a817c" 1447 ) 1448 1449 def tomesh(self, bounds=(), shrink=0) -> "vedo.Mesh": 1450 """ 1451 Extract boundary geometry from dataset (or convert data to polygonal type). 1452 1453 Two new arrays are added to the mesh: `OriginalCellIds` and `OriginalPointIds` 1454 to keep track of the original mesh elements. 1455 1456 Arguments: 1457 bounds : (list) 1458 specify a sub-region to extract 1459 shrink : (float) 1460 shrink the cells to a fraction of their original size 1461 """ 1462 geo = vtki.new("GeometryFilter") 1463 1464 if shrink: 1465 sf = vtki.new("ShrinkFilter") 1466 sf.SetInputData(self.dataset) 1467 sf.SetShrinkFactor(shrink) 1468 sf.Update() 1469 geo.SetInputData(sf.GetOutput()) 1470 else: 1471 geo.SetInputData(self.dataset) 1472 1473 geo.SetPassThroughCellIds(1) 1474 geo.SetPassThroughPointIds(1) 1475 geo.SetOriginalCellIdsName("OriginalCellIds") 1476 geo.SetOriginalPointIdsName("OriginalPointIds") 1477 geo.SetNonlinearSubdivisionLevel(1) 1478 # geo.MergingOff() # crashes on StructuredGrids 1479 if bounds: 1480 geo.SetExtent(bounds) 1481 geo.ExtentClippingOn() 1482 geo.Update() 1483 msh = vedo.mesh.Mesh(geo.GetOutput()) 1484 msh.pipeline = utils.OperationNode("tomesh", parents=[self], c="#9e2a2b") 1485 return msh 1486 1487 def signed_distance(self, dims=(20, 20, 20), bounds=None, invert=False, max_radius=None) -> "vedo.Volume": 1488 """ 1489 Compute the `Volume` object whose voxels contains the signed distance from 1490 the object. The calling object must have "Normals" defined. 1491 1492 Arguments: 1493 bounds : (list, actor) 1494 bounding box sizes 1495 dims : (list) 1496 dimensions (nr. of voxels) of the output volume. 1497 invert : (bool) 1498 flip the sign 1499 max_radius : (float) 1500 specify how far out to propagate distance calculation 1501 1502 Examples: 1503 - [distance2mesh.py](https://github.com/marcomusy/vedo/blob/master/examples/basic/distance2mesh.py) 1504 1505 ![](https://vedo.embl.es/images/basic/distance2mesh.png) 1506 """ 1507 if bounds is None: 1508 bounds = self.bounds() 1509 if max_radius is None: 1510 max_radius = self.diagonal_size() / 2 1511 dist = vtki.new("SignedDistance") 1512 dist.SetInputData(self.dataset) 1513 dist.SetRadius(max_radius) 1514 dist.SetBounds(bounds) 1515 dist.SetDimensions(dims) 1516 dist.Update() 1517 img = dist.GetOutput() 1518 if invert: 1519 mat = vtki.new("ImageMathematics") 1520 mat.SetInput1Data(img) 1521 mat.SetOperationToMultiplyByK() 1522 mat.SetConstantK(-1) 1523 mat.Update() 1524 img = mat.GetOutput() 1525 1526 vol = vedo.Volume(img) 1527 vol.name = "SignedDistanceVolume" 1528 vol.pipeline = utils.OperationNode( 1529 "signed_distance", 1530 parents=[self], 1531 comment=f"dims={tuple(vol.dimensions())}", 1532 c="#e9c46a:#0096c7", 1533 ) 1534 return vol 1535 1536 def unsigned_distance( 1537 self, dims=(25,25,25), bounds=(), max_radius=0, cap_value=0) -> "vedo.Volume": 1538 """ 1539 Compute the `Volume` object whose voxels contains the unsigned distance. 1540 """ 1541 dist = vtki.new("UnsignedDistance") 1542 dist.SetInputData(self.dataset) 1543 dist.SetDimensions(dims) 1544 1545 if len(bounds) == 6: 1546 dist.SetBounds(bounds) 1547 # elif bounds == "auto": 1548 # dist.AdjustBoundsOn() 1549 else: 1550 dist.SetBounds(self.bounds()) 1551 if not max_radius: 1552 max_radius = self.diagonal_size() / 10 1553 dist.SetRadius(max_radius) 1554 1555 if self.point_locator: 1556 dist.SetLocator(self.point_locator) 1557 1558 if cap_value is not None: 1559 dist.CappingOn() 1560 dist.SetCapValue(cap_value) 1561 dist.SetOutputScalarTypeToFloat() 1562 dist.Update() 1563 vol = vedo.Volume(dist.GetOutput()) 1564 vol.name = "UnsignedDistanceVolume" 1565 vol.pipeline = utils.OperationNode( 1566 "unsigned_distance", parents=[self], c="#e9c46a:#0096c7") 1567 return vol 1568 1569 def smooth_data(self, 1570 niter=10, relaxation_factor=0.1, strategy=0, mask=None, 1571 mode="distance2", 1572 exclude=("Normals", "TextureCoordinates"), 1573 ) -> Self: 1574 """ 1575 Smooth point attribute data using distance weighted Laplacian kernel. 1576 The effect is to blur regions of high variation and emphasize low variation regions. 1577 1578 A central concept of this method is the point smoothing stencil. 1579 A smoothing stencil for a point p(i) is the list of points p(j) which connect to p(i) via an edge. 1580 To smooth the attributes of point p(i), p(i)'s attribute data a(i) are iteratively averaged using 1581 the distance weighted average of the attributes of a(j) (the weights w[j] sum to 1). 1582 This averaging process is repeated until the maximum number of iterations is reached. 1583 1584 The relaxation factor (R) is also important as the smoothing process proceeds in an iterative fashion. 1585 The a(i+1) attributes are determined from the a(i) attributes as follows: 1586 a(i+1) = (1-R)*a(i) + R*sum(w(j)*a(j)) 1587 1588 Convergence occurs faster for larger relaxation factors. 1589 Typically a small number of iterations is required for large relaxation factors, 1590 and in cases where only points adjacent to the boundary are being smoothed, a single iteration with R=1 may be 1591 adequate (i.e., just a distance weighted average is computed). 1592 1593 Warning: 1594 Certain data attributes cannot be correctly interpolated. For example, surface normals are expected to be |n|=1; 1595 after attribute smoothing this constraint is likely to be violated. 1596 Other vectors and tensors may suffer from similar issues. 1597 In such a situation, specify `exclude=...` which will not be smoothed (and simply passed through to the output). 1598 Distance weighting function is based on averaging, 1/r, or 1/r**2 weights, where r is the distance 1599 between the point to be smoothed and an edge connected neighbor (defined by the smoothing stencil). 1600 The weights are normalized so that sum(w(i))==1. When smoothing based on averaging, the weights are simply 1/n, 1601 where n is the number of connected points in the stencil. 1602 The smoothing process reduces high frequency information in the data attributes. 1603 With excessive smoothing (large numbers of iterations, and/or a large relaxation factor) important details may be lost, 1604 and the attributes will move towards an "average" value. 1605 While this filter will process any dataset type, if the input data is a 3D image volume, it's likely much faster to use 1606 an image-based algorithm to perform data smoothing. 1607 To determine boundary points in polygonal data, edges used by only one cell are considered boundary 1608 (and hence the associated points defining the edge). 1609 1610 Arguments: 1611 niter : (int) 1612 number of iterations 1613 relaxation_factor : (float) 1614 relaxation factor controlling the amount of Laplacian smoothing applied 1615 strategy : (int) 1616 strategy to use for Laplacian smoothing 1617 - 0: use all points, all point data attributes are smoothed 1618 - 1: smooth all point attribute data except those on the boundary 1619 - 2: only point data connected to a boundary point are smoothed 1620 mask : (str, np.ndarray) 1621 array to be used as a mask (ignore then the strategy keyword) 1622 mode : (str) 1623 smoothing mode, either "distance2", "distance" or "average" 1624 - distance**2 weighted (i.e., 1/r**2 interpolation weights) 1625 - distance weighted (i.e., 1/r) approach; 1626 - simple average of all connected points in the stencil 1627 exclude : (list) 1628 list of arrays to be excluded from smoothing 1629 """ 1630 try: 1631 saf = vtki.new("AttributeSmoothingFilter") 1632 except: 1633 vedo.logger.error("smooth_data() only avaialble in VTK>=9.3.0") 1634 return self 1635 saf.SetInputData(self.dataset) 1636 saf.SetRelaxationFactor(relaxation_factor) 1637 saf.SetNumberOfIterations(niter) 1638 1639 for ex in exclude: 1640 saf.AddExcludedArray(ex) 1641 1642 if mode == "distance": 1643 saf.SetWeightsTypeToDistance() 1644 elif mode == "distance2": 1645 saf.SetWeightsTypeToDistance2() 1646 elif mode == "average": 1647 saf.SetWeightsTypeToAverage() 1648 else: 1649 vedo.logger.error(f"smooth_data(): unknown mode {mode}") 1650 raise TypeError 1651 1652 saf.SetSmoothingStrategy(strategy) 1653 if mask is not None: 1654 saf.SetSmoothingStrategyToSmoothingMask() 1655 if isinstance(mask, str): 1656 mask_ = self.dataset.GetPointData().GetArray(mask) 1657 if not mask_: 1658 vedo.logger.error(f"smooth_data(): mask array {mask} not found") 1659 return self 1660 mask_array = vtki.vtkUnsignedCharArray() 1661 mask_array.ShallowCopy(mask_) 1662 mask_array.SetName(mask_.GetName()) 1663 else: 1664 mask_array = utils.numpy2vtk(mask, dtype=np.uint8) 1665 saf.SetSmoothingMask(mask_array) 1666 1667 saf.Update() 1668 1669 self._update(saf.GetOutput()) 1670 self.pipeline = utils.OperationNode( 1671 "smooth_data", comment=f"strategy {strategy}", parents=[self], c="#9e2a2b" 1672 ) 1673 return self 1674 1675 def compute_streamlines( 1676 self, 1677 seeds: Any, 1678 integrator="rk4", 1679 direction="forward", 1680 initial_step_size=None, 1681 max_propagation=None, 1682 max_steps=10000, 1683 step_length=0, 1684 surface_constrained=False, 1685 compute_vorticity=False, 1686 ) -> Union["vedo.Lines", None]: 1687 """ 1688 Integrate a vector field to generate streamlines. 1689 1690 Arguments: 1691 seeds : (Mesh, Points, list) 1692 starting points of the streamlines 1693 integrator : (str) 1694 type of integration method to be used: 1695 - "rk2" (Runge-Kutta 2) 1696 - "rk4" (Runge-Kutta 4) 1697 - "rk45" (Runge-Kutta 45) 1698 direction : (str) 1699 direction of integration, either "forward", "backward" or "both" 1700 initial_step_size : (float) 1701 initial step size used for line integration 1702 max_propagation : (float) 1703 maximum length of a streamline expressed in absolute units 1704 max_steps : (int) 1705 maximum number of steps for a streamline 1706 step_length : (float) 1707 maximum length of a step expressed in absolute units 1708 surface_constrained : (bool) 1709 whether to stop integrating when the streamline leaves the surface 1710 compute_vorticity : (bool) 1711 whether to compute the vorticity at each streamline point 1712 """ 1713 b = self.dataset.GetBounds() 1714 size = (b[5]-b[4] + b[3]-b[2] + b[1]-b[0]) / 3 1715 if initial_step_size is None: 1716 initial_step_size = size / 1000.0 1717 1718 if max_propagation is None: 1719 max_propagation = size * 2 1720 1721 if utils.is_sequence(seeds): 1722 seeds = vedo.Points(seeds) 1723 1724 sti = vtki.new("StreamTracer") 1725 sti.SetSourceData(seeds.dataset) 1726 if isinstance(self, vedo.RectilinearGrid): 1727 sti.SetInputData(vedo.UnstructuredGrid(self.dataset).dataset) 1728 else: 1729 sti.SetInputDataObject(self.dataset) 1730