Skip to content

Commit

Permalink
Add RecordingSessions table to SessionsDock (#1654)
Browse files Browse the repository at this point in the history
* enable user to add and remove session to dock

* add test to verify sessions dock is properly adding a RecordingSession object

* add tests to valid creation and modification of session table

* Set selected session to None after Remove/Add session

* Remove unused imports, use video fixture

* Return a dictionary for SessionsDock.table and model

* Add sesstions_table to layout (not table dict)

* Split SessionsDock test from sessions table test

* Lint

---------

Co-authored-by: roomrys <[email protected]>
  • Loading branch information
vaibhavtrip29 and roomrys authored Mar 15, 2024
1 parent cc78c91 commit 90a2c0e
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 17 deletions.
6 changes: 5 additions & 1 deletion sleap/gui/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
frame and instances listed in data view table.
"""


import os
import platform
import random
Expand Down Expand Up @@ -1093,6 +1092,7 @@ def _update_gui_state(self):
has_selected_node = self.state["selected_node"] is not None
has_selected_edge = self.state["selected_edge"] is not None
has_selected_video = self.state["selected_video"] is not None
has_selected_session = self.state["selected_session"] is not None
has_video = self.state["video"] is not None

has_frame_range = bool(self.state["has_frame_range"])
Expand Down Expand Up @@ -1151,6 +1151,7 @@ def _update_gui_state(self):
self.suggestions_dock.suggestions_form_widget.buttons[
"generate_button"
].setEnabled(has_videos)
self._buttons["remove session"].setEnabled(has_selected_session)

# Update overlays
self.overlays["track_labels"].visible = (
Expand All @@ -1176,6 +1177,9 @@ def _has_topic(topic_list):
):
self.plotFrame()

if _has_topic([UpdateTopic.sessions]):
self.sessions_dock.sessions_table.model().items = self.labels.sessions

if _has_topic(
[
UpdateTopic.frame,
Expand Down
28 changes: 25 additions & 3 deletions sleap/gui/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class UpdateTopic(Enum):
frame = 8
project = 9
project_instances = 10
sessions = 11


class AppCommand:
Expand Down Expand Up @@ -444,6 +445,10 @@ def addSession(self):
"""Shows gui for adding `RecordingSession`s to the project."""
self.execute(AddSession)

def removeSelectedSession(self):
"""Removes a session from the project and the sessions dock."""
self.execute(RemoveSession)

def openSkeletonTemplate(self):
"""Shows gui for loading saved skeleton into project."""
self.execute(OpenSkeleton, template=True)
Expand Down Expand Up @@ -1700,7 +1705,6 @@ def ask(cls, context: "CommandContext", params: dict) -> bool:
class GoAdjacentView(NavCommand):
@classmethod
def do_action(cls, context: CommandContext, params: dict):

operator = -1 if params["prev_or_next"] == "prev" else 1

labels = context.labels
Expand Down Expand Up @@ -1970,12 +1974,26 @@ def ask(context: CommandContext, params: dict) -> bool:
return True


class AddSession(EditCommand):
# topics = [UpdateTopic.session]
class RemoveSession(EditCommand):
topics = [UpdateTopic.sessions]

@staticmethod
def do_action(context: CommandContext, params: dict):
current_session = context.state["selected_session"]
try:
context.labels.remove_recording_session(current_session)
except Exception as e:
raise e
finally:
# Always set the selected session to None, even if it wasn't removed
context.state["selected_session"] = None


class AddSession(EditCommand):
topics = [UpdateTopic.sessions]

@staticmethod
def do_action(context: CommandContext, params: dict):
camera_calibration = params["camera_calibration"]
session = RecordingSession.load(filename=camera_calibration)

Expand All @@ -1986,6 +2004,10 @@ def do_action(context: CommandContext, params: dict):
if context.state["session"] is None:
context.state["session"] = session

# Reset since this action is also linked to a button in the SessionsDock and it
# is not visually apparent which session is selected after clicking the button
context.state["selected_session"] = None

@staticmethod
def ask(context: CommandContext, params: dict) -> bool:
"""Shows gui for adding video to project."""
Expand Down
12 changes: 11 additions & 1 deletion sleap/gui/dataviews.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,17 @@ def getSelectedRowItem(self) -> Any:
return self.model().original_items[idx.row()]


class SessionsTableModel(GenericTableModel):
properties = ("id", "videos", "cameras")

def item_to_data(self, obj, item):
res = {}
res["id"] = hash(item)
res["cameras"] = len(getattr(item, "cameras"))
res["videos"] = len(getattr(item, "videos"))
return res


class VideosTableModel(GenericTableModel):
properties = ("filename", "frames", "height", "width", "channels")

Expand Down Expand Up @@ -538,7 +549,6 @@ def sort(self, column_idx: int, order: QtCore.Qt.SortOrder):
if prop != "group":
super(SuggestionsTableModel, self).sort(column_idx, order)
else:

if not reverse:
# Use group_int (int) instead of group (str).
self.beginResetModel()
Expand Down
58 changes: 51 additions & 7 deletions sleap/gui/widgets/docks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Module for creating dock widgets for the `MainWindow`."""

from typing import Callable, Iterable, List, Optional, Type, Union
from typing import Callable, Dict, Iterable, List, Optional, Type, Union

from qtpy import QtGui
from qtpy.QtCore import Qt
Expand Down Expand Up @@ -31,6 +31,7 @@
SkeletonNodesTableModel,
SuggestionsTableModel,
VideosTableModel,
SessionsTableModel,
)
from sleap.gui.dialogs.formbuilder import YamlFormWidget
from sleap.gui.widgets.views import CollapsibleWidget
Expand Down Expand Up @@ -367,7 +368,6 @@ def create_templates_groupbox(self) -> QGroupBox:
vb.addWidget(hbw)

