Exemplo n.º 1
0
class Ball2:
    """Class containg a n number of balls. It uses vispy to visualization."""
    def __init__(self, position, velocity, boundaries = None, color = (1.0, 1.0, 1.0, 1.0)):
        self.n = position.shape[0]
        self.pos = position
        self.vel = velocity
        self.color = color
        self.rad = 0.1
        self.bound = None
        self.sizexyz = [None] * 3
        if boundaries is not None:
            self.set_bound(boundaries)
        self.visual = None

    def set_bound(self, boundaries):
        """Updates the boundaries."""
        self.bound = boundaries
        self.sizexyz = np.abs(boundaries[:,1] - boundaries[:,0])

    def step(self, time_step):
        """Calculate the new positions and speeds for all balls."""
        despl = self.vel * time_step
        self.pos = self.pos + despl
        for i in range(3):
            if self.sizexyz[i] is not None:
#-INF                offbound = np.where(self.pos[:,i] < self.bound[i,0])
#-INF                self.pos[offbound,i] = self.pos[offbound,i] + self.sizexyz[i]
#-INF                offbound = np.where(self.pos[:,i] > self.bound[i,1])
#-INF                self.pos[offbound,i] = self.pos[offbound,i] - self.sizexyz[i]
                offbound = np.where(self.pos[:,i] < self.bound[i,0])
                self.pos[offbound,i] = self.pos[offbound,i] - despl[offbound,i]
                self.vel[offbound,i] = - self.vel[offbound,i] * 0.8
                offbound = np.where(self.pos[:,i] > self.bound[i,1])
                self.pos[offbound,i] = self.pos[offbound,i] - despl[offbound,i]
                self.vel[offbound,i] = - self.vel[offbound,i] * 0.95
        self.vel[:,2] = self.vel[:,2] - 0.1
        self.update_visual()

    def init_visual(self, view):
        """Initialize the object visual."""
        num_seg = sphere_pt.shape[0]
        all_seg = np.zeros((num_seg * self.n, 3))
        for i in range(self.n):
            all_seg[i * num_seg: (i + 1) * num_seg] = sphere_pt * self.rad + self.pos[i,:]
        self.visual = Line(pos = all_seg, color=self.color, connect='segments')
        view.add(self.visual)

    def update_visual(self):
        """Updates the object visual."""
        num_seg = sphere_pt.shape[0]
        all_seg = np.zeros((num_seg * self.n, 3))
        for i in range(self.n):
            all_seg[i * num_seg: (i + 1) * num_seg] = sphere_pt * self.rad + self.pos[i,:]
        self.visual.set_data(pos = all_seg)

    def shake(self):
        """Inverts the z position and gives all the balls random velocity."""
        if self.sizexyz[2] is not None:
            self.pos[:,2] = self.bound[2, 1] - (self.pos[:,2] - self.bound[2, 0])
            self.vel = (np.random.rand(self.n, 3) - 0.5) * 10
Exemplo n.º 2
0
class Balloon:
    """Balloon Class. It uses vispy to visualization."""
    def __init__(self, position, velocity, boundaries = None, color = (1.0, 1.0, 1.0, 1.0)):
        self.pos = position
        self.vel = velocity
        self.color = color
        self.rad = 0.5
        self.bound = None
        self.sizexyz = [None] * 3
        if boundaries is not None:
            self.set_bound(boundaries)
        self.visual = None

    def set_bound(self, boundaries):
        """Updates the boundaries."""
        self.bound = boundaries
        self.sizexyz = np.abs(boundaries[:,1] - boundaries[:,0])

    def step(self, time_step):
        """Does nothing."""
        pass

    def init_visual(self, view):
        """Initialize the object visual."""
        self.visual = Line(pos = sphere_pt * self.rad + self.pos, color=self.color)
        view.add(self.visual)

    def update_visual(self):
        """Updates the object visual."""
        self.visual.set_data(pos = sphere_pt * self.rad + self.pos)

    def shake(self):
        """Changes to a random color."""
        self.color = np.random.rand(4) / 2 + 0.5
        self.visual.set_data(color=self.color)
Exemplo n.º 3
0
class Ball_trace:
    """Ball Class. It uses vispy to visualization."""
    def __init__(self, position, velocity, boundaries = None, color = (1.0, 1.0, 1.0, 1.0)):
        self.pos = position
        self.vel = velocity
        self.color = color
        self.rad = 0.1
        self.bound = None
        self.sizexyz = [None] * 3
        self.tail_steps = 20
        if boundaries is not None:
            self.set_bound(boundaries)
        self.visual = [None]

    def set_bound(self, boundaries):
        """Updates the boundaries."""
        self.bound = boundaries
        self.sizexyz = np.abs(boundaries[:,1] - boundaries[:,0])

    def step(self, time_step):
        """Calculate the new position and speed."""
        despl = self.vel * time_step
        self.pos = self.pos + despl
        for i in range(3):
#-INF            if self.sizexyz[i] is not None and self.pos[i] < self.bound[i,0]:
#-INF                self.pos[i] = self.pos[i] + self.sizexyz[i]
#-INF            elif self.sizexyz[i] is not None and self.pos[i] > self.bound[i,1]:
#-INF                self.pos[i] = self.pos[i] - self.sizexyz[i]
            if self.sizexyz[i] is not None:
                if self.pos[i] < self.bound[i,0] or self.pos[i] > self.bound[i,1]:
                    self.pos[i] = self.pos[i] - despl[i]
                    self.vel[i] = - self.vel[i] * 0.95
        self.vel[2] = self.vel[2] - 0.1
        self.update_visual()

    def init_visual(self, view):
        """Initialize the object visual."""
        self.trace = np.repeat(self.pos, self.tail_steps).reshape((3,self.tail_steps)).T
        pos = np.concatenate([sphere_pt * self.rad + self.pos, self.trace])
        self.visual = Line(pos = pos, color=self.color)
        view.add(self.visual)

    def update_visual(self):
        """Updates the object visual."""
        self.trace[1:] = self.trace[0:-1]
        self.trace[0] = self.pos
        pos = np.concatenate([sphere_pt * self.rad + self.pos, self.trace])
        self.visual.set_data(pos = pos)

    def shake(self):
        """Inverts the z position and gives the ball a random velocity."""
        if self.sizexyz[2] is not None:
            self.pos[2] = self.bound[2, 1] - (self.pos[2] - self.bound[2, 0])
            self.vel = (np.random.rand(3) - 0.5) * 10
            self.trace = np.repeat(self.pos, self.tail_steps).reshape((3,self.tail_steps)).T
