Skip to content

Commit 15c1352

Browse files
rmasseikostrykin
andauthored
New Tool: Extract frames with CV2 (#207)
* added new tools to extract frames in cv2 * linted the python script * corrected import order in the python script * fix: reduce video resolution to have smaller files * Apply suggestions from code review Co-authored-by: Leonid Kostrykin <void@evoid.de> * fix: change argument to name in the XML inputs * feature: renamed the folder and convert the tool into a suite * fix: Revise codec support details in cv2_extract_frames.xml Updated codec support information and added feedback request. * refactor: removed old structure * fix: corrected the linting error, sheed and xml * fix: small review fixes, help section plus formatting Co-authored-by: Leonid Kostrykin <void@evoid.de> * fix: standardize the sheed structure * fix: add default value to the input parameters * minor code fixes for better input and error handling Co-authored-by: Leonid Kostrykin <void@evoid.de> * Make `end_time` optional * Add min attribute to start_time parameter * Fix XML formatting for start_time parameter * fix: fixed the format code 'd' error * feature: new test for endtime equal 0 AND negative * fix: Update XML to use single quotes for command arguments * Fix quotes in cv2_extract_frames.xml command --------- Co-authored-by: Leonid Kostrykin <void@evoid.de>
1 parent 5ca3026 commit 15c1352

15 files changed

Lines changed: 300 additions & 0 deletions

tools/cv2/.shed.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
owner: imgteam
2+
3+
suite:
4+
name: suite_cv2
5+
6+
description: Suite of tools for wrapping the cv2 python module
7+
8+
long_description: |
9+
OpenCV is a library of programming functions mainly for real-time computer vision. OpenCV is the world's biggest and open source vision library
10+
with more the 2500 algorithms for tasks such as object detection, facial recognition, and image stitching,
11+
The Python interface cv2 allows accessing the functionalities provided by the OpenCV library.
12+
13+
This tool suite provides wrappers for the cv2 Python package enabling processing of image and video in Galaxy using OpenCV.
14+
15+
16+
auto_tool_repositories:
17+
name_template: "{{ tool_id }}"
18+
description_template: "{{ tool_name }}"
19+
20+
long_description: |
21+
Enables processing image and video in Galaxy using OpenCV. Based on the cv2 Python package.
22+
23+
type: unrestricted
24+
25+
categories:
26+
- Imaging
27+
28+
homepage_url: https://github.com/opencv/opencv
29+
remote_repository_url: https://github.com/BMCV/galaxy-image-analysis/tools/cv2

tools/cv2/creators.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../macros/creators.xml

tools/cv2/cv2_extract_frames.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import argparse
2+
import os
3+
from pathlib import Path
4+
from typing import Optional, Union
5+
6+
import cv2
7+
8+
9+
def _normalize_frame_number(
10+
frame_count: int,
11+
frame: int,
12+
) -> int:
13+
"""
14+
Translate negative frame numbers into positives by counting from the end.
15+
16+
Raises:
17+
-------
18+
ValueError: The given frame is beyond the end of the sequence.
19+
20+
Returns:
21+
--------
22+
Integer number between 0 and num_frames - 1.
23+
"""
24+
if frame >= frame_count:
25+
raise ValueError(
26+
f"Frame {frame} is beyond the end of the sequence ({frame_count} frames).",
27+
)
28+
return frame % frame_count
29+
30+
31+
def extract_frames(
32+
output_dir: Union[str, Path],
33+
video_path: Union[str, Path],
34+
start_time: float,
35+
end_time: Optional[float],
36+
convert_to_grey: str = "false",
37+
) -> Path:
38+
"""
39+
Extract frames from a video within a specified time range in seconds
40+
41+
Parameters
42+
----------
43+
video_path: Path to the input video file
44+
45+
start_time: Start time in seconds
46+
47+
end_time: End time in seconds
48+
49+
output_dir: Directory where extracted frames will be saved
50+
51+
convert_to_gray: Whether to convert frames to grayscale
52+
53+
Returns:
54+
---------
55+
Path to the directory containing the extracted frames.
56+
"""
57+
58+
try:
59+
video = cv2.VideoCapture(video_path)
60+
61+
# get the video frames per second
62+
fps = video.get(cv2.CAP_PROP_FPS)
63+
print('Frames per second:', fps)
64+
# get the video total frames
65+
frame_count = video.get(cv2.CAP_PROP_FRAME_COUNT)
66+
print('Total frames:', frame_count)
67+
68+
# determine first and last frame of the sequence to extract
69+
start_frame = _normalize_frame_number(
70+
frame_count,
71+
round(start_time * fps),
72+
)
73+
end_frame = (
74+
_normalize_frame_number(
75+
frame_count,
76+
round(end_time * fps),
77+
)
78+
if end_time is not None
79+
else frame_count - 1
80+
)
81+
if start_frame > end_frame:
82+
raise ValueError(
83+
f"Start frame {start_frame} is beyond the end frame {end_frame}.",
84+
)
85+
else:
86+
print(
87+
f'Starting extracting from frame {start_frame} until {end_frame}...',
88+
)
89+
90+
video.set(cv2.CAP_PROP_POS_FRAMES, start_frame)
91+
current_frame = start_frame
92+
93+
while current_frame <= end_frame:
94+
ret, frame = video.read()
95+
if not ret:
96+
break
97+
98+
# Convert to single-channel grayscale
99+
output_path = os.path.join(output_dir, f"frame_{int(current_frame):d}.tiff")
100+
if convert_to_grey == "true":
101+
cv2.imwrite(output_path, cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY), [cv2.IMWRITE_TIFF_COMPRESSION, 1])
102+
else:
103+
cv2.imwrite(output_path, frame, [cv2.IMWRITE_TIFF_COMPRESSION, 1])
104+
current_frame += 1
105+
106+
video.release()
107+
108+
print('Extraction was successfully executed. Enjoy your frames.')
109+
110+
except IOError:
111+
exit("Cannot open video file.")
112+
113+
except ValueError as err:
114+
exit(err.args[0])
115+
116+
117+
if __name__ == "__main__":
118+
parser = argparse.ArgumentParser(description="Extract frames from video files")
119+
parser.add_argument('output_dir', help="Name of the output folder")
120+
parser.add_argument('-v', '--video_path', required=True, help="Path to the video to convert")
121+
parser.add_argument('-s', '--start_time', required=True, type=float, help="Start time in seconds")
122+
parser.add_argument('-e', '--end_time', type=float, help="End time in seconds")
123+
parser.add_argument('-c', '--convert_to_grey', required=False, type=str, help="Convert the file to grayscale")
124+
125+
args = parser.parse_args()
126+
127+
extract_frames(args.output_dir, video_path=args.video_path, start_time=args.start_time, end_time=args.end_time, convert_to_grey=args.convert_to_grey)