def updatePreviewImage(preview_image_bytes: bytes):

# Decode the preview image
preview_image = decode_preview_image(preview_image_bytes)

Expand Down Expand Up @@ -574,11 +574,9 @@ def create_table_edit_buttons(self) -> QWidget:

class SessionsDock(DockWidget):
def __init__(self, main_window: Optional[QMainWindow]):
super().__init__(name="Sessions", main_window=main_window, model_type=None)

def lay_everything_out(self) -> None:
triangulation_options = self.create_triangulation_options()
self.wgt_layout.addWidget(triangulation_options)
super().__init__(
name="Sessions", main_window=main_window, model_type=SessionsTableModel
)

def create_triangulation_options(self) -> QWidget:
main_window = self.main_window
Expand All @@ -601,3 +599,49 @@ def create_triangulation_options(self) -> QWidget:
hbw = QWidget()
hbw.setLayout(hb)
return hbw

def create_models(self) -> Union[GenericTableModel, Dict[str, GenericTableModel]]:
main_window = self.main_window
self.sessions_model = self.model_type(
items=main_window.state["labels"].sessions, context=main_window.commands
)

self.model = {"sessions_model": self.sessions_model}
return self.model

def create_tables(self) -> Union[GenericTableView, Dict[str, GenericTableView]]:
if self.sessions_model is None:
self.create_models()

main_window = self.main_window
self.sessions_table = GenericTableView(
state=main_window.state, row_name="session", model=self.sessions_model
)

self.table = {"sessions_table": self.sessions_table}
return self.table

def create_table_edit_buttons(self) -> QWidget:
main_window = self.main_window

hb = QHBoxLayout()
self.add_button(hb, "Add Session", lambda x: main_window.commands.addSession())
self.add_button(
hb, "Remove Session", main_window.commands.removeSelectedSession
)

hbw = QWidget()
hbw.setLayout(hb)
return hbw

def lay_everything_out(self) -> None:
if self.table is None:
self.create_tables()

self.wgt_layout.addWidget(self.sessions_table)

table_edit_buttons = self.create_table_edit_buttons()
self.wgt_layout.addWidget(table_edit_buttons)

