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 TileGrid:
    """The grid that shows the outlines of all the tiles."""

    def __init__(self):
        self.reset()
        self.line = Line(
            connect='segments', color=GRID_COLOR, width=GRID_WIDTH
        )
        self.line.order = 10

    def reset(self) -> None:
        """Reset the grid to have no lines."""
        self.verts = np.zeros((0, 2), dtype=np.float32)

    def add_chunk(self, chunk: ChunkData) -> None:
        """Add the outline of the given chunk to the grid.

        Parameters
        ----------
        chunk : ChunkData
            Add the outline of this chunk.
        """
        chunk_verts = _chunk_outline(chunk)

        # Clear verts first. Prevents segfault when we modify self.verts.
        self.line.set_data([])

        self.verts = np.vstack([self.verts, chunk_verts])
        self.line.set_data(self.verts)
Exemplo n.º 3
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.º 4
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.º 5
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.º 6
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.º 7
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]
Exemplo n.º 8
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.º 9
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.º 10
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
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)