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

Add options to set background color when exporting video #1328

Merged
merged 12 commits into from
Sep 18, 2023

Conversation

scott-yj-yang
Copy link
Contributor

@scott-yj-yang scott-yj-yang commented May 25, 2023

Description

Added background color configuration in video output. It can output with videos, without videos using (white, grey, black) backgrounds.

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?

#921

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 Feature: Added a new optional argument --background to the command-line interface, allowing users to specify the background color when saving videos. The available options are "original", "black", "white", and "grey".
  • Test: Introduced new tests for the rendering of videos with different background colors.
  • Refactor: Updated several functions (do_action, ask, reader, save_labeled_video) to accept an additional parameter background for specifying the output video background color.

@codecov
Copy link

codecov bot commented May 25, 2023

Codecov Report

Merging #1328 (cd1663f) into develop (a2092f0) will increase coverage by 0.00%.
Report is 4 commits behind head on develop.
The diff coverage is 75.00%.

@@           Coverage Diff            @@
##           develop    #1328   +/-   ##
========================================
  Coverage    73.11%   73.11%           
========================================
  Files          134      134           
  Lines        23963    23972    +9     
========================================
+ Hits         17521    17528    +7     
- Misses        6442     6444    +2     
Files Changed Coverage Δ
sleap/gui/commands.py 61.35% <0.00%> (-0.04%) ⬇️
sleap/gui/overlays/base.py 58.33% <ø> (+0.68%) ⬆️
sleap/io/video.py 92.04% <ø> (ø)
sleap/io/visuals.py 90.96% <81.81%> (-0.40%) ⬇️

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

Copy link
Collaborator

@roomrys roomrys left a comment

Choose a reason for hiding this comment

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

Works well, suggested some implementation simplifications

sleap/io/visuals.py Outdated Show resolved Hide resolved
sleap/config/labeled_clip_form.yaml Outdated Show resolved Hide resolved
@roomrys roomrys changed the title implement #921 Add options to set background color when exporting video May 25, 2023
@scott-yj-yang
Copy link
Contributor Author

Hi Liezl, I refractor and simplified the implementation with one option of Video Background. Please let me know if any other changes are needed.

Copy link
Collaborator

@roomrys roomrys left a comment

Choose a reason for hiding this comment

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

What we have right now looks good. To add,

  1. we can create a sleap-render cli argument for background and pass that in our call to save_labeled_video in the sleap.io.visuals.main function.
  2. Document the new optional argument in the cli.md
  3. Then, we can add a test function to run through the code via a psuedo sleap-render call.

sleap/io/visuals.py Outdated Show resolved Hide resolved
@coderabbitai
Copy link

coderabbitai bot commented Sep 10, 2023

Walkthrough

This pull request introduces a new feature that allows users to specify the background color when saving videos. The changes span across several files, including modifications to function signatures, addition of command-line arguments, and updates to tests to cover the new functionality.

Changes

File(s) Summary
sleap/gui/commands.py Added a new parameter "background" in the do_action and ask functions.
sleap/io/video.py Updated the docstring of the get_frames_safely method with a new argument description.
sleap/io/visuals.py Introduced a new parameter "background" in the reader and save_labeled_video functions. Also added a new command-line argument --background.
docs/guides/cli.md Documented the new optional argument --background for specifying the video background.
tests/io/test_visuals.py Added a new test function test_sleap_render_with_different_backgrounds to test the new feature.

🐇💻

With a hop and a skip, we've made quite a trip,

To a place where your videos can flip.

Choose a hue, black, white or blue,

Your background is now up to you! 🎉🌈


Tips

Chat with CodeRabbit Bot (@coderabbitai)

  • Mention @coderabbitai in any review comment for bot assistance.
  • Note: Review comments are made on code diffs or files, not on the PR overview.

Pause Incremental Reviews

  • Insert @coderabbitai: ignore in the PR description to halt the bot's ongoing reviews. Remove the line to resume.

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.

Review Status

Actionable comments generated: 1

