Skip to content

Commit

Permalink
Add menu item for deleting instances beyond frame limit (#1797)
Browse files Browse the repository at this point in the history
* Add menu item for deleting instances beyond frame limit

* Add test function to test the instances returned

* typos

* Update docstring

* Add frame range form

* Extend command to use frame range

---------

Co-authored-by: Talmo Pereira <[email protected]>
  • Loading branch information
shrivaths16 and talmo authored Dec 16, 2024
1 parent 4728aba commit 5e23ddc
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 0 deletions.
13 changes: 13 additions & 0 deletions sleap/config/frame_range_form.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions sleap/gui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,12 @@ def new_instance_menu_action():
"Delete Predictions beyond Max Instances...",
self.commands.deleteInstanceLimitPredictions,
)
add_menu_item(
labelMenu,
"delete frame limit predictions",
"Delete Predictions beyond Frame Limit...",
self.commands.deleteFrameLimitPredictions,
)

### Tracks Menu ###

Expand Down
35 changes: 35 additions & 0 deletions sleap/gui/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -494,6 +495,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)
Expand Down Expand Up @@ -2472,6 +2477,36 @@ 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):
"""Called from the parent `InstanceDeleteCommand.ask` method.
Returns:
List of instances to be deleted.
"""
instances = []
# Select the instances to be deleted
for lf in context.labels.labeled_frames:
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"]
dialog = FrameRangeDialog(
title="Delete Instances in Frame Range...", max_frame_idx=len(current_video)
)
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)


class TransposeInstances(EditCommand):
topics = [UpdateTopic.project_instances, UpdateTopic.tracks]

Expand Down
42 changes: 42 additions & 0 deletions sleap/gui/dialogs/frame_range.py
Original file line number Diff line number Diff line change
@@ -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())
22 changes: 22 additions & 0 deletions tests/gui/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
ReplaceVideo,
OpenSkeleton,
SaveProjectAs,
DeleteFrameLimitPredictions,
get_new_version_filename,
)
from sleap.instance import Instance, LabeledFrame
Expand Down Expand Up @@ -851,6 +852,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):
Expand Down

0 comments on commit 5e23ddc

Please sign in to comment.