Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
adf8b71
added new tools to extract frames in cv2
rmassei Feb 25, 2026
876f543
linted the python script
rmassei Feb 25, 2026
b9adc6d
corrected import order in the python script
rmassei Feb 25, 2026
b3d882f
fix: reduce video resolution to have smaller files
rmassei Feb 25, 2026
9b66e20
Apply suggestions from code review
rmassei Mar 9, 2026
97eb66b
fix: change argument to name in the XML inputs
rmassei Mar 9, 2026
164d215
feature: renamed the folder and convert the tool into a suite
rmassei Mar 9, 2026
bbede1d
fix: Revise codec support details in cv2_extract_frames.xml
rmassei Mar 9, 2026
27f675c
refactor: removed old structure
rmassei Mar 9, 2026
2395ca3
fix: corrected the linting error, sheed and xml
rmassei Mar 9, 2026
118fd36
fix: small review fixes, help section plus formatting
rmassei Mar 10, 2026
ca749df
fix: standardize the sheed structure
rmassei Mar 10, 2026
4f011e1
fix: add default value to the input parameters
rmassei Mar 10, 2026
3fc1cb3
minor code fixes for better input and error handling
rmassei Mar 11, 2026
1b4a65a
Make `end_time` optional
kostrykin Mar 11, 2026
71f96a9
Merge pull request #1 from kostrykin/dev/cv2_extract_frames
rmassei Mar 12, 2026
2baa016
Add min attribute to start_time parameter
rmassei Mar 12, 2026
905f18e
Fix XML formatting for start_time parameter
rmassei Mar 12, 2026
ded5ed4
fix: fixed the format code 'd' error
rmassei Mar 16, 2026
9b3ec26
feature: new test for endtime equal 0 AND negative
rmassei Mar 16, 2026
21b2862
fix: Update XML to use single quotes for command arguments
rmassei Mar 17, 2026
37487fe
Fix quotes in cv2_extract_frames.xml command
rmassei Mar 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions tools/cv2/.shed.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
owner: imgteam

suite:
name: suite_cv2

description: Suite of tools for wrapping the cv2 python module

long_description: |
OpenCV is a library of programming functions mainly for real-time computer vision. OpenCV is the world's biggest and open source vision library
with more the 2500 algorithms for tasks such as object detection, facial recognition, and image stitching,
The Python interface cv2 allows accessing the functionalities provided by the OpenCV library.

This tool suite provides wrappers for the cv2 Python package enabling processing of image and video in Galaxy using OpenCV.


auto_tool_repositories:
name_template: "{{ tool_id }}"
description_template: "{{ tool_name }}"

long_description: |
Enables processing image and video in Galaxy using OpenCV. Based on the cv2 Python package.

type: unrestricted

categories:
- Imaging

homepage_url: https://github.com/opencv/opencv
remote_repository_url: https://github.com/BMCV/galaxy-image-analysis/tools/cv2
1 change: 1 addition & 0 deletions tools/cv2/creators.xml
127 changes: 127 additions & 0 deletions tools/cv2/cv2_extract_frames.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import argparse
import os
from pathlib import Path
from typing import Optional, Union

import cv2


def _normalize_frame_number(
frame_count: int,
frame: int,
) -> int:
"""
Translate negative frame numbers into positives by counting from the end.

Raises:
-------
ValueError: The given frame is beyond the end of the sequence.

Returns:
--------
Integer number between 0 and num_frames - 1.
"""
if frame >= frame_count:
raise ValueError(
f"Frame {frame} is beyond the end of the sequence ({frame_count} frames).",
)
return frame % frame_count


def extract_frames(
output_dir: Union[str, Path],
video_path: Union[str, Path],
start_time: float,
end_time: Optional[float],
convert_to_grey: str = "false",
) -> Path:
"""
Extract frames from a video within a specified time range in seconds

Parameters
----------
video_path: Path to the input video file

start_time: Start time in seconds

end_time: End time in seconds

output_dir: Directory where extracted frames will be saved

convert_to_gray: Whether to convert frames to grayscale

Returns:
---------
Path to the directory containing the extracted frames.
"""

try:
video = cv2.VideoCapture(video_path)

# get the video frames per second
fps = video.get(cv2.CAP_PROP_FPS)
print('Frames per second:', fps)
# get the video total frames
frame_count = video.get(cv2.CAP_PROP_FRAME_COUNT)
print('Total frames:', frame_count)

