Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allowing inference on multiple videos via sleap-track #1784

Merged

Conversation

emdavis02
Copy link
Contributor

@emdavis02 emdavis02 commented May 24, 2024

Description

Adding an option to add a directory to sleap-track to allow the command to iterate through the files in the directory and run inferences on all of them.

Types of changes

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

Does this address any currently open issues?

#1777

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!

❤️

Summary by CodeRabbit

  • New Features

    • Added support for handling multiple video inputs for inference, accommodating various video paths.
    • Inference results now include detailed provenance metadata.
  • Improvements

    • Enhanced error handling for smoother processing of multiple inputs and outputs.
  • Bug Fixes

    • Fixed issues related to running inference on existing predictions and managing output paths.
  • Tests

    • Introduced new test scenarios for multiple video inputs and output handling.
    • Improved existing tests to cover additional edge cases and invalid inputs.

Copy link

coderabbitai bot commented May 24, 2024

Walkthrough

The recent updates to the sleap/nn/inference.py and corresponding test files enhance the software to handle multiple video inputs. The changes refactor the code to iterate over a list of file paths, create providers for each file, run the inference, and save the results. Enhancements include better error handling, improved metadata management, and robust testing for various scenarios.

Changes

File Change Summary
sleap/nn/inference.py Refactored logic to handle multiple video inputs, created a provider list, managed output paths, and added provenance metadata.
tests/nn/test_inference.py Added fixtures and test functions for multiple inference scenarios, including parameterized tests and handling invalid inputs.

Poem

In the land of code where videos roam,
A rabbit worked to bring them home.
With paths aplenty and providers in tow,
Each file's secrets would soon show.
Tests were written, robust and clear,
Ensuring the code ran without fear.
🎥✨🐇 The rabbit's joy was quick to grow!


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

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>.
    • 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 generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @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 as 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 resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Review Details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits Files that changed from the base of the PR and between 6a0cf9c and 132a1ea.
Files selected for processing (1)
  • sleap/nn/inference.py (4 hunks)
Additional Context Used
Ruff (3)
sleap/nn/inference.py (3)

149-149: Undefined name Task


584-584: Local variable outputs is assigned to but never used


4615-4615: Do not compare types, use isinstance()

Additional comments not posted (2)
sleap/nn/inference.py (2)

5291-5340: Refactor to handle multiple data paths and providers looks good. Consider adding comments to explain the handling of different file types.


5474-5474: Integration of the new provider and data path handling in data loader setup looks correct. Ensure compatibility with the tracker setup.

Verification successful

The _make_tracker_from_cli function and its usage appear to be correctly integrated with the new data structures. The function creates a tracker based on CLI arguments, and the test file verifies its output, ensuring compatibility.

  • sleap/nn/inference.py:

    • Function definition: _make_tracker_from_cli(args: argparse.Namespace) -> Optional[Tracker]
    • The function creates a tracker based on parsed CLI arguments.
  • tests/nn/test_inference.py:

    • The function is used to create a tracker and verify its output.

The integration of the new provider and data path handling in the data loader setup looks correct. Ensure compatibility with the tracker setup.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify tracker setup compatibility with new data structures.

# Test: Search for the tracker setup function and its usage. Expect: Compatibility with new data structures.
rg --type python $'_make_tracker_from_cli'

Length of output: 72



Script:

#!/bin/bash
# Description: Verify tracker setup compatibility with new data structures.

# Test: Search for the tracker setup function and its usage. Expect: Compatibility with new data structures.
rg --type py '_make_tracker_from_cli'

Length of output: 328



Script:

#!/bin/bash
# Description: Extract the `_make_tracker_from_cli` function definition and its usage.

# Extract the function definition.
rg --type py --context 10 'def _make_tracker_from_cli' sleap/nn/inference.py

# Extract the usage in the test file.
rg --type py --context 10 '_make_tracker_from_cli' tests/nn/test_inference.py

Length of output: 1899

sleap/nn/inference.py Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Review Details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits Files that changed from the base of the PR and between 132a1ea and e867ec0.
Files selected for processing (1)
  • sleap/nn/inference.py (3 hunks)
Additional Context Used
Ruff (3)
sleap/nn/inference.py (3)

149-149: Undefined name Task


584-584: Local variable outputs is assigned to but never used


4615-4615: Do not compare types, use isinstance()

Additional comments not posted (1)
sleap/nn/inference.py (1)

5600-5600: Refine the error message to provide more context and guidance.

- "Use \"sleap-track --tracking.tracker ...' to specify tracker to use."
+ "Specify the tracker using 'sleap-track --tracking.tracker'. Refer to the documentation for more details."

sleap/nn/inference.py Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Review Details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits Files that changed from the base of the PR and between e867ec0 and babaa77.
Files selected for processing (1)
  • sleap/nn/inference.py (4 hunks)
Additional Context Used
Ruff (3)
sleap/nn/inference.py (3)

149-149: Undefined name Task


584-584: Local variable outputs is assigned to but never used


4615-4615: Do not compare types, use isinstance()

Additional comments not posted (1)
sleap/nn/inference.py (1)

5607-5607: The device setup logic is correctly implemented to handle both CPU and GPU based on the user's configuration.

sleap/nn/inference.py Outdated Show resolved Hide resolved
sleap/nn/inference.py Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Review Details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits Files that changed from the base of the PR and between babaa77 and 83f444a.
Files selected for processing (1)
  • sleap/nn/inference.py (4 hunks)
Additional Context Used
Ruff (3)
sleap/nn/inference.py (3)

149-149: Undefined name Task


584-584: Local variable outputs is assigned to but never used


4615-4615: Do not compare types, use isinstance()

Additional comments not posted (2)
sleap/nn/inference.py (2)

5611-5611: Refine the error message to provide more context and guidance.


5611-5611: Refine the error message to provide more context and guidance.

sleap/nn/inference.py Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Outside diff range and nitpick comments (10)
tests/nn/test_inference.py (10)

Line range hint 11-11: Remove unused import: tensorflow_hub.

- import tensorflow_hub as hub
Tools
Ruff

6-6: os imported but unused (F401)

Remove unused import: os


Line range hint 15-15: Remove unused import: sleap.gui.learning.runners.

- from sleap.gui.learning import runners
Tools
Ruff

6-6: os imported but unused (F401)

Remove unused import: os


Line range hint 23-24: Remove unused imports related to InferenceLayer and InferenceModel.

- from sleap.nn.inference import (
-     InferenceLayer,
-     InferenceModel,
Tools
Ruff

6-6: os imported but unused (F401)

Remove unused import: os


Line range hint 57-57: Remove unused import: sleap.nn.tracking.MatchedFrameInstance.

- from sleap.nn.tracking import (
-     MatchedFrameInstance,
Tools
Ruff

6-6: os imported but unused (F401)

Remove unused import: os


Line range hint 62-62: Remove unused import: sleap.instance.Track.

- from sleap.instance import Track
Tools
Ruff

6-6: os imported but unused (F401)

Remove unused import: os


Line range hint 605-605: Replace equality comparisons to False and True with more Pythonic expressions.

- if predictor.is_grayscale == False
+ if not predictor.is_grayscale

- if predictor.is_grayscale == True
+ if predictor.is_grayscale

Also applies to: 653-653, 702-702, 735-735, 1644-1644

Tools
Ruff

6-6: os imported but unused (F401)

Remove unused import: os


Line range hint 890-890: Remove assignment to unused variable predictor.

- predictor = load_model(model_path)
Tools
Ruff

6-6: os imported but unused (F401)

Remove unused import: os


Line range hint 1249-1249: Consider replacing pytest.raises(Exception) with a more specific exception type to improve test clarity and error handling.

Tools
Ruff

6-6: os imported but unused (F401)

Remove unused import: os


Line range hint 1379-1379: Remove assignment to unused variable labels.

- labels = min_tracks_2node_labels
Tools
Ruff

6-6: os imported but unused (F401)

Remove unused import: os


Line range hint 1565-1565: Use key in dict instead of key in dict.keys() for more efficient dictionary lookups.

- for track in tracker.track_matching_queue_dict.keys():
+ for track in tracker.track_matching_queue_dict:

- for key in tracker.candidate_maker.shifted_instances.keys():
+ for key in tracker.candidate_maker.shifted_instances:

Also applies to: 1609-1609, 1614-1614

Tools
Ruff

6-6: os imported but unused (F401)

Remove unused import: os

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 83f444a and b0ac880.

Files selected for processing (2)
  • sleap/nn/inference.py (4 hunks)
  • tests/nn/test_inference.py (4 hunks)
Additional context used
Ruff
tests/nn/test_inference.py

6-6: os imported but unused (F401)

Remove unused import: os


11-11: tensorflow_hub imported but unused (F401)

Remove unused import: tensorflow_hub


15-15: sleap.gui.learning.runners imported but unused (F401)

Remove unused import: sleap.gui.learning.runners


23-23: sleap.nn.inference.InferenceLayer imported but unused (F401)

Remove unused import


24-24: sleap.nn.inference.InferenceModel imported but unused (F401)

Remove unused import


57-57: sleap.nn.tracking.MatchedFrameInstance imported but unused (F401)

Remove unused import: sleap.nn.tracking.MatchedFrameInstance


62-62: sleap.instance.Track imported but unused (F401)

Remove unused import: sleap.instance.Track


605-605: Avoid equality comparisons to False; use if not predictor.is_grayscale: for false checks (E712)

Replace with not predictor.is_grayscale


653-653: Avoid equality comparisons to True; use if predictor.is_grayscale: for truth checks (E712)

Replace with predictor.is_grayscale


702-702: Avoid equality comparisons to True; use if predictor.is_grayscale: for truth checks (E712)

Replace with predictor.is_grayscale


735-735: Avoid equality comparisons to True; use if predictor.is_grayscale: for truth checks (E712)

Replace with predictor.is_grayscale


794-794: Pointless comparison. Did you mean to assign a value? Otherwise, prepend assert or remove it. (B015)


795-795: Pointless comparison. Did you mean to assign a value? Otherwise, prepend assert or remove it. (B015)


890-890: Local variable predictor is assigned to but never used (F841)

Remove assignment to unused variable predictor


1249-1249: pytest.raises(Exception) should be considered evil (B017)


1379-1379: Local variable labels is assigned to but never used (F841)

Remove assignment to unused variable labels


1565-1565: Use key in dict instead of key in dict.keys() (SIM118)

Remove .keys()


1609-1609: Use key in dict instead of key in dict.keys() (SIM118)

Remove .keys()


1614-1614: Use key in dict instead of key in dict.keys() (SIM118)

Remove .keys()


1644-1644: Avoid equality comparisons to False; use if not predictor.is_grayscale: for false checks (E712)

Replace with not predictor.is_grayscale

sleap/nn/inference.py

131-131: typing.Text is deprecated, use str (UP019)

Replace with str


149-149: Undefined name Task (F821)


584-584: Local variable outputs is assigned to but never used (F841)

Remove assignment to unused variable outputs


605-605: typing.Text is deprecated, use str (UP019)

Replace with str


613-613: typing.Text is deprecated, use str (UP019)

Replace with str


627-627: typing.Text is deprecated, use str (UP019)

Replace with str


641-641: typing.Text is deprecated, use str (UP019)

Replace with str


695-696: Use a single if statement instead of nested if statements (SIM102)

Combine if statements using and


1183-1183: No explicit stacklevel keyword argument found (B028)


1325-1328: Use ternary operator imgs = data["image"] if isinstance(data, dict) else data instead of if-else-block (SIM108)

Replace if-else-block with imgs = data["image"] if isinstance(data, dict) else data


1468-1468: typing.Text is deprecated, use str (UP019)

Replace with str


1758-1763: Use ternary operator imgs = inputs["image"] if isinstance(inputs, dict) else inputs instead of if-else-block (SIM108)


2394-2394: typing.Text is deprecated, use str (UP019)

Replace with str


2395-2395: typing.Text is deprecated, use str (UP019)

Replace with str


2822-2825: Use ternary operator imgs = data["image"] if isinstance(data, dict) else data instead of if-else-block (SIM108)

Replace if-else-block with imgs = data["image"] if isinstance(data, dict) else data


3110-3110: typing.Text is deprecated, use str (UP019)

Replace with str


3424-3427: Use ternary operator imgs = data["image"] if isinstance(data, dict) else data instead of if-else-block (SIM108)

Replace if-else-block with imgs = data["image"] if isinstance(data, dict) else data


3436-3439: Use ternary operator offsets = preds[self.offsets_ind] if self.offsets_ind is not None else None instead of if-else-block (SIM108)

Replace if-else-block with offsets = preds[self.offsets_ind] if self.offsets_ind is not None else None


3654-3654: typing.Text is deprecated, use str (UP019)

Replace with str


3756-3756: Loop control variable image not used within loop body (B007)

Rename unused image to _image


4262-4262: typing.Text is deprecated, use str (UP019)

Replace with str


4263-4263: typing.Text is deprecated, use str (UP019)

Replace with str


4452-4452: Loop control variable image not used within loop body (B007)

Rename unused image to _image


4615-4619: Use ternary operator img = ex["image"] if type(ex) == dict else ex instead of if-else-block (SIM108)

Replace if-else-block with img = ex["image"] if type(ex) == dict else ex


4615-4615: Do not compare types, use isinstance() (E721)


4709-4709: typing.Text is deprecated, use str (UP019)

Replace with str


4873-4876: Use ternary operator model_paths = [model_path] if isinstance(model_path, str) else model_path instead of if-else-block (SIM108)

Replace if-else-block with model_paths = [model_path] if isinstance(model_path, str) else model_path


5294-5297: Use ternary operator data_path = Path(labels_path) if labels_path is not None else Path(args.data_path) instead of if-else-block (SIM108)

Replace if-else-block with data_path = Path(labels_path) if labels_path is not None else Path(args.data_path)


5350-5350: Local variable e is assigned to but never used (F841)

Remove assignment to unused variable e

Additional comments not posted (7)
tests/nn/test_inference.py (1)

1486-1537: The test function test_sleap_track_mult_input is designed to handle multiple input files for tracking. Ensure that the test covers all necessary assertions and cleanup operations correctly.

Verification successful

The test function test_sleap_track_mult_input and its related functions test_sleap_track_mult_inputs_folder_slp_files and test_sleap_track_mult_inputs_folder have been successfully extracted. The implementation shows that the test function is designed to handle multiple input files for tracking and includes assertions to ensure the existence of prediction files and cleanup operations.

  • test_sleap_track_mult_input handles multiple files by iterating over the directory contents and asserting the existence of files before and after running the sleap_track command.
  • The function test_sleap_track_mult_inputs_folder_slp_files returns a path to a directory containing multiple input files.
  • The function test_sleap_track_mult_inputs_folder is defined as a fixture but its implementation is not fully shown.

Based on the extracted code, the test function appears to cover the necessary assertions and cleanup operations correctly.

Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify that the test function `test_sleap_track_mult_input` handles multiple files correctly.

# Test: Search for the function usage. Expect: Proper handling of multiple files.
rg --type python $'test_sleap_track_mult_input'

Length of output: 77



Script:

#!/bin/bash
# Description: Verify that the test function `test_sleap_track_mult_input` handles multiple files correctly.

# Test: Search for the function usage. Expect: Proper handling of multiple files.
rg --type py $'test_sleap_track_mult_input'

Length of output: 578



Script:

#!/bin/bash
# Description: Extract the implementation of `test_sleap_track_mult_input` and related functions from `tests/nn/test_inference.py`.

# Extract the implementation of the function `test_sleap_track_mult_input` and related functions.
ast-grep --lang python --pattern $'def test_sleap_track_mult_input($_$$$) {
  $$$
}' tests/nn/test_inference.py

ast-grep --lang python --pattern $'def test_sleap_track_mult_inputs_folder_slp_files($_$$$) {
  $$$
}' tests/nn/test_inference.py