Exemplo n.º 4
0
 def __init__(self, **kwds):
     verts = np.array([[0, 0, 0],
                       [1, 0, 0],
                       [0, 0, 0],
                       [0, 1, 0],
                       [0, 0, 0],
                       [0, 0, 1]])
     color = np.array([[1, 0, 0, 1],
                       [1, 0, 0, 1],
                       [0, 1, 0, 1],
                       [0, 1, 0, 1],
                       [0, 0, 1, 1],
                       [0, 0, 1, 1]])
     Line.__init__(self, pos=verts, color=color, connect='segments',
                   mode='gl', **kwds)
Exemplo n.º 5
0
 def init_visual(self, view):
     """Initialize the object visual."""
     self.trace = np.repeat(self.pos, self.tail_steps).reshape((3,self.tail_steps)).T
     self.trace[:,0] = range(self.tail_steps,0,-1)
     self.trace = self.trace / 100
     self.visual = Line(pos = self.trace, color=self.color)
     view.add(self.visual)
Exemplo n.º 6
0
 def init_visual(self, view):
     """Initialize the object visual."""
     num_seg = sphere_pt.shape[0]
     all_seg = np.zeros((num_seg * self.n, 3))
     for i in range(self.n):
         all_seg[i * num_seg: (i + 1) * num_seg] = sphere_pt * self.rad + self.pos[i,:]
     self.visual = Line(pos = all_seg, color=self.color, connect='segments')
     view.add(self.visual)
Exemplo n.º 7
0
class Mouse_trace:
    """Mouse tracing Class. It uses vispy to visualization."""
    def __init__(self, color = (1.0, 1.0, 1.0, 1.0)):
        self.mouse = PyMouse()
        self.pos = np.asarray([0, 0, 0])
        self.color = color
        self.rad = 20.0
        size = self.mouse.screen_size()
        boundaries = np.asarray([[0, size[0]], [0, size[1]], [0, 1]])
        print(boundaries)
        self.sizexyz = [None] * 3
        self.tail_steps = 200
        self.set_bound(boundaries)
        self.visual = [None]

    def set_bound(self, boundaries):
        """Updates the boundaries."""
        self.bound = boundaries
        self.sizexyz = np.abs(boundaries[:,1] - boundaries[:,0])

    def step(self, time_step):
        """Calculate the new position and speed."""
        mpos = self.mouse.position()
        self.pos = np.asarray([mpos[0], self.bound[1,1] - mpos[1], 0])
        self.update_visual()

    def init_visual(self, view):
        """Initialize the object visual."""
        self.trace = np.repeat(self.pos, self.tail_steps).reshape((3,self.tail_steps)).T
        pos = np.concatenate([sphere_pt * self.rad + self.pos, self.trace])
        self.visual = Line(pos = pos, color=self.color)
        view.add(self.visual)

    def update_visual(self):
        """Updates the object visual."""
        self.trace[1:] = self.trace[0:-1]
        self.trace[0] = self.pos
        pos = np.concatenate([sphere_pt * self.rad + self.pos, self.trace])
        self.visual.set_data(pos = pos)

    def shake(self):
        """Inverts the z position and gives the ball a random velocity."""
        pass
Exemplo n.º 8
0
class Cpu_trace:
    """Mouse tracing Class. It uses vispy to visualization."""
    def __init__(self, color = (1.0, 1.0, 1.0, 1.0)):
        self.pos = np.asarray([0, 0, 0], dtype = np.float32)
        self.color = color
        self.sizexyz = [None] * 3
        self.tail_steps = 100
        self.bound = None
        self.visual = [None]

    def set_bound(self, boundaries):
        """Updates the boundaries."""
        self.bound = boundaries
        self.sizexyz = np.abs(boundaries[:,1] - boundaries[:,0])

    def step(self, time_step):
        """Calculate the new position and speed."""
        cpu = np.float(cpuutilization.get_utilization())
        self.pos = np.asarray([100, cpu, 0])
        self.update_visual()

    def init_visual(self, view):
        """Initialize the object visual."""
        self.trace = np.repeat(self.pos, self.tail_steps).reshape((3,self.tail_steps)).T
        self.trace[:,0] = range(self.tail_steps,0,-1)
        self.trace = self.trace / 100
        self.visual = Line(pos = self.trace, color=self.color)
        view.add(self.visual)

    def update_visual(self):
        """Updates the object visual."""
        self.trace[1:,1] = self.trace[0:-1,1]
        self.trace[0] = self.pos / 100
        self.visual.set_data(pos = self.trace)

    def shake(self):
        """Inverts the z position and gives the ball a random velocity."""
        pass
Exemplo n.º 9
0
    def __init__(self, layer):
        # Create a compound visual with the following four subvisuals:
        # Lines: The lines of the interaction box used for highlights.
        # Markers: The the outlines for each point used for highlights.
        # Markers: The actual markers of each point.
        node = Compound([Markers(), Markers(), Line()])

        super().__init__(layer, node)

        self.layer.events.symbol.connect(lambda e: self._on_data_change())
        self.layer.events.edge_width.connect(lambda e: self._on_data_change())
        self.layer.events.edge_color.connect(lambda e: self._on_data_change())
        self.layer.events.face_color.connect(lambda e: self._on_data_change())
        self.layer.events.highlight.connect(
            lambda e: self._on_highlight_change())

        self._on_display_change()
        self._on_data_change()
Exemplo n.º 10
0
    def __init__(self, layer):
        # Create a compound visual with the following four subvisuals:
        # Markers: corresponding to the vertices of the interaction box or the
        # shapes that are used for highlights.
        # Lines: The lines of the interaction box used for highlights.
        # Mesh: The mesh of the outlines for each shape used for highlights.
        # Mesh: The actual meshes of the shape faces and edges
        node = Compound([Mesh(), Mesh(), Line(), Markers()])

        super().__init__(layer, node)

        self.layer.events.edge_width.connect(self._on_data_change)
        self.layer.events.edge_color.connect(self._on_data_change)
        self.layer.events.face_color.connect(self._on_data_change)
        self.layer.events.highlight.connect(self._on_highlight_change)

        self._reset_base()
        self._on_data_change()
        self._on_highlight_change()
Exemplo n.º 11
0
    def attach(self, viewer, view, canvas, parent=None, order=0):
        super().attach(viewer, view, canvas, parent, order)

        self._update_line_data()

        self.node = Compound(
            [Line(connect='segments', method='gl', width=4)],
            parent=parent,
        )
        self.node.transform = STTransform()
        self.node.order = order

        self._nodes = [self.node]

        canvas.connect(self.on_mouse_press)
        canvas.connect(self.on_mouse_move)
        canvas.connect(self.on_mouse_release)
        self._viewer.camera.events.zoom.connect(self._on_zoom_change)
        self._viewer.dims.events.ndisplay.connect(self._on_data_change)

        self._on_data_change(None)
