changeset 0:0cb07fefbe70 draft

planemo upload for repository https://github.com/BMCV/galaxy-image-analysis/tree/master/tools/ridge_filter/ commit 85b0f6afacb8933db19e03682559cc4d71031cf1
author imgteam
date Fri, 12 Dec 2025 22:21:46 +0000
parents
children b8867c9e0526
files creators.xml filter_skimage.py skimage.xml test-data/retina_0c_0z_0t_0q.tiff test-data/retina_0c_0z_0t_0q_hessian.tiff test-data/retina_0c_0z_0t_2q.tiff test-data/retina_0c_0z_0t_2q_sato.tiff test-data/retina_0c_0z_3t_0q.tiff test-data/retina_0c_0z_3t_0q_meijering.tiff test-data/retina_0c_5z_0t_0q.tiff test-data/retina_0c_5z_0t_0q_laplace.tiff test-data/retina_frangi.tiff test-data/retina_inv_0c_0z_0t_0q.tiff test-data/retina_inv_0c_0z_0t_0q_frangi.tiff test-data/scikit-image/LICENSE.txt test-data/scikit-image/retina.png tests.xml
diffstat 17 files changed, 547 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/creators.xml	Fri Dec 12 22:21:46 2025 +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/filter_skimage.py	Fri Dec 12 22:21:46 2025 +0000
@@ -0,0 +1,111 @@
+import argparse
+import json
+from typing import (
+    Any,
+    Callable,
+)
+
+import giatools
+import numpy as np
+import skimage.filters
+
+
+filters = {
+    'frangi': lambda img, **kwargs: (
+        apply_nd_filter(skimage.filters.frangi, img, **kwargs)
+    ),
+    'hessian': lambda img, **kwargs: (
+        apply_nd_filter(skimage.filters.hessian, img, **kwargs)
+    ),
+    'laplace': lambda img, **kwargs: (
+        apply_nd_filter(skimage.filters.laplace, img, **kwargs)
+    ),
+    'meijering': lambda img, **kwargs: (
+        apply_nd_filter(skimage.filters.meijering, img, **kwargs)
+    ),
+    'sato': lambda img, **kwargs: (
+        apply_nd_filter(skimage.filters.sato, img, **kwargs)
+    ),
+}
+
+
+def apply_nd_filter(
+    filter_impl: Callable[[np.ndarray, Any, ...], np.ndarray],
+    img: giatools.Image,
+    dtype: str,
+    **kwargs: Any,
+) -> giatools.Image:
+    """
+    Apply the filter to the 2-D/3-D, potentially multi-frame and multi-channel image.
+    """
+    result_data = np.empty(img.data.shape, dtype=dtype)
+    for qtc in np.ndindex(
+        img.data.shape[ 0],  # Q axis
+        img.data.shape[ 1],  # T axis
+        img.data.shape[-1],  # C axis
+    ):
+        sl = np.s_[*qtc[:2], ..., qtc[2]]  # noqa: E999
+        arr = img.data[sl]
+        assert arr.ndim == 3  # sanity check, should always be True
+
+        # Perform 2-D or 3-D filtering
+        if arr.shape[0] == 1:
+            info = 'Performing 2-D filtering'
+            result_data[sl][0] = filter_impl(arr[0], **kwargs).astype(dtype)
+        else:
+            info = 'Performing 3-D filtering'
+            result_data[sl] = filter_impl(arr, **kwargs).astype(dtype)
+
+    # Print status info
+    print(info)
+
+    # Return results as 16bit, 32bit, or 64bit floating point
+    return giatools.Image(result_data.astype(dtype), img.axes)
+
+
+def apply_filter(
+    input_filepath: str,
+    output_filepath: str,
+    filter_type: str,
+    **kwargs: Any,
+):
+    # Validate and transform input parameters
+    params = dict(kwargs)
+    if (sigma_min := params.pop('sigma_min', None)) is not None and (sigma_max := params.pop('sigma_max', None)) is not None:
+        num_sigma = params.pop('num_sigma')
+        if sigma_min < sigma_max:
+            params['sigmas'] = np.linspace(sigma_min, sigma_max, num_sigma)
+        elif sigma_min == sigma_max:
+            params['sigmas'] = [sigma_min]
+        else:
+            raise ValueError(f'Minimum sigma ({sigma_min:g}) must not be greater than Maximum sigma ({sigma_max:g})')
+
+    # Read the input image
+    img = giatools.Image.read(input_filepath)
+
+    # Perform filtering
+    print(f'Applying filter: "{filter_type}"')
+    filter_impl = filters[filter_type]
+    res = filter_impl(img, **params).normalize_axes_like(img.original_axes)
+
+    # Adopt metadata and write the result
+    res.metadata = img.metadata
+    res.write(output_filepath, backend='tifffile')
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser()
+    parser.add_argument('input', type=str)
+    parser.add_argument('output', type=str)
+    parser.add_argument('params', type=str)
+    args = parser.parse_args()
+
+    # Read the config file
+    with open(args.params) as cfgf:
+        cfg = json.load(cfgf)
+
+    apply_filter(
+        args.input,
+        args.output,
+        **cfg,
+    )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/skimage.xml	Fri Dec 12 22:21:46 2025 +0000
@@ -0,0 +1,270 @@
+<tool id="ridge_filter_skimage" name="Apply ridge filter" version="@TOOL_VERSION@+galaxy@VERSION_SUFFIX@" profile="20.05">
+    <description>with scikit-image</description>
+    <macros>
+        <import>creators.xml</import>
+        <import>tests.xml</import>
+        <token name="@TOOL_VERSION@">0.22.0</token>
+        <token name="@VERSION_SUFFIX@">1</token>
+        <xml name="base_params">
+            <param name="black_ridges" type="select" label="Mode of operation">
+                <option value="true" selected="true">Enhance dark ridges (low image intensities)</option>
+                <option value="false">Enhance bright ridges (high image intensities)</option>
+            </param>
+            <param name="sigma_min" type="float" value="1" min="0.1" label="Minimum sigma" help="Lower bound of the scale for multi-scale analysis."/>
+            <param name="sigma_max" type="float" value="3" min="0.1" label="Maximum sigma" help="Upper bound of the scale for multi-scale analysis."/>
+            <param name="num_sigma" type="integer" value="10" min="2" label="Number of sigma steps for multi-scale analysis"/>
+        </xml>
+        <xml name="frangi_params">
+            <expand macro="base_params"/>
+            <param name="alpha" type="float" min="0" value="0.5" label="Alpha"
+                   help="Frangi correction constant that adjusts the filter's sensitivity to deviation from a plate-like structure."/>
+            <param name="beta" type="float" min="0" value="0.5" label="Beta"
+                   help="Frangi correction constant that adjusts the filter's sensitivity to deviation from a blob-like structure."/>
+        </xml>
+    </macros>
+    <creator>
+        <expand macro="creators/bmcv"/>
+        <expand macro="creators/kostrykin"/>
+    </creator>
+    <edam_operations>
+        <edam_operation>operation_3443</edam_operation>
+    </edam_operations>
+    <xrefs>
+        <xref type="bio.tools">galaxy_image_analysis</xref>
+        <xref type="bio.tools">scikit-image</xref>
+        <xref type="biii">scikit-image</xref>
+    </xrefs>
+    <requirements>
+        <requirement type="package" version="@TOOL_VERSION@">scikit-image</requirement>
+        <requirement type="package" version="0.5.2">giatools</requirement>
+    </requirements>
+    <command detect_errors="aggressive"><![CDATA[
+
+        python '$__tool_directory__/filter_skimage.py'
+
+        '$input'
+        '$output'
+        '$params'
+
+    ]]></command>
+    <configfiles>
+        <configfile name="params"><![CDATA[
+            {
+
+            #if $filter.filter_type == "frangi" or $filter.filter_type == "hessian" or $filter.filter_type == "meijering" or $filter.filter_type == "sato"
+                "black_ridges": $filter.black_ridges,
+                "sigma_min": $filter.sigma_min,
+                "sigma_max": $filter.sigma_max,
+                "num_sigma": $filter.num_sigma,
+            #end if
+
+            #if $filter.filter_type == "frangi" or $filter.filter_type == "hessian"
+                "alpha": $filter.alpha,
+                "beta": $filter.beta,
+            #end if
+
+            #if $filter.filter_type == "laplace"
+                "ksize": $filter.ksize,
+            #end if
+
+                "dtype": "$dtype",
+                "filter_type": "$filter.filter_type"
+
+            }
+        ]]></configfile>
+    </configfiles>
+    <inputs>
+        <param name="input" type="data" format="tiff,png" label="Input image"/>
+        <param name="dtype" type="select" label="Output pixel type"
+               help="Data type used to store the pixel values in the output image.">
+            <option value="float64" selected="True">64-bit floating point</option>
+            <option value="float32">32-bit floating point</option>
+            <option value="float16">16-bit floating point</option>
+        </param>
+        <conditional name="filter">
+            <param name="filter_type" type="select" label="Filter">
+                <option value="frangi" selected="True">Frangi vesselness filter</option>
+                <option value="hessian">Hybrid Hessian filter</option>
+                <option value="laplace">Laplace filter</option>
+                <option value="meijering">Meijering neuriteness filter</option>
+                <option value="sato">Sato tubeness filter</option>
+            </param>
+            <when value="frangi">
+                <expand macro="frangi_params"/>
+            </when>
+            <when value="hessian">
+                <expand macro="frangi_params"/>
+            </when>
+            <when value="laplace">
+                <param name="ksize" type="integer" value="3" label="Kernel size"
+                       help="Define the size of the discrete Laplacian operator such that it will have the size of a hypercube with this edge length (in pixels)."/>
+            </when>
+            <when value="meijering">
+                <expand macro="base_params"/>
+            </when>
+            <when value="sato">
+                <expand macro="base_params"/>
+            </when>
+        </conditional>
+    </inputs>
+    <outputs>
+       <data format="tiff" name="output"/>
+    </outputs>
+    <tests>
+        <!-- Single-image (Q), Single-frame (T), Single-slice (Z), multi-channel (C), Frangi (dark) -->
+        <test>
+            <param name="input" value="scikit-image/retina.png"/>
+            <param name="dtype" value="float16"/>
+            <conditional name="filter">
+                <param name="filter_type" value="frangi"/>
+                <param name="black_ridges" value="true"/>
+                <param name="sigma_min" value="0.5"/>
+                <param name="sigma_max" value="20"/>
+                <param name="num_sigma" value="10"/>
+                <param name="alpha" value="0.5"/>
+                <param name="beta" value="0.5"/>
+            </conditional>
+            <expand macro="tests/intensity_image_diff" name="output" value="retina_frangi.tiff" ftype="tiff"/>
+            <assert_stdout>
+                <has_line line='Applying filter: "frangi"'/>
+                <has_line line="Performing 2-D filtering"/>
+            </assert_stdout>
+        </test>
+        <!-- Single-image (Q), Single-frame (T), Single-slice (Z), single-channel (C), Hessian (dark) -->
+        <test>
+            <param name="input" value="retina_0c_0z_0t_0q.tiff"/>
+            <param name="dtype" value="float16"/>
+            <conditional name="filter">
+                <param name="filter_type" value="hessian"/>
+                <param name="black_ridges" value="true"/>
+                <param name="sigma_min" value="0.5"/>
+                <param name="sigma_max" value="20"/>
+                <param name="num_sigma" value="10"/>
+                <param name="alpha" value="0.5"/>
+                <param name="beta" value="0.5"/>
+            </conditional>
+            <expand macro="tests/intensity_image_diff" name="output" value="retina_0c_0z_0t_0q_hessian.tiff" ftype="tiff"/>
+            <assert_stdout>
+                <has_line line='Applying filter: "hessian"'/>
+                <has_line line="Performing 2-D filtering"/>
+            </assert_stdout>
+        </test>
+        <!-- Single-image (Q), Multi-frame (T), Single-slice (Z), single-channel (C), Meijering (dark) -->
+        <test>
+            <param name="input" value="retina_0c_0z_3t_0q.tiff"/>
+            <param name="dtype" value="float16"/>
+            <conditional name="filter">
+                <param name="filter_type" value="meijering"/>
+                <param name="black_ridges" value="true"/>
+                <param name="sigma_min" value="0.5"/>
+                <param name="sigma_max" value="20"/>
+                <param name="num_sigma" value="10"/>
+            </conditional>
+            <expand macro="tests/intensity_image_diff" name="output" value="retina_0c_0z_3t_0q_meijering.tiff" ftype="tiff"/>
+            <assert_stdout>
+                <has_line line='Applying filter: "meijering"'/>
+                <has_line line="Performing 2-D filtering"/>
+            </assert_stdout>
+        </test>
+        <!-- Multi-image (Q), Single-frame (T), Single-slice (Z), single-channel (C), Sato (dark) -->
+        <test>
+            <param name="input" value="retina_0c_0z_0t_2q.tiff"/>
+            <param name="dtype" value="float16"/>
+            <conditional name="filter">
+                <param name="filter_type" value="sato"/>
+                <param name="black_ridges" value="true"/>
+                <param name="sigma_min" value="0.5"/>
+                <param name="sigma_max" value="20"/>
+                <param name="num_sigma" value="10"/>
+            </conditional>
+            <expand macro="tests/intensity_image_diff" name="output" value="retina_0c_0z_0t_2q_sato.tiff" ftype="tiff"/>
+            <assert_stdout>
+                <has_line line='Applying filter: "sato"'/>
+                <has_line line="Performing 2-D filtering"/>
+            </assert_stdout>
+        </test>
+        <!-- Single-image (Q), Single-frame (T), Multi-slice (Z), single-channel (C), Laplace -->
+        <test>
+            <param name="input" value="retina_0c_5z_0t_0q.tiff"/>
+            <param name="dtype" value="float16"/>
+            <conditional name="filter">
+                <param name="filter_type" value="laplace"/>
+                <param name="ksize" value="5"/>
+            </conditional>
+            <expand macro="tests/intensity_image_diff" name="output" value="retina_0c_5z_0t_0q_laplace.tiff" ftype="tiff"/>
+            <assert_stdout>
+                <has_line line='Applying filter: "laplace"'/>
+                <has_line line="Performing 3-D filtering"/>
+            </assert_stdout>
+        </test>
+        <!-- Bright ridges, Single-image (Q), Single-frame (T), Multi-slice (Z), single-channel (C), Frangi (bright) -->
+        <test>
+            <param name="input" value="retina_inv_0c_0z_0t_0q.tiff"/>
+            <param name="dtype" value="float16"/>
+            <conditional name="filter">
+                <param name="filter_type" value="frangi"/>
+                <param name="black_ridges" value="false"/>
+                <param name="sigma_min" value="0.5"/>
+                <param name="sigma_max" value="20"/>
+                <param name="num_sigma" value="10"/>
+                <param name="alpha" value="0.5"/>
+                <param name="beta" value="0.5"/>
+            </conditional>
+            <expand macro="tests/intensity_image_diff" name="output" value="retina_inv_0c_0z_0t_0q_frangi.tiff" ftype="tiff"/>
+            <assert_stdout>
+                <has_line line='Applying filter: "frangi"'/>
+                <has_line line="Performing 2-D filtering"/>
+            </assert_stdout>
+        </test>
+    </tests>
+    <help>
+
+**Applies a ridge filter to an image.**
+
+Ridge filters can be used to detect ridge-like structures, such as neurites, tubes, vessels, wrinkles, or rivers. Different
+ridge filters may be suited for detecting different structures, e.g., depending on contrast or noise level. 2-D and 3-D
+images are supported. For multi-channel images, the filter is applied to all channels of the image. For time-series images,
+the filter is also applied for all time steps.
+
+Frangi vesselness filter
+========================
+
+Filter an image with the Frangi vesselness filter. This filter can be used to detect continuous ridges, e.g. vessels,
+wrinkles, rivers. It can be used to calculate the fraction of the whole image containing such objects. Calculates the
+eigenvalues of the Hessian to compute the similarity of an image region to vessels, according to the method described in
+Frangi et al. (1998).
+
+Hybrid Hessian filter 
+=====================
+
+Filter an image with the Hybrid Hessian filter (Schrijver 2001, Kroon 2009). This filter can be used to detect continuous
+edges, e.g. vessels, wrinkles, rivers. It can be used to calculate the fraction of the whole image containing such objects.
+Almost equal to Frangi filter, but uses alternative method of smoothing.
+
+Laplace filter
+==============
+
+Find the edges of an image using the Laplace operator.
+
+Meijering neuriteness filter
+============================
+
+Filter an image with the Meijering neuriteness filter. This filter can be used to detect continuous ridges, e.g. neurites,
+wrinkles, rivers. It can be used to calculate the fraction of the whole image containing such objects. Calculates the
+eigenvalues of the Hessian to compute the similarity of an image region to neurites, according to the method described in
+Meijering et al. (2004).
+
+Sato tubeness filter
+====================
+
+Filter an image with the Sato tubeness filter. This filter can be used to detect continuous ridges, e.g. tubes, wrinkles,
+rivers. It can be used to calculate the fraction of the whole image containing such objects. Calculates the eigenvalues of
+the Hessian to compute the similarity of an image region to tubes, according to the method described in Sato et al. (1998).
+
+    </help>
+    <citations>
+       <citation type="doi">10.1007/BFb0056195</citation>
+       <citation type="doi">10.1002/cyto.a.20022</citation>
+       <citation type="doi">10.1016/S1361-8415(98)80009-1</citation>
+    </citations>
+</tool>
Binary file test-data/retina_0c_0z_0t_0q.tiff has changed
Binary file test-data/retina_0c_0z_0t_0q_hessian.tiff has changed
Binary file test-data/retina_0c_0z_0t_2q.tiff has changed
Binary file test-data/retina_0c_0z_0t_2q_sato.tiff has changed
Binary file test-data/retina_0c_0z_3t_0q.tiff has changed
Binary file test-data/retina_0c_0z_3t_0q_meijering.tiff has changed
Binary file test-data/retina_0c_5z_0t_0q.tiff has changed
Binary file test-data/retina_0c_5z_0t_0q_laplace.tiff has changed
Binary file test-data/retina_frangi.tiff has changed
Binary file test-data/retina_inv_0c_0z_0t_0q.tiff has changed
Binary file test-data/retina_inv_0c_0z_0t_0q_frangi.tiff has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/scikit-image/LICENSE.txt	Fri Dec 12 22:21:46 2025 +0000
@@ -0,0 +1,28 @@
+Files: *
+Copyright: 2009-2022 the scikit-image team
+License: BSD-3-Clause
+
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. Neither the name of the University nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+.
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE HOLDERS OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Binary file test-data/scikit-image/retina.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests.xml	Fri Dec 12 22:21:46 2025 +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>