ast-grep --lang python --pattern $'def test_sleap_track_mult_inputs_folder($_$$$) {
  $$$
}' tests/nn/test_inference.py

Length of output: 1803



Script:

#!/bin/bash
# Description: Extract the implementation of `test_sleap_track_mult_input` and related functions from `tests/nn/test_inference.py`.

# Extract the implementation of the function `test_sleap_track_mult_input` and related functions.
ast-grep --lang python --pattern 'def test_sleap_track_mult_input($_$$$): $$$' tests/nn/test_inference.py

ast-grep --lang python --pattern 'def test_sleap_track_mult_inputs_folder_slp_files($_$$$): $$$' tests/nn/test_inference.py

ast-grep --lang python --pattern 'def test_sleap_track_mult_inputs_folder($_$$$): $$$' tests/nn/test_inference.py

Length of output: 349



Script:

#!/bin/bash
# Description: Search for the function definitions of `test_sleap_track_mult_input` and related functions in `tests/nn/test_inference.py`.

# Search for the function definitions.
rg 'def test_sleap_track_mult_input' tests/nn/test_inference.py

rg 'def test_sleap_track_mult_inputs_folder_slp_files' tests/nn/test_inference.py

rg 'def test_sleap_track_mult_inputs_folder' tests/nn/test_inference.py

Length of output: 501



Script:

#!/bin/bash
# Description: Extract the full implementation of `test_sleap_track_mult_input` and related functions from `tests/nn/test_inference.py`.

# Extract the full implementation of the function `test_sleap_track_mult_input`.
sed -n '/def test_sleap_track_mult_input/,/^def/p' tests/nn/test_inference.py

# Extract the full implementation of the function `test_sleap_track_mult_inputs_folder_slp_files`.
sed -n '/def test_sleap_track_mult_inputs_folder_slp_files/,/^def/p' tests/nn/test_inference.py

# Extract the full implementation of the function `test_sleap_track_mult_inputs_folder`.
sed -n '/def test_sleap_track_mult_inputs_folder/,/^def/p' tests/nn/test_inference.py

Length of output: 2565

sleap/nn/inference.py (6)

5300-5307: Ensure proper handling of directories and files. The current implementation is correct but consider adding a comment about the expected directory structure or file types for clarity.


5448-5448: Logging the parsed arguments is good for debugging. Ensure sensitive information is not logged.


5488-5488: Correct setup of data loaders and trackers. Ensure that _make_provider_from_cli and _make_tracker_from_cli are robust and handle errors appropriately.


5510-5564: This block handles the core functionality of running inference on multiple inputs. Ensure that all edge cases are handled, such as empty input lists or errors during processing.


5568-5617: The tracking logic appears robust. Consider adding more detailed logging to help with debugging and ensure that all paths are correctly reset after each iteration.


5627-5627: Refine the error message to provide more context and guidance.

sleap/nn/inference.py Outdated Show resolved Hide resolved
tests/nn/test_inference.py Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Outside diff range and nitpick comments (12)
tests/nn/test_inference.py (11)

Line range hint 10-10: Remove unused import: tensorflow_hub.

- import tensorflow_hub as hub

Line range hint 14-14: Remove unused import: sleap.gui.learning.runners.

- from sleap.gui.learning import runners

Line range hint 22-23: Remove unused imports related to inference layers and models.

- from sleap.nn.inference import InferenceLayer
- from sleap.nn.inference import InferenceModel

Line range hint 56-56: Remove unused import: sleap.nn.tracking.MatchedFrameInstance.

- from sleap.nn.tracking import MatchedFrameInstance

Line range hint 61-61: Remove unused import: sleap.instance.Track.

- from sleap.instance import Track

Line range hint 608-608: Replace equality comparisons to boolean values with direct truthy or falsy checks.

- if predictor.is_grayscale == False
+ if not predictor.is_grayscale

- if predictor.is_grayscale == True
+ if predictor.is_grayscale

Also applies to: 656-656, 705-705, 738-738, 1743-1743


Line range hint 797-798: These lines appear to be comparisons without assertions or any other form of logical continuation. If these are intended to be assertions, please add the assert keyword; otherwise, these lines should be removed or corrected.

- out["instance_peaks"].shape == (2, 1, 17, 2)
+ assert out["instance_peaks"].shape == (2, 1, 17, 2)
- out["instance_peak_vals"].shape == (2, 1, 17)
+ assert out["instance_peak_vals"].shape == (2, 1, 17)

Line range hint 893-893: Remove assignment to unused variable predictor.

- predictor = load_model(model_path)

Line range hint 1252-1252: Using pytest.raises(Exception) is generally considered too broad. It is better to specify the exact type of exception expected to make the test more precise and informative.

- with pytest.raises(Exception):
+ with pytest.raises(SpecificExceptionType):

Line range hint 1382-1382: Remove assignment to unused variable labels.

- labels: Labels = centered_pair_predictions

Line range hint 1664-1664: Use direct dictionary key checks instead of calling .keys().

- if key in dict.keys():
+ if key in dict:

Also applies to: 1708-1708, 1713-1713

sleap/nn/inference.py (1)

5448-5448: Consider adding more descriptive comments or logging for debugging purposes.

It might be helpful to add more context to the printed arguments for easier debugging and maintenance.

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between b0ac880 and dcc7a63.

Files selected for processing (2)
  • sleap/nn/inference.py (4 hunks)
  • tests/nn/test_inference.py (3 hunks)
Additional context used
Ruff
tests/nn/test_inference.py

10-10: tensorflow_hub imported but unused (F401)

Remove unused import: tensorflow_hub


14-14: sleap.gui.learning.runners imported but unused (F401)

Remove unused import: sleap.gui.learning.runners


22-22: sleap.nn.inference.InferenceLayer imported but unused (F401)

Remove unused import


23-23: sleap.nn.inference.InferenceModel imported but unused (F401)

Remove unused import


56-56: sleap.nn.tracking.MatchedFrameInstance imported but unused (F401)

Remove unused import: sleap.nn.tracking.MatchedFrameInstance


61-61: sleap.instance.Track imported but unused (F401)

Remove unused import: sleap.instance.Track


608-608: Avoid equality comparisons to False; use if not predictor.is_grayscale: for false checks (E712)

Replace with not predictor.is_grayscale


656-656: Avoid equality comparisons to True; use if predictor.is_grayscale: for truth checks (E712)

Replace with predictor.is_grayscale


705-705: Avoid equality comparisons to True; use if predictor.is_grayscale: for truth checks (E712)

Replace with predictor.is_grayscale


738-738: Avoid equality comparisons to True; use if predictor.is_grayscale: for truth checks (E712)

Replace with predictor.is_grayscale


797-797: Pointless comparison. Did you mean to assign a value? Otherwise, prepend assert or remove it. (B015)


798-798: Pointless comparison. Did you mean to assign a value? Otherwise, prepend assert or remove it. (B015)


893-893: Local variable predictor is assigned to but never used (F841)

Remove assignment to unused variable predictor


1252-1252: pytest.raises(Exception) should be considered evil (B017)


1382-1382: Local variable labels is assigned to but never used (F841)

Remove assignment to unused variable labels


1664-1664: Use key in dict instead of key in dict.keys() (SIM118)

Remove .keys()


1708-1708: Use key in dict instead of key in dict.keys() (SIM118)

Remove .keys()


1713-1713: Use key in dict instead of key in dict.keys() (SIM118)

Remove .keys()


1743-1743: Avoid equality comparisons to False; use if not predictor.is_grayscale: for false checks (E712)

Replace with not predictor.is_grayscale

sleap/nn/inference.py

131-131: typing.Text is deprecated, use str (UP019)

Replace with str


149-149: Undefined name Task (F821)


584-584: Local variable outputs is assigned to but never used (F841)

Remove assignment to unused variable outputs


605-605: typing.Text is deprecated, use str (UP019)

Replace with str


613-613: typing.Text is deprecated, use str (UP019)

Replace with str


627-627: typing.Text is deprecated, use str (UP019)

Replace with str


641-641: typing.Text is deprecated, use str (UP019)

Replace with str


695-696: Use a single if statement instead of nested if statements (SIM102)

Combine if statements using and


1183-1183: No explicit stacklevel keyword argument found (B028)


1325-1328: Use ternary operator imgs = data["image"] if isinstance(data, dict) else data instead of if-else-block (SIM108)

Replace if-else-block with imgs = data["image"] if isinstance(data, dict) else data


1468-1468: typing.Text is deprecated, use str (UP019)

Replace with str


1758-1763: Use ternary operator imgs = inputs["image"] if isinstance(inputs, dict) else inputs instead of if-else-block (SIM108)


2394-2394: typing.Text is deprecated, use str (UP019)

Replace with str


2395-2395: typing.Text is deprecated, use str (UP019)

Replace with str


2822-2825: Use ternary operator imgs = data["image"] if isinstance(data, dict) else data instead of if-else-block (SIM108)

Replace if-else-block with imgs = data["image"] if isinstance(data, dict) else data


3110-3110: typing.Text is deprecated, use str (UP019)

Replace with str


3424-3427: Use ternary operator imgs = data["image"] if isinstance(data, dict) else data instead of if-else-block (SIM108)

Replace if-else-block with imgs = data["image"] if isinstance(data, dict) else data


3436-3439: Use ternary operator offsets = preds[self.offsets_ind] if self.offsets_ind is not None else None instead of if-else-block (SIM108)

Replace if-else-block with offsets = preds[self.offsets_ind] if self.offsets_ind is not None else None


3654-3654: typing.Text is deprecated, use str (UP019)

Replace with str


3756-3756: Loop control variable image not used within loop body (B007)

Rename unused image to _image


4262-4262: typing.Text is deprecated, use str (UP019)

Replace with str


4263-4263: typing.Text is deprecated, use str (UP019)

Replace with str


4452-4452: Loop control variable image not used within loop body (B007)

Rename unused image to _image


4615-4619: Use ternary operator img = ex["image"] if type(ex) == dict else ex instead of if-else-block (SIM108)

Replace if-else-block with img = ex["image"] if type(ex) == dict else ex


4615-4615: Do not compare types, use isinstance() (E721)


4709-4709: typing.Text is deprecated, use str (UP019)