Commits Files that changed from the base of the PR and between ce33e01 and 4762308 commits.
Files ignored due to filter (1)
  • sleap/config/labeled_clip_form.yaml
Files selected for processing (3)
  • sleap/gui/commands.py (2 hunks)
  • sleap/io/video.py (1 hunks)
  • sleap/io/visuals.py (6 hunks)
Files skipped from review due to trivial changes (1)
  • sleap/io/video.py
Additional comments (Suppressed): 5
sleap/gui/commands.py (2)
  • 1295-1301: Ensure that all calls to this function throughout the codebase have been updated to include the new "background" parameter. Also, verify that the "background" parameter is always included in the params dictionary before it's used.

  • 1355-1361: The new "background" parameter is being correctly extracted from the export_options dictionary. However, ensure that the "background" key is always present in the export_options dictionary before this code is executed to avoid a potential KeyError.

sleap/io/visuals.py (3)
  • 30-36: The reader function has been updated to include a new parameter, "background". Ensure that all calls to this function throughout the codebase have been updated to match the new signature.

  • 514-518: The save_labeled_video function has been updated to include a new parameter, "background". Ensure that all calls to this function throughout the codebase have been updated to match the new signature.

  • 559-560: The reader function is being called with the new "background" parameter in the Thread constructor. This looks good as long as all calls to reader have been updated to include this parameter.

Comment on lines 71 to 83
loaded_chunk_idxs, video_frame_images = video.get_frames_safely(
frames_idx_chunk
)
if background != "Original":
# fill the frame with the color
if background == "Black":
fill = 0
elif background == "Grey":
fill = 127
else:
# background == White
fill = 255
video_frame_images = video_frame_images * 0 + fill
Copy link

Choose a reason for hiding this comment

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

This block of code changes the background color of the video frames based on the value of the "background" parameter. However, it assumes that the "background" parameter will always be one of "Original", "Black", "Grey", or "White". If an unexpected value is passed, the code may behave unpredictably. Consider adding error handling for unexpected values.

            if background != "Original":
                # fill the frame with the color
                if background == "Black":
                    fill = 0
                elif background == "Grey":
                    fill = 127
                elif background == "White":
                    fill = 255
                else:
                    raise ValueError(f"Invalid background color: {background}")
                video_frame_images = video_frame_images * 0 + fill

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.

Review Status

Actionable comments generated: 2

Commits Files that changed from the base of the PR and between 4762308 and d8bdb43 commits.
Files selected for processing (3)
  • docs/guides/cli.md (1 hunks)
  • sleap/io/visuals.py (8 hunks)
  • tests/io/test_visuals.py (1 hunks)
Files skipped from review due to trivial changes (1)
  • docs/guides/cli.md
Additional comments (Suppressed): 5
tests/io/test_visuals.py (1)
  • 76-78: Ensure that all calls to the test_sleap_render function throughout the codebase have been updated to match the new signature.
sleap/io/visuals.py (4)
  • 30-36: The function signature of reader has been updated to include a new parameter, "background". Ensure that all calls to this function throughout the codebase have been updated to match the new signature.

  • 517-520: The function signature of save_labeled_video has been updated to include a new parameter, "background". Ensure that all calls to this function throughout the codebase have been updated to match the new signature.

  • 720-728: A new command-line argument, "--background", has been added for setting the background color when saving videos. This is a good addition as it allows users to customize the output video's background color from the command line.

  • 761-765: The call to save_labeled_video now includes the "background" argument. This is consistent with the changes made to the function signature.

Comment on lines 66 to 73
@pytest.mark.parametrize("background", ["original", "black", "white", "grey"])
def test_sleap_render_with_different_backgrounds(background):
args = (
f"-o test_{background}.avi -f 2 --scale 1.2 --frames 1,2 --video-index 0 --background {background} "
"tests/data/json_format_v2/centered_pair_predictions.json".split()
)
sleap_render(args)
assert os.path.exists(f"test_{background}.avi")
Copy link

Choose a reason for hiding this comment

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