# determine first and last frame of the sequence to extract
start_frame = _normalize_frame_number(
frame_count,
round(start_time * fps),
)
end_frame = (
_normalize_frame_number(
frame_count,
round(end_time * fps),
)
if end_time is not None
else frame_count - 1
)
if start_frame > end_frame:
raise ValueError(
f"Start frame {start_frame} is beyond the end frame {end_frame}.",
)
else:
print(
f'Starting extracting from frame {start_frame} until {end_frame}...',
)

video.set(cv2.CAP_PROP_POS_FRAMES, start_frame)
current_frame = start_frame

while current_frame <= end_frame:
ret, frame = video.read()
if not ret:
break

# Convert to single-channel grayscale
output_path = os.path.join(output_dir, f"frame_{int(current_frame):d}.tiff")
if convert_to_grey == "true":
cv2.imwrite(output_path, cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY), [cv2.IMWRITE_TIFF_COMPRESSION, 1])
else:
cv2.imwrite(output_path, frame, [cv2.IMWRITE_TIFF_COMPRESSION, 1])
current_frame += 1

video.release()

print('Extraction was successfully executed. Enjoy your frames.')

except IOError:
exit("Cannot open video file.")

except ValueError as err:
exit(err.args[0])


if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Extract frames from video files")
parser.add_argument('output_dir', help="Name of the output folder")
parser.add_argument('-v', '--video_path', required=True, help="Path to the video to convert")
parser.add_argument('-s', '--start_time', required=True, type=float, help="Start time in seconds")
parser.add_argument('-e', '--end_time', type=float, help="End time in seconds")
parser.add_argument('-c', '--convert_to_grey', required=False, type=str, help="Convert the file to grayscale")

args = parser.parse_args()

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)
142 changes: 142 additions & 0 deletions tools/cv2/cv2_extract_frames.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<tool id="cv2_extract_frames" name="Extract video frames" version="@TOOL_VERSION@+galaxy@VERSION_SUFFIX@" profile="21.05">
<description>with cv2</description>
<macros>
<token name="@TOOL_VERSION@">4.13.0</token>
<token name="@VERSION_SUFFIX@">0</token>
<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>
<import>tests.xml</import>
<import>creators.xml</import>
</macros>
<creator>
<expand macro="creators/rmassei"/>
</creator>
Comment thread
rmassei marked this conversation as resolved.
<edam_operations>
<edam_operation>operation_3443</edam_operation>
</edam_operations>
<xrefs>
<xref type="biii">opencv</xref>
</xrefs>
<requirements>
<requirement type="package" version="4.13.0">opencv</requirement>
</requirements>
<required_files>
<include type="literal" path="cv2_extract_frames.py"/>
</required_files>
<command detect_errors="aggressive">
<![CDATA[
mkdir ./output_frames &&

python '$__tool_directory__/cv2_extract_frames.py' output_frames
-v '$video_path'
-s '$start_time'
#if str($end_time) != ""
-e '$end_time'
#end if
-c '$convert_to_grey'

&& ls -l ./output_frames
]]>
</command>
<inputs>
<param name="video_path" type="data" optional="False" format="mp4,avi" label="Input video to convert"/>
<param name="start_time" type="float" value="0" optional="false" label="Start time"
Comment thread
rmassei marked this conversation as resolved.
help="Start time in seconds. @HELP_SECONDS@"/>
<param name="end_time" type="float" optional="true" label="End time"
help="End time in seconds (optional). Leave empty to extract the sequence until the end of the video. @HELP_SECONDS@"/>
<param name="convert_to_grey" type="boolean" label="Convert output to single-channel grayscale?"
help="Convert the file to grayscale (will be RGB otherwise)."/>
</inputs>
<outputs>
<collection name="frames" type="list" label="Output frames">
<discover_datasets directory="output_frames" format="tiff" pattern="__name__"/>
</collection>
</outputs>
<tests>
<test>
<!-- test MP4 w/o grayscale conversion -->
<param name="video_path" value="input1.mp4"/>
<param name="start_time" value="0"/>
<param name="end_time" value="0.1"/>
<param name="convert_to_grey" value="False"/>
<output_collection name="frames" type="list">
<expand macro="tests/intensity_image_diff/element" name="frame_0.tiff" value="frame_0.tiff" ftype="tiff"/>
<expand macro="tests/intensity_image_diff/element" name="frame_1.tiff" value="frame_1.tiff" ftype="tiff"/>
</output_collection>
</test>
<test>
<!-- test MP4 w grayscale conversion -->
<param name="video_path" value="input1.mp4"/>
<param name="start_time" value="0"/>
<param name="end_time" value="0.1"/>
<param name="convert_to_grey" value="True"/>
<output_collection name="frames" type="list">
<expand macro="tests/intensity_image_diff/element" name="frame_2.tiff" value="frame_2.tiff" ftype="tiff"/>
<expand macro="tests/intensity_image_diff/element" name="frame_3.tiff" value="frame_3.tiff" ftype="tiff"/>
</output_collection>
</test>
<test>
<!-- test AVI w/o grayscale conversion -->
<param name="video_path" value="input2.avi"/>
<param name="start_time" value="0"/>
<param name="end_time" value="1"/>
<param name="convert_to_grey" value="False"/>
<output_collection name="frames" type="list">
<expand macro="tests/intensity_image_diff/element" name="frame_4.tiff" value="frame_4.tiff" ftype="tiff"/>
<expand macro="tests/intensity_image_diff/element" name="frame_5.tiff" value="frame_5.tiff" ftype="tiff"/>
</output_collection>
</test>
<test>
<!-- test AVI w grayscale conversion -->
<param name="video_path" value="input2.avi"/>
<param name="start_time" value="0"/>
<param name="end_time" value="1"/>
<param name="convert_to_grey" value="True"/>
<output_collection name="frames" type="list">
<expand macro="tests/intensity_image_diff/element" name="frame_6.tiff" value="frame_6.tiff" ftype="tiff"/>
<expand macro="tests/intensity_image_diff/element" name="frame_7.tiff" value="frame_7.tiff" ftype="tiff"/>
</output_collection>
</test>
<test>
<!-- test AVI with endtime 0 -->
<param name="video_path" value="input2.avi"/>
<param name="start_time" value="0"/>
<param name="end_time" value="0"/>
<param name="convert_to_grey" value="True"/>
<output_collection name="frames" type="list" count="1"/>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Cool that this works!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep that's pretty neat!

