changeset 2:b2d9c92bc431 draft

planemo upload for repository https://github.com/BMCV/galaxy-image-analysis/tree/master/tools/2d_simple_filter/ commit a6fd77be465068f709a71d377900da99becf94d8
author imgteam
date Fri, 12 Dec 2025 21:18:04 +0000
parents e2542d0ac64f
children 53c55776a974
files creators.xml filter.py filter.xml filter_image.py test-data/input1_gaussian.tiff test-data/input1_median.tiff test-data/input1_prewitt_h.tiff test-data/input1_prewitt_v.tiff test-data/input1_sobel_h.tiff test-data/input1_sobel_v.tiff test-data/input1_uint8.tiff test-data/input2_float.tiff test-data/input2_gaussian.tiff test-data/input2_uniform.tiff test-data/res.tif test-data/retina_gaussian_order0.tiff test-data/retina_gaussian_order2_axis0.tiff test-data/sample.tif test-data/scikit-image/LICENSE.txt test-data/scikit-image/retina.png tests.xml
diffstat 21 files changed, 511 insertions(+), 57 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/creators.xml	Fri Dec 12 21:18:04 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.py	Fri Dec 12 21:18:04 2025 +0000
@@ -0,0 +1,110 @@
+import argparse
+import json
+from typing import (
+    Any,
+    Callable,
+)
+
+import giatools
+import numpy as np
+import scipy.ndimage as ndi
+from skimage.morphology import disk
+
+
+def image_astype(img: giatools.Image, dtype: np.dtype) -> giatools.Image:
+    return giatools.Image(
+        data=img.data.astype(dtype),
+        axes=img.axes,
+        original_axes=img.original_axes,
+        metadata=img.metadata,
+    )
+
+
+filters = {
+    'gaussian': lambda img, sigma, order=0, axis=None: (
+        apply_2d_filter(
+            ndi.gaussian_filter,
+            img if order == 0 else image_astype(img, float),
+            sigma=sigma,
+            order=order,
+            axes=axis,
+        )
+    ),
+    'uniform': lambda img, size: (
+        apply_2d_filter(ndi.uniform_filter, img, size=size)
+    ),
+    'median': lambda img, radius: (
+        apply_2d_filter(ndi.median_filter, img, footprint=disk(radius))
+    ),
+    'prewitt': lambda img, axis: (
+        apply_2d_filter(ndi.prewitt, img, axis=axis)
+    ),
+    'sobel': lambda img, axis: (
+        apply_2d_filter(ndi.sobel, img, axis=axis)
+    ),
+}
+
+
+def apply_2d_filter(
+    filter_impl: Callable[[np.ndarray, Any, ...], np.ndarray],
+    img: giatools.Image,
+    **kwargs: Any,
+) -> giatools.Image:
+    """
+    Apply the 2-D filter to the 2-D/3-D, potentially multi-frame and multi-channel image.
+    """
+    result_data = None
+    for qtzc in np.ndindex(
+        img.data.shape[ 0],  # Q axis
+        img.data.shape[ 1],  # T axis
+        img.data.shape[ 2],  # Z axis
+        img.data.shape[-1],  # C axis
+    ):
+        sl = np.s_[*qtzc[:3], ..., qtzc[3]]  # noqa: E999
+        arr = img.data[sl]
+        assert arr.ndim == 2  # sanity check, should always be True
+
+        # Perform 2-D filtering
+        res = filter_impl(arr, **kwargs)
+        if result_data is None:
+            result_data = np.empty(img.data.shape, res.dtype)
+        result_data[sl] = res
+
+    # Return results
+    return giatools.Image(result_data, img.axes)
+
+
+def apply_filter(
+    input_filepath: str,
+    output_filepath: str,
+    filter_type: str,
+    **kwargs: Any,
+):
+    # Read the input image
+    img = giatools.Image.read(input_filepath)
+
+    # Perform filtering
+    filter_impl = filters[filter_type]
+    res = filter_impl(img, **kwargs).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, help='Input image filepath')
+    parser.add_argument('output', type=str, help='Output image filepath (TIFF)')
+    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,
+    )
--- a/filter.xml	Thu Jul 18 08:51:37 2019 -0400
+++ b/filter.xml	Fri Dec 12 21:18:04 2025 +0000
@@ -1,42 +1,249 @@
-<tool id="ip_filter_standard" name="Filter Image" version="0.0.3">
-    <description>applies a standard filter to an image</description>
+<tool id="ip_filter_standard" name="Apply 2-D image filter" version="@TOOL_VERSION@+galaxy@VERSION_SUFFIX@" profile="20.05">
+    <description>with scipy</description>
+    <macros>
+        <import>creators.xml</import>
+        <import>tests.xml</import>
+        <token name="@TOOL_VERSION@">1.16.3</token>
+        <token name="@VERSION_SUFFIX@">0</token>
+        <xml name="select_axis">
+            <param name="axis" type="select" label="Direction">
+                <option value="1" selected="true">Horizontal</option>
+                <option value="0">Vertical</option>
+            </param>
+        </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="biii">scipy</xref>
+    </xrefs>
     <requirements>