Exemplo n.º 12
0
    def __init__(self, layer):
        # Create a compound visual with the following four subvisuals:
        # Lines: The lines of the interaction box used for highlights.
        # Markers: The the outlines for each point used for highlights.
        # Markers: The actual markers of each point.
        node = Compound([Markers(), Markers(), Line(), Text()])

        super().__init__(layer, node)

        self.layer.events.symbol.connect(self._on_data_change)
        self.layer.events.edge_width.connect(self._on_data_change)
        self.layer.events.edge_color.connect(self._on_data_change)
        self.layer._edge.events.colors.connect(self._on_data_change)
        self.layer._edge.events.color_properties.connect(self._on_data_change)
        self.layer.events.face_color.connect(self._on_data_change)
        self.layer._face.events.colors.connect(self._on_data_change)
        self.layer._face.events.color_properties.connect(self._on_data_change)
        self.layer.text._connect_update_events(self._on_text_change,
                                               self._on_blending_change)
        self.layer.events.highlight.connect(self._on_highlight_change)
        self._on_data_change()
        self._reset_base()
Exemplo n.º 13
0
def chromaticity_diagram_construction_visual(
        cmfs='CIE 1931 2 Degree Standard Observer',
        width=2.0,
        method='gl',
        parent=None):
    """
    Returns a :class:`vispy.scene.visuals.Node` class instance representing
    the chromaticity diagram construction with the spectral locus.

    Parameters
    ----------
    cmfs : unicode, optional
        Standard observer colour matching functions used to draw the spectral
        locus.
    width : numeric, optional
        Line width.
    method : unicode, optional
        **{'gl', 'agg'}**,
        Line drawing method.
    parent : Node, optional
        Parent of the spectral locus visual in the `SceneGraph`.

    Returns
    -------
    Node
        Chromaticity diagram construction visual.
    """

    from colour_analysis.visuals import Primitive

    node = Node(parent=parent)

    simplex_p = np.array([(1, 0, 0), (0, 1, 0), (0, 0, 1)])
    simplex_f = np.array([(0, 1, 2)])
    simplex_c = np.array([(1, 1, 1), (1, 1, 1), (1, 1, 1)])

    Primitive(
        simplex_p,
        simplex_f,
        uniform_opacity=0.5,
        vertex_colours=simplex_c,
        parent=node)

    simplex_f = np.array([(0, 1, 2), (1, 2, 0), (2, 0, 1)])
    Primitive(
        simplex_p,
        simplex_f,
        uniform_opacity=1.0,
        vertex_colours=simplex_c,
        wireframe=True,
        parent=node)

    lines = []
    for XYZ in first_item(filter_cmfs(cmfs).values()).values:
        lines.append(XYZ * 1.75)
        lines.append((0, 0, 0))
    lines = np.array(lines)

    Line(lines, (0, 0, 0), width=width, method=method, parent=node)

    return node
Exemplo n.º 14
0
    def __init__(
        self,
        coords,
        *,
        symbol='o',
        size=10,
        edge_width=1,
        edge_color='black',
        face_color='white',
        n_dimensional=False,
        name=None,
    ):

        # Create a compound visual with the following four subvisuals:
        # Lines: The lines of the interaction box used for highlights.
        # Markers: The the outlines for each point used for highlights.
        # Markers: The actual markers of each point.
        visual = Compound([Line(), Markers(), Markers()])

        super().__init__(visual, name)

        self.events.add(
            mode=Event,
            size=Event,
            face_color=Event,
            edge_color=Event,
            symbol=Event,
            n_dimensional=Event,
        )
        self._colors = get_color_names()

        # Freeze refreshes
        with self.freeze_refresh():
            # Save the point coordinates
            self._data = coords

            # Save the point style params
            self.symbol = symbol
            self.n_dimensional = n_dimensional
            self.edge_width = edge_width

            self.sizes = size
            self.edge_colors = list(
                itertools.islice(ensure_iterable(edge_color, color=True), 0,
                                 len(self.data)))
            self.face_colors = list(
                itertools.islice(ensure_iterable(face_color, color=True), 0,
                                 len(self.data)))

            # The following point properties are for the new points that will
            # be added. For any given property, if a list is passed to the
            # constructor so each point gets its own value then the default
            # value is used when adding new points
            if np.isscalar(size):
                self._size = size
            else:
                self._size = 10

            if type(edge_color) is str:
                self._edge_color = edge_color
            else:
                self._edge_color = 'black'

            if type(face_color) is str:
                self._face_color = face_color
            else:
                self._face_color = 'white'

            # Indices of selected points
            self._selected_data = []
            self._selected_data_stored = []
            self._selected_data_history = []
            # Indices of selected points within the currently viewed slice
            self._selected_view = []
            # Index of hovered point
            self._hover_point = None
            self._hover_point_stored = None
            self._selected_box = None
            self._mode = Mode.PAN_ZOOM
            self._mode_history = self._mode
            self._status = self.mode

            self._drag_start = None

            # Nx2 array of points in the currently viewed slice
            self._data_view = np.empty((0, 2))
            # Sizes of points in the currently viewed slice
            self._sizes_view = 0
            # Full data indices of points located in the currently viewed slice
            self._indices_view = []

            self._drag_box = None
            self._drag_box_stored = None
            self._is_selecting = False
            self._clipboard = {}

            # update flags
            self._need_display_update = False
            self._need_visual_update = False

            # Re intitialize indices depending on image dims
            self._indices = (0, ) * (self.ndim - 2) + (
                slice(None, None, None),
                slice(None, None, None),
            )

            # Trigger generation of view slice and thumbnail
            self._set_view_slice()
Exemplo n.º 15
0
 def __init__(self):
     super().__init__([Mesh(), Mesh(), Line(), Markers(), Text()])