The new test function test_sleap_render_with_different_backgrounds is a good addition to the test suite. It tests the rendering functionality with different background options, which aligns with the changes made in this PR. However, it would be better to also check if the output video has the correct background color. This can be done by reading the first frame of the output video and checking its color. If this is not feasible due to the complexity of the video processing library, at least ensure that the videos are not only created but also have non-zero size, indicating that they contain some data.

-    assert os.path.exists(f"test_{background}.avi")
+    assert os.path.exists(f"test_{background}.avi") and os.path.getsize(f"test_{background}.avi") > 0

Comment on lines 72 to 86
loaded_chunk_idxs, video_frame_images = video.get_frames_safely(
frames_idx_chunk
)
if background != "original":
# fill the frame with the color
if background == "black":
fill = 0
elif background == "grey":
fill = 127
elif background == "white":
# background == White
fill = 255
else:
raise ValueError(f"Invalid background color: {background}")
video_frame_images = video_frame_images * 0 + fill
Copy link

Choose a reason for hiding this comment

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

This block of code is used to fill the frame with the specified color. However, it only supports three colors: black, grey, and white. If an unsupported color is passed, it raises a ValueError. Consider adding support for more colors or providing a better error message to guide users on what values are acceptable.

-                    raise ValueError(f"Invalid background color: {background}")
+                    raise ValueError(f"Invalid background color: {background}. Supported colors are 'black', 'grey', and 'white'.")

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.

Review Status

Actionable comments generated: 1

Commits Files that changed from the base of the PR and between d8bdb43 and 2a82d3c commits.
Files selected for processing (1)
  • tests/io/test_visuals.py (2 hunks)
Additional comments (Suppressed): 1
tests/io/test_visuals.py (1)
  • 1-7: The import of cv2 is new in this hunk. Ensure that the OpenCV library (cv2) is included in your project dependencies.

Comment on lines 67 to 102
@pytest.mark.parametrize("background", ["original", "black", "white", "grey"])
def test_sleap_render_with_different_backgrounds(background):
args = (
f"-o test_{background}.avi -f 2 --scale 1.2 --frames 1,2 --video-index 0 --background {background} "
"tests/data/json_format_v2/centered_pair_predictions.json".split()
)
sleap_render(args)
assert (
os.path.exists(f"test_{background}.avi")
and os.path.getsize(f"test_{background}.avi") > 0
)

# Check if the background is set correctly if not original background
if background != "original":
saved_video_path = f"test_{background}.avi"
cap = cv2.VideoCapture(saved_video_path)
ret, frame = cap.read()

# Accumulate color channels
b, g, r = cv2.split(frame)
mean_b = np.mean(b)
mean_g = np.mean(g)
mean_r = np.mean(r)

# Set threshold values.
white_threshold = 240
grey_threshold = 40

# Check if the average color is white, grey, or black
if all(val > white_threshold for val in [mean_b, mean_g, mean_r]):
background_color = "white"
elif all(val < grey_threshold for val in [mean_b, mean_g, mean_r]):
background_color = "black"
else:
background_color = "grey"
assert background_color == background
Copy link

Choose a reason for hiding this comment

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

This test function checks if the video rendering with different background colors works as expected. It's a good practice to test with various inputs, and the use of pytest's parametrize decorator is appropriate here. However, the way you're checking the background color could be improved. Instead of splitting the frame into color channels and calculating the mean for each channel, you can calculate the mean color of the entire frame using np.mean(frame, axis=(0, 1)). This will give you the mean color for all pixels in the frame, which should be more accurate.

-        # Accumulate color channels
-        b, g, r = cv2.split(frame)
-        mean_b = np.mean(b)
-        mean_g = np.mean(g)
-        mean_r = np.mean(r)
+        # Calculate mean color of the frame
+        mean_color = np.mean(frame, axis=(0, 1))

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.

Review Status

Actionable comments generated: 1

Commits Files that changed from the base of the PR and between 2a82d3c and bd64c2a commits.
Files selected for processing (1)
  • tests/io/test_visuals.py (2 hunks)
