Example #1
0
    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)
Example #2
0
    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])
Example #3
0
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
Example #4
0
 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)
Example #5
0
 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
Example #6
0
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
Example #7
0
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