diff --git a/docs/changes.md b/docs/changes.md index bea33d95..e8e02864 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -14,6 +14,7 @@ - add `transformations.__call__()` to apply it - fix small bug in `pointcloud.distance_to()` - add `applications.MorphPlotter()` to morph a polygonal mesh to a target mesh +- add `smooth_data()` to smooth/diffuse data attributes ## Breaking changes @@ -40,6 +41,7 @@ ## New/Revised Examples ``` examples/advanced/warp4c.py +examples/advanced/diffuse_data.py examples/volumetric/slab_vol.py examples/other/madcad1.py examples/other/tetgen1.py diff --git a/docs/examples_db.js b/docs/examples_db.js index 2431e5a8..8ee91934 100755 --- a/docs/examples_db.js +++ b/docs/examples_db.js @@ -702,6 +702,14 @@ vedo_example_db = long : 'Interpolate cell values from a quad-mesh to a tri-mesh of different resolution', imgsrc: 'images/advanced/interpolateScalar4.png', }, + { + pyname: 'diffuse_data', + kbd : '', + categ : 'advanced', + short : 'smooth array', + long : 'Smooth/diffuse an array of scalars on a mesh', + imgsrc: 'images/advanced/diffuse_data.png', + }, { pyname: 'cut_with_mesh1', kbd : '', diff --git a/examples/advanced/diffuse_data.py b/examples/advanced/diffuse_data.py new file mode 100644 index 00000000..28c7f057 --- /dev/null +++ b/examples/advanced/diffuse_data.py @@ -0,0 +1,36 @@ +import numpy as np +from vedo import Grid, settings, show +from vedo.pyplot import histogram + +settings.default_font = "FiraMonoMedium" + +grid = Grid(res=[50,50]) +grid.wireframe(False).lw(0) + +values = np.zeros(grid.npoints) +values[int(grid.npoints/2)] = 1 +values[int(grid.npoints/5)] = 1 + +grid.pointdata["scalars"] = values +grid.cmap("Set1_r").add_scalarbar() + +grid2 = grid.clone() +grid2.smooth_data(niter=750, relaxation_factor=0.1, strategy=1) +grid2.cmap("Set1_r").add_scalarbar() + +his = histogram( + grid2.pointdata["scalars"], + c='k4', + xtitle="Concentration", + ytitle="Frequency", + axes=dict(htitle="", axes_linewidth=2, xyframe_line=0), +) +his = his.clone2d() # fix it to screen coords + +print("integrated over domain:", grid2.integrate_arrays_over_domain()) + +show([ + ["Initial state", grid], + ["After diffusion", grid2, his]], + N=2, axes=1, +).close() diff --git a/vedo/core.py b/vedo/core.py index 9e4e7c5b..5b5beae4 100644 --- a/vedo/core.py +++ b/vedo/core.py @@ -1384,6 +1384,63 @@ def shrink(self, fraction=0.8): ) return self + def smooth_data(self, + niter=10, relaxation_factor=0.1, strategy=0, mask=None, + exclude=("Normals", "TextureCoordinates"), + ): + """ + Smooth point attribute data using distance weighted Laplacian kernel. + + The effect is to blur regions of high variation and emphasize low variation regions. + + Arguments: + niter : (int) + number of iterations + relaxation_factor : (float) + relaxation factor controlling the amount of Laplacian smoothing applied + strategy : (int) + strategy to use for Laplacian smoothing + - 0: use all points, all point data attributes are smoothed + - 1: smooth all point attribute data except those on the boundary + - 2: only point data connected to a boundary point are smoothed + mask : (str, np.ndarray) + array to be used as a mask (ignore then the strategy keyword) + exclude : (list) + list of arrays to be excluded from smoothing + """ + saf = vtk.new("AttributeSmoothingFilter") + saf.SetInputData(self.dataset) + saf.SetRelaxationFactor(relaxation_factor) + saf.SetNumberOfIterations(niter) + + for ex in exclude: + saf.AddExcludedArray(ex) + + saf.SetWeightsTypeToDistance2() + + saf.SetSmoothingStrategy(strategy) + if mask is not None: + saf.SetSmoothingStrategyToSmoothingMask() + if isinstance(mask, str): + mask_ = self.dataset.GetPointData().GetArray(mask) + if not mask_: + vedo.logger.error(f"smooth_data(): mask array {mask} not found") + return self + mask_array = vtk.vtkUnsignedCharArray() + mask_array.ShallowCopy(mask_) + mask_array.SetName(mask_.GetName()) + else: + mask_array = utils.numpy2vtk(mask, dtype=np.uint8) + saf.SetSmoothingMask(mask_array) + + saf.Update() + + self._update(saf.GetOutput()) + self.pipeline = utils.OperationNode( + "smooth_data", comment=f"strategy {strategy}", parents=[self], c="#9e2a2b" + ) + return self + ############################################################################### class PointAlgorithms(CommonAlgorithms): diff --git a/vedo/vtkclasses.py b/vedo/vtkclasses.py index 4d7f8483..995d5d21 100644 --- a/vedo/vtkclasses.py +++ b/vedo/vtkclasses.py @@ -336,8 +336,9 @@ location["vtkCellTreeLocator"] = "vtkFiltersGeneral" -location["vtkGeometryFilter"] = "vtkFiltersGeometry" +location["vtkAttributeSmoothingFilter"] = "vtkFiltersGeometry" location["vtkDataSetSurfaceFilter"] = "vtkFiltersGeometry" +location["vtkGeometryFilter"] = "vtkFiltersGeometry" location["vtkImageDataGeometryFilter"] = "vtkFiltersGeometry"