sleap icon indicating copy to clipboard operation
sleap copied to clipboard

(4->3) Add GUI Elements for Interacting With `RecordingSession`

Open roomrys opened this issue 2 years ago • 2 comments

Description

This is a pretty large PR that aims to do a few high-level GUI tasks:

  • [x] Add a SessionsDock (for all/most RecordingSession-related GUI items)
  • [x] Allow navigate between views
  • [x] Only update "incomplete" nodes
  • [x] #1654 (@vaibhavtrip29)
  • [x] #1671 (@AdvaithRavishankar)
  • [x] #1720 (@7174Andy)
  • [x] #1747 (@ramizhajj1)
  • [x] #1760 (@7174Andy)
  • [x] #1775 (@vaibhavtrip29)
  • [x] #1783 (@justinvshen)
  • [x] #1788 (@7174Andy)
  • [x] #1789 (@ramizhajj1)

See the subsections below for more detailed mini-tasks:


Add SesssionsDock


Allow navigate between views


Only update "incomplete" nodes


Add RecordingSession table to SessionsDock (#1654)

The overall goal here is to add a new table to the SessionsDock that displays all RecordingSessions in the SessionsDock.main_window.label.sessions: List[RecordingSession] list. The widget should be a table that displays the RecordingSessions hash (eventually name), number of RecordingSession.cameras: List[Camcorder], and number of RecordingSession.videos: List[Video] (see Figure 1). This SessionsTable will be used to dynamically populate our CamerasTable later on.

  • [x] Subclass GenericTableModel to create a new model called SessionsTableModel with the class attribute properties = ("id", "videos", "cameras") (similar to VideosTableModel)
  • [x] Implement the method SessionsTableModel.item_to_data(self, obj: List[RecordingSession], item: RecordingSession) which returns the following dictionary: {id: item.__hash__(), videos: len(item.videos), cameras: len(item.cameras)} (similar to VideosTableModel.item_to_data)
  • [x] Add a sessions_model_type = SessionsTableModel attribute to SessionsDock (similar to SkeletonDock.nodes_model_type) and pass into the model_type list for super().__init__
  • [x] Add a SessionsDock.create_models method (or append to it if it already exists) that sets a SessionsDock.sessions_model = SessionsDock.sessions_model_type(items=main_window.state["labels"].sessions, context=main_window.commands) and returns a list of all models created (see SkeletonDock.create_models)
  • [x] Add a SessionsDock.create_tables method (or append to it if it already exists) that sets a SessionsDock.sessions_table = GenericTableView(state=..., row_name="session", ...) and returns a list of all tables created (see SkeletonDock.create_tables)
  • [x] Add button to "Add Session"
  • [x] Add button to "Remove Session"
  • [x] Disable "Remove Session" button when no selected RecordingSession
  • [x] In the SessionsDock.lay_everything_out method, add the SessionsDock.sessions_table to the SessionsDock.wgt_layout: QVBoxLayout (see the InstancesDock.lay_everything_out).
  • [x] Add a sessions option to UpdateTopic: Enum
  • [x] In MainWindow.on_data_update add (or append to) the if _has_topic(UpdateTopic.sessions) that updates the items to display in the SessionsTable (see video_dock.table update)

Test the SessionsTable

Tests for the SessionsTable will be added to tests/gui/test_dataviews.py.

  • [x] Test that the expected RecordingSession is returned when we select a specific row of the table (see existing test for inspiration)
  • [x] Test that when we update a RecordingSession in labels.sessions, the RecordingSession in the table is updated as well

Test the SessionsTable as part of the SessionsDock

Tests for the SessionsDock will be added to tests/gui/widgets/test_docks.py.

  • [x] Test interactions with table and GuiState (see existing test)

Figure 1: Depiction of all tables to add, including SessionsTable, CamerasTable, and UnlinkedVideosTable.


Add CamerasTable to SessionsDock

The overall goal is to add Camcorder/Video assignment table to the SessionsDock that is linked to the SessionsTable created above. This CamerasTable will have two columns. One column will display the Camcorder.names of all Camcorders in the RecordingSession.cameras: List[Camcorder] list. The second column will show which Videos are assigned to the adjacent Camcorder.name by displaying the returned value of RecordingSession.get_video(Camcorder) (or "" if None).

  • [x] Subclass GenericTableModel to create a new model called CamerasTableModel with properties = ("camera", "video")
  • [x] Implement the method CamerasTableModel.object_to_items(self, obj: RecordingSession) which returns the list of Camcorders in obj.cameras: List[Camcorder]
  • [x] Implement the method CamerasTableModel.item_to_data(self, obj: RecordingSession, item: Camcorder) which returns the following dictionary: {camera: item.name, video: obj.get_video(item)}
  • [x] Add a cameras_model_type = CamerasTableModel attribute to SessionsDock (similar to SkeletonDock.nodes_model_type) and pass into the model_type list for super().__init__
  • [x] Add a SessionsDock.create_models method (or append to it if it already exists) that sets a SessionsDock.cameras_model = SessionsDock.sessions_model_type(items=main_window.state["selected_session"], context=main_window.commands) and returns a list of all models created (see SkeletonDock.create_models)
  • [x] Add a SessionsDock.create_tables method (or append to it if it already exists) that sets a SessionsDock.cameras_table = GenericTableView(state=..., row_name="camera", ...) and returns a list of all tables created (see SkeletonDock.create_tables)
  • [x] Add a command class and CommandContext.unlink_video_from_camera method to un-link a Video from a Camcorder
  • [x] Add a button to "Unlink Video" and connect it to the CommandContext.unlink_video_from_camera method
  • [x] Disable button to "Unlink Video" when there is no selected Camcorder for the CamerasTable
  • [x] In the SessionsDock.lay_everything_out method, add the SessionsDock.cameras_table to the SessionsDock.wgt_layout: QVBoxLayout (see the InstancesDock.lay_everything_out).
  • [x] Add a sessions option to UpdateTopic: Enum if not already there
  • [x] In MainWindow.on_data_update add (or append to) the if _has_topic(UpdateTopic.sessions) that updates the items to display in the CamerasTable (see video_dock.table update)

Test the CamerasTable

Tests for the CamerasTable will be added to tests/gui/test_dataviews.py. These tests will only test the functionality of the table as it's own entity.

  • [x] Test that the expected Camcorder is returned when we select a specific row of the table (see existing test for inspiration)
  • [x] Test that when we update a Camcorder in labels.sessions, the Camcorder in the table is updated as well

Test the CamerasTable as part of the SessionsDock

Tests for the SessionsDock will be added to tests/gui/widgets/test_docks.py.

  • [x] Test interactions with table and GuiState (see existing test)

Add UnlinkedVideosTable to SessionsDock

Very similar steps to SessionsTable and CamerasTable (to be filled in), except for these few items:

  • [x] Add a dictionary {linked: ..., unlinked: ...} to LabelsDataCache that remembers which videos are linked/unlinked to a RecordingSession in labels.sessions (this dictionary will be used for the items in the UnlinkedVideosTable)
  • [x] Add command class and CommandContext.link_video_to_camera method to link a Video to a RecordingSession
  • [x] Add button to "Link Video" (connecting it to the CommandContext method created above)

Assign Instance to an InstanceGroup (waiting on 3a-3f to be merged)

  • [x] Create a sessionsMenu named "Sessions" (similar to the tracksMenu)
  • [x] Add a self.inst_groups_menu attribute to the MainWindow that adds a menu to the sessionsMenu called "Set Instance Group" (similar to self.track_menu)
  • [x] Enable/disable the self.inst_groups_menu when has_selected_instance == True (similar to self.track_menu)
  • [x] Create a MainWindow._update_sessions_menu method that takes in self and frame_idx, then retrieves the session: Optional[RecordingSession] = self.state["session"] and the frame_group: FrameGroup = session.frame_groups[frame_idx]. First clear the self.inst_groups_menu. Then, enumerate through all instance_groups in frame_group.instance_groups: List[InstanceGroup]. For the first 9 InstanceGroups use the key_command = Qt.SHIFT + Qt.Key_0 + inst_group_ind + 1 (otherwise use a default key_command = ""). For all instance_groups, add a self.inst_groups_menu action with name instance_group.name, callback lambda x=instance_group: self.commands.setInstanceGroup(x), and shortcut of key_command. Outside the enumeration, add a "New Instance Group" action to the self.inst_groups_menu with name "New Instance Group", callback self.commands.addInstanceGroup, and hotkey Qt.SHIFT + Qt.Key_0. Very similar to MainWindow._update_track_menu.
  • [ ] Add a CommandContext.setInstanceGroup(self, instance_group: Optional[InstanceGroup]) method that executes the SetSelectedInstanceGroup class with input instance_group=instance_group (see CommandContext.setInstanceTrack).
  • [ ] Add a CommandContext.addInstanceGroup(self) method that executes the AddInstanceGroup class (see CommandContext.addTrack).
  • [ ] In sleap/gui/commands.py add a SetSelectedInstanceGroup class that subclasses EditCommand. Add a SetSelectedInstanceGroup.do_action(context, params) staticmethod that first retrieves the selected_instance = context.state[selected_instance], frame_idx: int = context.state["frame_idx"], video: Video = context.state["video"], session: RecordingSession = context.state["session"], frame_group: FrameGroup = session.frame_groups.get(frame_idx, None), and camera = session.get_camera(video=video). You will need to use frame_group = session.new_frame_group(frame_idx=frame_idx) if frame_group is None. Then, use frame_group.add_instance(instance=selected_instance, camera=camera, instance_group=instance_group) to add the selected_instance to the instance_group (and frame_group). Similar to the SetSelectedInstanceTrack class.
  • [ ] In sleap/gui/commands.py add a AddInstanceGroup class that subclasses EditCommand. Add a AddInstanceGroup.do_action(context, params) staticmethod that first retrieves the frame_idx: int = context.state["frame_idx"], session: RecordingSession = context.state["session"], and frame_group: FrameGroup = session.frame_groups[frame_idx]. Then, use frame_group.add_instance_group(instance_group=None) to create and add a new empty InstanceGroup to the frame_group. Similar to the AddTrack class.

Using the workflow above (but with less detailed write-up), please also:

  • [ ] Add a "Delete Instance Group" menu that populates and functions similarly to the "Set Instance Group" menu.

Figure 2: GUI elements for assigning Instances to InstanceGroups will be similar to how we assign Instances to Tracks.


Types of changes

  • [ ] Bugfix
  • [x] New feature
  • [ ] Refactor / Code style update (no logical changes)
  • [ ] Build / CI changes
  • [ ] Documentation Update
  • [ ] Other (explain)

Does this address any currently open issues?

[list open issues here]

Outside contributors checklist

  • [ ] Review the guidelines for contributing to this repository
  • [ ] Read and sign the CLA and add yourself to the authors list
  • [ ] Make sure you are making a pull request against the develop branch (not main). Also you should start your branch off develop
  • [ ] Add tests that prove your fix is effective or that your feature works
  • [ ] Add necessary documentation (if appropriate)

Thank you for contributing to SLEAP!

:heart:

roomrys avatar Apr 20 '23 16:04 roomrys

Codecov Report

Attention: Patch coverage is 83.50669% with 111 lines in your changes missing coverage. Please review.

Project coverage is 74.26%. Comparing base (07ea17b) to head (5014f15).

Files with missing lines Patch % Lines
sleap/gui/commands.py 84.35% 28 Missing :warning:
sleap/gui/color.py 20.83% 19 Missing :warning:
sleap/gui/dataviews.py 56.41% 17 Missing :warning:
sleap/gui/widgets/video.py 59.52% 17 Missing :warning:
sleap/gui/app.py 86.48% 10 Missing :warning:
sleap/io/cameras.py 91.81% 9 Missing :warning:
sleap/gui/widgets/docks.py 91.48% 8 Missing :warning:
sleap/util.py 92.85% 3 Missing :warning:
Additional details and impacted files
@@                                 Coverage Diff                                 @@
##           liezl/asc-initial-update-instances-across-views    #1283      +/-   ##
===================================================================================
+ Coverage                                            73.99%   74.26%   +0.27%     
===================================================================================
  Files                                                  135      135              
  Lines                                                24923    25465     +542     
===================================================================================
+ Hits                                                 18441    18912     +471     
- Misses                                                6482     6553      +71     

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.

:rocket: New features to boost your workflow:
  • :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

codecov[bot] avatar Apr 20 '23 17:04 codecov[bot]

[!IMPORTANT]

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

coderabbitai[bot] avatar Mar 15 '24 23:03 coderabbitai[bot]