-        <requirement type="package" version="0.14.2">scikit-image</requirement>
-        <requirement type="package" version="1.15.4">numpy</requirement>
-        <requirement type="package" version="5.3.0">pillow</requirement>
-        <requirement type="package" version="0.15.1">tifffile</requirement>
+        <requirement type="package" version="@TOOL_VERSION@">scipy</requirement>
+        <requirement type="package" version="2.3.5">numpy</requirement>
+        <requirement type="package" version="0.25.2">scikit-image</requirement>
+        <requirement type="package" version="2025.10.16">tifffile</requirement>
+        <requirement type="package" version="0.5.2">giatools</requirement>
     </requirements>
-    <command detect_errors="aggressive">
-      <![CDATA[
-        python '$__tool_directory__/filter_image.py' '$input' '$output' $filter_type $radius
-      ]]>
-    </command>
+    <command detect_errors="aggressive"><![CDATA[
+
+        python '$__tool_directory__/filter.py'
+
+        '$input'
+        '$output'
+        '$params'
+
+    ]]></command>
+    <configfiles>
+        <configfile name="params"><![CDATA[
+            {
+
+            ## =====================================================================
+            #if $filter.filter_type == "gaussian"
+                "sigma": $filter.sigma,
+                "order": $filter.derivative.order,
+
+                #if $filter.derivative.order != "0"
+                    "axis": $filter.derivative.axis,
+                #end if
+
+            ## =====================================================================
+            #elif $filter.filter_type == "uniform"
+                "size": $filter.size,
+
+            ## =====================================================================
+            #elif $filter.filter_type == "median"
+                "radius": $filter.radius,
+
+            ## =====================================================================
+            #elif $filter.filter_type == "prewitt" or $filter.filter_type == "sobel"
+                "axis": $filter.axis,
+
+            ## =====================================================================
+            #end if
+
+                "filter_type": "$filter.filter_type"
+
+            }
+        ]]></configfile>
+    </configfiles>
     <inputs>
-        <param name="filter_type" type="select" label="Image type">
-          <option value="median" selected="True">Median</option>
-          <option value="gaussian">Gaussian Blur</option>
-          <option value="prewitt">Prewitt</option>
-          <option value="sobel">Sobel</option>
-          <option value="scharr">Scharr</option>
-        </param>
-        <param name="radius" type="integer" value="3" label="Radius/Sigma" />
-        <param name="input" type="data" format="tiff" label="Source file" />
+        <param name="input" type="data" format="tiff,png" label="Input image"/>
+        <conditional name="filter">
+            <param name="filter_type" type="select" label="Filter type">
+                <option value="gaussian" selected="True">Gaussian</option>
+                <option value="uniform">Box filter (uniform filter)</option>
+                <option value="median">Median</option>
+                <option value="prewitt">Prewitt</option>
+                <option value="sobel">Sobel</option>
+            </param>
+            <when value="gaussian">
+                <param name="sigma" type="float" value="3" min="0.1" label="Sigma"
+                       help="The half width of the Gaussian bell (in pixels)."/>
+                <conditional name="derivative">
+                    <param name="order" type="select" label="Use a derivative?">
+                        <option value="0">No derivative (mean filter)</option>
+                        <option value="1">1st-order derivative</option>
+                        <option value="2">2nd-order derivative</option>
+                    </param>
+                    <when value="0">
+                    </when>
+                    <when value="1">
+                        <expand macro="select_axis"/>
+                    </when>
+                    <when value="2">
+                        <expand macro="select_axis"/>
+                    </when>
+                </conditional>
+            </when>
+            <when value="uniform">
+                <param name="size" type="integer" min="2" value="3" label="Size"
+                       help="Edge length of the neighborhood (square, in pixels)."/>
+            </when>
+            <when value="median">
+                <param name="radius" type="integer" min="2" value="3" label="Radius"
+                       help="Radius of the neighborhood (circle, in pixels)." />
+            </when>
+            <when value="prewitt">
+                <expand macro="select_axis"/>
+            </when>
+            <when value="sobel">
+                <expand macro="select_axis"/>
+            </when>
+        </conditional>
     </inputs>
     <outputs>
        <data format="tiff" name="output" />
     </outputs>
     <tests>