Replace with str


4873-4876: Use ternary operator model_paths = [model_path] if isinstance(model_path, str) else model_path instead of if-else-block (SIM108)

Replace if-else-block with model_paths = [model_path] if isinstance(model_path, str) else model_path


5294-5297: Use ternary operator data_path = Path(labels_path) if labels_path is not None else Path(args.data_path) instead of if-else-block (SIM108)

Replace if-else-block with data_path = Path(labels_path) if labels_path is not None else Path(args.data_path)


5350-5350: Local variable e is assigned to but never used (F841)

Remove assignment to unused variable e

Additional comments not posted (1)
tests/nn/test_inference.py (1)

67-77: The new fixtures introduced here are crucial for testing the multi-video inference capabilities. They correctly set up paths to test data for different input formats. Ensure that the paths are valid and the data is correctly formatted for these tests.

sleap/nn/inference.py Outdated Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Outside diff range and nitpick comments (2)
sleap/nn/inference.py (2)

5368-5368: Consider removing or guarding debug print statements in production code.

It's generally good practice to avoid leaving debug prints in production code or guard them with a verbosity check.


5449-5449: Ensure that the debug prints are necessary or are appropriately gated by a debug or verbose flag.

Consider wrapping debug statements with a verbosity check to avoid cluttering the output in production environments.

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between dcc7a63 and 35db452.

Files selected for processing (2)
  • sleap/nn/inference.py (5 hunks)
  • tests/nn/test_inference.py (3 hunks)
Additional context used
Ruff
tests/nn/test_inference.py

10-10: tensorflow_hub imported but unused (F401)

Remove unused import: tensorflow_hub


14-14: sleap.gui.learning.runners imported but unused (F401)

Remove unused import: sleap.gui.learning.runners


22-22: sleap.nn.inference.InferenceLayer imported but unused (F401)

Remove unused import


23-23: sleap.nn.inference.InferenceModel imported but unused (F401)

Remove unused import


56-56: sleap.nn.tracking.MatchedFrameInstance imported but unused (F401)

Remove unused import: sleap.nn.tracking.MatchedFrameInstance


61-61: sleap.instance.Track imported but unused (F401)

Remove unused import: sleap.instance.Track


612-612: Avoid equality comparisons to False; use if not predictor.is_grayscale: for false checks (E712)

Replace with not predictor.is_grayscale


660-660: Avoid equality comparisons to True; use if predictor.is_grayscale: for truth checks (E712)

Replace with predictor.is_grayscale


709-709: Avoid equality comparisons to True; use if predictor.is_grayscale: for truth checks (E712)

Replace with predictor.is_grayscale


742-742: Avoid equality comparisons to True; use if predictor.is_grayscale: for truth checks (E712)

Replace with predictor.is_grayscale


801-801: Pointless comparison. Did you mean to assign a value? Otherwise, prepend assert or remove it. (B015)


802-802: Pointless comparison. Did you mean to assign a value? Otherwise, prepend assert or remove it. (B015)


897-897: Local variable predictor is assigned to but never used (F841)

Remove assignment to unused variable predictor


1256-1256: pytest.raises(Exception) should be considered evil (B017)


1386-1386: Local variable labels is assigned to but never used (F841)

Remove assignment to unused variable labels


1708-1708: Use key in dict instead of key in dict.keys() (SIM118)

Remove .keys()


1752-1752: Use key in dict instead of key in dict.keys() (SIM118)

Remove .keys()


1757-1757: Use key in dict instead of key in dict.keys() (SIM118)

Remove .keys()


1787-1787: Avoid equality comparisons to False; use if not predictor.is_grayscale: for false checks (E712)

Replace with not predictor.is_grayscale

sleap/nn/inference.py

131-131: typing.Text is deprecated, use str (UP019)

Replace with str


149-149: Undefined name Task (F821)


584-584: Local variable outputs is assigned to but never used (F841)

Remove assignment to unused variable outputs


605-605: typing.Text is deprecated, use str (UP019)

Replace with str


613-613: typing.Text is deprecated, use str (UP019)

Replace with str


627-627: typing.Text is deprecated, use str (UP019)

Replace with str


641-641: typing.Text is deprecated, use str (UP019)

Replace with str


695-696: Use a single if statement instead of nested if statements (SIM102)

Combine if statements using and


1183-1183: No explicit stacklevel keyword argument found (B028)


1325-1328: Use ternary operator imgs = data["image"] if isinstance(data, dict) else data instead of if-else-block (SIM108)

Replace if-else-block with imgs = data["image"] if isinstance(data, dict) else data


1468-1468: typing.Text is deprecated, use str (UP019)

Replace with str


1758-1763: Use ternary operator imgs = inputs["image"] if isinstance(inputs, dict) else inputs instead of if-else-block (SIM108)


2394-2394: typing.Text is deprecated, use str (UP019)

Replace with str


2395-2395: typing.Text is deprecated, use str (UP019)

Replace with str


2822-2825: Use ternary operator imgs = data["image"] if isinstance(data, dict) else data instead of if-else-block (SIM108)

Replace if-else-block with imgs = data["image"] if isinstance(data, dict) else data


3110-3110: typing.Text is deprecated, use str (UP019)

Replace with str


3424-3427: Use ternary operator imgs = data["image"] if isinstance(data, dict) else data instead of if-else-block (SIM108)

Replace if-else-block with imgs = data["image"] if isinstance(data, dict) else data


3436-3439: Use ternary operator offsets = preds[self.offsets_ind] if self.offsets_ind is not None else None instead of if-else-block (SIM108)

Replace if-else-block with offsets = preds[self.offsets_ind] if self.offsets_ind is not None else None


3654-3654: typing.Text is deprecated, use str (UP019)

Replace with str


3756-3756: Loop control variable image not used within loop body (B007)

Rename unused image to _image


4262-4262: typing.Text is deprecated, use str (UP019)

Replace with str


4263-4263: typing.Text is deprecated, use str (UP019)

Replace with str


4452-4452: Loop control variable image not used within loop body (B007)

Rename unused image to _image


4615-4619: Use ternary operator img = ex["image"] if type(ex) == dict else ex instead of if-else-block (SIM108)

Replace if-else-block with img = ex["image"] if type(ex) == dict else ex


4615-4615: Do not compare types, use isinstance() (E721)


4709-4709: typing.Text is deprecated, use str (UP019)

Replace with str


4873-4876: Use ternary operator model_paths = [model_path] if isinstance(model_path, str) else model_path instead of if-else-block (SIM108)

Replace if-else-block with model_paths = [model_path] if isinstance(model_path, str) else model_path


5294-5297: Use ternary operator data_path = Path(labels_path) if labels_path is not None else Path(args.data_path) instead of if-else-block (SIM108)

Replace if-else-block with data_path = Path(labels_path) if labels_path is not None else Path(args.data_path)


5350-5350: Local variable e is assigned to but never used (F841)

Remove assignment to unused variable e

Additional comments not posted (4)
tests/nn/test_inference.py (4)

66-69: LGTM! This fixture correctly provides a path for multiple .slp inputs as expected.


74-77: LGTM! This fixture properly returns a directory path for testing with both .slp and .mp4 inputs.


78-81: LGTM! The fixture correctly provides a path for directories with multiple .mp4 files, facilitating testing of the new multi-input feature.


1524-1637: Excellent work on testing the new multi-input functionality. The test is well-structured and covers the essential aspects of the feature by checking the existence of expected output files.


if args.open_in_gui:
subprocess.call(["sleap-label", output_path])

Copy link

Choose a reason for hiding this comment

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

Refine the error message to provide more context and guidance.

- "Use \"sleap-track --tracking.tracker ...' to specify tracker to use."
+ "Specify the tracker using 'sleap-track --tracking.tracker'. Refer to the documentation for more details."

Committable suggestion was skipped due to low confidence.

Comment on lines 5291 to 5298

# Figure out which input path to use.
labels_path = getattr(args, "labels", None)
if labels_path is not None:
data_path = labels_path
data_path = Path(labels_path)
else:
data_path = Path(args.data_path)

Copy link

Choose a reason for hiding this comment

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

Consider using a ternary operator for cleaner and more concise code.

-    if labels_path is not None:
-        data_path = Path(labels_path)
-    else:
-        data_path = Path(args.data_path)
+    data_path = Path(labels_path) if labels_path is not None else Path(args.data_path)
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Figure out which input path to use.
labels_path = getattr(args, "labels", None)
if labels_path is not None:
data_path = labels_path
data_path = Path(labels_path)
else:
data_path = Path(args.data_path)
# Figure out which input path to use.
labels_path = getattr(args, "labels", None)
data_path = Path(labels_path) if labels_path is not None else Path(args.data_path)
Tools
Ruff

5294-5297: Use ternary operator data_path = Path(labels_path) if labels_path is not None else Path(args.data_path) instead of if-else-block (SIM108)

Replace if-else-block with data_path = Path(labels_path) if labels_path is not None else Path(args.data_path)

Comment on lines 5499 to 5502
if output_path is not None and (Path.is_file(output_path_obj) and len(data_path_list) > 1):
raise ValueError(
"output_path argument must be a directory if multiple video inputs are given"
)
Copy link

Choose a reason for hiding this comment

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

Validate the output path more robustly to handle cases where the output path is not a directory but should be.

-    if output_path is not None and (Path.is_file(output_path_obj) and len(data_path_list) > 1):
+    if output_path is not None and not Path(output_path_obj).is_dir() and len(data_path_list) > 1:
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if output_path is not None and (Path.is_file(output_path_obj) and len(data_path_list) > 1):
raise ValueError(
"output_path argument must be a directory if multiple video inputs are given"
)
if output_path is not None and not Path(output_path_obj).is_dir() and len(data_path_list) > 1:
raise ValueError(
"output_path argument must be a directory if multiple video inputs are given"
)

Comment on lines 5299 to 5307
# Check for multiple video inputs
# Compile file(s) into a list for later itteration
if data_path.is_dir:
data_path_list = []
for file_path in data_path.iterdir():
if file_path.is_file():
data_path_list.append(file_path)
elif data_path.is_file:
data_path_list = [args.data_path]
Copy link

Choose a reason for hiding this comment

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

Ensure is_dir is called as a method to check if the path is a directory.

-    if data_path.is_dir:
+    if data_path.is_dir():
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Check for multiple video inputs
# Compile file(s) into a list for later itteration
if data_path.is_dir:
data_path_list = []
for file_path in data_path.iterdir():
if file_path.is_file():
data_path_list.append(file_path)
elif data_path.is_file:
data_path_list = [args.data_path]
# Check for multiple video inputs
# Compile file(s) into a list for later itteration
if data_path.is_dir():
data_path_list = []
for file_path in data_path.iterdir():
if file_path.is_file():
data_path_list.append(file_path)
elif data_path.is_file:
data_path_list = [args.data_path]

Comment on lines 5314 to 5354
# Provider list to accomodate multiple video inputs
provider_list = []
tmp_data_path_list = []
for data_path_file in data_path_list:
data_path_file = Path(data_path_file)
# Create a provider for each file
if data_path_file.as_posix().endswith(".slp"):
print(f"Sleap file: {data_path_file}")
labels = sleap.load_file(data_path_file.as_posix())

if args.only_labeled_frames:
provider_list.append(LabelsReader.from_user_labeled_frames(labels))
elif args.only_suggested_frames:
provider_list.append(LabelsReader.from_unlabeled_suggestions(labels))
elif getattr(args, "video.index") != "":
provider_list.append(VideoReader(
video=labels.videos[int(getattr(args, "video.index"))],
example_indices=frame_list(args.frames),
))
else:
provider_list.append(LabelsReader(labels))

tmp_data_path_list.append(data_path_file)

else:
print(f"Video: {data_path}")
# TODO: Clean this up.
video_kwargs = dict(
dataset=vars(args).get("video.dataset"),
input_format=vars(args).get("video.input_format"),
)
provider = VideoReader.from_filepath(
filename=data_path, example_indices=frame_list(args.frames), **video_kwargs
)
else:
try:
video_kwargs = dict(
dataset=vars(args).get("video.dataset"),
input_format=vars(args).get("video.input_format"),
)
provider_list.append(VideoReader.from_filepath(
filename=data_path_file.as_posix(), example_indices=frame_list(args.frames), **video_kwargs
))
print(f"Video: {data_path_file}")
tmp_data_path_list.append(data_path_file)
# TODO: Clean this up.
except Exception as e:
print(f"Error reading file: {data_path_file}")

data_path_list = tmp_data_path_list

Copy link

Choose a reason for hiding this comment

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

Refactor exception handling to catch specific exceptions and remove the assignment to the unused variable e.

