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
3 changes: 3 additions & 0 deletions docs/guides/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,9 @@ optional arguments:
--distinctly_color DISTINCTLY_COLOR
Specify how to color instances. Options include: "instances",
"edges", and "nodes" (default: "instances")
--background BACKGROUND
Specify the type of background to be used to save the videos.
Options: original, black, white and grey. (default: "original")
```

## Debugging
Expand Down
4 changes: 4 additions & 0 deletions sleap/config/labeled_clip_form.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ main:
label: Use GUI Visual Settings (colors, line widths)
type: bool
default: true
- 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.

- name: open_when_done
label: Open When Done Saving
type: bool
Expand Down
2 changes: 2 additions & 0 deletions sleap/gui/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -1295,6 +1295,7 @@ def do_action(context: CommandContext, params: dict):
frames=list(params["frames"]),
fps=params["fps"],
color_manager=params["color_manager"],
background=params["background"],
show_edges=params["show edges"],
edge_is_wedge=params["edge_is_wedge"],
marker_size=params["marker size"],
Expand Down Expand Up @@ -1354,6 +1355,7 @@ def ask(context: CommandContext, params: dict) -> bool:
params["fps"] = export_options["fps"]
params["scale"] = export_options["scale"]
params["open_when_done"] = export_options["open_when_done"]
params["background"] = export_options["background"]

params["crop"] = None

Expand Down
3 changes: 2 additions & 1 deletion sleap/io/video.py
Original file line number Diff line number Diff line change
Expand Up @@ -1118,8 +1118,9 @@ def get_frames(self, idxs: Union[int, Iterable[int]]) -> np.ndarray:

def get_frames_safely(self, idxs: Iterable[int]) -> Tuple[List[int], np.ndarray]:
"""Return list of frame indices and frames which were successfully loaded.
Args:
idxs: An iterable object that contains the indices of frames.

idxs: An iterable object that contains the indices of frames.

Returns: A tuple of (frame indices, frames), where
* frame indices is a subset of the specified idxs, and
Expand Down
36 changes: 34 additions & 2 deletions sleap/io/visuals.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@
_sentinel = object()


def reader(out_q: Queue, video: Video, frames: List[int], scale: float = 1.0):
def reader(
out_q: Queue,
video: Video,
frames: List[int],
scale: float = 1.0,
background: str = "original",
):
"""Read frame images from video and send them into queue.

Args:
Expand All @@ -36,11 +42,13 @@ def reader(out_q: Queue, video: Video, frames: List[int], scale: float = 1.0):
video: The `Video` object to read.
frames: Full list frame indexes we want to read.
scale: Output scale for frame images.
background: output video background. Either Original, Black, White, Grey

Returns:
None.
"""

background = background.lower()
cv2.setNumThreads(usable_cpu_count())

total_count = len(frames)
Expand All @@ -64,6 +72,18 @@ def reader(out_q: Queue, video: Video, frames: List[int], scale: float = 1.0):
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'.")


if not loaded_chunk_idxs:
print(f"No frames could be loaded from chunk {chunk_i}")
Expand Down Expand Up @@ -497,6 +517,7 @@ def save_labeled_video(
fps: int = 15,
scale: float = 1.0,
crop_size_xy: Optional[Tuple[int, int]] = None,
background: str = "original",
show_edges: bool = True,
edge_is_wedge: bool = False,
marker_size: int = 4,
Expand All @@ -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
shrivaths16 marked this conversation as resolved.
Show resolved Hide resolved
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

show_edges: whether to draw lines between nodes
edge_is_wedge: whether to draw edges as wedges (draw as line if False)
marker_size: Size of marker in pixels before scaling by `scale`
Expand All @@ -537,7 +559,7 @@ def save_labeled_video(
q2 = Queue(maxsize=10)
progress_queue = Queue()

thread_read = Thread(target=reader, args=(q1, video, frames, scale))
thread_read = Thread(target=reader, args=(q1, video, frames, scale, background))
thread_mark = VideoMarkerThread(
in_q=q1,
out_q=q2,
Expand Down Expand Up @@ -695,6 +717,15 @@ def main(args: list = None):
"and 'nodes' (default: 'nodes')"
),
)
parser.add_argument(
"--background",
type=str,
default="original",
help=(
"Specify the type of background to be used to save the videos."
"Options for background: original, black, white and grey"
),
)
args = parser.parse_args(args=args)
labels = Labels.load_file(
args.data_path, video_search=[os.path.dirname(args.data_path)]
Expand Down Expand Up @@ -730,6 +761,7 @@ def main(args: list = None):
marker_size=args.marker_size,
palette=args.palette,
distinctly_color=args.distinctly_color,
background=args.background,
)

print(f"Video saved as: {filename}")
Expand Down
10 changes: 10 additions & 0 deletions tests/io/test_visuals.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ def test_serial_pipeline(centered_pair_predictions, tmpdir):
)


@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



def test_sleap_render(centered_pair_predictions):
args = (
"-o testvis.avi -f 2 --scale 1.2 --frames 1,2 --video-index 0 "
Expand Down
Loading