-      <test>
-        <param name="input" value="sample.tif" />
-        <output name="output" value="res.tif" ftype="tiff" compare="sim_size"/>
-        <param name="filter_type" value="prewitt" />
-        <param name="radius" value="4" />
-      </test>
+        <!-- Tests with uint8 TIFF input image -->
+        <test>
+            <param name="input" value="input1_uint8.tiff"/>
+            <conditional name="filter">
+                <param name="filter_type" value="gaussian"/>
+            </conditional>
+            <expand macro="tests/intensity_image_diff" name="output" value="input1_gaussian.tiff" ftype="tiff">
+                <!--
+
+                The input file `input1_uint8.tiff` has values ranging between 23 and 254, with a mean value of 63.67.
+
+                Below, we use an assertion in addition to the `image_diff` comparison, to ensure that the range of
+                values is preserved. The motiviation behind this is that the expectation images are usually checked
+                visually, which means that the `image_diff` comparison is likely to ensure that the brightness of
+                the image is correct, thus it's good to double-check the range of values (hence the comparably large
+                value for `eps`). This also concerns the median filter.
+
+                -->
+                <has_image_mean_intensity mean_intensity="63.67" eps="10"/>
+            </expand>
+        </test>
+        <test>
+            <param name="input" value="input1_uint8.tiff"/>
+            <conditional name="filter">
+                <param name="filter_type" value="median"/>
+            </conditional>
+            <expand macro="tests/intensity_image_diff" name="output" value="input1_median.tiff" ftype="tiff">
+                <!-- See note for Gaussian filter above. -->
+                <has_image_mean_intensity mean_intensity="63.67" eps="10"/>
+            </expand>
+        </test>
+        <test>
+            <param name="input" value="input1_uint8.tiff"/>
+            <conditional name="filter">
+                <param name="filter_type" value="prewitt"/>
+                <param name="axis" value="1"/>
+            </conditional>
+            <expand macro="tests/intensity_image_diff" name="output" value="input1_prewitt_h.tiff" ftype="tiff"/>
+        </test>
+        <test>
+            <param name="input" value="input1_uint8.tiff"/>
+            <conditional name="filter">
+                <param name="filter_type" value="prewitt"/>
+                <param name="axis" value="0"/>
+            </conditional>
+            <expand macro="tests/intensity_image_diff" name="output" value="input1_prewitt_v.tiff" ftype="tiff"/>
+        </test>
+        <test>
+            <param name="input" value="input1_uint8.tiff"/>
+            <conditional name="filter">
+                <param name="filter_type" value="sobel"/>
+                <param name="axis" value="1"/>
+            </conditional>
+            <expand macro="tests/intensity_image_diff" name="output" value="input1_sobel_h.tiff" ftype="tiff"/>
+        </test>
+        <test>
+            <param name="input" value="input1_uint8.tiff"/>
+            <conditional name="filter">
+                <param name="filter_type" value="sobel"/>
+                <param name="axis" value="0"/>
+            </conditional>
+            <expand macro="tests/intensity_image_diff" name="output" value="input1_sobel_v.tiff" ftype="tiff"/>
+        </test>
+        <!-- Tests with float TIFF input image -->
+        <test>
+            <param name="input" value="input2_float.tiff"/>
+            <conditional name="filter">
+                <param name="filter_type" value="gaussian"/>
+            </conditional>
+            <expand macro="tests/intensity_image_diff" name="output" value="input2_gaussian.tiff" ftype="tiff">
+                <!-- See note for Gaussian filter above. -->
+                <has_image_mean_intensity mean_intensity="0.25" eps="0.01"/>
+            </expand>
+        </test>
+        <test>
+            <param name="input" value="input2_float.tiff"/>
+            <conditional name="filter">
+                <param name="filter_type" value="uniform"/>
+                <param name="size" value="4"/>
+            </conditional>
+            <expand macro="tests/intensity_image_diff" name="output" value="input2_uniform.tiff" ftype="tiff">
+                <!-- See note for Gaussian filter above. -->
+                <has_image_mean_intensity mean_intensity="0.25" eps="0.01"/>
+            </expand>
+        </test>
+        <!-- Tests with multi-channel image (RGB) -->
+        <test>
+            <param name="input" value="scikit-image/retina.png"/>
+            <conditional name="filter">
+                <param name="filter_type" value="gaussian"/>
+                <param name="sigma" value="5"/>
+                <conditional name="derivative">
+                    <param name="order" value="0"/>
+                </conditional>
+            </conditional>
+            <expand macro="tests/intensity_image_diff" name="output" value="retina_gaussian_order0.tiff" ftype="tiff"/>
+        </test>
+        <test>
+            <param name="input" value="scikit-image/retina.png"/>
+            <conditional name="filter">
+                <param name="filter_type" value="gaussian"/>
+                <param name="sigma" value="3"/>
+                <conditional name="derivative">
+                    <param name="order" value="2"/>
+                    <param name="axis" value="0"/>
+                </conditional>
+            </conditional>
+            <expand macro="tests/intensity_image_diff" name="output" value="retina_gaussian_order2_axis0.tiff" ftype="tiff"/>
+        </test>
     </tests>
     <help>