-            except Exception as e:
+            except (FileNotFoundError, PermissionError):
-                print(f"Error reading file: {data_path_file}")
+                print(f"Error reading file: {data_path_file}. Please check the file's existence and permissions.")
-            except Exception as e:
+            except Exception:
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Provider list to accomodate multiple video inputs
provider_list = []
tmp_data_path_list = []
for data_path_file in data_path_list:
data_path_file = Path(data_path_file)
# Create a provider for each file
if data_path_file.as_posix().endswith(".slp"):
print(f"Sleap file: {data_path_file}")
labels = sleap.load_file(data_path_file.as_posix())
if args.only_labeled_frames:
provider_list.append(LabelsReader.from_user_labeled_frames(labels))
elif args.only_suggested_frames:
provider_list.append(LabelsReader.from_unlabeled_suggestions(labels))
elif getattr(args, "video.index") != "":
provider_list.append(VideoReader(
video=labels.videos[int(getattr(args, "video.index"))],
example_indices=frame_list(args.frames),
))
else:
provider_list.append(LabelsReader(labels))
tmp_data_path_list.append(data_path_file)
else:
print(f"Video: {data_path}")
# TODO: Clean this up.
video_kwargs = dict(
dataset=vars(args).get("video.dataset"),
input_format=vars(args).get("video.input_format"),
)
provider = VideoReader.from_filepath(
filename=data_path, example_indices=frame_list(args.frames), **video_kwargs
)
else:
try:
video_kwargs = dict(
dataset=vars(args).get("video.dataset"),
input_format=vars(args).get("video.input_format"),
)
provider_list.append(VideoReader.from_filepath(
filename=data_path_file.as_posix(), example_indices=frame_list(args.frames), **video_kwargs
))
print(f"Video: {data_path_file}")
tmp_data_path_list.append(data_path_file)
# TODO: Clean this up.
except Exception as e:
print(f"Error reading file: {data_path_file}")
data_path_list = tmp_data_path_list
# Provider list to accomodate multiple video inputs
provider_list = []
tmp_data_path_list = []
for data_path_file in data_path_list:
data_path_file = Path(data_path_file)
# Create a provider for each file
if data_path_file.as_posix().endswith(".slp"):
print(f"Sleap file: {data_path_file}")
labels = sleap.load_file(data_path_file.as_posix())
if args.only_labeled_frames:
provider_list.append(LabelsReader.from_user_labeled_frames(labels))
elif args.only_suggested_frames:
provider_list.append(LabelsReader.from_unlabeled_suggestions(labels))
elif getattr(args, "video.index") != "":
provider_list.append(VideoReader(
video=labels.videos[int(getattr(args, "video.index"))],
example_indices=frame_list(args.frames),
))
else:
provider_list.append(LabelsReader(labels))
tmp_data_path_list.append(data_path_file)
else:
try:
video_kwargs = dict(
dataset=vars(args).get("video.dataset"),
input_format=vars(args).get("video.input_format"),
)
provider_list.append(VideoReader.from_filepath(
filename=data_path_file.as_posix(), example_indices=frame_list(args.frames), **video_kwargs
))
print(f"Video: {data_path_file}")
tmp_data_path_list.append(data_path_file)
# TODO: Clean this up.
except (FileNotFoundError, PermissionError):
print(f"Error reading file: {data_path_file}. Please check the file's existence and permissions.")
except Exception:
print(f"Error reading file: {data_path_file}")
data_path_list = tmp_data_path_list
Tools
Ruff

5350-5350: Local variable e is assigned to but never used (F841)

Remove assignment to unused variable e

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Outside diff range and nitpick comments (10)
tests/nn/test_inference.py (10)

Line range hint 10-10: Remove unused import: tensorflow_hub

The import tensorflow_hub is not used anywhere in the code. To maintain clean and efficient code, it's best to remove unused imports.

- import tensorflow_hub as hub

Line range hint 14-14: Remove unused import: sleap.gui.learning.runners

This import is not utilized in the file and should be removed to keep the codebase clean and efficient.

- from sleap.gui.learning import runners

Line range hint 22-23: Remove unused imports related to inference

The imports InferenceLayer and InferenceModel from sleap.nn.inference are not used in this file. Removing unused imports can help reduce confusion and improve code cleanliness.

- from sleap.nn.inference import InferenceLayer
- from sleap.nn.inference import InferenceModel

Line range hint 56-56: Remove unused import: sleap.nn.tracking.MatchedFrameInstance

This import is not utilized in the file and should be removed to keep the codebase clean and efficient.

- from sleap.nn.tracking import MatchedFrameInstance

Line range hint 61-61: Remove unused import: sleap.instance.Track

This import is not utilized in the file and should be removed to keep the codebase clean and efficient.

- from sleap.instance import Track

Line range hint 616-616: Refactor boolean checks for clarity

Boolean comparisons to True or False are not necessary and can be simplified for better readability and performance.

- if predictor.is_grayscale == False:
+ if not predictor.is_grayscale:

- if predictor.is_grayscale == True:
+ if predictor.is_grayscale:

Also applies to: 664-664, 713-713, 746-746, 1821-1821


Line range hint 901-901: Remove unused variable: predictor

The variable predictor is assigned but never used, which can lead to unnecessary memory usage and potential confusion.

- predictor = load_model(model_path)

Line range hint 1260-1260: Avoid general exception types in tests

Using pytest.raises(Exception) is too broad and can potentially catch unintended exceptions. It's recommended to specify the exact exception type expected.

- with pytest.raises(Exception):
+ with pytest.raises(SpecificExceptionType):

Line range hint 1390-1390: Remove unused variable: labels

The variable labels is assigned but never used, which can lead to unnecessary memory usage and potential confusion.

- labels = Labels(min_tracks_2node_labels[[0]])

Line range hint 1742-1742: Simplify dictionary key checks

Using key in dict.keys() is redundant and can be simplified to key in dict for clarity and efficiency.

- for key in tracker.track_matching_queue_dict.keys():
+ for key in tracker.track_matching_queue_dict:

Also applies to: 1786-1786, 1791-1791

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 35db452 and 6f0c929.

Files selected for processing (2)
  • sleap/nn/inference.py (5 hunks)
  • tests/nn/test_inference.py (3 hunks)
Additional context used
Ruff
tests/nn/test_inference.py

10-10: tensorflow_hub imported but unused (F401)

Remove unused import: tensorflow_hub


14-14: sleap.gui.learning.runners imported but unused (F401)

Remove unused import: sleap.gui.learning.runners


22-22: sleap.nn.inference.InferenceLayer imported but unused (F401)

Remove unused import


23-23: sleap.nn.inference.InferenceModel imported but unused (F401)

Remove unused import


56-56: sleap.nn.tracking.MatchedFrameInstance imported but unused (F401)

Remove unused import: sleap.nn.tracking.MatchedFrameInstance


61-61: sleap.instance.Track imported but unused (F401)

Remove unused import: sleap.instance.Track


616-616: Avoid equality comparisons to False; use if not predictor.is_grayscale: for false checks (E712)

Replace with not predictor.is_grayscale


664-664: Avoid equality comparisons to True; use if predictor.is_grayscale: for truth checks (E712)

Replace with predictor.is_grayscale


713-713: Avoid equality comparisons to True; use if predictor.is_grayscale: for truth checks (E712)

Replace with predictor.is_grayscale


746-746: Avoid equality comparisons to True; use if predictor.is_grayscale: for truth checks (E712)

Replace with predictor.is_grayscale


805-805: Pointless comparison. Did you mean to assign a value? Otherwise, prepend assert or remove it. (B015)


806-806: Pointless comparison. Did you mean to assign a value? Otherwise, prepend assert or remove it. (B015)


901-901: Local variable predictor is assigned to but never used (F841)

Remove assignment to unused variable predictor


1260-1260: pytest.raises(Exception) should be considered evil (B017)


1390-1390: Local variable labels is assigned to but never used (F841)

Remove assignment to unused variable labels


1742-1742: Use key in dict instead of key in dict.keys() (SIM118)

Remove .keys()


1786-1786: Use key in dict instead of key in dict.keys() (SIM118)

Remove .keys()


1791-1791: Use key in dict instead of key in dict.keys() (SIM118)

Remove .keys()


1821-1821: Avoid equality comparisons to False; use if not predictor.is_grayscale: for false checks (E712)

Replace with not predictor.is_grayscale

sleap/nn/inference.py

131-131: typing.Text is deprecated, use str (UP019)

Replace with str


149-149: Undefined name Task (F821)


584-584: Local variable outputs is assigned to but never used (F841)

Remove assignment to unused variable outputs


605-605: typing.Text is deprecated, use str (UP019)

Replace with str


613-613: typing.Text is deprecated, use str (UP019)

Replace with str


627-627: typing.Text is deprecated, use str (UP019)

Replace with str


641-641: typing.Text is deprecated, use str (UP019)

Replace with str


695-696: Use a single if statement instead of nested if statements (SIM102)

Combine if statements using and


1183-1183: No explicit stacklevel keyword argument found (B028)


1325-1328: Use ternary operator imgs = data["image"] if isinstance(data, dict) else data instead of if-else-block (SIM108)

Replace if-else-block with imgs = data["image"] if isinstance(data, dict) else data


1468-1468: typing.Text is deprecated, use str (UP019)

Replace with str


1758-1763: Use ternary operator imgs = inputs["image"] if isinstance(inputs, dict) else inputs instead of if-else-block (SIM108)


2394-2394: typing.Text is deprecated, use str (UP019)

Replace with str


2395-2395: typing.Text is deprecated, use str (UP019)

Replace with str


2822-2825: Use ternary operator imgs = data["image"] if isinstance(data, dict) else data instead of if-else-block (SIM108)

Replace if-else-block with imgs = data["image"] if isinstance(data, dict) else data


3110-3110: typing.Text is deprecated, use str (UP019)

Replace with str


3424-3427: Use ternary operator imgs = data["image"] if isinstance(data, dict) else data instead of if-else-block (SIM108)

Replace if-else-block with imgs = data["image"] if isinstance(data, dict) else data


3436-3439: Use ternary operator offsets = preds[self.offsets_ind] if self.offsets_ind is not None else None instead of if-else-block (SIM108)

Replace if-else-block with offsets = preds[self.offsets_ind] if self.offsets_ind is not None else None


3654-3654: typing.Text is deprecated, use str (UP019)

Replace with str


3756-3756: Loop control variable image not used within loop body (B007)

Rename unused image to _image


4262-4262: typing.Text is deprecated, use str (UP019)

Replace with str


4263-4263: typing.Text is deprecated, use str (UP019)

Replace with str


4452-4452: Loop control variable image not used within loop body (B007)

Rename unused image to _image


4615-4619: Use ternary operator img = ex["image"] if type(ex) == dict else ex instead of if-else-block (SIM108)

Replace if-else-block with img = ex["image"] if type(ex) == dict else ex


4615-4615: Do not compare types, use isinstance() (E721)


4709-4709: typing.Text is deprecated, use str (UP019)

Replace with str


4873-4876: Use ternary operator model_paths = [model_path] if isinstance(model_path, str) else model_path instead of if-else-block (SIM108)

Replace if-else-block with model_paths = [model_path] if isinstance(model_path, str) else model_path


5294-5297: Use ternary operator data_path = Path(labels_path) if labels_path is not None else Path(args.data_path) instead of if-else-block (SIM108)

Replace if-else-block with data_path = Path(labels_path) if labels_path is not None else Path(args.data_path)


5356-5356: Local variable e is assigned to but never used (F841)

Remove assignment to unused variable e

Additional comments not posted (3)
sleap/nn/inference.py (3)

5373-5373: Consider removing debug statements.

The print(args) statement appears to be for debugging purposes. If it's no longer needed, consider removing it to clean up the code.

-    print(args)

5454-5454: Review necessity of verbose argument logging.

Printing all arguments might expose sensitive information in logs and could clutter the output. Ensure that this is necessary, or consider logging at a debug level or to a file that is not generally accessible.


5503-5510: Error handling for output path conflicts is robust.

The check to ensure the output_path is a directory when multiple inputs are provided is crucial for preventing file overwriting and data loss. This is a good practice in handling user input for batch processing scenarios.

Comment on lines 5300 to 5307
# Compile file(s) into a list for later itteration
if data_path.is_dir():
data_path_list = []
for file_path in data_path.iterdir():
if file_path.is_file():
data_path_list.append(file_path)
elif data_path.is_file:
data_path_list = [args.data_path]
Copy link

Choose a reason for hiding this comment

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

Implementation of multiple video input handling appears robust.

The code effectively handles multiple video inputs by checking if the path is a directory and iterating over all files. This implementation aligns with the PR's objective to allow sleap-track to process multiple videos. The use of lists to manage providers and paths is appropriate, and the error handling for unsupported paths is a good practice.

However, the use of data_path.is_file without calling it (i.e., data_path.is_file()) appears to be a typo that could lead to unexpected behavior.

-    elif data_path.is_file:
+    elif data_path.is_file():

Also applies to: 5314-5361

Comment on lines 5517 to 5573
# Run inference on all files inputed
for data_path, provider in zip(data_path_list, provider_list):
# Setup models.
predictor = _make_predictor_from_cli(args)
print(f"predictor.tracker: {tracker}")
predictor.tracker = tracker

if output_path is None:
output_path = data_path + ".predictions.slp"
# Run inference!
labels_pr = predictor.predict(provider)

