changeset 0:d2919cd7e314 draft default tip

planemo upload for repository https://github.com/BMCV/galaxy-image-analysis/tools/cv2 commit 15c13526f0bf7274b785e4f03e7b6ad9049e5bae
author imgteam
date Wed, 18 Mar 2026 09:15:00 +0000
parents
children
files creators.xml cv2_extract_frames.py cv2_extract_frames.xml test-data/frame_0.tiff test-data/frame_1.tiff test-data/frame_2.tiff test-data/frame_3.tiff test-data/frame_4.tiff test-data/frame_5.tiff test-data/frame_6.tiff test-data/frame_7.tiff test-data/input1.mp4 test-data/input2.avi tests.xml
diffstat 14 files changed, 407 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/creators.xml	Wed Mar 18 09:15:00 2026 +0000
@@ -0,0 +1,43 @@
+<macros>
+
+    <xml name="creators/bmcv">
+        <organization name="Biomedical Computer Vision Group, Heidelberg Universtiy" alternateName="BMCV" url="http://www.bioquant.uni-heidelberg.de/research/groups/biomedical_computer_vision.html" />
+        <yield />
+    </xml>
+
+    <xml name="creators/kostrykin">
+        <person givenName="Leonid" familyName="Kostrykin"/>
+        <yield/>
+    </xml>
+
+    <xml name="creators/rmassei">
+        <person givenName="Riccardo" familyName="Massei"/>
+        <yield/>
+    </xml>
+
+    <xml name="creators/alliecreason">
+        <person givenName="Allison" familyName="Creason"/>
+        <yield/>
+    </xml>
+
+    <xml name="creators/bugraoezdemir">
+        <person givenName="Bugra" familyName="Oezdemir"/>
+        <yield/>
+    </xml>
+
+    <xml name="creators/thawn">
+        <person givenName="Till" familyName="Korten"/>
+        <yield/>
+    </xml>
+
+    <xml name="creators/pavanvidem">
+        <person givenName="Pavan" familyName="Videm"/>
+        <yield/>
+    </xml>
+
+    <xml name="creators/tuncK">
+        <person givenName="Tunc" familyName="Kayikcioglu"/>
+        <yield/>
+    </xml>
+
+</macros>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cv2_extract_frames.py	Wed Mar 18 09:15:00 2026 +0000
@@ -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)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cv2_extract_frames.xml	Wed Mar 18 09:15:00 2026 +0000
@@ -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
+        -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"
+               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"/>
+        </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 test-data/frame_0.tiff has changed
Binary file test-data/frame_1.tiff has changed
Binary file test-data/frame_2.tiff has changed
Binary file test-data/frame_3.tiff has changed
Binary file test-data/frame_4.tiff has changed
Binary file test-data/frame_5.tiff has changed
Binary file test-data/frame_6.tiff has changed
Binary file test-data/frame_7.tiff has changed
Binary file test-data/input1.mp4 has changed
Binary file test-data/input2.avi has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests.xml	Wed Mar 18 09:15:00 2026 +0000
@@ -0,0 +1,95 @@
+<macros>
+
+    <!-- Macros for verification of image outputs -->
+
+    <xml
+        name="tests/binary_image_diff"
+        tokens="name,value,ftype,metric,eps"
+        token_metric="mae"
+        token_eps="0.01">
+
+        <output name="@NAME@" value="@VALUE@" ftype="@FTYPE@" compare="image_diff" metric="@METRIC@" eps="@EPS@" pin_labels="0">
+            <assert_contents>
+                <has_image_n_labels n="2"/>
+                <yield/>
+            </assert_contents>
+        </output>
+
+    </xml>
+
+    <xml
+        name="tests/label_image_diff"
+        tokens="name,value,ftype,metric,eps,pin_labels"
+        token_metric="iou"
+        token_eps="0.01"
+        token_pin_labels="0">
+
+        <output name="@NAME@" value="@VALUE@" ftype="@FTYPE@" compare="image_diff" metric="@METRIC@" eps="@EPS@" pin_labels="@PIN_LABELS@">
+            <assert_contents>
+                <yield/>
+            </assert_contents>
+        </output>
+
+    </xml>
+
+    <xml
+        name="tests/intensity_image_diff"
+        tokens="name,value,ftype,metric,eps"
+        token_metric="rms"
+        token_eps="0.01">
+
+        <output name="@NAME@" value="@VALUE@" ftype="@FTYPE@" compare="image_diff" metric="@METRIC@" eps="@EPS@">
+            <assert_contents>
+                <yield/>
+            </assert_contents>
+        </output>
+
+    </xml>
+
+    <!-- Variants of the above for verification of collection elements -->
+
+    <xml
+        name="tests/binary_image_diff/element"
+        tokens="name,value,ftype,metric,eps"
+        token_metric="mae"
+        token_eps="0.01">
+
+        <element name="@NAME@" value="@VALUE@" ftype="@FTYPE@" compare="image_diff" metric="@METRIC@" eps="@EPS@" pin_labels="0">
+            <assert_contents>
+                <has_image_n_labels n="2"/>
+                <yield/>
+            </assert_contents>
+        </element>
+
+    </xml>
+
+    <xml
+        name="tests/label_image_diff/element"
+        tokens="name,value,ftype,metric,eps"
+        token_metric="iou"
+        token_eps="0.01"
+        token_pin_labels="0">
+
+        <element name="@NAME@" value="@VALUE@" ftype="@FTYPE@" compare="image_diff" metric="@METRIC@" eps="@EPS@" pin_labels="@PIN_LABELS@">
+            <assert_contents>
+                <yield/>
+            </assert_contents>
+        </element>
+
+    </xml>
+
+    <xml
+        name="tests/intensity_image_diff/element"
+        tokens="name,value,ftype,metric,eps"
+        token_metric="rms"
+        token_eps="0.01">
+
+        <element name="@NAME@" value="@VALUE@" ftype="@FTYPE@" compare="image_diff" metric="@METRIC@" eps="@EPS@">
+            <assert_contents>
+                <yield/>
+            </assert_contents>
+        </element>
+
+    </xml>
+
+</macros>