Additional comments (Suppressed): 1
tests/io/test_visuals.py (1)
  • 107-109: The existing test function test_sleap_render has been modified to remove the background argument. This change is fine if the default behavior (without specifying a background) is being tested here. Ensure that this modification aligns with the intended testing strategy.

Comment on lines +67 to +104
@pytest.mark.parametrize("background", ["original", "black", "white", "grey"])
def test_sleap_render_with_different_backgrounds(background):
args = (
f"-o test_{background}.avi -f 2 --scale 1.2 --frames 1,2 --video-index 0 "
f"--background {background} "
"tests/data/json_format_v2/centered_pair_predictions.json".split()
)
sleap_render(args)
assert (
os.path.exists(f"test_{background}.avi")
and os.path.getsize(f"test_{background}.avi") > 0
)

# Check if the background is set correctly if not original background
if background != "original":
saved_video_path = f"test_{background}.avi"
cap = cv2.VideoCapture(saved_video_path)
ret, frame = cap.read()

# Calculate mean color of the channels
b, g, r = cv2.split(frame)
mean_b = np.mean(b)
mean_g = np.mean(g)
mean_r = np.mean(r)

# Set threshold values. Color is white if greater than white threshold, black
# if less than grey threshold and grey if in between both threshold values.
white_threshold = 240
grey_threshold = 40

# Check if the average color is white, grey, or black
if all(val > white_threshold for val in [mean_b, mean_g, mean_r]):
background_color = "white"
elif all(val < grey_threshold for val in [mean_b, mean_g, mean_r]):
background_color = "black"
else:
background_color = "grey"
assert background_color == background
Copy link

Choose a reason for hiding this comment

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

This new test function test_sleap_render_with_different_backgrounds is a good addition to the test suite. It checks whether the video rendering with different background colors works as expected. However, there's a potential issue with the way the average color of the frame is calculated and compared to the threshold values. The mean color calculation does not take into account the distribution of the colors in the frame. A frame with a majority of white pixels and some black pixels could still have an average color that is grey. Consider using a more robust method for determining the predominant color in the frame.

-         # Calculate mean color of the channels
-         b, g, r = cv2.split(frame)
-         mean_b = np.mean(b)
-         mean_g = np.mean(g)
-         mean_r = np.mean(r)
+         # Calculate the most frequent color of the channels
+         b, g, r = cv2.split(frame)
+         mode_b = scipy.stats.mode(b, axis=None)[0][0]
+         mode_g = scipy.stats.mode(g, axis=None)[0][0]
+         mode_r = scipy.stats.mode(r, axis=None)[0][0]

Copy link
Collaborator

@roomrys roomrys left a comment

Choose a reason for hiding this comment

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

I like that you changed the strings to lower case! Some of our docstrings still list uppercase and the option for the GUI are also in upper case. Maybe we make this case insensitive? Or just change everything to lowercase?

- name: background
label: Video Background
type: list
options: Original,Black,White,Grey
Copy link
Collaborator

Choose a reason for hiding this comment

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

These options should match case sensitivity, unless we handle this in visuals.py.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure I'll make it lowercase in the docstrings and the option for GUI.
We could keep it in lowercase to be consistent.