labels_pr.provenance["model_paths"] = predictor.model_paths
labels_pr.provenance["predictor"] = type(predictor).__name__
if output_path is None:

output_path = data_path.parent / (data_path.stem + ".predictions.slp")
output_path_obj = Path(output_path)

else:
output_path = output_path + "/" + (data_path.stem + ".predictions.slp")

labels_pr.provenance["model_paths"] = predictor.model_paths
labels_pr.provenance["predictor"] = type(predictor).__name__

if args.no_empty_frames:
# Clear empty frames if specified.
labels_pr.remove_empty_frames()

finish_timestamp = str(datetime.now())
total_elapsed = time() - t0
print("Finished inference at:", finish_timestamp)
print(f"Total runtime: {total_elapsed} secs")
print(f"Predicted frames: {len(labels_pr)}/{len(provider)}")

# Add provenance metadata to predictions.
labels_pr.provenance["sleap_version"] = sleap.__version__
labels_pr.provenance["platform"] = platform.platform()
labels_pr.provenance["command"] = " ".join(sys.argv)
labels_pr.provenance["data_path"] = data_path.as_posix()
labels_pr.provenance["output_path"] = output_path_obj.as_posix()
labels_pr.provenance["total_elapsed"] = total_elapsed
labels_pr.provenance["start_timestamp"] = start_timestamp
labels_pr.provenance["finish_timestamp"] = finish_timestamp

print("Provenance:")
print(labels_pr.provenance)
print()

labels_pr.provenance["args"] = vars(args)

# Save results.
labels_pr.save(output_path)
print("Saved output:", output_path)

if args.open_in_gui:
subprocess.call(["sleap-label", output_path])

# Reset output_path for next iteration
output_path = args.output

Copy link

Choose a reason for hiding this comment

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

Ensure consistency in output path handling.

The loop handles inference for multiple files, but the handling of output_path could lead to inconsistencies or errors if not managed correctly, especially when it's reset at the end of the loop. Consider encapsulating output path generation and handling in a separate function to reduce complexity and improve maintainability.

def generate_output_path(data_path, output_dir, suffix=".predictions.slp"):
    return output_dir / (data_path.stem + suffix)

# Usage within the loop
output_path = generate_output_path(data_path, output_path_obj)

Comment on lines 5356 to 5357
except Exception as e:
print(f"Error reading file: {data_path_file}")
Copy link

Choose a reason for hiding this comment

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

Remove unused variable and improve error handling.

The variable e in the exception block is unused, which can be cleaned up. Additionally, consider logging the error or providing more detailed feedback to the user.

-            except Exception as e:
-                print(f"Error reading file: {data_path_file}")
+            except Exception:
+                print(f"Error reading file: {data_path_file}. Please check the file format and accessibility.")
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
except Exception as e:
print(f"Error reading file: {data_path_file}")
except Exception:
print(f"Error reading file: {data_path_file}. Please check the file format and accessibility.")
Tools
Ruff

5356-5356: Local variable e is assigned to but never used (F841)

Remove assignment to unused variable e

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 6f0c929 and bd2b016.

Files selected for processing (1)
  • sleap/nn/inference.py (5 hunks)
Additional context used
Ruff
sleap/nn/inference.py

131-131: typing.Text is deprecated, use str (UP019)

Replace with str


149-149: Undefined name Task (F821)


584-584: Local variable outputs is assigned to but never used (F841)

Remove assignment to unused variable outputs


605-605: typing.Text is deprecated, use str (UP019)

Replace with str


613-613: typing.Text is deprecated, use str (UP019)

Replace with str


627-627: typing.Text is deprecated, use str (UP019)

Replace with str


641-641: typing.Text is deprecated, use str (UP019)

Replace with str


695-696: Use a single if statement instead of nested if statements (SIM102)

Combine if statements using and


1183-1183: No explicit stacklevel keyword argument found (B028)


1325-1328: Use ternary operator imgs = data["image"] if isinstance(data, dict) else data instead of if-else-block (SIM108)

Replace if-else-block with imgs = data["image"] if isinstance(data, dict) else data


1468-1468: typing.Text is deprecated, use str (UP019)

Replace with str


1758-1763: Use ternary operator imgs = inputs["image"] if isinstance(inputs, dict) else inputs instead of if-else-block (SIM108)


2394-2394: typing.Text is deprecated, use str (UP019)

Replace with str


2395-2395: typing.Text is deprecated, use str (UP019)

Replace with str


2822-2825: Use ternary operator imgs = data["image"] if isinstance(data, dict) else data instead of if-else-block (SIM108)

Replace if-else-block with imgs = data["image"] if isinstance(data, dict) else data


3110-3110: typing.Text is deprecated, use str (UP019)

Replace with str


3424-3427: Use ternary operator imgs = data["image"] if isinstance(data, dict) else data instead of if-else-block (SIM108)

Replace if-else-block with imgs = data["image"] if isinstance(data, dict) else data


3436-3439: Use ternary operator offsets = preds[self.offsets_ind] if self.offsets_ind is not None else None instead of if-else-block (SIM108)

Replace if-else-block with offsets = preds[self.offsets_ind] if self.offsets_ind is not None else None


3654-3654: typing.Text is deprecated, use str (UP019)

Replace with str


3756-3756: Loop control variable image not used within loop body (B007)

Rename unused image to _image


4262-4262: typing.Text is deprecated, use str (UP019)

Replace with str


4263-4263: typing.Text is deprecated, use str (UP019)

Replace with str


4452-4452: Loop control variable image not used within loop body (B007)

Rename unused image to _image


4615-4619: Use ternary operator img = ex["image"] if type(ex) == dict else ex instead of if-else-block (SIM108)

Replace if-else-block with img = ex["image"] if type(ex) == dict else ex


4615-4615: Do not compare types, use isinstance() (E721)


4709-4709: typing.Text is deprecated, use str (UP019)

Replace with str


4873-4876: Use ternary operator model_paths = [model_path] if isinstance(model_path, str) else model_path instead of if-else-block (SIM108)

Replace if-else-block with model_paths = [model_path] if isinstance(model_path, str) else model_path


5294-5297: Use ternary operator data_path = Path(labels_path) if labels_path is not None else Path(args.data_path) instead of if-else-block (SIM108)

Replace if-else-block with data_path = Path(labels_path) if labels_path is not None else Path(args.data_path)

Additional comments not posted (3)
sleap/nn/inference.py (3)

5504-5509: Validate output path for multiple inputs.

The validation for output_path when multiple video inputs are provided is crucial. Ensure that this logic is robust and tested to prevent runtime errors.


5575-5626: Refactor tracking logic for clarity.

The tracking logic, especially with the handling of existing prediction files, could benefit from refactoring for better clarity and maintainability. Consider separating the concerns into more discrete functions or methods.
[REFACTOR_Suggestion]

def handle_existing_predictions(data_path, provider, tracker, args):
    print("Loading predictions...")
    labels_pr = sleap.load_file(args.data_path.as_posix())
    frames = sorted(labels_pr.labeled_frames, key=lambda lf: lf.frame_idx)
    print("Starting tracker...")
    frames = run_tracker(frames=frames, tracker=tracker)
    tracker.final_pass(frames)
    return Labels(labeled_frames=frames)

5524-5568: Review inference execution and result handling.

The inference execution and handling of results appear to be implemented correctly. However, ensure that all edge cases are covered, especially with file paths and output handling.

Verification successful

Edge cases for file paths and output handling are covered.

The test case test_sleap_track_output_mult in tests/nn/test_inference.py verifies the correct construction of output file paths and ensures that predictions files are created as expected. It checks for the existence of the output files and handles different video formats.

  • tests/nn/test_inference.py:
    • test_sleap_track_output_mult
Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify that all edge cases for file paths and output handling are covered in tests.

# Test: Search for test cases covering file paths and output handling.
rg --type python "test_sleap_track_output_mult"

Length of output: 78



Script:

#!/bin/bash
# Description: Verify that all edge cases for file paths and output handling are covered in tests.

# Correcting the file type argument to 'py' instead of 'python'.
rg --type py "test_sleap_track_output_mult"

Length of output: 104



Script:

#!/bin/bash
# Description: Extract and review the identified test case to ensure it covers edge cases for file paths and output handling.

# Extract the content of the identified test case.
rg --type py -A 20 "def test_sleap_track_output_mult" tests/nn/test_inference.py

Length of output: 821



Script:

#!/bin/bash
# Description: Extract and review the identified test case to ensure it covers edge cases for file paths and output handling.

# Extract more lines of the identified test case for comprehensive review.
rg --type py -A 50 "def test_sleap_track_output_mult" tests/nn/test_inference.py

Length of output: 1749

Comment on lines 5320 to 5337
if data_path_file.as_posix().endswith(".slp"):
print(f"Sleap file: {data_path_file}")
labels = sleap.load_file(data_path_file.as_posix())

if args.only_labeled_frames:
provider_list.append(LabelsReader.from_user_labeled_frames(labels))
elif args.only_suggested_frames:
provider_list.append(LabelsReader.from_unlabeled_suggestions(labels))
elif getattr(args, "video.index") != "":
provider_list.append(
VideoReader(
video=labels.videos[int(getattr(args, "video.index"))],
example_indices=frame_list(args.frames),
)
)
else:
provider_list.append(LabelsReader(labels))

Copy link

Choose a reason for hiding this comment

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

Consider refactoring provider creation for clarity and modularity.

The code for creating providers based on file types is spread across multiple conditional branches. Consider refactoring this into a separate function or method to improve clarity and maintainability.

def create_provider(data_path_file, args):
    if data_path_file.as_posix().endswith(".slp"):
        labels = sleap.load_file(data_path_file.as_posix())
        # Additional conditions based on args
    else:
        video_kwargs = dict(
            dataset=vars(args).get("video.dataset"),
            input_format=vars(args).get("video.input_format"),
        )
        return VideoReader.from_filepath(filename=data_path_file.as_posix(), example_indices=frame_list(args.frames), **video_kwargs)

    return None  # or appropriate return

Also applies to: 5347-5352

Comment on lines 5356 to 5357
except Exception:
print(f"Error reading file: {data_path_file}")
Copy link

Choose a reason for hiding this comment

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

Improve error handling in file reading.

Currently, the error handling in the file reading process just prints an error message. It might be beneficial to either raise an exception or handle the error in a way that doesn't stop the execution, depending on the intended behavior of the application.

-            except Exception:
-                print(f"Error reading file: {data_path_file}")
+            except Exception as e:
+                logging.error(f"Failed to read file {data_path_file}: {e}")
+                continue  # or other handling logic

Committable suggestion was skipped due to low confidence.

Comment on lines 5301 to 5307
if data_path.is_dir():
data_path_list = []
for file_path in data_path.iterdir():
if file_path.is_file():
data_path_list.append(file_path)
elif data_path.is_file():
data_path_list = [args.data_path]
Copy link

Choose a reason for hiding this comment

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

Ensure robust directory and file handling.

When handling directories and files, it's crucial to ensure that the code robustly checks for valid directories and handles potential errors gracefully. Consider adding more specific error handling for directory-related issues.

+    if not data_path.exists():
+        raise FileNotFoundError(f"The specified path does not exist: {data_path}")
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if data_path.is_dir():
data_path_list = []
for file_path in data_path.iterdir():
if file_path.is_file():
data_path_list.append(file_path)
elif data_path.is_file():
data_path_list = [args.data_path]
if not data_path.exists():
raise FileNotFoundError(f"The specified path does not exist: {data_path}")
if data_path.is_dir():
data_path_list = []
for file_path in data_path.iterdir():
if file_path.is_file():
data_path_list.append(file_path)
elif data_path.is_file():
data_path_list = [args.data_path]

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between bd2b016 and ec4c26d.

Files selected for processing (1)
  • sleap/nn/inference.py (5 hunks)
Additional context used
Ruff
sleap/nn/inference.py

131-131: typing.Text is deprecated, use str

Replace with str

(UP019)


149-149: Undefined name Task

(F821)


584-584: Local variable outputs is assigned to but never used

Remove assignment to unused variable outputs

(F841)


605-605: typing.Text is deprecated, use str

Replace with str

(UP019)


613-613: typing.Text is deprecated, use str

Replace with str

(UP019)


627-627: typing.Text is deprecated, use str

Replace with str

(UP019)


641-641: typing.Text is deprecated, use str

Replace with str

(UP019)


695-696: Use a single if statement instead of nested if statements

Combine if statements using and

(SIM102)


1183-1183: No explicit stacklevel keyword argument found

(B028)


1325-1328: Use ternary operator imgs = data["image"] if isinstance(data, dict) else data instead of if-else-block

Replace if-else-block with imgs = data["image"] if isinstance(data, dict) else data

(SIM108)


1468-1468: typing.Text is deprecated, use str

Replace with str

(UP019)


1758-1763: Use ternary operator imgs = inputs["image"] if isinstance(inputs, dict) else inputs instead of if-else-block

(SIM108)


2394-2394: typing.Text is deprecated, use str

Replace with str

(UP019)


2395-2395: typing.Text is deprecated, use str