Exemplo n.º 16
0
class Animate:
    def __init__(self,
                 data,
                 c=None,
                 labels=None,
                 display=Scatter,
                 tour_path=grand_tour(),
                 start=None,
                 parent=None,
                 draw_boundary=None):
        self.curr_aps = 0
        self.base_aps = 1
        self.framerate = 144
        self.draw_boundary = draw_boundary
        self.scale = 1000

        self.s = MinMaxScaler(feature_range=(-1, 1))

        self.tour = Tour(data, tour_path, start)
        self.step = self.tour.proj * self.scale
        self.data = self.s.fit_transform(data)
        self.mat = np.matmul(self.data, self.step, dtype=np.float32)

        self.display = Scatter(self.mat,
                               self.step,
                               c=c,
                               labels=labels,
                               parent=parent)
        self.wires = get_wires(p=data.shape[1])
        self.vertices = get_vertices(p=data.shape[1])
        self.res = np.matmul(self.vertices, self.step, dtype=np.float32)
        if self.draw_boundary == "hull":
            self.hull_frame = get_hull(self.res, self.wires)
            self.frame = Line(pos=self.hull_frame,
                              method="gl",
                              connect="segments",
                              parent=parent,
                              color="black")
        elif self.draw_boundary == "wire":
            res = get_hypercube(self.res, self.wires)
            self.frame = Line(pos=res,
                              method="gl",
                              connect="segments",
                              parent=parent,
                              color="black")

    def on_timer(self, event):

        if self.curr_aps != 0:
            self.step = self.tour.interpolate(
                self.curr_aps / self.framerate) * self.scale
            self.mat = np.matmul(self.data, self.step, dtype=np.float32)
            self.display.set_data(self.mat, self.step)
            self.display.update()

            if self.draw_boundary == "hull":
                self.res = np.matmul(self.vertices,
                                     self.step,
                                     dtype=np.float32)
                self.hull_frame = get_hull(self.res, self.wires)
                self.frame.set_data(pos=self.hull_frame,
                                    connect="segments",
                                    color="black")
            elif self.draw_boundary == "wire":
                self.res = np.matmul(self.vertices,
                                     self.step,
                                     dtype=np.float32)
                res = get_hypercube(self.res, self.wires)
                self.frame.set_data(pos=res, connect="segments", color="black")

    def on_key_press(self, event):
        if event.key == "=": self.curr_aps = self.base_aps
        if event.key == "-": self.curr_aps = -self.base_aps

        if event.key == "F8":
            print(self.step / self.scale)
            np.savetxt("matrix.csv", self.step / self.scale, delimiter=",")
            print("Projection saved to: matrix.csv")

    def on_key_release(self, event):
        if event.key == "-" or event.key == "=": self.curr_aps = 0

    def on_mouse_press(self, event):
        # print(event.pos)
        pass
Exemplo n.º 17
0
 def init_visual(self, view):
     """Initialize the object visual."""
     self.visual = Line(pos = sphere_pt * self.rad + self.pos, color=self.color)
     view.add(self.visual)
Exemplo n.º 18
0
def spectral_locus_visual(reference_colourspace='CIE xyY',
                          cmfs='CIE 1931 2 Degree Standard Observer',
                          width=2.0,
                          uniform_colour=None,
                          uniform_opacity=1.0,
                          method='gl',
                          parent=None):
    """
    Returns a :class:`vispy.scene.visuals.Line` class instance representing
    the spectral locus.

    Parameters
    ----------
    reference_colourspace : unicode, optional
        **{'CIE XYZ', 'CIE xyY', 'CIE Lab', 'CIE Luv', 'CIE UCS', 'CIE UVW',
        'IPT', 'Hunter Lab', 'Hunter Rdab'}**,
        Reference colourspace to use for colour conversions / transformations.
    cmfs : unicode, optional
        Standard observer colour matching functions used to draw the spectral
        locus.
    width : numeric, optional
        Line width.
    uniform_colour : array_like, optional
        Uniform symbol colour.
    uniform_opacity : numeric, optional
        Uniform symbol opacity.
    method : unicode, optional
        **{'gl', 'agg'}**,
        Line drawing method.
    parent : Node, optional
        Parent of the spectral locus visual in the `SceneGraph`.

    Returns
    -------
    Line
        Spectral locus visual.
    """

    cmfs = first_item(filter_cmfs(cmfs).values())
    XYZ = cmfs.values

    XYZ = np.vstack([XYZ, XYZ[0, ...]])

    illuminant = DEFAULT_PLOTTING_ILLUMINANT

    points = common_colourspace_model_axis_reorder(
        XYZ_to_colourspace_model(XYZ, illuminant, reference_colourspace),
        reference_colourspace)
    points[np.isnan(points)] = 0

    if uniform_colour is None:
        RGB = normalise_maximum(XYZ_to_sRGB(XYZ, illuminant), axis=-1)
        RGB = np.hstack([
            RGB,
            np.full((RGB.shape[0], 1), uniform_opacity, DEFAULT_FLOAT_DTYPE)
        ])
    else:
        RGB = ColorArray(uniform_colour, alpha=uniform_opacity).rgba

    line = Line(points,
                np.clip(RGB, 0, 1),
                width=width,
                method=method,
                parent=parent)

    return line
Exemplo n.º 19
0
    def __init__(self, viewer, parent=None, order=0):
        self._viewer = viewer

        self._data = np.array([
            [0, 0, -1],
            [1, 0, -1],
            [0, -5, -1],
            [0, 5, -1],
            [1, -5, -1],
            [1, 5, -1],
        ])
        self._target_length = 150
        self._scale = 1
        self._quantity = None
        self._unit_reg = None

        self.line_node = Line(connect='segments',
                              method='gl',
                              parent=parent,
                              width=3)
        self.line_node.order = order + 1
        self.line_node.transform = STTransform()

        # In order for the text and box to always appear centered on the scale
        # bar, the text and rect nodes should use the line node as the parent.
        self.text_node = Text(pos=[0.5, -1], parent=self.line_node)
        self.text_node.order = order + 1
        self.text_node.transform = STTransform()
        self.text_node.font_size = 10
        self.text_node.anchors = ("center", "center")
        self.text_node.text = f"{1}px"

        self.rect_node = Rectangle(
            center=[0.5, 0.5],
            width=1.1,
            height=36,
            color=self._viewer.scale_bar.box_color,
            parent=self.line_node,
        )
        self.rect_node.order = order
        self.rect_node.transform = STTransform()

        # the two canvas are not the same object, better be safe.
        self.rect_node.canvas._backend.destroyed.connect(self._set_canvas_none)
        self.line_node.canvas._backend.destroyed.connect(self._set_canvas_none)
        self.text_node.canvas._backend.destroyed.connect(self._set_canvas_none)
        assert self.rect_node.canvas is self.line_node.canvas
        assert self.line_node.canvas is self.text_node.canvas
        # End Note

        self._viewer.events.theme.connect(self._on_data_change)
        self._viewer.scale_bar.events.visible.connect(self._on_visible_change)
        self._viewer.scale_bar.events.colored.connect(self._on_data_change)
        self._viewer.scale_bar.events.ticks.connect(self._on_data_change)
        self._viewer.scale_bar.events.box_color.connect(self._on_data_change)
        self._viewer.scale_bar.events.color.connect(self._on_data_change)
        self._viewer.scale_bar.events.position.connect(
            self._on_position_change)
        self._viewer.camera.events.zoom.connect(self._on_zoom_change)
        self._viewer.scale_bar.events.font_size.connect(self._on_text_change)
        self._viewer.scale_bar.events.unit.connect(self._on_dimension_change)
        self._viewer.scale_bar.events.box.connect(self._on_visible_change)

        self._on_visible_change()
        self._on_data_change()
        self._on_dimension_change()
        self._on_position_change()
