From 170048a86fd07bca0f31244dd54c167dd3b8d74a Mon Sep 17 00:00:00 2001 From: Shrivaths Shyam Date: Tue, 4 Jun 2024 22:51:55 -0700 Subject: [PATCH 1/6] Add menu item for deleting instances beyond frame limit --- sleap/gui/app.py | 6 ++++++ sleap/gui/commands.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/sleap/gui/app.py b/sleap/gui/app.py index becc1d83a..2450a076e 100644 --- a/sleap/gui/app.py +++ b/sleap/gui/app.py @@ -788,6 +788,12 @@ def new_instance_menu_action(): "Delete Predictions beyond Max Instances...", self.commands.deleteInstanceLimitPredictions, ) + add_menu_item( + labelMenu, + "delete frame limit predictions new item", + "Delete Predictions beyond Frame Limit new item...", + self.commands.deleteFrameLimitPredictions, + ) ### Tracks Menu ### diff --git a/sleap/gui/commands.py b/sleap/gui/commands.py index 342ceec26..45bf2a05e 100644 --- a/sleap/gui/commands.py +++ b/sleap/gui/commands.py @@ -494,6 +494,10 @@ def deleteInstanceLimitPredictions(self): """Gui for deleting instances beyond some number in each frame.""" self.execute(DeleteInstanceLimitPredictions) + def deleteFrameLimitPredictions(self): + """Gui for deleting instances beyond some frame number.""" + self.execute(DeleteFrameLimitPredictions) + def completeInstanceNodes(self, instance: Instance): """Adds missing nodes to given instance.""" self.execute(AddMissingInstanceNodes, instance=instance) @@ -2468,6 +2472,34 @@ def ask(cls, context: CommandContext, params: dict) -> bool: return super().ask(context, params) +class DeleteFrameLimitPredictions(InstanceDeleteCommand): + @staticmethod + def get_frame_instance_list(context: CommandContext, params: Dict): + predicted_instances = [] + # Select the instances to be deleted + for lf in context.labels.find(context.state["video"]): + if lf.frame_idx >= params["frame_idx_threshold"]: + predicted_instances.extend( + [(lf, inst) for inst in lf.predicted_instances] + ) + return predicted_instances + + @classmethod + def ask(cls, context: CommandContext, params: Dict) -> bool: + current_video = context.state["video"] + frame_idx_thresh, okay = QtWidgets.QInputDialog.getInt( + context.app, + "Delete Instance beyond Frame Number...", + "Frame number after which instances to be deleted:", + 1, + 1, + len(current_video), + ) + if okay: + params["frame_idx_threshold"] = frame_idx_thresh + return super().ask(context, params) + + class TransposeInstances(EditCommand): topics = [UpdateTopic.project_instances, UpdateTopic.tracks] From b336e9f2774686ff5101f3d07c3ebac24400be69 Mon Sep 17 00:00:00 2001 From: Shrivaths Shyam Date: Wed, 12 Jun 2024 00:46:04 -0700 Subject: [PATCH 2/6] Add test function to test the instances returned --- sleap/gui/app.py | 4 ++-- sleap/gui/commands.py | 4 +++- tests/gui/test_commands.py | 22 ++++++++++++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/sleap/gui/app.py b/sleap/gui/app.py index 2450a076e..3d1f7c443 100644 --- a/sleap/gui/app.py +++ b/sleap/gui/app.py @@ -790,8 +790,8 @@ def new_instance_menu_action(): ) add_menu_item( labelMenu, - "delete frame limit predictions new item", - "Delete Predictions beyond Frame Limit new item...", + "delete frame limit predictions", + "Delete Predictions beyond Frame Limit...", self.commands.deleteFrameLimitPredictions, ) diff --git a/sleap/gui/commands.py b/sleap/gui/commands.py index 45bf2a05e..ef8932df7 100644 --- a/sleap/gui/commands.py +++ b/sleap/gui/commands.py @@ -2475,9 +2475,11 @@ def ask(cls, context: CommandContext, params: dict) -> bool: class DeleteFrameLimitPredictions(InstanceDeleteCommand): @staticmethod def get_frame_instance_list(context: CommandContext, params: Dict): + """Called from the parent `InstanceDeleteCommand` class. Returns a list of + instances to be deleted.""" predicted_instances = [] # Select the instances to be deleted - for lf in context.labels.find(context.state["video"]): + for lf in context.labels.labeled_frames: if lf.frame_idx >= params["frame_idx_threshold"]: predicted_instances.extend( [(lf, inst) for inst in lf.predicted_instances] diff --git a/tests/gui/test_commands.py b/tests/gui/test_commands.py index 899b1f4a0..cc9267858 100644 --- a/tests/gui/test_commands.py +++ b/tests/gui/test_commands.py @@ -16,6 +16,7 @@ ReplaceVideo, OpenSkeleton, SaveProjectAs, + DeleteFrameLimitPredictions, get_new_version_filename, ) from sleap.instance import Instance, LabeledFrame @@ -847,6 +848,27 @@ def load_and_assert_changes(new_video_path: Path): shutil.move(new_video_path, expected_video_path) +def test_DeleteFrameLimitPredictions( + centered_pair_predictions: Labels, centered_pair_vid: Video +): + """Test deleting instances beyond a certain frame limit.""" + labels = centered_pair_predictions + + # Set-up command context + context = CommandContext.from_labels(labels) + context.state["video"] = centered_pair_vid + + # Set-up params for the command + params = {"frame_idx_threshold": 900} + + expected_instances = 423 + predicted_instances = DeleteFrameLimitPredictions.get_frame_instance_list( + context, params + ) + + assert len(predicted_instances) == expected_instances + + @pytest.mark.parametrize("export_extension", [".json.zip", ".slp"]) def test_exportLabelsPackage(export_extension, centered_pair_labels: Labels, tmpdir): def assert_loaded_package_similar(path_to_pkg: Path, sugg=False, pred=False): From 4e6aed28d2559fe941d4598941226caf1360d521 Mon Sep 17 00:00:00 2001 From: Shrivaths Shyam Date: Wed, 12 Jun 2024 09:43:58 -0700 Subject: [PATCH 3/6] typos --- sleap/gui/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sleap/gui/commands.py b/sleap/gui/commands.py index ef8932df7..a647580de 100644 --- a/sleap/gui/commands.py +++ b/sleap/gui/commands.py @@ -2475,7 +2475,7 @@ def ask(cls, context: CommandContext, params: dict) -> bool: class DeleteFrameLimitPredictions(InstanceDeleteCommand): @staticmethod def get_frame_instance_list(context: CommandContext, params: Dict): - """Called from the parent `InstanceDeleteCommand` class. Returns a list of + """Called from the parent `InstanceDeleteCommand.ask` class. Returns a list of instances to be deleted.""" predicted_instances = [] # Select the instances to be deleted From 4102ef1081e4f02d76f28e3815d1df5d0dd9d0f3 Mon Sep 17 00:00:00 2001 From: Shrivaths Shyam Date: Wed, 12 Jun 2024 15:55:58 -0700 Subject: [PATCH 4/6] Update docstring --- sleap/gui/commands.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sleap/gui/commands.py b/sleap/gui/commands.py index a647580de..9c92a409e 100644 --- a/sleap/gui/commands.py +++ b/sleap/gui/commands.py @@ -2475,8 +2475,11 @@ def ask(cls, context: CommandContext, params: dict) -> bool: class DeleteFrameLimitPredictions(InstanceDeleteCommand): @staticmethod def get_frame_instance_list(context: CommandContext, params: Dict): - """Called from the parent `InstanceDeleteCommand.ask` class. Returns a list of - instances to be deleted.""" + """Called from the parent `InstanceDeleteCommand.ask` method. + + Returns: + List of instances to be deleted. + """ predicted_instances = [] # Select the instances to be deleted for lf in context.labels.labeled_frames: From 500cd25431ee736dbbf45a8df8b73e6405bdf68e Mon Sep 17 00:00:00 2001 From: Talmo Pereira Date: Mon, 16 Dec 2024 14:27:37 -0800 Subject: [PATCH 5/6] Add frame range form --- sleap/config/frame_range_form.yaml | 13 +++++++++ sleap/gui/dialogs/frame_range.py | 42 ++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 sleap/config/frame_range_form.yaml create mode 100644 sleap/gui/dialogs/frame_range.py diff --git a/sleap/config/frame_range_form.yaml b/sleap/config/frame_range_form.yaml new file mode 100644 index 000000000..3f01eade4 --- /dev/null +++ b/sleap/config/frame_range_form.yaml @@ -0,0 +1,13 @@ +main: + + - name: min_frame_idx + label: Minimum frame index + type: int + range: 1,1000000 + default: 1 + + - name: max_frame_idx + label: Maximum frame index + type: int + range: 1,1000000 + default: 1000 \ No newline at end of file diff --git a/sleap/gui/dialogs/frame_range.py b/sleap/gui/dialogs/frame_range.py new file mode 100644 index 000000000..cb0e9cc2e --- /dev/null +++ b/sleap/gui/dialogs/frame_range.py @@ -0,0 +1,42 @@ +"""Frame range dialog.""" +from PySide2 import QtWidgets +from sleap.gui.dialogs.formbuilder import FormBuilderModalDialog +from typing import Optional + + +class FrameRangeDialog(FormBuilderModalDialog): + def __init__(self, max_frame_idx: Optional[int] = None, title: str = "Frame Range"): + + super().__init__(form_name="frame_range_form") + min_frame_idx_field = self.form_widget.fields["min_frame_idx"] + max_frame_idx_field = self.form_widget.fields["max_frame_idx"] + + if max_frame_idx is not None: + min_frame_idx_field.setRange(1, max_frame_idx) + min_frame_idx_field.setValue(1) + + max_frame_idx_field.setRange(1, max_frame_idx) + max_frame_idx_field.setValue(max_frame_idx) + + min_frame_idx_field.valueChanged.connect(self._update_max_frame_range) + max_frame_idx_field.valueChanged.connect(self._update_min_frame_range) + + self.setWindowTitle(title) + + def _update_max_frame_range(self, value): + min_frame_idx_field = self.form_widget.fields["min_frame_idx"] + max_frame_idx_field = self.form_widget.fields["max_frame_idx"] + + max_frame_idx_field.setRange(value, max_frame_idx_field.maximum()) + + def _update_min_frame_range(self, value): + min_frame_idx_field = self.form_widget.fields["min_frame_idx"] + max_frame_idx_field = self.form_widget.fields["max_frame_idx"] + + min_frame_idx_field.setRange(min_frame_idx_field.minimum(), value) + + +if __name__ == "__main__": + app = QtWidgets.QApplication([]) + dialog = FrameRangeDialog(max_frame_idx=100) + print(dialog.get_results()) From e4b4a117dda731a986c2c5d517d44813d12e8423 Mon Sep 17 00:00:00 2001 From: Talmo Pereira Date: Mon, 16 Dec 2024 14:27:46 -0800 Subject: [PATCH 6/6] Extend command to use frame range --- sleap/gui/commands.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/sleap/gui/commands.py b/sleap/gui/commands.py index d01b95569..fca982327 100644 --- a/sleap/gui/commands.py +++ b/sleap/gui/commands.py @@ -49,6 +49,7 @@ class which inherits from `AppCommand` (or a more specialized class such as from sleap.gui.dialogs.merge import MergeDialog, ReplaceSkeletonTableDialog from sleap.gui.dialogs.message import MessageDialog from sleap.gui.dialogs.missingfiles import MissingFilesDialog +from sleap.gui.dialogs.frame_range import FrameRangeDialog from sleap.gui.state import GuiState from sleap.gui.suggestions import VideoFrameSuggestions from sleap.instance import Instance, LabeledFrame, Point, PredictedInstance, Track @@ -2484,28 +2485,25 @@ def get_frame_instance_list(context: CommandContext, params: Dict): Returns: List of instances to be deleted. """ - predicted_instances = [] + instances = [] # Select the instances to be deleted for lf in context.labels.labeled_frames: - if lf.frame_idx >= params["frame_idx_threshold"]: - predicted_instances.extend( - [(lf, inst) for inst in lf.predicted_instances] - ) - return predicted_instances + if lf.frame_idx < (params["min_frame_idx"] - 1) or lf.frame_idx > ( + params["max_frame_idx"] - 1 + ): + instances.extend([(lf, inst) for inst in lf.instances]) + return instances @classmethod def ask(cls, context: CommandContext, params: Dict) -> bool: current_video = context.state["video"] - frame_idx_thresh, okay = QtWidgets.QInputDialog.getInt( - context.app, - "Delete Instance beyond Frame Number...", - "Frame number after which instances to be deleted:", - 1, - 1, - len(current_video), + dialog = FrameRangeDialog( + title="Delete Instances in Frame Range...", max_frame_idx=len(current_video) ) - if okay: - params["frame_idx_threshold"] = frame_idx_thresh + results = dialog.get_results() + if results: + params["min_frame_idx"] = results["min_frame_idx"] + params["max_frame_idx"] = results["max_frame_idx"] return super().ask(context, params)