tools/cv2/cv2_extract_frames.xml

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<tool id="cv2_extract_frames" name="Extract video frames" version="@TOOL_VERSION@+galaxy@VERSION_SUFFIX@" profile="21.05">
2+
<description>with cv2</description>
3+
<macros>
4+
<token name="@TOOL_VERSION@">4.13.0</token>
5+
<token name="@VERSION_SUFFIX@">0</token>
6+
<token name="@HELP_SECONDS@">Use a negative value to specify the time from the end of the video (e.g., a value of -1 corresponds to "1 second before the end of the video").</token>
7+
<import>tests.xml</import>
8+
<import>creators.xml</import>
9+
</macros>
10+
<creator>
11+
<expand macro="creators/rmassei"/>
12+
</creator>
13+
<edam_operations>
14+
<edam_operation>operation_3443</edam_operation>
15+
</edam_operations>
16+
<xrefs>
17+
<xref type="biii">opencv</xref>
18+
</xrefs>
19+
<requirements>
20+
<requirement type="package" version="4.13.0">opencv</requirement>
21+
</requirements>
22+
<required_files>
23+
<include type="literal" path="cv2_extract_frames.py"/>
24+
</required_files>
25+
<command detect_errors="aggressive">
26+
<![CDATA[
27+
mkdir ./output_frames &&
28+
29+
python '$__tool_directory__/cv2_extract_frames.py' output_frames
30+
-v '$video_path'
31+
-s '$start_time'
32+
#if str($end_time) != ""
33+
-e '$end_time'
34+
#end if
35+
-c '$convert_to_grey'
36+
37+
&& ls -l ./output_frames
38+
]]>
39+
</command>
40+
<inputs>
41+
<param name="video_path" type="data" optional="False" format="mp4,avi" label="Input video to convert"/>
42+
<param name="start_time" type="float" value="0" optional="false" label="Start time"
43+
help="Start time in seconds. @HELP_SECONDS@"/>
44+
<param name="end_time" type="float" optional="true" label="End time"
45+
help="End time in seconds (optional). Leave empty to extract the sequence until the end of the video. @HELP_SECONDS@"/>
46+
<param name="convert_to_grey" type="boolean" label="Convert output to single-channel grayscale?"
47+
help="Convert the file to grayscale (will be RGB otherwise)."/>
48+
</inputs>
49+
<outputs>
50+
<collection name="frames" type="list" label="Output frames">
51+
<discover_datasets directory="output_frames" format="tiff" pattern="__name__"/>
52+
</collection>
53+
</outputs>
54+
<tests>
55+
<test>
56+
<!-- test MP4 w/o grayscale conversion -->
57+
<param name="video_path" value="input1.mp4"/>
58+
<param name="start_time" value="0"/>
59+
<param name="end_time" value="0.1"/>
60+
<param name="convert_to_grey" value="False"/>
61+
<output_collection name="frames" type="list">
62+
<expand macro="tests/intensity_image_diff/element" name="frame_0.tiff" value="frame_0.tiff" ftype="tiff"/>
63+
<expand macro="tests/intensity_image_diff/element" name="frame_1.tiff" value="frame_1.tiff" ftype="tiff"/>
64+
</output_collection>
65+
</test>
66+
<test>
67+
<!-- test MP4 w grayscale conversion -->
68+
<param name="video_path" value="input1.mp4"/>
69+
<param name="start_time" value="0"/>
70+
<param name="end_time" value="0.1"/>
71+
<param name="convert_to_grey" value="True"/>
72+
<output_collection name="frames" type="list">
73+
<expand macro="tests/intensity_image_diff/element" name="frame_2.tiff" value="frame_2.tiff" ftype="tiff"/>
74+
<expand macro="tests/intensity_image_diff/element" name="frame_3.tiff" value="frame_3.tiff" ftype="tiff"/>
75+
</output_collection>
76+
</test>
77+
<test>
78+
<!-- test AVI w/o grayscale conversion -->
79+
<param name="video_path" value="input2.avi"/>
80+
<param name="start_time" value="0"/>
81+
<param name="end_time" value="1"/>
82+
<param name="convert_to_grey" value="False"/>
83+
<output_collection name="frames" type="list">
84+
<expand macro="tests/intensity_image_diff/element" name="frame_4.tiff" value="frame_4.tiff" ftype="tiff"/>
85+
<expand macro="tests/intensity_image_diff/element" name="frame_5.tiff" value="frame_5.tiff" ftype="tiff"/>
86+
</output_collection>
87+
</test>
88+
<test>
89+
<!-- test AVI w grayscale conversion -->
90+
<param name="video_path" value="input2.avi"/>
91+
<param name="start_time" value="0"/>
92+
<param name="end_time" value="1"/>
93+
<param name="convert_to_grey" value="True"/>
94+
<output_collection name="frames" type="list">
95+
<expand macro="tests/intensity_image_diff/element" name="frame_6.tiff" value="frame_6.tiff" ftype="tiff"/>
96+
<expand macro="tests/intensity_image_diff/element" name="frame_7.tiff" value="frame_7.tiff" ftype="tiff"/>
97+
</output_collection>
98+
</test>
99+
<test>
100+
<!-- test AVI with endtime 0 -->
101+
<param name="video_path" value="input2.avi"/>
102+
<param name="start_time" value="0"/>
103+
<param name="end_time" value="0"/>
104+
<param name="convert_to_grey" value="True"/>
105+
<output_collection name="frames" type="list" count="1"/>
106+
</test>
107+
<test>
108+
<!-- test MP4 with endtime negative-->
109+
<param name="video_path" value="input1.mp4"/>
110+
<param name="start_time" value="0"/>
111+
<param name="end_time" value="-1"/>
112+
<param name="convert_to_grey" value="True"/>
113+
<output_collection name="frames" type="list" count="238"/>
114+
</test>
115+
</tests>
116+
<help>
117+
118+
**Extract a sequence of image frames as TIFFs from MP4 and AVI files.**
119+
120+
Frames captured within a specified time interval (in seconds) will be extracted from the video file as individual TIFF images.
121+
Optionally, the extracted frames can be converted to grayscale.
122+
123+
**Codecs Support:**
124+
The tool was tested with H.264 and H.265 (HEVC) video codecs.
125+
The tool may support other codecs, though this has not been tested yet.
126+
If you have successfully converted files using alternative video formats or codecs, we would be grateful for your feedback at https://github.com/BMCV/galaxy-image-analysis.
127+
128+
</help>
129+
<citations>
130+
<citation type="bibtex">@article{opencv_library,
131+
author = {Bradski, G.},
132+
citeulike-article-id = {2236121},
133+
journal = {Dr. Dobb's Journal of Software Tools},
134+
keywords = {bibtex-import},
135+
posted-at = {2008-01-15 19:21:54},
136+
priority = {4},
137+
title = {{The OpenCV Library}},
138+
year = {2000}
139+
}</citation>
140+
</citations>
141+
</tool>
142+

tools/cv2/test-data/frame_0.tiff

698 KB
Binary file not shown.

tools/cv2/test-data/frame_1.tiff

698 KB
Binary file not shown.

tools/cv2/test-data/frame_2.tiff

233 KB
Binary file not shown.

tools/cv2/test-data/frame_3.tiff

233 KB
Binary file not shown.

tools/cv2/test-data/frame_4.tiff

676 KB
Binary file not shown.

tools/cv2/test-data/frame_5.tiff

676 KB
Binary file not shown.

0 commit comments

Comments
 (0)