triangulation_options = self.create_triangulation_options()
self.wgt_layout.addWidget(triangulation_options)
4 changes: 4 additions & 0 deletions sleap/io/cameras.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Module for storing information for camera groups."""

import logging
import tempfile
from pathlib import Path
Expand Down Expand Up @@ -580,6 +581,9 @@ def get_videos_from_selected_cameras(

return videos

def __bool__(self):
return True

def __attrs_post_init__(self):
self.camera_cluster.add_session(self)

Expand Down
10 changes: 9 additions & 1 deletion sleap/io/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -1714,6 +1714,15 @@ def remove_session_video(self, session: RecordingSession, video: Video):
if video in session.videos:
session.remove_video(video)

def remove_recording_session(self, session: RecordingSession):
"""Remove a session from self.sessions.
Args:
session: `RecordingSession` instance
"""
if session in self._sessions:
self._sessions.remove(session)

@classmethod
def from_json(cls, *args, **kwargs):
from sleap.io.format.labels_json import LabelsJsonAdaptor
Expand Down Expand Up @@ -2854,7 +2863,6 @@ def find_path_using_paths(missing_path: Text, search_paths: List[Text]) -> Text:

# Look for file with that name in each of the search path directories
for search_path in search_paths:

if os.path.isfile(search_path):
path_dir = os.path.dirname(search_path)
else:
Expand Down
31 changes: 27 additions & 4 deletions tests/gui/test_dataviews.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import pytest
import pytestqt

from sleap.gui.dataviews import *


def test_skeleton_nodes(qtbot, centered_pair_predictions):

table = GenericTableView(
model=SkeletonNodesTableModel(items=centered_pair_predictions.skeletons[0])
)
Expand Down Expand Up @@ -74,6 +70,33 @@ def test_table_sort(qtbot, centered_pair_predictions):
assert table.getSelectedRowItem().score == inst.score


def test_sessions_table(qtbot, min_session_session, hdf5_vid):
sessions = []
sessions.append(min_session_session)
table = GenericTableView(
row_name="session",
is_sortable=True,
name_prefix="",
model=SessionsTableModel(items=sessions),
)
table.selectRow(0)
assert len(table.getSelectedRowItem().videos) == 0
assert len(table.getSelectedRowItem().camera_cluster.cameras) == 8
assert len(table.getSelectedRowItem().camera_cluster.sessions) == 1

video = hdf5_vid
min_session_session.add_video(
video,
table.getSelectedRowItem().camera_cluster.cameras[0],
)

# Verify that modification of the recording session is reflected in the recording session stored in the table
assert len(table.getSelectedRowItem().videos) == 1

min_session_session.remove_video(video)
assert len(table.getSelectedRowItem().videos) == 0


def test_table_sort_string(qtbot):
table_model = GenericTableModel(
items=[dict(a=1, b=2), dict(a=2, b="")], properties=["a", "b"]
Expand Down
35 changes: 35 additions & 0 deletions tests/gui/widgets/test_docks.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
SuggestionsDock,
VideosDock,
SkeletonDock,
SessionsDock,
)


Expand Down Expand Up @@ -107,3 +108,37 @@ def test_instances_dock(qtbot):
assert dock.name == "Instances"
assert dock.main_window is main_window
assert dock.wgt_layout is dock.widget().layout()


def test_sessions_dock(qtbot):
"""Test the `SessionsDock` class."""
main_window = MainWindow()
dock = SessionsDock(main_window)

assert dock.name == "Sessions"
assert dock.main_window is main_window
assert dock.wgt_layout is dock.widget().layout()


def test_sessions_dock_session_table(qtbot, multiview_min_session_labels):
"""Test the SessionsDock.sessions_table."""

# Create dock
main_window = MainWindow()
SessionsDock(main_window)

# Loading label file
main_window.commands.loadLabelsObject(multiview_min_session_labels)

# Testing if sessions table is loaded correctly
sessions = multiview_min_session_labels.sessions
main_window.sessions_dock.sessions_table.selectRow(0)
assert main_window.sessions_dock.sessions_table.getSelectedRowItem() == sessions[0]

# Testing if removal of selected session is reflected in sessions dock
main_window.state["selected_session"] = sessions[0]
main_window._buttons["remove session"].click()

with pytest.raises(IndexError):
# There are no longer any sessions in the table
main_window.sessions_dock.sessions_table.selectRow(0)

0 comments on commit 90a2c0e

Please sign in to comment.