Exemplo n.º 20
0
    def debug(self, measurements, file_path, add_geodesic):

        # Model object
        model = mesh_lib.Model(file_path)
        model_point_coordinates = model.get_coords()

        canvas = scene.SceneCanvas(keys='interactive')
        view = canvas.central_widget.add_view()

        # all model - GREEN
        points = Markers(parent=view.scene)
        points.set_data(
            pos=model_point_coordinates,
            edge_color=None,
            face_color=(0, 1, 0, .3),
            size=5
        )
        data_list = []
        for m in measurements:  # measurements in config file
            # parsing key vertexes and description text
            point_1 = int(m[1]) + 1
            point_2 = int(m[2]) + 1
            point_3 = int(m[3]) + 1
            text = " ".join(m[4:])

            # coordinates of key vertexes
            key_coords = model.get_coords((point_1, point_2, point_3))
            # plane that goes through all three key vertexes
            plane = mesh_lib.get_plane(key_coords)

            # key vertexes WHITE
            points = Markers()
            points.set_data(
                pos=key_coords,
                edge_color=None,
                face_color=(1, 1, 1, 1),
                size=5
            )

            # "C" - circumference
            if m[0] == "C":
                # 3 segments of path (indexes)
                p_1 = model.get_path(point_1, point_2)
                p_2 = model.get_path(point_2, point_3)
                p_3 = model.get_path(point_3, point_1)
                # full path
                path = p_1 + p_2[1:] + p_3[1:]
            # "L" - Length
            if m[0] == "L":
                # 2 segments of path (indexes)
                p_1 = model.get_path(point_1, point_2)
                p_2 = model.get_path(point_2, point_3)
                # full path
                path = p_1 + p_2[1:]

            # geodesic
            geodesic_coordinates = model.get_coords(path)
            geodesic_length = mesh_lib.get_length(geodesic_coordinates)
            print("{0}:".format(text))
            print(
                "  Geodesic distance: {0} cm".format(
                    round(100 * geodesic_length, 3)
                )
            )

            if add_geodesic:  # if debug_full
                # geodesic line - RED
                line = Line(parent=view.scene)
                line.set_data(
                    pos=geodesic_coordinates,
                    color=(1, 0, 0, 1)
                )

            # approximated
            flattened_coordinates = mesh_lib.get_projections(plane, geodesic_coordinates)
            flattened_length = mesh_lib.get_length(flattened_coordinates)
            print(
                "  Approximated distance: {0} cm".format(
                    round(100 * flattened_length, 3)
                )
            )
            
            data_list.append(geodesic_length)
            data_list.append(flattened_length)
            # flattened line - BLUE
            line = Line(parent=view.scene)
            line.set_data(
                pos=flattened_coordinates,
                color=(0, 0, 1, 1)
            )

        view.camera = 'turntable'
        view.camera.fov = 45
        view.camera.distance = 3

        axis = XYZAxis(parent=view.scene)
        final_result = {"canvas":canvas,"data":data_list}
        return final_result
Exemplo n.º 21
0
 def init_visual(self, view):
     """Initialize the object visual."""
     self.trace = np.repeat(self.pos, self.tail_steps).reshape((3,self.tail_steps)).T
     pos = np.concatenate([sphere_pt * self.rad + self.pos, self.trace])
     self.visual = Line(pos = pos, color=self.color)
     view.add(self.visual)
Exemplo n.º 22
0
class VispyScaleBarVisual:
    """Scale bar in world coordinates.
    """
    def __init__(self, scale_bar, parent=None, order=0):

        self._data = np.array([
            [0, 0, -1],
            [1, 0, -1],
            [0, -5, -1],
            [0, 5, -1],
            [1, -5, -1],
            [1, 5, -1],
        ])
        self._default_color = np.array([1, 0, 1, 1])
        self._target_length = 100
        self._scale = 1
        self.scale_bar = scale_bar

        self.node = Line(connect='segments',
                         method='gl',
                         parent=parent,
                         width=3)
        self.node.order = order
        self.node.transform = STTransform()

        self.text_node = Text(pos=[0, 0], parent=parent)
        self.text_node.order = order
        self.text_node.transform = STTransform()
        self.text_node.font_size = 10
        self.text_node.anchors = ('center', 'center')
        self.text_node.text = f'{1}'

        self.scale_bar.events.visible.connect(self._on_visible_change)
        self.scale_bar.events.colored.connect(self._on_data_change)
        self.scale_bar.events.ticks.connect(self._on_data_change)
        self.scale_bar.events.position.connect(self._on_position_change)

        self._on_visible_change(None)
        self._on_data_change(None)
        self._on_position_change(None)

    def update_scale(self, scale):
        """Update scale bar length based on canvas2world scale.

        Parameters
        ----------
        scale : float
            Scale going from canvas pixels to world coorindates.
        """
        # If scale has not changed, do not redraw
        if abs(np.log10(self._scale) - np.log10(scale)) < 1e-4:
            return
        self._scale = scale

        scale_canvas2world = self._scale
        target_canvas_pixels = self._target_length
        target_world_pixels = scale_canvas2world * target_canvas_pixels

        # Round scalebar to nearest factor of 1, 2, 5, 10 in world pixels on a log scale
        resolutions = [1, 2, 5, 10]
        log_target = np.log10(target_world_pixels)
        mult_ind = np.argmin(
            np.abs(np.subtract(np.log10(resolutions), log_target % 1)))
        power_val = np.floor(log_target)
        target_world_pixels_rounded = resolutions[mult_ind] * np.power(
            10, power_val)

        # Convert back to canvas pixels to get actual length of scale bar
        target_canvas_pixels_rounded = (target_world_pixels_rounded /
                                        scale_canvas2world)
        scale = target_canvas_pixels_rounded

        if self.scale_bar._position in [
                Position.TOP_RIGHT,
                Position.BOTTOM_RIGHT,
        ]:
            sign = -1
        else:
            sign = 1

        # Update scalebar and text
        self.node.transform.scale = [sign * scale, 1, 1, 1]
        self.text_node.text = f'{target_world_pixels_rounded:.4g}'

    def _on_data_change(self, event):
        """Change color and data of scale bar."""
        if self.scale_bar.colored:
            color = self._default_color
        else:
            color = np.subtract(1, self.scale_bar.background_color)[:3]

        if self.scale_bar.ticks:
            data = self._data
        else:
            data = self._data[:2]

        self.node.set_data(data, color)
        self.text_node.color = color

    def _on_visible_change(self, event):
        """Change visibiliy of scale bar."""
        self.node.visible = self.scale_bar.visible
        self.text_node.visible = self.scale_bar.visible

    def _on_position_change(self, event):
        """Change position of scale bar."""
        if self.scale_bar._position == Position.TOP_LEFT:
            sign = 1
            self.node.transform.translate = [66, 14, 0, 0]
            self.text_node.transform.translate = [33, 16, 0, 0]
        elif self.scale_bar._position == Position.TOP_RIGHT:
            sign = -1
            canvas_size = list(self.node.canvas.size)
            self.node.transform.translate = [canvas_size[0] - 66, 14, 0, 0]
            self.text_node.transform.translate = [
                canvas_size[0] - 33,
                16,
                0,
                0,
            ]
        elif self.scale_bar._position == Position.BOTTOM_RIGHT:
            sign = -1
            canvas_size = list(self.node.canvas.size)
            self.node.transform.translate = [
                canvas_size[0] - 66,
                canvas_size[1] - 16,
                0,
                0,
            ]
            self.text_node.transform.translate = [
                canvas_size[0] - 33,
                canvas_size[1] - 14,
                0,
                0,
            ]
        elif self.scale_bar._position == Position.BOTTOM_LEFT:
            sign = 1
            canvas_size = list(self.node.canvas.size)
            self.node.transform.translate = [66, canvas_size[1] - 16, 0, 0]
            self.text_node.transform.translate = [
                33,
                canvas_size[1] - 14,
                0,
                0,
            ]
        else:
            raise ValueError(f'Position {self.scale_bar.position}'
                             ' not recognized.')

        scale = abs(self.node.transform.scale[0])
        self.node.transform.scale = [sign * scale, 1, 1, 1]
