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
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)
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)
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
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
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
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 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
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
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)