@@ -515,6 +536,7 @@ def save_labeled_video(
fps: Frames per second for output video.
scale: scale of image (so we can scale point locations to match)
crop_size_xy: size of crop around instances, or None for full images
background: output video background. Either Original, Black, White, Grey
Copy link
Collaborator

Choose a reason for hiding this comment

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

Match case sensitivity

Suggested change
background: output video background. Either Original, Black, White, Grey
background: output video background. Either original, black, white, grey

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.

Review Status

Actionable comments generated: 1

Commits Files that changed from the base of the PR and between bd64c2a and 5a71262 commits.
Files ignored due to filter (1)
  • sleap/config/labeled_clip_form.yaml
Files selected for processing (1)
  • sleap/io/visuals.py (8 hunks)
Additional comments (Suppressed): 4
sleap/io/visuals.py (4)
  • 30-36: The function signature for reader has been updated to include a new parameter, "background". Ensure that all calls to this function throughout the codebase have been updated to match the new signature.

  • 72-86: This block of code is used to fill the frame with the specified color. However, it's not clear how this interacts with the original video frames. If the original frames are completely overwritten by the fill color, then the video content will be lost. Please verify if this is the intended behavior.

  • 516-519: The function signature for save_labeled_video has been updated to include a new parameter, "background". Ensure that all calls to this function throughout the codebase have been updated to match the new signature.

  • 716-727: A new command-line argument, "--background", has been added. Ensure that the documentation has been updated to reflect this change.

Comment on lines 76 to 84
# fill the frame with the color
if background == "black":
fill = 0
elif background == "grey":
fill = 127
elif background == "white":
fill = 255
else:
raise ValueError(f"Invalid background color: {background}")
Copy link

Choose a reason for hiding this comment

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

This block of code checks the value of the background variable and assigns a corresponding fill value. This could be simplified and made more maintainable by using a dictionary to map background names to fill values. Here's an example:

-                if background == "black":
-                    fill = 0
-                elif background == "grey":
-                    fill = 127
-                elif background == "white":
-                    fill = 255
-                else:
-                    raise ValueError(f"Invalid background color: {background}")
+                fill_values = {"black": 0, "grey": 127, "white": 255}
+                try:
+                    fill = fill_values[background]
+                except KeyError:
+                    raise ValueError(f"Invalid background color: {background}")

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.

Review Status

Actionable comments generated: 1

Commits Files that changed from the base of the PR and between 5a71262 and c7ec0a1 commits.
Files selected for processing (1)
  • sleap/io/visuals.py (8 hunks)
Additional comments (Suppressed): 5
sleap/io/visuals.py (5)
  • 30-36: The function signature of reader has been updated to include a new parameter background. Ensure that all calls to this function throughout the codebase have been updated to match the new signature.

  • 515-518: The function signature of save_labeled_video has been updated to include a new parameter background. Ensure that all calls to this function throughout the codebase have been updated to match the new signature.

  • 560-561: The reader function is being called with the new background argument. This is consistent with the changes made to the function signature.

  • 718-726: A new command-line argument --background has been added to specify the background color when saving videos. The help text clearly explains the purpose of this argument and the available options.

  • 762-763: The save_labeled_video function is being called with the new background argument. This is consistent with the changes made to the function signature.

Comment on lines 72 to +84
loaded_chunk_idxs, video_frame_images = video.get_frames_safely(
frames_idx_chunk
)
if background != "original":
# fill the frame with the color
fill_values = {"black": 0, "grey": 127, "white": 255}
try:
fill = fill_values[background]
except KeyError:
raise ValueError(
f"Invalid background color: {background}. Options include: {', '.join(fill_values.keys())}"
)
video_frame_images = video_frame_images * 0 + fill
Copy link

Choose a reason for hiding this comment

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

This block of code introduces a new feature to set the background color of the video. It checks if the provided background is not "original", and if so, it fills the frame with the specified color. However, there's no validation for the case when background is "original". If an invalid value is passed, the error will only be raised after some computation has already been done (i.e., after frames are loaded). To improve efficiency, consider validating the background argument at the beginning of the function.

+    fill_values = {"black": 0, "grey": 127, "white": 255, "original": None}
+    try:
+        fill = fill_values[background]
+    except KeyError:
+        raise ValueError(
+            f"Invalid background color: {background}. Options include: {', '.join(fill_values.keys())}"
+        )
     loaded_chunk_idxs, video_frame_images = video.get_frames_safely(
         frames_idx_chunk
     )
-    if background != "original":
-        # fill the frame with the color
-        fill_values = {"black": 0, "grey": 127, "white": 255}
-        try:
-            fill = fill_values[background]
-        except KeyError:
-            raise ValueError(
-                f"Invalid background color: {background}. Options include: {', '.join(fill_values.keys())}"
-            )
-        video_frame_images = video_frame_images * 0 + fill

@roomrys roomrys merged commit c76e602 into develop Sep 18, 2023
9 checks passed
@roomrys roomrys deleted the output_only_motion branch September 18, 2023 20:02
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.

3 participants