class VispyScaleBarVisual:
    """Scale bar in world coordinates."""
    def __init__(self, viewer, parent=None, order=0):
        self._viewer = viewer

        self._data = np.array([
            [0, 0, -1],
            [1, 0, -1],
            [0, -5, -1],
            [0, 5, -1],
            [1, -5, -1],
            [1, 5, -1],
        ])
        self._default_color = np.array([1, 0, 1, 1])
        self._target_length = 150
        self._scale = 1
        self._quantity = None
        self._unit_reg = None

        self.node = Line(connect='segments',
                         method='gl',
                         parent=parent,
                         width=3)
        self.node.order = order
        self.node.transform = STTransform()

        # In order for the text to always appear centered on the scale bar,
        # the text node should use the line node as the parent.
        self.text_node = Text(pos=[0.5, -1], parent=self.node)
        self.text_node.order = order
        self.text_node.transform = STTransform()
        self.text_node.font_size = 10
        self.text_node.anchors = ("center", "center")
        self.text_node.text = f"{1}px"

        # Note:
        # There are issues on MacOS + GitHub action about destroyed
        # C/C++ object during test if those don't get disconnected.
        def set_none():
            self.node._set_canvas(None)
            self.text_node._set_canvas(None)

        # the two canvas are not the same object, better be safe.
        self.node.canvas._backend.destroyed.connect(set_none)
        self.text_node.canvas._backend.destroyed.connect(set_none)
        assert self.node.canvas is self.text_node.canvas
        # End Note

        self._viewer.events.theme.connect(self._on_data_change)
        self._viewer.scale_bar.events.visible.connect(self._on_visible_change)
        self._viewer.scale_bar.events.colored.connect(self._on_data_change)
        self._viewer.scale_bar.events.ticks.connect(self._on_data_change)
        self._viewer.scale_bar.events.position.connect(
            self._on_position_change)
        self._viewer.camera.events.zoom.connect(self._on_zoom_change)
        self._viewer.scale_bar.events.font_size.connect(self._on_text_change)
        self._viewer.scale_bar.events.unit.connect(self._on_dimension_change)

        self._on_visible_change(None)
        self._on_data_change(None)
        self._on_dimension_change(None)
        self._on_position_change(None)

    @property
    def unit_registry(self):
        """Get unit registry.

        Rather than instantiating UnitRegistry earlier on, it is instantiated
        only when it is needed. The reason for this is that importing `pint`
        at module level can be time consuming.

        Notes
        -----
        https://github.com/napari/napari/pull/2617#issuecomment-827716325
        https://github.com/napari/napari/pull/2325
        """
        if self._unit_reg is None:
            self._unit_reg = get_unit_registry()
        return self._unit_reg

    def _on_dimension_change(self, event):
        """Update dimension."""
        if not self._viewer.scale_bar.visible and self._unit_reg is None:
            return
        unit = self._viewer.scale_bar.unit
        self._quantity = self.unit_registry(unit)
        self._on_zoom_change(None, True)

    def _calculate_best_length(self, desired_length: float):
        """Calculate new quantity based on the pixel length of the bar.

        Parameters
        ----------
        desired_length : float
            Desired length of the scale bar in world size.

        Returns
        -------
        new_length : float
            New length of the scale bar in world size based
            on the preferred scale bar value.
        new_quantity : pint.Quantity
            New quantity with abbreviated base unit.
        """
        current_quantity = self._quantity * desired_length
        # convert the value to compact representation
        new_quantity = current_quantity.to_compact()
        # calculate the scaling factor taking into account any conversion
        # that might have occurred (e.g. um -> cm)
        factor = current_quantity / new_quantity

        # select value closest to one of our preferred values
        index = bisect.bisect_left(PREFERRED_VALUES, new_quantity.magnitude)
        if index > 0:
            # When we get the lowest index of the list, removing -1 will
            # return the last index.
            index -= 1
        new_value = PREFERRED_VALUES[index]

        # get the new pixel length utilizing the user-specified units
        new_length = ((new_value * factor) /
                      self._quantity.magnitude).magnitude
        new_quantity = new_value * new_quantity.units
        return new_length, new_quantity

    def _on_zoom_change(self, event, force: bool = False):
        """Update axes length based on zoom scale."""
        if not self._viewer.scale_bar.visible:
            return

        # If scale has not changed, do not redraw
        scale = 1 / self._viewer.camera.zoom
        if abs(np.log10(self._scale) - np.log10(scale)) < 1e-4 and not force:
            return
        self._scale = scale

        scale_canvas2world = self._scale
        target_canvas_pixels = self._target_length
        # convert desired length to world size
        target_world_pixels = scale_canvas2world * target_canvas_pixels

        # calculate the desired length as well as update the value and units
        target_world_pixels_rounded, new_dim = self._calculate_best_length(
            target_world_pixels)
        target_canvas_pixels_rounded = (target_world_pixels_rounded /
                                        scale_canvas2world)
        scale = target_canvas_pixels_rounded

        sign = (-1 if self._viewer.scale_bar.position
                in [Position.TOP_RIGHT, Position.BOTTOM_RIGHT] else 1)

        # Update scalebar and text
        self.node.transform.scale = [sign * scale, 1, 1, 1]
        self.text_node.text = f'{new_dim:~}'

    def _on_data_change(self, event):
        """Change color and data of scale bar."""
        if self._viewer.scale_bar.colored:
            color = self._default_color
        else:
            background_color = get_theme(self._viewer.theme)['canvas']
            background_color = transform_color(background_color)[0]
            color = np.subtract(1, background_color)
            color[-1] = background_color[-1]

        if self._viewer.scale_bar.ticks:
            data = self._data
        else:
            data = self._data[:2]

        self.node.set_data(data, color)
        self.text_node.color = color

    def _on_visible_change(self, event):
        """Change visibility of scale bar."""
        self.node.visible = self._viewer.scale_bar.visible
        self.text_node.visible = self._viewer.scale_bar.visible

        # update unit if scale bar is visible and quantity
        # has not been specified yet or current unit is not
        # equivalent
        if self._viewer.scale_bar.visible and (
                self._quantity is None
                or self._quantity.units != self._viewer.scale_bar.unit):
            self._quantity = self.unit_registry(self._viewer.scale_bar.unit)
        # only force zoom update if the scale bar is visible
        self._on_zoom_change(None, self._viewer.scale_bar.visible)

    def _on_text_change(self, event):
        """Update text information"""
        self.text_node.font_size = self._viewer.scale_bar.font_size

    def _on_position_change(self, event):
        """Change position of scale bar."""
        position = self._viewer.scale_bar.position
        x_bar_offset, y_bar_offset = 10, 30
        canvas_size = list(self.node.canvas.size)

        if position == Position.TOP_LEFT:
            sign = 1
            bar_transform = [x_bar_offset, 10, 0, 0]
        elif position == Position.TOP_RIGHT:
            sign = -1
            bar_transform = [canvas_size[0] - x_bar_offset, 10, 0, 0]
        elif position == Position.BOTTOM_RIGHT:
            sign = -1
            bar_transform = [
                canvas_size[0] - x_bar_offset,
                canvas_size[1] - y_bar_offset,
                0,
                0,
            ]
        elif position == Position.BOTTOM_LEFT:
            sign = 1
            bar_transform = [x_bar_offset, canvas_size[1] - 30, 0, 0]
        else:
            raise ValueError(
                trans._(
                    'Position {position} not recognized.',
                    deferred=True,
                    position=self._viewer.scale_bar.position,
                ))

        self.node.transform.translate = bar_transform
        scale = abs(self.node.transform.scale[0])
        self.node.transform.scale = [sign * scale, 1, 1, 1]
        self.text_node.transform.translate = (0, 20, 0, 0)
