Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions sleap/config/shortcuts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ goto next user: Ctrl+U
goto next labeled: Alt+Right
goto prev suggestion: Shift+Space
goto prev labeled: Alt+Left
goto next instance change: Ctrl+Shift+Right
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would also be nice to go the other direction please and thank you!

goto prev instance change: Ctrl+Shift+Left
learning: Ctrl+L
mark frame: Ctrl+M
new: Ctrl+N
Expand Down
14 changes: 14 additions & 0 deletions sleap/gui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,20 @@ def add_submenu_choices(menu, title, options, key):
self.commands.nextTrackFrame,
)

add_menu_item(
goMenu,
"goto next instance change",
"Next Instance Count Change",
self.commands.nextInstanceChange,
)

add_menu_item(
goMenu,
"goto prev instance change",
"Previous Instance Count Change",
self.commands.prevInstanceChange,
)

goMenu.addSeparator()

def next_vid():
Expand Down
56 changes: 56 additions & 0 deletions sleap/gui/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,14 @@ def openPrereleaseVersion(self):
"""Open the current prerelease version."""
self.execute(OpenPrereleaseVersion)

def nextInstanceChange(self):
"""Go to next frame where number of instances changes."""
self.execute(GoNextInstanceChange)

def prevInstanceChange(self):
"""Goes to previous frame with different instance count."""
self.execute(GoPrevInstanceChange)


# File Commands

Expand Down Expand Up @@ -1601,6 +1609,54 @@ class GoPrevSuggestedFrame(GoNextSuggestedFrame):
seek_direction = -1


class GoNextInstanceChange(NavCommand):
@classmethod
def do_action(cls, context: CommandContext, params: dict):
video = context.state["video"]
cur_idx = context.state["frame_idx"]

# Get current number of instances
current_frame = context.labels.find(video, cur_idx, return_new=True)[0]
current_instance_count = len(current_frame.instances_to_show)

# Get all labeled frames after current frame
for frame in range(cur_idx + 1, video.frames):
if (
len(
context.labels.find(video, frame, return_new=True)[
0
].instances_to_show
)
!= current_instance_count
):
cls.go_to(context, frame)
break


class GoPrevInstanceChange(NavCommand):
@classmethod
def do_action(cls, context: CommandContext, params: dict):
video = context.state["video"]
cur_idx = context.state["frame_idx"]

# Get current number of instances
current_frame = context.labels.find(video, cur_idx, return_new=True)[0]
current_instance_count = len(current_frame.instances_to_show)

# Get all labeled frames after current frame
for frame in range(cur_idx - 1, -1, -1):
if (
len(
context.labels.find(video, frame, return_new=True)[
0
].instances_to_show
)
!= current_instance_count
):
cls.go_to(context, frame)
break


class GoNextTrackFrame(NavCommand):
@classmethod
def do_action(cls, context: CommandContext, params: dict):
Expand Down
2 changes: 2 additions & 0 deletions sleap/gui/shortcuts.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class Shortcuts(object):
"goto next suggestion",
"goto prev suggestion",
"goto next track spawn",
"goto next instance change",
"goto prev instance change",
"show instances",
"show labels",
"show edges",
Expand Down
62 changes: 62 additions & 0 deletions tests/gui/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
SaveProjectAs,
DeleteFrameLimitPredictions,
get_new_version_filename,
GoNextInstanceChange,
GoPrevInstanceChange,
)
from sleap.instance import Instance, LabeledFrame
from sleap.io.convert import default_analysis_filename
Expand Down Expand Up @@ -1046,3 +1048,63 @@ def on_triggered():
)
diff = np.nan_to_num(new_inst.numpy() - copy_instance.numpy(), nan=offset)
assert np.all(diff == offset)


def test_go_next_instance_change(centered_pair_predictions: Labels):
"""Test that goto next instance change command works correctly."""
# Set up test data
labels = centered_pair_predictions
video = labels.videos[0]

# Create frames with different numbers of instances
lf1 = labels.find(video, 0, return_new=True)[0] # First frame (already exists)
initial_count = len(lf1.instances_to_show)

for frame_idx in range(1, video.num_frames):
lf = labels.find(video, frame_idx, return_new=True)[0]
if len(lf.instances_to_show) != initial_count:
skipped_to_frame = lf.frame_idx
break

# Set up command context
context = CommandContext.from_labels(labels)
context.state["video"] = video
context.state["frame_idx"] = lf1.frame_idx

# Execute the command
context.execute(GoNextInstanceChange)

# Verify that we moved to the frame with different instance count
assert context.state["frame_idx"] == skipped_to_frame
assert len(labels.find(video, skipped_to_frame)[0].instances) != initial_count


def test_go_prev_instance_change(centered_pair_predictions: Labels):
"""Test that goto next instance change command works correctly."""
# Set up test data
labels = centered_pair_predictions
video = labels.videos[0]

# Create frames with different numbers of instances
lf1 = labels.find(video, video.num_frames - 1, return_new=True)[
0
] # First frame (already exists)
initial_count = len(lf1.instances_to_show)

for frame_idx in range(video.num_frames - 1, 0, -1):
lf = labels.find(video, frame_idx, return_new=True)[0]
if len(lf.instances_to_show) != initial_count:
skipped_to_frame = lf.frame_idx
break

# Set up command context
context = CommandContext.from_labels(labels)
context.state["video"] = video
context.state["frame_idx"] = lf1.frame_idx

# Execute the command
context.execute(GoPrevInstanceChange)

# Verify that we moved to the frame with different instance count
assert context.state["frame_idx"] == skipped_to_frame
assert len(labels.find(video, skipped_to_frame)[0].instances) != initial_count
Loading