Replace with str

(UP019)


2822-2825: Use ternary operator imgs = data["image"] if isinstance(data, dict) else data instead of if-else-block

Replace if-else-block with imgs = data["image"] if isinstance(data, dict) else data

(SIM108)


3110-3110: typing.Text is deprecated, use str

Replace with str

(UP019)


3424-3427: Use ternary operator imgs = data["image"] if isinstance(data, dict) else data instead of if-else-block

Replace if-else-block with imgs = data["image"] if isinstance(data, dict) else data

(SIM108)


3436-3439: Use ternary operator offsets = preds[self.offsets_ind] if self.offsets_ind is not None else None instead of if-else-block

Replace if-else-block with offsets = preds[self.offsets_ind] if self.offsets_ind is not None else None

(SIM108)


3654-3654: typing.Text is deprecated, use str

Replace with str

(UP019)


3756-3756: Loop control variable image not used within loop body

Rename unused image to _image

(B007)


4262-4262: typing.Text is deprecated, use str

Replace with str

(UP019)


4263-4263: typing.Text is deprecated, use str

Replace with str

(UP019)


4452-4452: Loop control variable image not used within loop body

Rename unused image to _image

(B007)


4615-4619: Use ternary operator img = ex["image"] if type(ex) == dict else ex instead of if-else-block

Replace if-else-block with img = ex["image"] if type(ex) == dict else ex

(SIM108)


4615-4615: Do not compare types, use isinstance()

(E721)


4709-4709: typing.Text is deprecated, use str

Replace with str

(UP019)


4873-4876: Use ternary operator model_paths = [model_path] if isinstance(model_path, str) else model_path instead of if-else-block

Replace if-else-block with model_paths = [model_path] if isinstance(model_path, str) else model_path

(SIM108)


5294-5297: Use ternary operator data_path = Path(labels_path) if labels_path is not None else Path(args.data_path) instead of if-else-block

Replace if-else-block with data_path = Path(labels_path) if labels_path is not None else Path(args.data_path)

(SIM108)

Comment on lines 5301 to 5360
if data_path.is_dir():
data_path_list = []
for file_path in data_path.iterdir():
if file_path.is_file():
data_path_list.append(Path(file_path))
elif data_path.is_file():
data_path_list = [data_path]
else:
data_path = args.data_path

if data_path is None or data_path == "":
raise ValueError(
"You must specify a path to a video or a labels dataset. "
"Run 'sleap-track -h' to see full command documentation."
)

if data_path.endswith(".slp"):
labels = sleap.load_file(data_path)

if args.only_labeled_frames:
provider = LabelsReader.from_user_labeled_frames(labels)
elif args.only_suggested_frames:
provider = LabelsReader.from_unlabeled_suggestions(labels)
elif getattr(args, "video.index") != "":
provider = VideoReader(
video=labels.videos[int(getattr(args, "video.index"))],
example_indices=frame_list(args.frames),
)
# Provider list to accomodate multiple video inputs
provider_list = []
tmp_data_path_list = []
for data_path_file in data_path_list:
# Create a provider for each file
if data_path_file.as_posix().endswith(".slp"):
print(f"Sleap file: {data_path_file}")
labels = sleap.load_file(data_path_file.as_posix())

if args.only_labeled_frames:
provider_list.append(LabelsReader.from_user_labeled_frames(labels))
elif args.only_suggested_frames:
provider_list.append(LabelsReader.from_unlabeled_suggestions(labels))
elif getattr(args, "video.index") != "":
provider_list.append(
VideoReader(
video=labels.videos[int(getattr(args, "video.index"))],
example_indices=frame_list(args.frames),
)
)
else:
provider_list.append(LabelsReader(labels))

tmp_data_path_list.append(data_path_file)

else:
provider = LabelsReader(labels)
try:
video_kwargs = dict(
dataset=vars(args).get("video.dataset"),
input_format=vars(args).get("video.input_format"),
)
provider_list.append(
VideoReader.from_filepath(
filename=data_path_file.as_posix(),
example_indices=frame_list(args.frames),
**video_kwargs,
)
)
print(f"Video: {data_path_file}")
tmp_data_path_list.append(data_path_file)
# TODO: Clean this up.
except Exception:
print(f"Error reading file: {data_path_file}")

else:
print(f"Video: {data_path}")
# TODO: Clean this up.
video_kwargs = dict(
dataset=vars(args).get("video.dataset"),
input_format=vars(args).get("video.input_format"),
)
provider = VideoReader.from_filepath(
filename=data_path, example_indices=frame_list(args.frames), **video_kwargs
)
data_path_list = tmp_data_path_list

return provider, data_path
return provider_list, data_path_list
Copy link

Choose a reason for hiding this comment

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

Logic to handle multiple video inputs looks good.

The logic to compile files into a list for iteration and the creation of providers based on file types are well implemented. However, consider refactoring the provider creation into a separate function for better modularity and clarity.

def create_provider(data_path_file, args):
    if data_path_file.as_posix().endswith(".slp"):
        labels = sleap.load_file(data_path_file.as_posix())
        # Additional conditions based on args
    else:
        video_kwargs = dict(
            dataset=vars(args).get("video.dataset"),
            input_format=vars(args).get("video.input_format"),
        )
        return VideoReader.from_filepath(filename=data_path_file.as_posix(), example_indices=frame_list(args.frames), **video_kwargs)

    return None  # or appropriate return

# Reset output_path for next iteration
output_path = args.output

# running tracking on existing prediction file
Copy link

Choose a reason for hiding this comment

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

Improve error handling for missing tracker specification.

The error message is clear, but consider providing more guidance or a direct link to documentation on how to specify a tracker.

raise ValueError(
    "To retrack on predictions, must specify tracker. "
    "Use 'sleap-track --tracking.tracker ...' to specify tracker to use. "
    "See [documentation link] for more details."
)

Comment on lines 5493 to 5627
labels_pr.provenance["predictor"] = type(predictor).__name__

if args.no_empty_frames:
# Clear empty frames if specified.
labels_pr.remove_empty_frames()

finish_timestamp = str(datetime.now())
total_elapsed = time() - t0
print("Finished inference at:", finish_timestamp)
print(f"Total runtime: {total_elapsed} secs")
print(f"Predicted frames: {len(labels_pr)}/{len(provider)}")

# Add provenance metadata to predictions.
labels_pr.provenance["sleap_version"] = sleap.__version__
labels_pr.provenance["platform"] = platform.platform()
labels_pr.provenance["command"] = " ".join(sys.argv)
labels_pr.provenance["data_path"] = data_path.as_posix()
labels_pr.provenance["output_path"] = output_path_obj.as_posix()
labels_pr.provenance["total_elapsed"] = total_elapsed
labels_pr.provenance["start_timestamp"] = start_timestamp
labels_pr.provenance["finish_timestamp"] = finish_timestamp

print("Provenance:")
print(labels_pr.provenance)
print()

labels_pr.provenance["args"] = vars(args)

# Save results.
labels_pr.save(output_path)
print("Saved output:", output_path)

if args.open_in_gui:
subprocess.call(["sleap-label", output_path])

# Reset output_path for next iteration
output_path = args.output

# running tracking on existing prediction file
elif getattr(args, "tracking.tracker") is not None:
# Load predictions
print("Loading predictions...")
labels_pr = sleap.load_file(args.data_path)
frames = sorted(labels_pr.labeled_frames, key=lambda lf: lf.frame_idx)
for data_path, provider in zip(data_path_list, provider_list):
# Load predictions
print("Loading predictions...")
labels_pr = sleap.load_file(data_path.as_posix())
frames = sorted(labels_pr.labeled_frames, key=lambda lf: lf.frame_idx)

print("Starting tracker...")
frames = run_tracker(frames=frames, tracker=tracker)
tracker.final_pass(frames)

print("Starting tracker...")
frames = run_tracker(frames=frames, tracker=tracker)
tracker.final_pass(frames)
labels_pr = Labels(labeled_frames=frames)

labels_pr = Labels(labeled_frames=frames)
if output_path is None:
output_path = f"{data_path}.{tracker.get_name()}.slp"

if output_path is None:
output_path = f"{data_path}.{tracker.get_name()}.slp"
if args.no_empty_frames:
# Clear empty frames if specified.
labels_pr.remove_empty_frames()

finish_timestamp = str(datetime.now())
total_elapsed = time() - t0
print("Finished inference at:", finish_timestamp)
print(f"Total runtime: {total_elapsed} secs")
print(f"Predicted frames: {len(labels_pr)}/{len(provider)}")

# Add provenance metadata to predictions.
labels_pr.provenance["sleap_version"] = sleap.__version__
labels_pr.provenance["platform"] = platform.platform()
labels_pr.provenance["command"] = " ".join(sys.argv)
labels_pr.provenance["data_path"] = data_path
labels_pr.provenance["output_path"] = output_path
labels_pr.provenance["total_elapsed"] = total_elapsed
labels_pr.provenance["start_timestamp"] = start_timestamp
labels_pr.provenance["finish_timestamp"] = finish_timestamp

print("Provenance:")
pprint(labels_pr.provenance)
print()

labels_pr.provenance["args"] = vars(args)

# Save results.
labels_pr.save(output_path)
print("Saved output:", output_path)

if args.open_in_gui:
subprocess.call(["sleap-label", output_path])

# Reset output_path for next iteration
output_path = args.output
Copy link

Choose a reason for hiding this comment

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

Review output path handling logic.

The handling of the output path, especially when multiple inputs are given, needs careful attention. Consider refining the logic to ensure that the output path is correctly set in all scenarios.

if output_path is not None and Path.is_file(output_path_obj) and len(data_path_list) > 1:
    raise ValueError(
        "output_path argument must be a directory if multiple video inputs are given"
    )
# Ensure that the output path is reset correctly after each iteration
+ original_output_path = args.output
for data_path, provider in zip(data_path_list, provider_list):
    ...