Exemplo n.º 24
0
 def __init__(self):
     self.reset()
     self.line = Line(
         connect='segments', color=GRID_COLOR, width=GRID_WIDTH
     )
     self.line.order = 10
Exemplo n.º 25
0
    def __init__(self, axes, dims, parent=None, order=0):
        self.axes = axes
        self.dims = dims

        # note order is z, y, x
        self._default_data = np.array(
            [[0, 0, 0], [0, 0, 1], [0, 0, 0], [0, 1, 0], [0, 0, 0], [1, 0, 0]]
        )
        self._default_color = np.concatenate(
            [[[0, 1, 1, 1]] * 2, [[1, 1, 0, 1]] * 2, [[1, 0, 1, 1]] * 2],
            axis=0,
        )
        # note order is z, y, x
        self._dashed_data = np.concatenate(
            [
                [[0, 0, 0], [0, 0, 1]],
                make_dashed_line(4, axis=1),
                make_dashed_line(8, axis=0),
            ],
            axis=0,
        )
        self._dashed_color = np.concatenate(
            [
                [[0, 1, 1, 1]] * 2,
                [[1, 1, 0, 1]] * 4 * 2,
                [[1, 0, 1, 1]] * 8 * 2,
            ],
            axis=0,
        )
        vertices = np.empty((0, 3))
        faces = np.empty((0, 3))
        # note order is z, y, x
        for axis in range(3):
            v, f = make_arrow_head(self._NUM_SEGMENTS_ARROWHEAD, 2 - axis)
            faces = np.concatenate([faces, f + len(vertices)], axis=0)
            vertices = np.concatenate([vertices, v], axis=0)
        self._default_arrow_vertices = vertices
        self._default_arrow_faces = faces.astype(int)
        self._default_arrow_color = np.concatenate(
            [
                [[0, 1, 1, 1]] * self._NUM_SEGMENTS_ARROWHEAD,
                [[1, 1, 0, 1]] * self._NUM_SEGMENTS_ARROWHEAD,
                [[1, 0, 1, 1]] * self._NUM_SEGMENTS_ARROWHEAD,
            ],
            axis=0,
        )
        self._target_length = 80
        self.node = Compound(
            [Line(connect='segments', method='gl', width=3), Mesh()],
            parent=parent,
        )
        self.node.transform = STTransform()
        self.node.order = order

        self.axes.events.visible.connect(self._on_visible_change)
        self.axes.events.colored.connect(self._on_data_change)
        self.axes.events.dashed.connect(self._on_data_change)
        self.axes.events.arrows.connect(self._on_data_change)
        self.dims.events.order.connect(self._on_data_change)

        self._on_visible_change(None)
        self._on_data_change(None)

        self._scale = 1
Exemplo n.º 26
0
 def __init__(self):
     self.clamp_filter = ClampSizeFilter()
     super().__init__([Markers(), Markers(), Line(), Text()])
     self.attach(self.clamp_filter)
     self.scaling = True
Exemplo n.º 27
0
 def __init__(self):
     super().__init__([Markers(), Markers(), Line(), Text()])
     self.scaling = True
