From c5b3ef21570262da9d27a9582e80a83d4dfecade Mon Sep 17 00:00:00 2001 From: 7174Andy Date: Thu, 19 Dec 2024 15:14:38 -0800 Subject: [PATCH 01/16] create new ctrl + click methods --- sleap/gui/widgets/video.py | 60 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/sleap/gui/widgets/video.py b/sleap/gui/widgets/video.py index 08ee5bf36..97d1798d6 100644 --- a/sleap/gui/widgets/video.py +++ b/sleap/gui/widgets/video.py @@ -12,6 +12,7 @@ >>> vp.addInstance(instance=my_instance, color=(r, g, b)) """ + from collections import deque # FORCE_REQUESTS controls whether we emit a signal to process frame requests @@ -1886,7 +1887,7 @@ def __init__( self.track_label.setHtml(instance_label_text) # Add nodes - for (node, point) in self.instance.nodes_points: + for node, point in self.instance.nodes_points: if point.visible or self.show_non_visible: node_item = QtNode( parent=self, @@ -1901,7 +1902,7 @@ def __init__( self.nodes[node.name] = node_item # Add edges - for (src, dst) in self.skeleton.edge_names: + for src, dst in self.skeleton.edge_names: # Make sure that both nodes are present in this instance before drawing edge if src in self.nodes and dst in self.nodes: edge_item = QtEdge( @@ -2124,6 +2125,61 @@ def hoverLeaveEvent(self, event): self.updateBox() return super().hoverLeaveEvent(event) + def mousePressEvent(self, event): + """Custom event handler to emit signal on event.""" + if event.buttons() == Qt.LeftButton: + if event.modifiers() == Qt.ControlModifier: + self._duplicate_instance(event) + + def _duplicate_instance(self, event): + """Duplicate the instance and add it to the scene.""" + scene = self.scene() + + # Create a new instance by copying this one + new_instance = Instance.from_numpy( + self.instance.numpy(), skeleton=self.skeleton + ) + new_instance.track = None + + # Add instance to the context + context = self.player.context + current_frame = self.instance.frame + if context: + context.labels.add_instance(current_frame, new_instance) + + # Create a new QtInstance object for the new instance + new_instance_gui = QtInstance( + instance=new_instance, + player=self.player, + markerRadius=self.markerRadius, + nodeLabelSize=self.nodeLabelSize, + show_non_visible=self.show_non_visible, + ) + scene.addItem(new_instance_gui) + + # Select the duplicated QtInstance object + new_instance_gui.setFlag(QGraphicsItem.ItemIsMovable) + new_instance_gui.setTransformOriginPoint(self.pos()) + new_instance_gui.setSelected(True) + new_instance_gui.setCursor(Qt.ClosedHandCursor) + new_instance_gui.grabMouse() + self.player.state["instance"] = new_instance + + def mouseMoveEvent(self, event): + """Custom event handler to emit signal on event.""" + is_move = self.flags() & QGraphicsItem.ItemIsMovable + is_ctrl_pressed = (event.modifiers() & Qt.ControlModifier) == Qt.ControlModifier + + if is_move and is_ctrl_pressed: + super().mouseMoveEvent(event) + + def mouseReleaseEvent(self, event): + """Custom event handler to emit signal on event.""" + if self.flags() & QGraphicsItem.ItemIsMovable: + self.setFlag(QGraphicsItem.ItemIsMovable, False) + self.ungrabMouse() + super().mouseReleaseEvent(event) + class VisibleBoundingBox(QtWidgets.QGraphicsRectItem): """QGraphicsRectItem for user instance bounding boxes. From 38871a35fe53b2ae7e734bea32fd500842b705dc Mon Sep 17 00:00:00 2001 From: 7174Andy Date: Thu, 19 Dec 2024 15:44:02 -0800 Subject: [PATCH 02/16] update backend init --- sleap/gui/widgets/video.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sleap/gui/widgets/video.py b/sleap/gui/widgets/video.py index 97d1798d6..bbf16db4c 100644 --- a/sleap/gui/widgets/video.py +++ b/sleap/gui/widgets/video.py @@ -2143,8 +2143,11 @@ def _duplicate_instance(self, event): # Add instance to the context context = self.player.context - current_frame = self.instance.frame + current_frame = self.player.state["labeled_frame"] if context: + context.labels.labeled_frames.insert( + self.player.state["frame_idx"], new_instance + ) context.labels.add_instance(current_frame, new_instance) # Create a new QtInstance object for the new instance @@ -2156,6 +2159,7 @@ def _duplicate_instance(self, event): show_non_visible=self.show_non_visible, ) scene.addItem(new_instance_gui) + self.player.update_plot() # Select the duplicated QtInstance object new_instance_gui.setFlag(QGraphicsItem.ItemIsMovable) From 9116ef3620e3e5e52de85584f88c9c4ba3bd440d Mon Sep 17 00:00:00 2001 From: 7174Andy Date: Thu, 19 Dec 2024 19:15:40 -0800 Subject: [PATCH 03/16] used commands from the context --- sleap/gui/widgets/video.py | 35 +++++++---------------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/sleap/gui/widgets/video.py b/sleap/gui/widgets/video.py index bbf16db4c..635b4af22 100644 --- a/sleap/gui/widgets/video.py +++ b/sleap/gui/widgets/video.py @@ -2135,39 +2135,18 @@ def _duplicate_instance(self, event): """Duplicate the instance and add it to the scene.""" scene = self.scene() - # Create a new instance by copying this one - new_instance = Instance.from_numpy( - self.instance.numpy(), skeleton=self.skeleton - ) - new_instance.track = None - # Add instance to the context context = self.player.context - current_frame = self.player.state["labeled_frame"] - if context: - context.labels.labeled_frames.insert( - self.player.state["frame_idx"], new_instance - ) - context.labels.add_instance(current_frame, new_instance) - - # Create a new QtInstance object for the new instance - new_instance_gui = QtInstance( - instance=new_instance, - player=self.player, - markerRadius=self.markerRadius, - nodeLabelSize=self.nodeLabelSize, - show_non_visible=self.show_non_visible, - ) - scene.addItem(new_instance_gui) - self.player.update_plot() + context.newInstance(copy_instance=self.instance) + + lf = context.labels.find( + context.state["video"], context.state["frame_idx"], return_new=True + )[0] + new_instance = lf.instances[-1] # Select the duplicated QtInstance object - new_instance_gui.setFlag(QGraphicsItem.ItemIsMovable) - new_instance_gui.setTransformOriginPoint(self.pos()) - new_instance_gui.setSelected(True) - new_instance_gui.setCursor(Qt.ClosedHandCursor) - new_instance_gui.grabMouse() self.player.state["instance"] = new_instance + self.player.plot() def mouseMoveEvent(self, event): """Custom event handler to emit signal on event.""" From f0d7dd01bddd0bc1e1af9334a78271f23dd2fb49 Mon Sep 17 00:00:00 2001 From: 7174Andy Date: Thu, 19 Dec 2024 19:34:05 -0800 Subject: [PATCH 04/16] drag works --- sleap/gui/widgets/video.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/sleap/gui/widgets/video.py b/sleap/gui/widgets/video.py index 635b4af22..6f95529e3 100644 --- a/sleap/gui/widgets/video.py +++ b/sleap/gui/widgets/video.py @@ -2146,8 +2146,29 @@ def _duplicate_instance(self, event): # Select the duplicated QtInstance object self.player.state["instance"] = new_instance + + # Refresh the plot self.player.plot() + def on_selection_update(): + """Callback to set the new QtInstance to be movable.""" + # Find the QtInstance corresponding to the newly created instance + for qt_inst in self.player.view.all_instances: + if qt_inst.instance == new_instance: + self.player.view.updatedSelection.disconnect(on_selection_update) + + print("Setting flag") + # Set this QtInstance to be movable + qt_inst.setFlag(QGraphicsItem.ItemIsMovable) + + # Optionally grab the mouse and change cursor, so user can immediately drag + qt_inst.setCursor(Qt.ClosedHandCursor) + qt_inst.grabMouse() + break + + self.player.view.updatedSelection.connect(on_selection_update) + self.player.view.updatedSelection.emit() + def mouseMoveEvent(self, event): """Custom event handler to emit signal on event.""" is_move = self.flags() & QGraphicsItem.ItemIsMovable From 4d50c87018d33215c182cbe1184941263e5e24c9 Mon Sep 17 00:00:00 2001 From: 7174Andy Date: Thu, 19 Dec 2024 19:40:56 -0800 Subject: [PATCH 05/16] resolve bouding box out of instance error --- sleap/gui/widgets/video.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sleap/gui/widgets/video.py b/sleap/gui/widgets/video.py index 6f95529e3..d3bca15d2 100644 --- a/sleap/gui/widgets/video.py +++ b/sleap/gui/widgets/video.py @@ -2157,13 +2157,13 @@ def on_selection_update(): if qt_inst.instance == new_instance: self.player.view.updatedSelection.disconnect(on_selection_update) - print("Setting flag") # Set this QtInstance to be movable qt_inst.setFlag(QGraphicsItem.ItemIsMovable) # Optionally grab the mouse and change cursor, so user can immediately drag qt_inst.setCursor(Qt.ClosedHandCursor) qt_inst.grabMouse() + qt_inst.updateBox() break self.player.view.updatedSelection.connect(on_selection_update) @@ -2181,6 +2181,9 @@ def mouseReleaseEvent(self, event): """Custom event handler to emit signal on event.""" if self.flags() & QGraphicsItem.ItemIsMovable: self.setFlag(QGraphicsItem.ItemIsMovable, False) + self.setCursor(Qt.ArrowCursor) + self.updatePoints(user_change=True) + self.updateBox() self.ungrabMouse() super().mouseReleaseEvent(event) From 7acd5b75061ca73dd874eb47fb4dec7b8272144a Mon Sep 17 00:00:00 2001 From: 7174Andy Date: Thu, 19 Dec 2024 19:45:40 -0800 Subject: [PATCH 06/16] cleanup code --- sleap/gui/widgets/video.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/sleap/gui/widgets/video.py b/sleap/gui/widgets/video.py index d3bca15d2..7431710dd 100644 --- a/sleap/gui/widgets/video.py +++ b/sleap/gui/widgets/video.py @@ -2126,19 +2126,17 @@ def hoverLeaveEvent(self, event): return super().hoverLeaveEvent(event) def mousePressEvent(self, event): - """Custom event handler to emit signal on event.""" if event.buttons() == Qt.LeftButton: if event.modifiers() == Qt.ControlModifier: - self._duplicate_instance(event) + self._duplicate_instance() - def _duplicate_instance(self, event): + def _duplicate_instance(self): """Duplicate the instance and add it to the scene.""" - scene = self.scene() - # Add instance to the context context = self.player.context context.newInstance(copy_instance=self.instance) + # Find the new instance and its last label lf = context.labels.find( context.state["video"], context.state["frame_idx"], return_new=True )[0] @@ -2163,7 +2161,6 @@ def on_selection_update(): # Optionally grab the mouse and change cursor, so user can immediately drag qt_inst.setCursor(Qt.ClosedHandCursor) qt_inst.grabMouse() - qt_inst.updateBox() break self.player.view.updatedSelection.connect(on_selection_update) @@ -2181,7 +2178,6 @@ def mouseReleaseEvent(self, event): """Custom event handler to emit signal on event.""" if self.flags() & QGraphicsItem.ItemIsMovable: self.setFlag(QGraphicsItem.ItemIsMovable, False) - self.setCursor(Qt.ArrowCursor) self.updatePoints(user_change=True) self.updateBox() self.ungrabMouse() From 44ee11d49d6d0248f398ebfbb7ce749feede6304 Mon Sep 17 00:00:00 2001 From: 7174Andy Date: Fri, 20 Dec 2024 09:46:19 -0800 Subject: [PATCH 07/16] handle edge cases --- sleap/gui/widgets/video.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/sleap/gui/widgets/video.py b/sleap/gui/widgets/video.py index 7431710dd..7dddd2919 100644 --- a/sleap/gui/widgets/video.py +++ b/sleap/gui/widgets/video.py @@ -2126,6 +2126,9 @@ def hoverLeaveEvent(self, event): return super().hoverLeaveEvent(event) def mousePressEvent(self, event): + """Custom event handler for mouse press. + + This method is called when the user clicks on the labeled instance.""" if event.buttons() == Qt.LeftButton: if event.modifiers() == Qt.ControlModifier: self._duplicate_instance() @@ -2133,6 +2136,9 @@ def mousePressEvent(self, event): def _duplicate_instance(self): """Duplicate the instance and add it to the scene.""" # Add instance to the context + if self.player.context is None: + return + context = self.player.context context.newInstance(copy_instance=self.instance) @@ -2148,12 +2154,17 @@ def _duplicate_instance(self): # Refresh the plot self.player.plot() + # Track if the new instance is connected for cleanu + callback_connected = False + def on_selection_update(): """Callback to set the new QtInstance to be movable.""" # Find the QtInstance corresponding to the newly created instance for qt_inst in self.player.view.all_instances: if qt_inst.instance == new_instance: self.player.view.updatedSelection.disconnect(on_selection_update) + nonlocal callback_connected + callback_connected = True # Set this QtInstance to be movable qt_inst.setFlag(QGraphicsItem.ItemIsMovable) @@ -2166,6 +2177,10 @@ def on_selection_update(): self.player.view.updatedSelection.connect(on_selection_update) self.player.view.updatedSelection.emit() + # Clean up callback if QtInstance was not found + if not callback_connected: + self.player.view.updatedSelection.disconnect(on_selection_update) + def mouseMoveEvent(self, event): """Custom event handler to emit signal on event.""" is_move = self.flags() & QGraphicsItem.ItemIsMovable From 69e9f5042e9bc5b09a6ecd073e132947410ab92d Mon Sep 17 00:00:00 2001 From: 7174Andy Date: Fri, 20 Dec 2024 09:58:11 -0800 Subject: [PATCH 08/16] fix alt click for nodes --- sleap/gui/widgets/video.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sleap/gui/widgets/video.py b/sleap/gui/widgets/video.py index 7dddd2919..8a27f35d3 100644 --- a/sleap/gui/widgets/video.py +++ b/sleap/gui/widgets/video.py @@ -2126,9 +2126,7 @@ def hoverLeaveEvent(self, event): return super().hoverLeaveEvent(event) def mousePressEvent(self, event): - """Custom event handler for mouse press. - - This method is called when the user clicks on the labeled instance.""" + """Custom event handler for mouse press.""" if event.buttons() == Qt.LeftButton: if event.modifiers() == Qt.ControlModifier: self._duplicate_instance() @@ -2185,8 +2183,9 @@ def mouseMoveEvent(self, event): """Custom event handler to emit signal on event.""" is_move = self.flags() & QGraphicsItem.ItemIsMovable is_ctrl_pressed = (event.modifiers() & Qt.ControlModifier) == Qt.ControlModifier + is_alt_pressed = (event.modifiers() & Qt.AltModifier) == Qt.AltModifier - if is_move and is_ctrl_pressed: + if is_move and (is_ctrl_pressed or is_alt_pressed): super().mouseMoveEvent(event) def mouseReleaseEvent(self, event): From 5fcc696a112c96e184ef8eec62f41dd1ca73aa75 Mon Sep 17 00:00:00 2001 From: 7174Andy Date: Fri, 20 Dec 2024 10:08:48 -0800 Subject: [PATCH 09/16] duplicate instance when node is selected --- sleap/gui/widgets/video.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sleap/gui/widgets/video.py b/sleap/gui/widgets/video.py index 8a27f35d3..e11b812f3 100644 --- a/sleap/gui/widgets/video.py +++ b/sleap/gui/widgets/video.py @@ -1569,6 +1569,8 @@ def mousePressEvent(self, event): # Shift-click to mark all points as complete elif event.modifiers() == Qt.ShiftModifier: self.parentObject().updatePoints(complete=True, user_change=True) + elif event.modifiers() == Qt.ControlModifier: + self.parentObject().duplicate_instance() else: self.dragParent = False super(QtNode, self).mousePressEvent(event) @@ -2129,9 +2131,9 @@ def mousePressEvent(self, event): """Custom event handler for mouse press.""" if event.buttons() == Qt.LeftButton: if event.modifiers() == Qt.ControlModifier: - self._duplicate_instance() + self.duplicate_instance() - def _duplicate_instance(self): + def duplicate_instance(self): """Duplicate the instance and add it to the scene.""" # Add instance to the context if self.player.context is None: @@ -2172,6 +2174,7 @@ def on_selection_update(): qt_inst.grabMouse() break + # Connect the callback to the updatedSelection signal self.player.view.updatedSelection.connect(on_selection_update) self.player.view.updatedSelection.emit() @@ -2185,6 +2188,7 @@ def mouseMoveEvent(self, event): is_ctrl_pressed = (event.modifiers() & Qt.ControlModifier) == Qt.ControlModifier is_alt_pressed = (event.modifiers() & Qt.AltModifier) == Qt.AltModifier + # Only allow moving if the instance is selected if is_move and (is_ctrl_pressed or is_alt_pressed): super().mouseMoveEvent(event) From 5e00b694920f5df987e6d97e2801508f1a4f217a Mon Sep 17 00:00:00 2001 From: 7174Andy Date: Fri, 20 Dec 2024 10:09:09 -0800 Subject: [PATCH 10/16] add comment --- sleap/gui/widgets/video.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sleap/gui/widgets/video.py b/sleap/gui/widgets/video.py index e11b812f3..888b05f77 100644 --- a/sleap/gui/widgets/video.py +++ b/sleap/gui/widgets/video.py @@ -1569,6 +1569,7 @@ def mousePressEvent(self, event): # Shift-click to mark all points as complete elif event.modifiers() == Qt.ShiftModifier: self.parentObject().updatePoints(complete=True, user_change=True) + # Ctrl-click to duplicate instance elif event.modifiers() == Qt.ControlModifier: self.parentObject().duplicate_instance() else: From 1dd6392b8e50818074427cf9097290a1ae6b5b67 Mon Sep 17 00:00:00 2001 From: 7174Andy Date: Fri, 20 Dec 2024 10:09:56 -0800 Subject: [PATCH 11/16] handle edge case --- sleap/gui/widgets/video.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sleap/gui/widgets/video.py b/sleap/gui/widgets/video.py index 888b05f77..47cc86c89 100644 --- a/sleap/gui/widgets/video.py +++ b/sleap/gui/widgets/video.py @@ -2140,6 +2140,7 @@ def duplicate_instance(self): if self.player.context is None: return + # Copy the instance and add it to the context context = self.player.context context.newInstance(copy_instance=self.instance) @@ -2149,6 +2150,9 @@ def duplicate_instance(self): )[0] new_instance = lf.instances[-1] + if not new_instance: + return + # Select the duplicated QtInstance object self.player.state["instance"] = new_instance From 7cb15534386ad302c0d7c749560dd1a303829ada Mon Sep 17 00:00:00 2001 From: 7174Andy Date: Fri, 20 Dec 2024 10:29:16 -0800 Subject: [PATCH 12/16] add documentation to sleap.ai --- docs/guides/gui.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/guides/gui.md b/docs/guides/gui.md index 813ed68fa..0cda29288 100644 --- a/docs/guides/gui.md +++ b/docs/guides/gui.md @@ -148,6 +148,8 @@ Note that many of the menu command have keyboard shortcuts which can be configur **Click** elsewhere on image: Clear selection +**Control + left-click (hold) + drag** on instance: Duplicate the selected instance and drag the entire copied instance + ## Navigation Keys **Right arrow**: Move one frame forward From 4e177c06cfb666beb71a5482acfea543fa235e99 Mon Sep 17 00:00:00 2001 From: 7174Andy Date: Fri, 20 Dec 2024 11:17:43 -0800 Subject: [PATCH 13/16] minor changes according to review --- sleap/gui/widgets/video.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sleap/gui/widgets/video.py b/sleap/gui/widgets/video.py index 47cc86c89..32ee2ba78 100644 --- a/sleap/gui/widgets/video.py +++ b/sleap/gui/widgets/video.py @@ -1571,7 +1571,7 @@ def mousePressEvent(self, event): self.parentObject().updatePoints(complete=True, user_change=True) # Ctrl-click to duplicate instance elif event.modifiers() == Qt.ControlModifier: - self.parentObject().duplicate_instance() + self.parentObject().mousePressEvent(event) else: self.dragParent = False super(QtNode, self).mousePressEvent(event) @@ -2150,16 +2150,13 @@ def duplicate_instance(self): )[0] new_instance = lf.instances[-1] - if not new_instance: - return - # Select the duplicated QtInstance object self.player.state["instance"] = new_instance # Refresh the plot self.player.plot() - # Track if the new instance is connected for cleanu + # Track if the new instance is connected for clean up callback_connected = False def on_selection_update(): From e9d2c979c0a1c55762a1aba7d2ea1df308e07e7a Mon Sep 17 00:00:00 2001 From: 7174Andy Date: Fri, 20 Dec 2024 11:22:07 -0800 Subject: [PATCH 14/16] modify doc --- sleap/gui/widgets/video.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sleap/gui/widgets/video.py b/sleap/gui/widgets/video.py index 32ee2ba78..09d113f8e 100644 --- a/sleap/gui/widgets/video.py +++ b/sleap/gui/widgets/video.py @@ -2195,7 +2195,7 @@ def mouseMoveEvent(self, event): super().mouseMoveEvent(event) def mouseReleaseEvent(self, event): - """Custom event handler to emit signal on event.""" + """Custom event handler for mouse release.""" if self.flags() & QGraphicsItem.ItemIsMovable: self.setFlag(QGraphicsItem.ItemIsMovable, False) self.updatePoints(user_change=True) From b68892a7b56f5c014982fa5fa8dd2040346ef670 Mon Sep 17 00:00:00 2001 From: 7174Andy Date: Fri, 20 Dec 2024 11:45:58 -0800 Subject: [PATCH 15/16] remove callback_connected --- sleap/gui/widgets/video.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/sleap/gui/widgets/video.py b/sleap/gui/widgets/video.py index 09d113f8e..4301684be 100644 --- a/sleap/gui/widgets/video.py +++ b/sleap/gui/widgets/video.py @@ -2156,17 +2156,12 @@ def duplicate_instance(self): # Refresh the plot self.player.plot() - # Track if the new instance is connected for clean up - callback_connected = False - def on_selection_update(): """Callback to set the new QtInstance to be movable.""" # Find the QtInstance corresponding to the newly created instance for qt_inst in self.player.view.all_instances: if qt_inst.instance == new_instance: self.player.view.updatedSelection.disconnect(on_selection_update) - nonlocal callback_connected - callback_connected = True # Set this QtInstance to be movable qt_inst.setFlag(QGraphicsItem.ItemIsMovable) @@ -2180,10 +2175,6 @@ def on_selection_update(): self.player.view.updatedSelection.connect(on_selection_update) self.player.view.updatedSelection.emit() - # Clean up callback if QtInstance was not found - if not callback_connected: - self.player.view.updatedSelection.disconnect(on_selection_update) - def mouseMoveEvent(self, event): """Custom event handler to emit signal on event.""" is_move = self.flags() & QGraphicsItem.ItemIsMovable From a9c8ac560b1acb93ea6555015ce005b6c26b8962 Mon Sep 17 00:00:00 2001 From: 7174Andy Date: Fri, 20 Dec 2024 12:02:10 -0800 Subject: [PATCH 16/16] add default behavior --- sleap/gui/widgets/video.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sleap/gui/widgets/video.py b/sleap/gui/widgets/video.py index 4301684be..593a8889e 100644 --- a/sleap/gui/widgets/video.py +++ b/sleap/gui/widgets/video.py @@ -2133,6 +2133,9 @@ def mousePressEvent(self, event): if event.buttons() == Qt.LeftButton: if event.modifiers() == Qt.ControlModifier: self.duplicate_instance() + else: + # Default behavior is to select the instance + super(QtInstance, self).mousePressEvent(event) def duplicate_instance(self): """Duplicate the instance and add it to the scene."""