</test>
<test>
<!-- test MP4 with endtime negative-->
<param name="video_path" value="input1.mp4"/>
<param name="start_time" value="0"/>
<param name="end_time" value="-1"/>
<param name="convert_to_grey" value="True"/>
<output_collection name="frames" type="list" count="238"/>
</test>
</tests>
<help>

**Extract a sequence of image frames as TIFFs from MP4 and AVI files.**

Frames captured within a specified time interval (in seconds) will be extracted from the video file as individual TIFF images.
Optionally, the extracted frames can be converted to grayscale.

**Codecs Support:**
The tool was tested with H.264 and H.265 (HEVC) video codecs.
The tool may support other codecs, though this has not been tested yet.
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.

</help>
<citations>
<citation type="bibtex">@article{opencv_library,
author = {Bradski, G.},
citeulike-article-id = {2236121},
journal = {Dr. Dobb's Journal of Software Tools},
keywords = {bibtex-import},
posted-at = {2008-01-15 19:21:54},
priority = {4},
title = {{The OpenCV Library}},
year = {2000}
}</citation>
</citations>
</tool>

Binary file added tools/cv2/test-data/frame_0.tiff
Binary file not shown.
Binary file added tools/cv2/test-data/frame_1.tiff
Binary file not shown.
Binary file added tools/cv2/test-data/frame_2.tiff
Binary file not shown.
Binary file added tools/cv2/test-data/frame_3.tiff
Binary file not shown.
Binary file added tools/cv2/test-data/frame_4.tiff
Binary file not shown.
Binary file added tools/cv2/test-data/frame_5.tiff
Binary file not shown.
Binary file added tools/cv2/test-data/frame_6.tiff
Binary file not shown.
Binary file added tools/cv2/test-data/frame_7.tiff
Binary file not shown.
Binary file added tools/cv2/test-data/input1.mp4
Binary file not shown.
Binary file added tools/cv2/test-data/input2.avi
Binary file not shown.
1 change: 1 addition & 0 deletions tools/cv2/tests.xml
Loading