def add_line_at_point(self, point, axis, color="blackboard", lw=3, **kwargs): """ Adds a line oriented on a given axis at a point :param point:list or 1d np array with coordinates of point where crosshair is centered :param replace_coord: index of the coordinate to replace (i.e. along which axis is the line oriented) :param bounds: list of two floats with lower and upper bound for line, determins the extent of the line :param kwargs: dictionary with arguments to specify how lines should look like """ # TODO bgspace could be used here axis_dict = dict(rostrocaudal=0, dorsoventral=1, mediolateral=2) replace_coord = axis_dict[axis] bounds = self.atlas._root_bounds[replace_coord] # Get line coords p0, p1 = point.copy(), point.copy() p0[replace_coord] = bounds[0] p1[replace_coord] = bounds[1] # Create line actor line = Line(p0, p1, c=color, lw=lw, **kwargs) line.name = f"line through {point}" line._br_class = "line" return self.add_actor(line)
def _onMouseMove(self, evt): if self.drawmode: cpt = self.computeWorldPosition( evt.picked2d) # make this 2d-screen point 3d if self.cpoints and mag(cpt - self.cpoints[-1] ) < self.mesh.diagonalSize() * self.tol: return # new point is too close to the last one. skip self.cpoints.append(cpt) if len(self.cpoints) > 2: self.remove( [self.points, self.spline, self.jline, self.topline]) self.points = Points(self.cpoints, r=self.linewidth).c( self.pointcolor).pickable(0) if self.splined: self.spline = Spline(self.cpoints, res=len(self.cpoints) * 4) # not closed here else: self.spline = Line(self.cpoints) if evt.actor: self.top_pts.append(evt.picked3d) # self.topline = Line(self.top_pts) # self.topline.lw(self.linewidth-1).c(self.linecolor).pickable(False) self.topline = Points(self.top_pts, r=self.linewidth) self.topline.c(self.linecolor).pickable(False) self.spline.lw(self.linewidth).c( self.linecolor).pickable(False) self.txt2d.background(self.linecolor) self.jline = Line(self.cpoints[0], self.cpoints[-1], lw=1, c=self.linecolor).pickable(0) self.add([self.points, self.spline, self.jline, self.topline])
def ruler(p1, p2, unit_scale=1, units=None, s=50): actors = [] # Make two line segments midpoint = np.array([(x + y) / 2 for x, y in zip(p1, p2)]) gap1 = ((midpoint - p1) * 0.8) + p1 gap2 = ((midpoint - p2) * 0.8) + p2 actors.append(Line(p1, gap1, lw=200)) actors.append(Line(gap2, p2, lw=200)) # Add label if units is None: units = "" dist = mag(p2 - p1) * unit_scale label = precision(dist, 3) + " " + units lbl = Text(label, pos=midpoint, s=s + 100, justify="center") lbl.SetOrientation([0, 0, 180]) actors.append(lbl) # Add spheres add end actors.append(Sphere(p1, r=s, c=[0.3, 0.3, 0.3])) actors.append(Sphere(p2, r=s, c=[0.3, 0.3, 0.3])) acts = merge(*actors).c((0.3, 0.3, 0.3)).alpha(1).lw(2) acts.name = "Ruler" acts.bg_class = "Ruler" return acts
def _onRightClick(self, evt): self.drawmode = not self.drawmode # toggle mode if self.drawmode: self.txt2d.background(self.linecolor, self.alpha) else: self.txt2d.background(self.color, self.alpha) if len(self.cpoints) > 2: self.remove([self.spline, self.jline]) if self.splined: # show the spline closed self.spline = Spline(self.cpoints, closed=True, res=len(self.cpoints)*4) else: self.spline = Line(self.cpoints, closed=True) self.spline.lw(self.linewidth).c(self.linecolor).pickable(False) self.add(self.spline)
def init(self, initpoints): if isinstance(initpoints, Points): self.cpoints = initpoints.points() else: self.cpoints = np.array(initpoints) self.points = Points(self.cpoints, r=self.linewidth).c(self.pointcolor).pickable(0) if self.splined: self.spline = Spline(self.cpoints, res=len(self.cpoints)*4) else: self.spline = Line(self.cpoints) self.spline.lw(self.linewidth).c(self.linecolor).pickable(False) self.jline = Line(self.cpoints[0], self.cpoints[-1], lw=1, c=self.linecolor).pickable(0) self.add([self.points, self.spline, self.jline], render=False) return self
def ruler(p1, p2, unit_scale=1, units=None, s=50): """ Creates a ruler showing the distance between two points. The ruler is composed of a line between the points and a text indicating the distance. :param p1: list, np.ndarray with coordinates of first point :param p2: list, np.ndarray with coordinates of second point :param unit_scale: float. To scale the units (e.g. show mm instead of µm) :param units: str, name of unit (e.g. 'mm') :param s: float size of text """ actors = [] # Make two line segments midpoint = np.array([(x + y) / 2 for x, y in zip(p1, p2)]) gap1 = ((midpoint - p1) * 0.8) + p1 gap2 = ((midpoint - p2) * 0.8) + p2 actors.append(Line(p1, gap1, lw=200)) actors.append(Line(gap2, p2, lw=200)) # Add label if units is None: # pragma: no cover units = "" # pragma: no cover dist = mag(p2 - p1) * unit_scale label = precision(dist, 3) + " " + units lbl = Text(label, pos=midpoint, s=s + 100, justify="center") lbl.SetOrientation([0, 0, 180]) actors.append(lbl) # Add spheres add end actors.append(Sphere(p1, r=s, c=[0.3, 0.3, 0.3])) actors.append(Sphere(p2, r=s, c=[0.3, 0.3, 0.3])) act = Actor(merge(*actors), name="Ruler", br_class="Ruler") act.c((0.3, 0.3, 0.3)).alpha(1).lw(2) return act
class FreeHandCutPlotter(Plotter): """ A Plotter derived class which edits polygonal meshes interactively. Can also be invoked from command line. E.g. with: ``vedo --edit https://vedo.embl.es/examples/data/porsche.ply`` Usage ----- - Left-click and hold to rotate - Right-click and move to draw line - Second right-click to stop drawing - Press c to clear points - z/Z to cut mesh (Z inverts inside-out the selection area) - L to keep only the largest connected surface - s to save mesh to file (tag _edited is appended to filename) - u to undo last action - h for help, i for info Parameters ---------- mesh : Mesh, Points The input Mesh or pointcloud. splined : bool, optional join points with a spline or a simple line. The default is True. font : str, optional Font name for the instructions. The default is "Bongas". alpha : float, optional transparency of the instruction message panel. The default is 0.9. lw : str, optional selection line width. The default is 3. lc : str, optional selection line color. The default is "red5". pc : str, optional selection points color. The default is "black". c : str, optional backgound color of instructions. The default is "green3". tc : str, optional text color of instructions. The default is "white". tol : int, optional tolerance of the point proximity. Default is 5. """ # thanks to Jakub Kaminski for the original version of this script def __init__( self, mesh, splined=True, font="Bongas", alpha=0.9, lw=4, lc="red5", pc="red4", c="green3", tc="k9", tol=0.008, ): if not isinstance(mesh, Points): printc("FreeHandCutPlotter input must be Points or Mesh.", c='r') raise RuntimeError() Plotter.__init__(self, title="Free-hand mesh cutter") self.mesh = mesh self.mesh_prev = mesh self.splined = splined self.linecolor = lc self.linewidth = lw self.pointcolor = pc self.color = c self.alpha = alpha self.msg = "Right-click and move to draw line\n" self.msg += "Second right-click to stop drawing\n" self.msg += "Press L to extract largest surface\n" self.msg += " z/Z to cut mesh (s to save)\n" self.msg += " c to clear points, u to undo" self.txt2d = Text2D(self.msg, pos='top-left', font=font, s=0.9) self.txt2d.c(tc).background(c, alpha).frame() self.idkeypress = self.addCallback('KeyPress', self._onKeyPress) self.idrightclck = self.addCallback('RightButton', self._onRightClick) self.idmousemove = self.addCallback('MouseMove', self._onMouseMove) self.drawmode = False self.tol = tol # tolerance of point distance self.cpoints = [] self.points = None self.spline = None self.jline = None self.topline = None self.top_pts = [] def init(self, initpoints): if isinstance(initpoints, Points): self.cpoints = initpoints.points() else: self.cpoints = np.array(initpoints) self.points = Points(self.cpoints, r=self.linewidth).c(self.pointcolor).pickable(0) if self.splined: self.spline = Spline(self.cpoints, res=len(self.cpoints) * 4) else: self.spline = Line(self.cpoints) self.spline.lw(self.linewidth).c(self.linecolor).pickable(False) self.jline = Line(self.cpoints[0], self.cpoints[-1], lw=1, c=self.linecolor).pickable(0) self.add([self.points, self.spline, self.jline], render=False) return self def _onRightClick(self, evt): self.drawmode = not self.drawmode # toggle mode if self.drawmode: self.txt2d.background(self.linecolor, self.alpha) else: self.txt2d.background(self.color, self.alpha) if len(self.cpoints) > 2: self.remove([self.spline, self.jline]) if self.splined: # show the spline closed self.spline = Spline(self.cpoints, closed=True, res=len(self.cpoints) * 4) else: self.spline = Line(self.cpoints, closed=True) self.spline.lw(self.linewidth).c( self.linecolor).pickable(False) self.add(self.spline) def _onMouseMove(self, evt): if self.drawmode: cpt = self.computeWorldPosition( evt.picked2d) # make this 2d-screen point 3d if self.cpoints and mag(cpt - self.cpoints[-1] ) < self.mesh.diagonalSize() * self.tol: return # new point is too close to the last one. skip self.cpoints.append(cpt) if len(self.cpoints) > 2: self.remove( [self.points, self.spline, self.jline, self.topline]) self.points = Points(self.cpoints, r=self.linewidth).c( self.pointcolor).pickable(0) if self.splined: self.spline = Spline(self.cpoints, res=len(self.cpoints) * 4) # not closed here else: self.spline = Line(self.cpoints) if evt.actor: self.top_pts.append(evt.picked3d) # self.topline = Line(self.top_pts) # self.topline.lw(self.linewidth-1).c(self.linecolor).pickable(False) self.topline = Points(self.top_pts, r=self.linewidth) self.topline.c(self.linecolor).pickable(False) self.spline.lw(self.linewidth).c( self.linecolor).pickable(False) self.txt2d.background(self.linecolor) self.jline = Line(self.cpoints[0], self.cpoints[-1], lw=1, c=self.linecolor).pickable(0) self.add([self.points, self.spline, self.jline, self.topline]) def _onKeyPress(self, evt): if evt.keyPressed.lower( ) == 'z' and self.spline: # Cut mesh with a ribbon-like surface inv = False if evt.keyPressed == 'Z': inv = True self.txt2d.background('red8').text(" ... working ... ") self.render() self.mesh_prev = self.mesh.clone() tol = self.mesh.diagonalSize() / 2 # size of ribbon (not shown) pts = self.spline.points() n = fitPlane(pts, signed=True).normal # compute normal vector to points rb = Ribbon(pts - tol * n, pts + tol * n, closed=True) self.mesh.cutWithMesh(rb, invert=inv) # CUT self.txt2d.text(self.msg) # put back original message if self.drawmode: self._onRightClick(evt) # toggle mode to normal else: self.txt2d.background(self.color, self.alpha) self.remove([self.spline, self.points, self.jline, self.topline]).render() self.cpoints, self.points, self.spline = [], None, None self.top_pts, self.topline = [], None elif evt.keyPressed == 'L': self.txt2d.background('red8') self.txt2d.text( " ... removing smaller ... \n ... parts of the mesh ... ") self.render() self.remove(self.mesh) self.mesh_prev = self.mesh mcut = self.mesh.extractLargestRegion() mcut.filename = self.mesh.filename # copy over various properties mcut.name = self.mesh.name mcut.scalarbar = self.mesh.scalarbar mcut.info = self.mesh.info self.mesh = mcut # discard old mesh by overwriting it self.txt2d.text(self.msg).background( self.color) # put back original message self.add(mcut) elif evt.keyPressed == 'u': # Undo last action if self.drawmode: self._onRightClick(evt) # toggle mode to normal else: self.txt2d.background(self.color, self.alpha) self.remove([ self.mesh, self.spline, self.jline, self.points, self.topline ]) self.mesh = self.mesh_prev self.cpoints, self.points, self.spline = [], None, None self.top_pts, self.topline = [], None self.add(self.mesh) elif evt.keyPressed == 'c' or evt.keyPressed == 'Delete': # clear all points self.remove([self.spline, self.points, self.jline, self.topline]).render() self.cpoints, self.points, self.spline = [], None, None self.top_pts, self.topline = [], None elif evt.keyPressed == 'r': # reset camera and axes try: self.remove(self.axes_instances[0]) self.axes_instances[0] = None self.addGlobalAxes(axtype=1, c=None) self.renderer.ResetCamera() self.interactor.Render() except: pass elif evt.keyPressed == 's': if self.mesh.filename: fname = os.path.basename(self.mesh.filename) fname, extension = os.path.splitext(fname) fname = fname.replace("_edited", "") fname = f"{fname}_edited{extension}" else: fname = "mesh_edited.vtk" self.write(fname) def write(self, filename="mesh_edited.vtk"): self.mesh.write(filename) printc("\save saved to file:", filename, c='lb', invert=True) return self def start(self, *args, **kwargs): acts = [self.txt2d, self.mesh, self.points, self.spline, self.jline] self.show(acts + list(args), **kwargs) return self