-    output_path = args.output
+    output_path = original_output_path
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
provider_list, data_path_list = _make_provider_from_cli(args)
# Setup tracker.
tracker = _make_tracker_from_cli(args)
output_path = args.output
if output_path is not None:
output_path_obj = Path(output_path)
# Output path given is a file, but multiple inputs were given
if output_path is not None and (
#TODO check if directory exists
#always specify output directory
Path.is_file(output_path_obj) and len(data_path_list) > 1
):
raise ValueError(
"output_path argument must be a directory if multiple video inputs are given"
)
if args.models is not None and "movenet" in args.models[0]:
args.models = args.models[0]
# Either run inference (and tracking) or just run tracking
# Either run inference (and tracking) or just run tracking (if using an existing prediction where inference has already been run)
if args.models is not None:
# Setup models.
predictor = _make_predictor_from_cli(args)
predictor.tracker = tracker
# Run inference!
labels_pr = predictor.predict(provider)
# Run inference on all files inputed
for data_path, provider in zip(data_path_list, provider_list):
# Setup models.
predictor = _make_predictor_from_cli(args)
print(f"predictor.tracker: {tracker}")
predictor.tracker = tracker
# Run inference!
labels_pr = predictor.predict(provider)
if output_path is None:
output_path = data_path + ".predictions.slp"
if output_path is None:
labels_pr.provenance["model_paths"] = predictor.model_paths
labels_pr.provenance["predictor"] = type(predictor).__name__
output_path = data_path.parent / (data_path.stem + ".predictions.slp")
output_path_obj = Path(output_path)
else:
output_path = output_path + "/" + (data_path.stem + ".predictions.slp")
output_path_obj = Path(output_path)
labels_pr.provenance["model_paths"] = predictor.model_paths
labels_pr.provenance["predictor"] = type(predictor).__name__
if args.no_empty_frames:
# Clear empty frames if specified.
labels_pr.remove_empty_frames()
finish_timestamp = str(datetime.now())
total_elapsed = time() - t0
print("Finished inference at:", finish_timestamp)
print(f"Total runtime: {total_elapsed} secs")
print(f"Predicted frames: {len(labels_pr)}/{len(provider)}")
# Add provenance metadata to predictions.
labels_pr.provenance["sleap_version"] = sleap.__version__
labels_pr.provenance["platform"] = platform.platform()
labels_pr.provenance["command"] = " ".join(sys.argv)
labels_pr.provenance["data_path"] = data_path.as_posix()
labels_pr.provenance["output_path"] = output_path_obj.as_posix()
labels_pr.provenance["total_elapsed"] = total_elapsed
labels_pr.provenance["start_timestamp"] = start_timestamp
labels_pr.provenance["finish_timestamp"] = finish_timestamp
print("Provenance:")
print(labels_pr.provenance)
print()
labels_pr.provenance["args"] = vars(args)
# Save results.
labels_pr.save(output_path)
print("Saved output:", output_path)
if args.open_in_gui:
subprocess.call(["sleap-label", output_path])
# Reset output_path for next iteration
output_path = args.output
# running tracking on existing prediction file
elif getattr(args, "tracking.tracker") is not None:
# Load predictions
print("Loading predictions...")
labels_pr = sleap.load_file(args.data_path)
frames = sorted(labels_pr.labeled_frames, key=lambda lf: lf.frame_idx)
for data_path, provider in zip(data_path_list, provider_list):
# Load predictions
print("Loading predictions...")
labels_pr = sleap.load_file(data_path.as_posix())
frames = sorted(labels_pr.labeled_frames, key=lambda lf: lf.frame_idx)
print("Starting tracker...")
frames = run_tracker(frames=frames, tracker=tracker)
tracker.final_pass(frames)
print("Starting tracker...")
frames = run_tracker(frames=frames, tracker=tracker)
tracker.final_pass(frames)
labels_pr = Labels(labeled_frames=frames)
labels_pr = Labels(labeled_frames=frames)
if output_path is None:
output_path = f"{data_path}.{tracker.get_name()}.slp"
if output_path is None:
output_path = f"{data_path}.{tracker.get_name()}.slp"
if args.no_empty_frames:
# Clear empty frames if specified.
labels_pr.remove_empty_frames()
finish_timestamp = str(datetime.now())
total_elapsed = time() - t0
print("Finished inference at:", finish_timestamp)
print(f"Total runtime: {total_elapsed} secs")
print(f"Predicted frames: {len(labels_pr)}/{len(provider)}")
# Add provenance metadata to predictions.
labels_pr.provenance["sleap_version"] = sleap.__version__
labels_pr.provenance["platform"] = platform.platform()
labels_pr.provenance["command"] = " ".join(sys.argv)
labels_pr.provenance["data_path"] = data_path
labels_pr.provenance["output_path"] = output_path
labels_pr.provenance["total_elapsed"] = total_elapsed
labels_pr.provenance["start_timestamp"] = start_timestamp
labels_pr.provenance["finish_timestamp"] = finish_timestamp
print("Provenance:")
pprint(labels_pr.provenance)
print()
labels_pr.provenance["args"] = vars(args)
# Save results.
labels_pr.save(output_path)
print("Saved output:", output_path)
if args.open_in_gui:
subprocess.call(["sleap-label", output_path])
# Reset output_path for next iteration
output_path = args.output
provider_list, data_path_list = _make_provider_from_cli(args)
# Setup tracker.
tracker = _make_tracker_from_cli(args)
output_path = args.output
if output_path is not None:
output_path_obj = Path(output_path)
# Output path given is a file, but multiple inputs were given
if output_path is not None and (
#TODO check if directory exists
#always specify output directory
Path.is_file(output_path_obj) and len(data_path_list) > 1
):
raise ValueError(
"output_path argument must be a directory if multiple video inputs are given"
)
if args.models is not None and "movenet" in args.models[0]:
args.models = args.models[0]
# Either run inference (and tracking) or just run tracking (if using an existing prediction where inference has already been run)
if args.models is not None:
# Run inference on all files inputed
original_output_path = args.output
for data_path, provider in zip(data_path_list, provider_list):
# Setup models.
predictor = _make_predictor_from_cli(args)
print(f"predictor.tracker: {tracker}")
predictor.tracker = tracker
# Run inference!
labels_pr = predictor.predict(provider)
if output_path is None:
output_path = data_path.parent / (data_path.stem + ".predictions.slp")
output_path_obj = Path(output_path)
else:
output_path = output_path + "/" + (data_path.stem + ".predictions.slp")
output_path_obj = Path(output_path)
labels_pr.provenance["model_paths"] = predictor.model_paths
labels_pr.provenance["predictor"] = type(predictor).__name__
if args.no_empty_frames:
# Clear empty frames if specified.
labels_pr.remove_empty_frames()
finish_timestamp = str(datetime.now())
total_elapsed = time() - t0
print("Finished inference at:", finish_timestamp)
print(f"Total runtime: {total_elapsed} secs")
print(f"Predicted frames: {len(labels_pr)}/{len(provider)}")
# Add provenance metadata to predictions.
labels_pr.provenance["sleap_version"] = sleap.__version__
labels_pr.provenance["platform"] = platform.platform()
labels_pr.provenance["command"] = " ".join(sys.argv)
labels_pr.provenance["data_path"] = data_path.as_posix()
labels_pr.provenance["output_path"] = output_path_obj.as_posix()
labels_pr.provenance["total_elapsed"] = total_elapsed
labels_pr.provenance["start_timestamp"] = start_timestamp
labels_pr.provenance["finish_timestamp"] = finish_timestamp
print("Provenance:")
print(labels_pr.provenance)
print()
labels_pr.provenance["args"] = vars(args)
# Save results.
labels_pr.save(output_path)
print("Saved output:", output_path)
if args.open_in_gui:
subprocess.call(["sleap-label", output_path])
# Reset output_path for next iteration
output_path = original_output_path
# running tracking on existing prediction file
elif getattr(args, "tracking.tracker") is not None:
for data_path, provider in zip(data_path_list, provider_list):
# Load predictions
print("Loading predictions...")
labels_pr = sleap.load_file(data_path.as_posix())
frames = sorted(labels_pr.labeled_frames, key=lambda lf: lf.frame_idx)
print("Starting tracker...")
frames = run_tracker(frames=frames, tracker=tracker)
tracker.final_pass(frames)
labels_pr = Labels(labeled_frames=frames)
if output_path is None:
output_path = f"{data_path}.{tracker.get_name()}.slp"
if args.no_empty_frames:
# Clear empty frames if specified.
labels_pr.remove_empty_frames()
finish_timestamp = str(datetime.now())
total_elapsed = time() - t0
print("Finished inference at:", finish_timestamp)
print(f"Total runtime: {total_elapsed} secs")
print(f"Predicted frames: {len(labels_pr)}/{len(provider)}")
# Add provenance metadata to predictions.
labels_pr.provenance["sleap_version"] = sleap.__version__
labels_pr.provenance["platform"] = platform.platform()
labels_pr.provenance["command"] = " ".join(sys.argv)
labels_pr.provenance["data_path"] = data_path
labels_pr.provenance["output_path"] = output_path
labels_pr.provenance["total_elapsed"] = total_elapsed
labels_pr.provenance["start_timestamp"] = start_timestamp
labels_pr.provenance["finish_timestamp"] = finish_timestamp
print("Provenance:")
pprint(labels_pr.provenance)
print()
labels_pr.provenance["args"] = vars(args)
# Save results.
labels_pr.save(output_path)
print("Saved output:", output_path)
if args.open_in_gui:
subprocess.call(["sleap-label", output_path])
# Reset output_path for next iteration
output_path = args.output

@emdavis02 emdavis02 force-pushed the elise/multiple-inputs-for-sleap-track branch from 734d441 to 5ffdc96 Compare July 8, 2024 18:19
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Outside diff range and nitpick comments (4)
sleap/nn/inference.py (4)

5291-5298: Optimize conditional assignment using a ternary operator.

The current if-else block for setting data_path can be simplified using a ternary operator, which makes the code cleaner and more concise.

-    if labels_path is not None:
-        data_path = Path(labels_path)
-    else:
-        data_path = Path(args.data_path)
+    data_path = Path(labels_path) if labels_path is not None else Path(args.data_path)
Tools
Ruff

5294-5297: Use ternary operator data_path = Path(labels_path) if labels_path is not None else Path(args.data_path) instead of if-else-block

Replace if-else-block with data_path = Path(labels_path) if labels_path is not None else Path(args.data_path)

(SIM108)


5307-5307: Ensure consistent path format.

Use as_posix() consistently to ensure the path format is consistent across different operating systems.

-    data_path_list = [data_path.as_posix()]
+    data_path_list = [data_path.as_posix() for data_path in data_path_list]

5373-5373: Use logging instead of print statements.

Using logging instead of print statements provides better control over the output and can be easily disabled or redirected.

-    print(args)
+    logging.debug(args)

5527-5528: Clarify error message.

The error message can be clarified to provide more information to the user.

-    "output_path argument must be a directory if multiple video inputs are given"
+    "The output_path argument must be a directory when multiple video inputs are given. Please specify a valid directory."
Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between ec4c26d and 5ffdc96.