-    **What it does**
+
+        **Applies a standard, general-purpose 2-D filter to an image.**
+
+        Support for different image types:
 
-    Applies a standard filter to an image.
+        - For 3-D images, the filter is applied to all z-slices of the image.
+        - 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.
+
+        Mean filters like the Gaussian filter, the box filter, or the median filter preserve both the brightness of the image, and
+        the range of values. This does not hold for the derivative variants of the Gaussian filter, which may produce negative values.
+
     </help>
     <citations>
        <citation type="doi">10.1016/j.jbiotec.2017.07.019</citation>
--- a/filter_image.py	Thu Jul 18 08:51:37 2019 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-import argparse
-import sys
-import warnings
-import numpy as np
-import skimage.io
-import skimage.filters
-import skimage.util
-from skimage.morphology import disk
-from skimage import img_as_uint
-
-filterOptions = {
-    'median' : lambda img_raw, radius: skimage.filters.median(img_raw, disk(radius)),
-    'gaussian' : lambda img_raw, radius: skimage.filters.gaussian(img_raw, sigma=radius),
-    'prewitt' : lambda img_raw, radius: skimage.filters.prewitt(img_raw),
-    'sobel' : lambda img_raw, radius: skimage.filters.sobel(img_raw),
-    'scharr' : lambda img_raw, radius: skimage.filters.scharr(img_raw),
-}
-
-if __name__ == "__main__":
-    parser = argparse.ArgumentParser()
-    parser.add_argument('input_file', type=argparse.FileType('r'), default=sys.stdin, help='input file')
-    parser.add_argument('out_file', type=argparse.FileType('w'), default=sys.stdin, help='out file (TIFF)')
-    parser.add_argument('filter_type', choices=filterOptions.keys(), help='conversion type')
-    parser.add_argument('radius', default=3.0, type=float, help='Radius/Sigma')
-    args = parser.parse_args()
-
-    img_in = skimage.io.imread(args.input_file.name)
-    res = img_as_uint(filterOptions[args.filter_type](img_in, args.radius))
-    skimage.io.imsave(args.out_file.name, res, plugin='tifffile')
Binary file test-data/input1_gaussian.tiff has changed
Binary file test-data/input1_median.tiff has changed
Binary file test-data/input1_prewitt_h.tiff has changed
Binary file test-data/input1_prewitt_v.tiff has changed
Binary file test-data/input1_sobel_h.tiff has changed
Binary file test-data/input1_sobel_v.tiff has changed
Binary file test-data/input1_uint8.tiff has changed
Binary file test-data/input2_float.tiff has changed
Binary file test-data/input2_gaussian.tiff has changed
Binary file test-data/input2_uniform.tiff has changed
Binary file test-data/res.tif has changed
Binary file test-data/retina_gaussian_order0.tiff has changed
Binary file test-data/retina_gaussian_order2_axis0.tiff has changed
Binary file test-data/sample.tif has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/scikit-image/LICENSE.txt	Fri Dec 12 21:18:04 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 21:18:04 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>