-
Notifications
You must be signed in to change notification settings - Fork 21
New Tool: Extract frames with CV2 #207
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
Changes from 20 commits
adf8b71
876f543
b9adc6d
b3d882f
9b66e20
97eb66b
164d215
bbede1d
27f675c
2395ca3
118fd36
ca749df
4f011e1
3fc1cb3
1b4a65a
71f96a9
2baa016
905f18e
ded5ed4
9b3ec26
21b2862
37487fe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ../../macros/creators.xml |
| 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) |
| 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> | ||||||
| <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 | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please use single-quotes |
||||||
| -v $video_path | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| -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" | ||||||
|
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"/> | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cool that this works!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ../../macros/tests.xml |
Uh oh!
There was an error while loading. Please reload this page.