def booleanOperation(actor1, actor2, operation='plus', c=None, alpha=1, wire=False, bc=None, edges=False, legend=None, texture=None): '''Volumetric union, intersection and subtraction of surfaces''' try: bf = vtk.vtkBooleanOperationPolyDataFilter() except AttributeError: vio.printc('Boolean operation only possible for vtk version >= 8', 'r') return None poly1 = vu.polydata(actor1, True) poly2 = vu.polydata(actor2, True) if operation.lower() == 'plus': bf.SetOperationToUnion() elif operation.lower() == 'intersect': bf.SetOperationToIntersection() elif operation.lower() == 'minus': bf.SetOperationToDifference() bf.ReorientDifferenceCellsOn() if vu.vtkMV: bf.SetInputData(0, poly1) bf.SetInputData(1, poly2) else: bf.SetInputConnection(0, poly1.GetProducerPort()) bf.SetInputConnection(1, poly2.GetProducerPort()) bf.Update() actor = vu.makeActor(bf.GetOutput(), c, alpha, wire, bc, edges, legend, texture) return actor
def removeOutliers(points, radius, c='k', alpha=1, legend=None): ''' Remove outliers from a cloud of points within radius search ''' isactor = False if isinstance(points, vtk.vtkActor): isactor = True poly = vu.polydata(points) else: src = vtk.vtkPointSource() src.SetNumberOfPoints(len(points)) src.Update() vpts = src.GetOutput().GetPoints() for i, p in enumerate(points): vpts.SetPoint(i, p) poly = src.GetOutput() removal = vtk.vtkRadiusOutlierRemoval() vu.setInput(removal, poly) removal.SetRadius(radius) removal.SetNumberOfNeighbors(5) removal.GenerateOutliersOff() removal.Update() rpoly = removal.GetOutput() print("# of removed outlier points: ", removal.GetNumberOfPointsRemoved(), '/', poly.GetNumberOfPoints()) outpts = [] for i in range(rpoly.GetNumberOfPoints()): outpts.append(list(rpoly.GetPoint(i))) outpts = np.array(outpts) if not isactor: return outpts actor = vs.points(outpts, c=c, alpha=alpha, legend=legend) return actor # return same obj for concatenation
def decimate(actor, fraction=0.5, N=None, verbose=True, boundaries=True): ''' Downsample the number of vertices in a mesh. fraction gives the desired target of reduction. E.g. fraction=0.1 leaves 10% of the original nr of vertices. ''' poly = vu.polydata(actor, True) if N: # N = desired number of points Np = poly.GetNumberOfPoints() fraction = float(N) / Np if fraction >= 1: return actor decimate = vtk.vtkDecimatePro() vu.setInput(decimate, poly) decimate.SetTargetReduction(1. - fraction) decimate.PreserveTopologyOff() if boundaries: decimate.BoundaryVertexDeletionOn() else: decimate.BoundaryVertexDeletionOff() decimate.Update() if verbose: print('Input nr. of pts:', poly.GetNumberOfPoints(), end='') print(' output:', decimate.GetOutput().GetNumberOfPoints()) mapper = actor.GetMapper() vu.setInput(mapper, decimate.GetOutput()) mapper.Update() actor.Modified() if hasattr(actor, 'poly'): actor.poly = decimate.GetOutput() return actor # return same obj for concatenation
def subdivide(actor, N=1, method=0, legend=None): ''' Increase the number of points in actor surface N = number of subdivisions method = 0, Loop method = 1, Linear method = 2, Adaptive method = 3, Butterfly ''' triangles = vtk.vtkTriangleFilter() vu.setInput(triangles, vu.polydata(actor)) triangles.Update() originalMesh = triangles.GetOutput() if method == 0: sdf = vtk.vtkLoopSubdivisionFilter() elif method == 1: sdf = vtk.vtkLinearSubdivisionFilter() elif method == 2: sdf = vtk.vtkAdaptiveSubdivisionFilter() elif method == 3: sdf = vtk.vtkButterflySubdivisionFilter() else: vio.printc('Error in subdivide: unknown method.', 'r') exit(1) if method != 2: sdf.SetNumberOfSubdivisions(N) vu.setInput(sdf, originalMesh) sdf.Update() out = sdf.GetOutput() if legend is None and hasattr(actor, 'legend'): legend = actor.legend sactor = vu.makeActor(out, legend=legend) sactor.GetProperty().SetOpacity(actor.GetProperty().GetOpacity()) sactor.GetProperty().SetColor(actor.GetProperty().GetColor()) sactor.GetProperty().SetRepresentation( actor.GetProperty().GetRepresentation()) return sactor
def surfaceIntersection(actor1, actor2, tol=1e-06, lw=3, c=None, alpha=1, legend=None): '''Intersect 2 surfaces and return a line actor''' try: bf = vtk.vtkIntersectionPolyDataFilter() except AttributeError: vio.printc('surfaceIntersection only possible for vtk version > 6', 'r') return None poly1 = vu.polydata(actor1, True) poly2 = vu.polydata(actor2, True) bf.SetInputData(0, poly1) bf.SetInputData(1, poly2) bf.Update() if c is None: c = actor1.GetProperty().GetColor() actor = vu.makeActor(bf.GetOutput(), c, alpha, 0, legend=legend) actor.GetProperty().SetLineWidth(lw) return actor
def align(source, target, iters=100, legend=None): ''' Return a copy of source actor which is aligned to target actor through vtkIterativeClosestPointTransform() method. ''' sprop = source.GetProperty() source = vu.polydata(source) target = vu.polydata(target) icp = vtk.vtkIterativeClosestPointTransform() icp.SetSource(source) icp.SetTarget(target) icp.SetMaximumNumberOfIterations(iters) icp.StartByMatchingCentroidsOn() icp.Update() icpTransformFilter = vtk.vtkTransformPolyDataFilter() vu.setInput(icpTransformFilter, source) icpTransformFilter.SetTransform(icp) icpTransformFilter.Update() poly = icpTransformFilter.GetOutput() actor = vu.makeActor(poly, legend=legend) actor.SetProperty(sprop) setattr(actor, 'transform', icp.GetLandmarkTransform()) return actor
def intersectWithLine(act, p0, p1): '''Return a list of points between p0 and p1 intersecting the actor''' if not hasattr(act, 'linelocator'): linelocator = vtk.vtkOBBTree() linelocator.SetDataSet(vu.polydata(act, True)) linelocator.BuildLocator() setattr(act, 'linelocator', linelocator) intersectPoints = vtk.vtkPoints() intersection = [0, 0, 0] act.linelocator.IntersectWithLine(p0, p1, intersectPoints, None) pts = [] for i in range(intersectPoints.GetNumberOfPoints()): intersectPoints.GetPoint(i, intersection) pts.append(list(intersection)) return pts
def cluster(points, radius, legend=None): ''' Clustering of points in space. radius, is the radius of local search. Individual subsets can be accessed through actor.clusters ''' if isinstance(points, vtk.vtkActor): poly = vu.polydata(points) else: src = vtk.vtkPointSource() src.SetNumberOfPoints(len(points)) src.Update() vpts = src.GetOutput().GetPoints() for i, p in enumerate(points): vpts.SetPoint(i, p) poly = src.GetOutput() cluster = vtk.vtkEuclideanClusterExtraction() vu.setInput(cluster, poly) cluster.SetExtractionModeToAllClusters() cluster.SetRadius(radius) cluster.ColorClustersOn() cluster.Update() idsarr = cluster.GetOutput().GetPointData().GetArray('ClusterId') Nc = cluster.GetNumberOfExtractedClusters() sets = [[] for i in range(Nc)] for i, p in enumerate(points): sets[idsarr.GetValue(i)].append(p) acts = [] for i, aset in enumerate(sets): acts.append(vs.points(aset, c=i)) actor = vu.makeAssembly(acts, legend=legend) setattr(actor, 'clusters', sets) print('Nr. of extracted clusters', Nc) if Nc > 10: print('First ten:') for i in range(Nc): if i > 9: print('...') break print('Cluster #' + str(i) + ', N =', len(sets[i])) print('Access individual clusters through attribute: actor.cluster') return actor
def cutPlane(actor, origin=(0, 0, 0), normal=(1, 0, 0), showcut=True): ''' Takes actor and cuts it with the plane defined by a point and a normal. showcut = shows the cut away part as thin wireframe showline = marks with a thick line the cut ''' plane = vtk.vtkPlane() plane.SetOrigin(origin) plane.SetNormal(normal) poly = vu.polydata(actor) clipper = vtk.vtkClipPolyData() vu.setInput(clipper, poly) clipper.SetClipFunction(plane) clipper.GenerateClippedOutputOn() clipper.SetValue(0.) clipper.Update() if hasattr(actor, 'GetProperty'): alpha = actor.GetProperty().GetOpacity() c = actor.GetProperty().GetColor() bf = actor.GetBackfaceProperty() else: alpha = 1 c = 'gold' bf = None leg = None if hasattr(actor, 'legend'): leg = actor.legend clipActor = vu.makeActor(clipper.GetOutput(), c=c, alpha=alpha, legend=leg) clipActor.SetBackfaceProperty(bf) acts = [clipActor] if showcut: cpoly = clipper.GetClippedOutput() restActor = vu.makeActor(cpoly, c=c, alpha=0.05, wire=1) acts.append(restActor) if len(acts) > 1: asse = vu.makeAssembly(acts) return asse else: return clipActor
def cutterWidget(obj, outputname='clipped.vtk', c=(0.2, 0.2, 1), alpha=1, bc=(0.7, 0.8, 1), legend=None): '''Pop up a box widget to cut parts of actor. Return largest part.''' apd = vu.polydata(obj) planes = vtk.vtkPlanes() planes.SetBounds(apd.GetBounds()) clipper = vtk.vtkClipPolyData() vu.setInput(clipper, apd) clipper.SetClipFunction(planes) clipper.InsideOutOn() clipper.GenerateClippedOutputOn() # check if color string contains a float, in this case ignore alpha al = vc.getAlpha(c) if al: alpha = al act0Mapper = vtk.vtkPolyDataMapper() # the part which stays act0Mapper.SetInputConnection(clipper.GetOutputPort()) act0 = vtk.vtkActor() act0.SetMapper(act0Mapper) act0.GetProperty().SetColor(vc.getColor(c)) act0.GetProperty().SetOpacity(alpha) backProp = vtk.vtkProperty() backProp.SetDiffuseColor(vc.getColor(bc)) backProp.SetOpacity(alpha) act0.SetBackfaceProperty(backProp) #act0 = makeActor(clipper.GetOutputPort()) act0.GetProperty().SetInterpolationToFlat() vu.assignPhysicsMethods(act0) vu.assignConvenienceMethods(act0, legend) act1Mapper = vtk.vtkPolyDataMapper() # the part which is cut away act1Mapper.SetInputConnection(clipper.GetClippedOutputPort()) act1 = vtk.vtkActor() act1.SetMapper(act1Mapper) act1.GetProperty().SetColor(vc.getColor(c)) act1.GetProperty().SetOpacity(alpha / 10.) act1.GetProperty().SetRepresentationToWireframe() act1.VisibilityOn() ren = vtk.vtkRenderer() ren.SetBackground(1, 1, 1) ren.AddActor(act0) ren.AddActor(act1) renWin = vtk.vtkRenderWindow() renWin.AddRenderer(ren) renWin.SetSize(600, 700) iren = vtk.vtkRenderWindowInteractor() iren.SetRenderWindow(renWin) istyl = vtk.vtkInteractorStyleSwitch() istyl.SetCurrentStyleToTrackballCamera() iren.SetInteractorStyle(istyl) def SelectPolygons(vobj, event): vobj.GetPlanes(planes) boxWidget = vtk.vtkBoxWidget() boxWidget.OutlineCursorWiresOn() boxWidget.GetSelectedOutlineProperty().SetColor(1, 0, 1) boxWidget.GetOutlineProperty().SetColor(0.1, 0.1, 0.1) boxWidget.GetOutlineProperty().SetOpacity(0.8) boxWidget.SetPlaceFactor(1.05) boxWidget.SetInteractor(iren) vu.setInput(boxWidget, apd) boxWidget.PlaceWidget() boxWidget.AddObserver("InteractionEvent", SelectPolygons) boxWidget.On() vio.printc('\nCutterWidget:\n Move handles to cut parts of the actor', 'm') vio.printc(' Press q to continue, Escape to exit', 'm') vio.printc((" Press X to save file to", outputname), 'm') def cwkeypress(obj, event): key = obj.GetKeySym() if key == "q" or key == "space" or key == "Return": iren.ExitCallback() elif key == "X": confilter = vtk.vtkPolyDataConnectivityFilter() vu.setInput(confilter, clipper.GetOutput()) confilter.SetExtractionModeToLargestRegion() confilter.Update() cpd = vtk.vtkCleanPolyData() vu.setInput(cpd, confilter.GetOutput()) cpd.Update() vio.write(cpd.GetOutput(), outputname) elif key == "Escape": exit(0) iren.Initialize() iren.AddObserver("KeyPressEvent", cwkeypress) iren.Start() boxWidget.Off() return act0
def smoothMLS(actor, f=0.2, decimate=1, recursive=0, showNPlanes=0): ''' Smooth actor or points with a Moving Least Squares variant. The list actor.variances contain the residue calculated for each point. Input actor's polydata is modified. f, smoothing factor - typical range s [0,2] decimate, decimation factor (an integer number) recursive, move points while algorithm proceedes showNPlanes, build an actor showing the fitting plane for N random points ''' coords = vu.coordinates(actor) ncoords = len(coords) Ncp = int(ncoords * f / 100) nshow = int(ncoords / decimate) if showNPlanes: ndiv = int(nshow / showNPlanes * decimate) if Ncp < 5: vio.printc('Please choose a higher fraction than' + str(f), 1) Ncp = 5 print('smoothMLS: Searching #neighbours, #pt:', Ncp, ncoords) poly = vu.polydata(actor, True) vpts = poly.GetPoints() locator = vtk.vtkPointLocator() locator.SetDataSet(poly) locator.BuildLocator() vtklist = vtk.vtkIdList() variances, newsurf, acts = [], [], [] pb = vio.ProgressBar(0, ncoords) for i, p in enumerate(coords): pb.print('smoothing...') if i % decimate: continue locator.FindClosestNPoints(Ncp, p, vtklist) points = [] for j in range(vtklist.GetNumberOfIds()): trgp = [0, 0, 0] vpts.GetPoint(vtklist.GetId(j), trgp) points.append(trgp) if len(points) < 5: continue points = np.array(points) pointsmean = points.mean(axis=0) # plane center uu, dd, vv = np.linalg.svd(points - pointsmean) a, b, c = np.cross(vv[0], vv[1]) # normal d, e, f = pointsmean # plane center x, y, z = p t = (a * d - a * x + b * e - b * y + c * f - c * z) #/(a*a+b*b+c*c) newp = [x + t * a, y + t * b, z + t * c] variances.append(dd[2]) newsurf.append(newp) if recursive: vpts.SetPoint(i, newp) if showNPlanes and not i % ndiv: plane = fitPlane(points, alpha=0.3) # fitting plane iapts = vs.points(points) # blue points acts += [plane, iapts] if decimate == 1 and not recursive: for i in range(ncoords): vpts.SetPoint(i, newsurf[i]) setattr(actor, 'variances', np.array(variances)) if showNPlanes: apts = vs.points(newsurf, c='r 0.6', r=2) ass = vu.makeAssembly([apts] + acts) return ass #NB: a demo actor is returned return actor #NB: original actor is modified
def fxy(z='sin(3*x)*log(x-y)/3', x=[0, 3], y=[0, 3], zlimits=[None, None], showNan=True, zlevels=10, wire=False, c='b', bc='aqua', alpha=1, legend=True, texture=None, res=100): ''' Build a surface representing the 3D function specified as a string or as a reference to an external function. Red points indicate where the function does not exist (showNan). zlevels will draw the specified number of z-levels contour lines. Examples: vp = plotter.vtkPlotter() vp.fxy('sin(3*x)*log(x-y)/3') or def z(x,y): return math.sin(x*y) vp.fxy(z) # or equivalently: vp.fxy(lambda x,y: math.sin(x*y)) ''' if isinstance(z, str): try: z = z.replace('math.', '').replace('np.', '') namespace = locals() code = "from math import*\ndef zfunc(x,y): return " + z exec(code, namespace) z = namespace['zfunc'] except: vio.printc('Syntax Error in fxy()', 1) return None ps = vtk.vtkPlaneSource() ps.SetResolution(res, res) ps.SetNormal([0, 0, 1]) ps.Update() poly = ps.GetOutput() dx = x[1] - x[0] dy = y[1] - y[0] todel, nans = [], [] if zlevels: tf = vtk.vtkTriangleFilter() vu.setInput(tf, poly) tf.Update() poly = tf.GetOutput() for i in range(poly.GetNumberOfPoints()): px, py, _ = poly.GetPoint(i) xv = (px + .5) * dx + x[0] yv = (py + .5) * dy + y[0] try: zv = z(xv, yv) poly.GetPoints().SetPoint(i, [xv, yv, zv]) except: todel.append(i) nans.append([xv, yv, 0]) if len(todel): cellIds = vtk.vtkIdList() poly.BuildLinks() for i in todel: poly.GetPointCells(i, cellIds) for j in range(cellIds.GetNumberOfIds()): poly.DeleteCell(cellIds.GetId(j)) #flag cell poly.RemoveDeletedCells() cl = vtk.vtkCleanPolyData() vu.setInput(cl, poly) cl.Update() poly = cl.GetOutput() if not poly.GetNumberOfPoints(): vio.printc('Function is not real in the domain', 1) return vtk.vtkActor() if zlimits[0]: a = cutPlane(poly, (0, 0, zlimits[0]), (0, 0, 1), False) poly = vu.polydata(a) if zlimits[1]: a = cutPlane(poly, (0, 0, zlimits[1]), (0, 0, -1), False) poly = vu.polydata(a) if c is None: elev = vtk.vtkElevationFilter() vu.setInput(elev, poly) elev.Update() poly = elev.GetOutput() actor = vu.makeActor(poly, c=c, bc=bc, alpha=alpha, wire=wire, legend=legend, texture=texture) acts = [actor] if zlevels: elevation = vtk.vtkElevationFilter() vu.setInput(elevation, poly) bounds = poly.GetBounds() elevation.SetLowPoint(0, 0, bounds[4]) elevation.SetHighPoint(0, 0, bounds[5]) elevation.Update() bcf = vtk.vtkBandedPolyDataContourFilter() vu.setInput(bcf, elevation.GetOutput()) bcf.SetScalarModeToValue() bcf.GenerateContourEdgesOn() bcf.GenerateValues(zlevels, elevation.GetScalarRange()) bcf.Update() zpoly = bcf.GetContourEdgesOutput() zbandsact = vu.makeActor(zpoly, c='k', alpha=alpha) zbandsact.GetProperty().SetLineWidth(1.5) acts.append(zbandsact) if showNan and len(todel): bb = actor.GetBounds() zm = (bb[4] + bb[5]) / 2 nans = np.array(nans) + [0, 0, zm] nansact = vs.points(nans, c='red', alpha=alpha / 2) acts.append(nansact) if len(acts) > 1: asse = vu.makeAssembly(acts) return asse else: return actor