Files ignored due to path filters (5)
  • tests/data/videos/multiple_inputs_mp4/centered_pair_small.mp4 is excluded by !**/*.mp4
  • tests/data/videos/multiple_inputs_mp4/centered_pair_small_2.mp4 is excluded by !**/*.mp4
  • tests/data/videos/multiple_inputs_mp4/centered_pair_small_3.mp4 is excluded by !**/*.mp4
  • tests/data/videos/multiple_inputs_slp_mp4/centered_pair_small_2.mp4 is excluded by !**/*.mp4
  • tests/data/videos/multiple_inputs_slp_mp4/centered_pair_small_3.mp4 is excluded by !**/*.mp4
Files selected for processing (1)
  • sleap/nn/inference.py (5 hunks)
Additional context used
Ruff
sleap/nn/inference.py

5294-5297: Use ternary operator data_path = Path(labels_path) if labels_path is not None else Path(args.data_path) instead of if-else-block

Replace if-else-block with data_path = Path(labels_path) if labels_path is not None else Path(args.data_path)

(SIM108)

Additional comments not posted (1)
sleap/nn/inference.py (1)

5608-5612: Ensure directory creation for output path.

Ensure that the directory for the output path is created if it does not exist.

output_path_obj.mkdir(parents=True, exist_ok=True)

sleap/nn/inference.py Show resolved Hide resolved
Comment on lines 5537 to 5539
output_path = output_path + "/" + (data_path_obj.stem + ".predictions.slp")
output_path_obj = Path(output_path)

Copy link

Choose a reason for hiding this comment

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

Ensure directory creation for output path.

Ensure that the directory for the output path is created if it does not exist.

output_path_obj.mkdir(parents=True, exist_ok=True)

@talmo
Copy link
Collaborator

talmo commented Jul 10, 2024

Review notes from subgroup:

  • Remove duplicate files from fixtures and instead just copy them on the fly inside the test into a temp dir
  • Update docs to reference the new functionality (at least add usage examples to the CLI doc page for sleap-track)

@emdavis02 emdavis02 force-pushed the elise/multiple-inputs-for-sleap-track branch from 6192d8a to 3b9cd45 Compare July 10, 2024 18:11
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 55bfe4b and fd558cd.

Files selected for processing (2)
  • sleap/nn/inference.py (5 hunks)
  • tests/nn/test_inference.py (3 hunks)
Files skipped from review as they are similar to previous changes (1)
  • sleap/nn/inference.py
Additional comments not posted (7)
tests/nn/test_inference.py (7)

Line range hint 1511-1538:
LGTM!

The function is well-structured and tests the sleap-track command with a single input.


1717-1736: LGTM!

The function is well-structured and tests the sleap-track command with an invalid output file.


1739-1755: LGTM!

The function is well-structured and tests the sleap-track command with an invalid input.


1487-1487: Use Path(test_sleap_track_mult_inputs_folder_slp).as_posix() instead of str(...).

This ensures consistency in handling file paths.

-    slp_path = Path(test_sleap_track_mult_inputs_folder_slp).as_posix()
+    slp_path = str(Path(test_sleap_track_mult_inputs_folder_slp))

Likely invalid or redundant comment.


1692-1710: Add comments explaining the removal of files.

The function test_sleap_track_output_mult is well-structured but lacks comments explaining why files are removed.

-    files_to_remove = set(new_output_path_list) - set(output_path_list)
+    # This ensures the output folder is reverted to its original state after the test.
+    files_to_remove = set(new_output_path_list) - set(output_path_list)

Likely invalid or redundant comment.


1575-1578: Add comments explaining the removal of files.

The function test_sleap_track_mult_input_slp is well-structured but lacks comments explaining why files are removed.

-    files_to_remove = set(new_slp_path_list) - set(slp_path_list)
+    # This ensures the input folder is reverted to its original state after the test.
+    files_to_remove = set(new_slp_path_list) - set(slp_path_list)

Likely invalid or redundant comment.


1487-1487: Use Path(test_sleap_track_mult_inputs_folder_slp).as_posix() instead of str(...).

This ensures consistency in handling file paths.

-    slp_path = Path(test_sleap_track_mult_inputs_folder_slp).as_posix()
+    slp_path = str(Path(test_sleap_track_mult_inputs_folder_slp))

Likely invalid or redundant comment.

Comment on lines 1712 to 1714
files_to_remove = set(new_output_path_list) - set(output_path_list)
for file in files_to_remove:
file.unlink()
Copy link

Choose a reason for hiding this comment

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

Add comments explaining the removal of files.

The function test_sleap_track_output_mult is well-structured but lacks comments explaining why files are removed.

-    files_to_remove = set(new_output_path_list) - set(output_path_list)
+    # This ensures the output folder is reverted to its original state after the test.
+    files_to_remove = set(new_output_path_list) - set(output_path_list)
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
files_to_remove = set(new_output_path_list) - set(output_path_list)
for file in files_to_remove:
file.unlink()
# This ensures the output folder is reverted to its original state after the test.
files_to_remove = set(new_output_path_list) - set(output_path_list)
for file in files_to_remove:
file.unlink()

Comment on lines 1617 to 1620
# remove the files that have been added within the test case to revert input folders to their original state
files_to_remove = set(new_slp_path_list) - set(slp_path_list)
for file in files_to_remove:
file.unlink()
Copy link

Choose a reason for hiding this comment

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

Add comments explaining the removal of files.

The function test_sleap_track_mult_input_slp_mp4 is well-structured but lacks comments explaining why files are removed.

-    files_to_remove = set(new_slp_path_list) - set(slp_path_list)
+    # This ensures the input folder is reverted to its original state after the test.
+    files_to_remove = set(new_slp_path_list) - set(slp_path_list)
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# remove the files that have been added within the test case to revert input folders to their original state
files_to_remove = set(new_slp_path_list) - set(slp_path_list)
for file in files_to_remove:
file.unlink()
# remove the files that have been added within the test case to revert input folders to their original state
# This ensures the input folder is reverted to its original state after the test.
files_to_remove = set(new_slp_path_list) - set(slp_path_list)
for file in files_to_remove:
file.unlink()

Comment on lines 1575 to 1578
# remove the files that have been added within the test case to revert input folders to their original state
files_to_remove = set(new_slp_path_list) - set(slp_path_list)
for file in files_to_remove:
file.unlink()
Copy link

Choose a reason for hiding this comment

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

Add comments explaining the removal of files.

The function test_sleap_track_mult_input_slp is well-structured but lacks comments explaining why files are removed.

-    files_to_remove = set(new_slp_path_list) - set(slp_path_list)
+    # This ensures the input folder is reverted to its original state after the test.
+    files_to_remove = set(new_slp_path_list) - set(slp_path_list)
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# remove the files that have been added within the test case to revert input folders to their original state
files_to_remove = set(new_slp_path_list) - set(slp_path_list)
for file in files_to_remove:
file.unlink()
# remove the files that have been added within the test case to revert input folders to their original state
# This ensures the input folder is reverted to its original state after the test.
files_to_remove = set(new_slp_path_list) - set(slp_path_list)
for file in files_to_remove:
file.unlink()

Comment on lines 1660 to 1662
files_to_remove = set(new_slp_path_list) - set(slp_path_list)
for file in files_to_remove:
file.unlink()
Copy link

Choose a reason for hiding this comment

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

Add comments explaining the removal of files.

The function test_sleap_track_mult_input_mp4 is well-structured but lacks comments explaining why files are removed.

-    files_to_remove = set(new_slp_path_list) - set(slp_path_list)
+    # This ensures the input folder is reverted to its original state after the test.
+    files_to_remove = set(new_slp_path_list) - set(slp_path_list)
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
files_to_remove = set(new_slp_path_list) - set(slp_path_list)
for file in files_to_remove:
file.unlink()
# This ensures the input folder is reverted to its original state after the test.
files_to_remove = set(new_slp_path_list) - set(slp_path_list)
for file in files_to_remove:
file.unlink()

@emdavis02 emdavis02 force-pushed the elise/multiple-inputs-for-sleap-track branch from fd558cd to be02a7d Compare July 10, 2024 20:54
@emdavis02 emdavis02 force-pushed the elise/multiple-inputs-for-sleap-track branch from 2fd8ab9 to 6a481c3 Compare July 10, 2024 20:57
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between fd558cd and 2fd8ab9.

Files selected for processing (2)
  • sleap/nn/inference.py (5 hunks)
  • tests/nn/test_inference.py (3 hunks)
Files skipped from review as they are similar to previous changes (1)
  • sleap/nn/inference.py
Additional comments not posted (9)
tests/nn/test_inference.py (9)

1451-1480: LGTM!

The function test_make_predictor_from_cli_mult_input correctly tests the creation of a predictor from CLI arguments with multiple inputs.


Line range hint 1482-1509: LGTM!

The function test_sleap_track_single_input correctly tests the sleap-track command with a single input, including both valid and invalid scenarios.


1511-1558: LGTM!

The function test_sleap_track_mult_input_slp correctly tests the sleap-track command with multiple SLP files as input, ensuring predictions are generated for each file.


1561-1607: LGTM!

The function test_sleap_track_mult_input_slp_mp4 correctly tests the sleap-track command with a mix of SLP and MP4 files as input, ensuring predictions are generated for each file.


1610-1653: LGTM!

The function test_sleap_track_mult_input_mp4 correctly tests the sleap-track command with multiple MP4 files as input, ensuring predictions are generated for each file.


1655-1704: LGTM!

The function test_sleap_track_output_mult correctly tests the sleap-track command with multiple outputs, ensuring predictions are saved to the specified output directory.


1706-1737: LGTM!

The function test_sleap_track_invalid_output correctly tests the sleap-track command with an invalid output path, ensuring a ValueError is raised.


1740-1757: LGTM!

The function test_sleap_track_invalid_input correctly tests the sleap-track command with an invalid input path, ensuring a ValueError is raised.


1560-1608: LGTM!

The function test_sleap_track_mult_inputs_folder_mp4 correctly tests the sleap-track command with multiple MP4 files as input, ensuring predictions are generated and saved for each file.

sleap/nn/inference.py Outdated Show resolved Hide resolved
sleap/nn/inference.py Outdated Show resolved Hide resolved
sleap/nn/inference.py Outdated Show resolved Hide resolved
sleap/nn/inference.py Outdated Show resolved Hide resolved
sleap/nn/inference.py Outdated Show resolved Hide resolved
sleap/nn/inference.py Show resolved Hide resolved
sleap/nn/inference.py Outdated Show resolved Hide resolved
sleap/nn/inference.py Outdated Show resolved Hide resolved
sleap/nn/inference.py Outdated Show resolved Hide resolved
sleap/nn/inference.py Outdated Show resolved Hide resolved
@talmo
Copy link
Collaborator

talmo commented Jul 11, 2024

In addition to the changes above, another comment re: supporting inputs that are multiple SLP files:

  • We do want to support having multiple SLP files that have predicted poses on all frames and that the user wants to retrack only.
  • We do want to support having multiple SLP files that have suggestion frames and that the user wants to generate predicted poses for, but not track.
  • We do not want to overwrite SLP files in any case.
  • We really do not want to mess around with .pkg.slp files, particularly not overwrite them. When re-saving labels that had embedded images, they'll get saved without the embedded images and will restore the source videos. If the user had embedded images, it'd be a serious data loss event to have that file get re-saved without the images. In this case, saving to a separate SLP file is the best compromise.

@roomrys am I missing any other dangerzone cases?

@emdavis02 emdavis02 force-pushed the elise/multiple-inputs-for-sleap-track branch from fc9dcb5 to 488edde Compare July 12, 2024 08:20
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 2fd8ab9 and 488edde.

Files selected for processing (2)
  • sleap/nn/inference.py (5 hunks)
  • tests/nn/test_inference.py (3 hunks)
Files skipped from review as they are similar to previous changes (1)
  • sleap/nn/inference.py
Additional context used
Ruff
tests/nn/test_inference.py

1726-1726: Undefined name as_posix

(F821)

Additional comments not posted (10)
tests/nn/test_inference.py (10)

1451-1491: Ensure proper handling of multiple input files.

The function test_make_predictor_from_cli_mult_input correctly handles multiple input SLP files by copying them into a temporary directory and running inference on each copy. The assertions ensure that the predictor correctly handles the maximum number of instances.


1521-1569: Ensure proper handling of multiple input SLP files.

The function test_sleap_track_mult_input_slp correctly handles multiple input SLP files by copying them into a temporary directory and running the sleap-track command on each copy. The assertions ensure that the predictions files are correctly generated for each input file.


1571-1618: Ensure proper handling of multiple input SLP and MP4 files.

The function test_sleap_track_mult_input_slp_mp4 correctly handles multiple input SLP and MP4 files by copying them into a temporary directory and running the sleap-track command on each copy. The assertions ensure that the predictions files are correctly generated for each input file.


1620-1664: Ensure proper handling of multiple input MP4 files.

The function test_sleap_track_mult_input_mp4 correctly handles multiple input MP4 files by copying them into a temporary directory and running the sleap-track command on each copy. The assertions ensure that the predictions files are correctly generated for each input file.


1666-1715: Ensure proper handling of multiple input files with specified output directory.

The function test_sleap_track_output_mult correctly handles multiple input MP4 files by copying them into a temporary directory and running the sleap-track command on each copy with a specified output directory. The assertions ensure that the predictions files are correctly generated in the specified output directory.


1717-1750: Ensure proper handling of invalid output path.

The function test_sleap_track_invalid_output correctly handles an invalid output path by raising a ValueError exception. The test ensures that the sleap-track command correctly handles invalid output paths.

Tools
Ruff

1726-1726: Undefined name as_posix

(F821)


1752-1769: Ensure proper handling of invalid input path.

The function test_sleap_track_invalid_input correctly handles an invalid input path by raising a ValueError exception. The test ensures that the sleap-track command correctly handles invalid input paths.


1521-1569: Ensure proper handling of multiple input SLP files with different tracking options.

The function test_sleap_track_mult_input_slp correctly handles multiple input SLP files by copying them into a temporary directory and running the sleap-track command on each copy with different tracking options. The assertions ensure that the predictions files are correctly generated for each input file and tracking option.


1571-1618: Ensure proper handling of multiple input SLP and MP4 files with different tracking options.

The function test_sleap_track_mult_input_slp_mp4 correctly handles multiple input SLP and MP4 files by copying them into a temporary directory and running the sleap-track command on each copy with different tracking options. The assertions ensure that the predictions files are correctly generated for each input file and tracking option.


1621-1664: Ensure proper handling of multiple input MP4 files with different tracking options.

The function test_sleap_track_mult_input_mp4 correctly handles multiple input MP4 files by copying them into a temporary directory and running the sleap-track command on each copy with different tracking options. The assertions ensure that the predictions files are correctly generated for each input file and tracking option.

):

output_path = Path(tmpdir, "output_file.slp").as_posix()
output_path = Path(tmpdir, "output_file.slp"),as_posix()
Copy link

Choose a reason for hiding this comment

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

Fix the typo in defining the output_path.

The output_path is defined incorrectly due to a typo. The correct method should be used to define the path.

-    output_path = Path(tmpdir, "output_file.slp"),as_posix()
+    output_path = Path(tmpdir, "output_file.slp").as_posix()
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
output_path = Path(tmpdir, "output_file.slp"),as_posix()
output_path = Path(tmpdir, "output_file.slp").as_posix()
Tools
Ruff

1726-1726: Undefined name as_posix

(F821)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 488edde and 4443686.

Files selected for processing (1)
  • tests/nn/test_inference.py (3 hunks)
Files skipped from review as they are similar to previous changes (1)
  • tests/nn/test_inference.py

@talmo
Copy link
Collaborator

talmo commented Jul 12, 2024

In addition to the changes above, another comment re: supporting inputs that are multiple SLP files:

  • We do want to support having multiple SLP files that have predicted poses on all frames and that the user wants to retrack only.
  • We do want to support having multiple SLP files that have suggestion frames and that the user wants to generate predicted poses for, but not track.
  • We do not want to overwrite SLP files in any case.
  • We really do not want to mess around with .pkg.slp files, particularly not overwrite them. When re-saving labels that had embedded images, they'll get saved without the embedded images and will restore the source videos. If the user had embedded images, it'd be a serious data loss event to have that file get re-saved without the images. In this case, saving to a separate SLP file is the best compromise.

@roomrys am I missing any other dangerzone cases?

On further assessment during subgroup: let's actually just not do multiple SLP file support.

@emdavis02 emdavis02 force-pushed the elise/multiple-inputs-for-sleap-track branch from 3915e56 to d86123d Compare July 15, 2024 18:39
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 4443686 and d86123d.

Files selected for processing (2)
  • sleap/nn/inference.py (4 hunks)
  • tests/nn/test_inference.py (3 hunks)
Files skipped from review as they are similar to previous changes (2)
  • sleap/nn/inference.py
  • tests/nn/test_inference.py

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between d86123d and ae11b8d.

Files selected for processing (1)
  • sleap/nn/inference.py (4 hunks)
Files skipped from review as they are similar to previous changes (1)
  • sleap/nn/inference.py

@emdavis02 emdavis02 requested a review from eberrigan July 15, 2024 21:36
)
output_path_obj = Path(output_path)
# Create the containing directory if needed.
output_path_obj.parent.mkdir(exist_ok=True, parents=True)
Copy link
Contributor

Choose a reason for hiding this comment

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

is this what you wanted to do, or did you want to make the directory itself instead of the parent?

@emdavis02 emdavis02 force-pushed the elise/multiple-inputs-for-sleap-track branch from 84674d0 to fb587e5 Compare July 17, 2024 17:27
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between ae11b8d and fb587e5.

Files selected for processing (1)
  • sleap/nn/inference.py (4 hunks)
Files skipped from review as they are similar to previous changes (1)
  • sleap/nn/inference.py

Copy link
Collaborator

@talmo talmo left a comment

Choose a reason for hiding this comment

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

LGTM!

@talmo talmo merged commit 3e2bd25 into talmolab:develop Jul 19, 2024
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants