def attach(self, viewer, view, canvas, parent=None, order=0): super().attach(viewer, view, canvas, parent, order) self.rect_node = Compound( [Line(connect='segments', method='gl', width=4)], parent=parent, ) self.rect_node.transform = STTransform() self.rect_node.order = order self.handle_node = Compound( [Line(connect='segments', method='gl', width=2)], parent=parent, ) self.handle_node.transform = STTransform() self.handle_node.order = order self._nodes = [self.rect_node, self.handle_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_zoom_change(None) self._on_data_change(None) self._update_position() self._update_size()
def _on_display_change(self): parent = self.node.parent self.node.transforms = ChainTransform() self.node.parent = None if self.layer.dims.ndisplay == 2: self.node = Compound([Markers(), Markers(), Line()]) else: self.node = Compound([Markers(), Markers()]) self.node.parent = parent self._reset_base()
def __init__(self, layer): node = Compound([Line(), Text(), Line()]) super().__init__(layer, node) self.layer.events.tail_width.connect(self._on_appearance_change) self.layer.events.tail_length.connect(self._on_appearance_change) self.layer.events.display_id.connect(self._on_appearance_change) self.layer.events.display_tail.connect(self._on_appearance_change) self.layer.events.display_graph.connect(self._on_appearance_change) self.layer.events.color_by.connect(self._on_appearance_change) self.layer.events.colormap.connect(self._on_appearance_change) # these events are fired when changes occur to the tracks or the # graph - as the vertex buffer of the shader needs to be updated # alongside the actual vertex data self.layer.events.rebuild_tracks.connect(self._on_tracks_change) self.layer.events.rebuild_graph.connect(self._on_graph_change) # build and attach the shader to the track self.track_shader = TrackShader() self.graph_shader = TrackShader() node._subvisuals[0].attach(self.track_shader) node._subvisuals[2].attach(self.graph_shader) # text label properties self.node._subvisuals[1].color = 'white' self.node._subvisuals[1].font_size = 8 self._reset_base() self._on_data_change() self._on_appearance_change()
def __init__(self, layer): node = Compound([Line(), Text(), Line()]) super().__init__(layer, node) self.layer.events.edge_width.connect(self._on_data_change) self.layer.events.tail_length.connect(self._on_data_change) self.layer.events.display_id.connect(self._on_data_change) self.layer.events.display_tail.connect(self._on_data_change) self.layer.events.display_graph.connect(self._on_data_change) self.layer.events.color_by.connect(self._on_data_change) # build and attach the shader to the track self.track_shader = TrackShader( current_time=self.layer.current_time, tail_length=self.layer.tail_length, vertex_time=self.layer.track_times, vertex_mask=self.layer._mask_data, ) self.graph_shader = TrackShader( current_time=self.layer.current_time, tail_length=self.layer.tail_length, vertex_time=self.layer.graph_times, vertex_mask=self.layer._mask_graph, ) node._subvisuals[0].attach(self.track_shader) node._subvisuals[2].attach(self.graph_shader) # text label properties self.node._subvisuals[1].color = 'white' self.node._subvisuals[1].font_size = 8 self._reset_base() self._on_data_change()
def __init__(self, viewer, parent=None, order=0): self._viewer = viewer self._interaction_box = viewer.overlays.interaction_box self.node = Compound([Line(), Markers(), Markers()], parent=parent) self.node.order = order self._on_interaction_box_change() self._interaction_box.events.points.connect( self._on_interaction_box_change) self._interaction_box.events.show.connect( self._on_interaction_box_change) self._interaction_box.events.show_handle.connect( self._on_interaction_box_change) self._interaction_box.events.show_vertices.connect( self._on_interaction_box_change) self._interaction_box.events.selected_vertex.connect( self._on_interaction_box_change) self._interaction_box.events.transform.connect( self._on_interaction_box_change) self._highlight_width = 1.5 self._vertex_size = 10 self._rotation_handle_length = 20 self._highlight_color = (0, 0.6, 1) self.square_marker_node.symbol = 'square' self.square_marker_node.scaling = False self.round_marker_node.symbol = 'disc' self.round_marker_node.scaling = False
def _on_display_change(self): parent = self.node.parent self.node.transforms = ChainTransform() self.node.parent = None if self.layer.dims.ndisplay == 2: self.node = Compound([Markers(), Markers(), Line()]) else: self.node = Markers() self.node.parent = parent self.layer._update_dims() self.layer._set_view_slice() self.reset()
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(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.events.face_color.connect(self._on_data_change) self.layer.events.highlight.connect(self._on_highlight_change) self._on_display_change() self._on_data_change()
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] 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)
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()
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()
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)
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()
class VispyAxesVisual: """Axes indicating world coordinate origin and orientation.""" _NUM_SEGMENTS_ARROWHEAD = 100 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}' # 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) self.node.canvas._backend.destroyed.connect(set_none) # End Note 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) def _on_visible_change(self, event): """Change visibiliy of axes.""" self.node.visible = self._viewer.axes.visible self._on_zoom_change(event) self._on_data_change(event) def _on_data_change(self, event): """Change style of axes.""" if not self._viewer.axes.visible: return # Determine which axes are displayed axes = self._viewer.dims.displayed # Actual number of displayed dims ndisplay = len(self._viewer.dims.displayed) # Determine the labels of those axes axes_labels = [self._viewer.dims.axis_labels[a] for a in axes[::-1]] # Counting backwards from total number of dimensions # determine axes positions. This is done as by default # the last NumPy axis corresponds to the first Vispy axis reversed_axes = [self._viewer.dims.ndim - 1 - a for a in axes[::-1]] # Determine colors of axes based on reverse position if self._viewer.axes.colored: axes_colors = [ self._default_color[ra % len(self._default_color)] for ra in reversed_axes ] 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] axes_colors = [color] * ndisplay # Determine data based on number of displayed dimensions and # axes visualization parameters if self._viewer.axes.dashed and ndisplay == 2: data = self._dashed_line_data2D color = color_dashed_lines(axes_colors) text_data = self._line_data2D[1::2] elif self._viewer.axes.dashed and ndisplay == 3: data = self._dashed_line_data3D color = color_dashed_lines(axes_colors) text_data = self._line_data3D[1::2] elif not self._viewer.axes.dashed and ndisplay == 2: data = self._line_data2D color = color_lines(axes_colors) text_data = self._line_data2D[1::2] elif not self._viewer.axes.dashed and ndisplay == 3: data = self._line_data3D color = color_lines(axes_colors) text_data = self._line_data3D[1::2] else: raise ValueError( trans._( 'Axes dash status and ndisplay combination not supported', deferred=True, )) if self._viewer.axes.arrows and ndisplay == 2: arrow_vertices = self._default_arrow_vertices2D arrow_faces = self._default_arrow_faces2D arrow_color = color_arrowheads(axes_colors, self._NUM_SEGMENTS_ARROWHEAD) elif self._viewer.axes.arrows and ndisplay == 3: arrow_vertices = self._default_arrow_vertices3D arrow_faces = self._default_arrow_faces3D arrow_color = color_arrowheads(axes_colors, self._NUM_SEGMENTS_ARROWHEAD) else: arrow_vertices = np.zeros((3, 3)) arrow_faces = np.array([[0, 1, 2]]) arrow_color = [[0, 0, 0, 0]] self.node._subvisuals[0].set_data(data, color) self.node._subvisuals[1].set_data( vertices=arrow_vertices, faces=arrow_faces, face_colors=arrow_color, ) # Set visibility status of text self.text_node.visible = (self._viewer.axes.visible and self._viewer.axes.labels) self.text_node.text = axes_labels self.text_node.color = axes_colors self.text_node.pos = text_data + self._text_offsets def _on_zoom_change(self, event): """Update axes length based on zoom scale.""" if not self._viewer.axes.visible: return scale = 1 / self._viewer.camera.zoom # 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 scale = target_canvas_pixels * scale_canvas2world # Update axes scale self.node.transform.scale = [scale, scale, scale, 1]
class VispyPointsLayer(VispyBaseLayer): _highlight_color = (0, 0.6, 1) _highlight_width = 2 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(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.events.face_color.connect(self._on_data_change) self.layer.events.highlight.connect(self._on_highlight_change) self._on_display_change() self._on_data_change() def _on_display_change(self): parent = self.node.parent self.node.transforms = ChainTransform() self.node.parent = None if self.layer.dims.ndisplay == 2: self.node = Compound([Markers(), Markers(), Line()]) else: self.node = Compound([Markers(), Markers()]) self.node.parent = parent self._reset_base() def _on_data_change(self, event=None): # Check if ndisplay has changed current node type needs updating if (self.layer.dims.ndisplay == 3 and len(self.node._subvisuals) != 2 ) or (self.layer.dims.ndisplay == 2 and len(self.node._subvisuals) != 3): self._on_display_change() self._on_highlight_change() if len(self.layer._indices_view) > 0: edge_color = self.layer._view_edge_color face_color = self.layer._view_face_color else: edge_color = np.array([[0.0, 0.0, 0.0, 1.0]], dtype=np.float32) face_color = np.array([[1.0, 1.0, 1.0, 1.0]], dtype=np.float32) # Set vispy data, noting that the order of the points needs to be # reversed to make the most recently added point appear on top # and the rows / columns need to be switch for vispys x / y ordering if len(self.layer._indices_view) == 0: data = np.zeros((1, self.layer.dims.ndisplay)) size = [0] else: data = self.layer._view_data size = self.layer._view_size set_data = self.node._subvisuals[0].set_data set_data( data[:, ::-1] + 0.5, size=size, edge_width=self.layer.edge_width, symbol=self.layer.symbol, edge_color=edge_color, face_color=face_color, scaling=True, ) self.node.update() # Call to update order of translation values with new dims: self._on_scale_change() self._on_translate_change() def _on_highlight_change(self, event=None): if len(self.layer._highlight_index) > 0: # Color the hovered or selected points data = self.layer._view_data[self.layer._highlight_index] if data.ndim == 1: data = np.expand_dims(data, axis=0) size = self.layer._view_size[self.layer._highlight_index] else: data = np.zeros((1, self.layer.dims.ndisplay)) size = 0 self.node._subvisuals[1].set_data( data[:, ::-1] + 0.5, size=size, edge_width=self._highlight_width, symbol=self.layer.symbol, edge_color=self._highlight_color, face_color=transform_color('transparent'), scaling=True, ) # only draw a box in 2D if self.layer.dims.ndisplay == 2: if (self.layer._highlight_box is None or 0 in self.layer._highlight_box.shape): pos = np.zeros((1, self.layer.dims.ndisplay)) width = 0 else: pos = self.layer._highlight_box width = self._highlight_width self.node._subvisuals[2].set_data( pos=pos[:, ::-1] + 0.5, color=self._highlight_color, width=width, ) self.node.update()
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