Exemplo n.º 28
0
    def __init__(self, viewer, parent=None, order=0):
        self._viewer = viewer
        self._scale = 1

        # Target axes length in canvas pixels
        self._target_length = 80
        # CMYRGB for 6 axes data in x, y, z, ... ordering
        self._default_color = [
            [0, 1, 1, 1],
            [1, 0, 1, 1],
            [1, 1, 0, 1],
            [1, 0, 0, 1],
            [0, 1, 0, 1],
            [0, 0, 1, 1],
        ]
        # Text offset from line end position
        self._text_offsets = 0.1 * np.array([1, 1, 1])

        # note order is x, y, z for VisPy
        self._line_data2D = np.array([[0, 0, 0], [1, 0, 0], [0, 0, 0],
                                      [0, 1, 0]])
        self._line_data3D = np.array([[0, 0, 0], [1, 0, 0], [0, 0, 0],
                                      [0, 1, 0], [0, 0, 0], [0, 0, 1]])

        # note order is x, y, z for VisPy
        self._dashed_line_data2D = np.concatenate(
            [[[1, 0, 0], [0, 0, 0]],
             make_dashed_line(4, axis=1)],
            axis=0,
        )
        self._dashed_line_data3D = np.concatenate(
            [
                [[1, 0, 0], [0, 0, 0]],
                make_dashed_line(4, axis=1),
                make_dashed_line(8, axis=2),
            ],
            axis=0,
        )

        # note order is x, y, z for VisPy
        vertices = np.empty((0, 3))
        faces = np.empty((0, 3))
        for axis in range(2):
            v, f = make_arrow_head(self._NUM_SEGMENTS_ARROWHEAD, axis)
            faces = np.concatenate([faces, f + len(vertices)], axis=0)
            vertices = np.concatenate([vertices, v], axis=0)
        self._default_arrow_vertices2D = vertices
        self._default_arrow_faces2D = faces.astype(int)

        vertices = np.empty((0, 3))
        faces = np.empty((0, 3))
        for axis in range(3):
            v, f = make_arrow_head(self._NUM_SEGMENTS_ARROWHEAD, axis)
            faces = np.concatenate([faces, f + len(vertices)], axis=0)
            vertices = np.concatenate([vertices, v], axis=0)
        self._default_arrow_vertices3D = vertices
        self._default_arrow_faces3D = faces.astype(int)

        self.node = Compound(
            [Line(connect='segments', method='gl', width=3),
             Mesh(),
             Text()],
            parent=parent,
        )
        self.node.transform = STTransform()
        self.node.order = order

        # Add a text node to display axes labels
        self.text_node = self.node._subvisuals[2]
        self.text_node.font_size = 10
        self.text_node.anchors = ('center', 'center')
        self.text_node.text = f'{1}'

        self._viewer.events.theme.connect(self._on_data_change)
        self._viewer.axes.events.visible.connect(self._on_visible_change)
        self._viewer.axes.events.colored.connect(self._on_data_change)
        self._viewer.axes.events.dashed.connect(self._on_data_change)
        self._viewer.axes.events.labels.connect(self._on_data_change)
        self._viewer.axes.events.arrows.connect(self._on_data_change)
        self._viewer.dims.events.order.connect(self._on_data_change)
        self._viewer.dims.events.ndisplay.connect(self._on_data_change)
        self._viewer.dims.events.axis_labels.connect(self._on_data_change)
        self._viewer.camera.events.zoom.connect(self._on_zoom_change)

        self._on_visible_change(None)
        self._on_data_change(None)
Exemplo n.º 29
0
class Bubble2:
    """Class containg a n number of bubbles. It uses vispy to visualization."""
    def __init__(self, position, velocity, boundaries = None, radius = None, color=(1.0, 1.0, 1.0, 1.0)):
        self.n = position.shape[0]
        self.pos = position
        self.vel = velocity
        self.color = color
        if radius is not None:
            self.rad = radius
        else:
            self.rad = np.zeros(self.n) + 0.1
        self.bound = None
        self.sizexyz = [None] * 3
        if boundaries is not None:
            self.set_bound(boundaries)
        self.visual = None

    def set_bound(self, boundaries):
        """Updates the boundaries."""
        self.bound = boundaries
        self.sizexyz = np.abs(boundaries[:,1] - boundaries[:,0])

    def step(self, time_step):
        """Calculate the new positions and speeds for all bubbles."""
        despl = self.vel * time_step
        self.pos = self.pos + despl
        if self.sizexyz[2] is not None:
            offbound = np.where(self.pos[:,2] > self.bound[2,1])
            off_n = offbound[0].size
            self.pos[offbound,0] = (self.sizexyz[0]) * np.random.rand(off_n) + self.bound[0,0]
            self.pos[offbound,1] = (self.sizexyz[1]) * np.random.rand(off_n) + self.bound[1,0]
            self.pos[offbound,2] = self.bound[2,0]
            self.rad[offbound] = 0.01
            self.vel[offbound,2] = 0.0
            on_bottom = np.where(self.pos[:,2] == self.bound[2,0])
            self.rad[on_bottom] = self.rad[on_bottom] + 0.02
            vel_free = self.vel[on_bottom,2][0]
            rad_free = self.rad[on_bottom]
            free = np.where(rad_free + np.random.rand(on_bottom[0].size) / 2 > 0.4)
            vel_free[free] = vel_free[free] + rad_free[free]
            self.vel[on_bottom,2] = vel_free
            not_on_bottom = np.where(self.pos[:,2] > self.bound[2,0])
            self.vel[not_on_bottom,2] = self.vel[not_on_bottom,2] + self.rad[not_on_bottom]
        self.update_visual()

    def init_visual(self, view):
        """Initialize the object visual."""
        num_seg = sphere_pt.shape[0]
        all_seg = np.zeros((num_seg * self.n, 3))
        for i in range(self.n):
            all_seg[i * num_seg: (i + 1) * num_seg] = sphere_pt * self.rad[i] + self.pos[i,:]
        self.visual = Line(pos = all_seg, color=self.color, connect='segments')
        view.add(self.visual)

    def update_visual(self):
        """Updates the object visual."""
        num_seg = sphere_pt.shape[0]
        all_seg = np.zeros((num_seg * self.n, 3))
        for i in range(self.n):
            all_seg[i * num_seg: (i + 1) * num_seg] = sphere_pt * self.rad[i] + self.pos[i,:]
        self.visual.set_data(pos = all_seg)

    def shake(self):
        """Loose all the bubbles from the bottom."""
        if self.sizexyz[2] is not None:
            self.pos[:,2] = self.pos[:,2] + 0.001