Mercurial > repos > imgteam > 2d_simple_filter
changeset 5:1b7e67168732 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:21 +0000 |
| parents | d51310ab328a |
| children | 6fecb8056a80 |
| files | creators.xml filter.py filter.xml filter_image.py test-data/input1_gaussian.tif test-data/input1_gaussian.tiff test-data/input1_median.tif test-data/input1_median.tiff test-data/input1_prewitt_h.tif test-data/input1_prewitt_h.tiff test-data/input1_prewitt_v.tif test-data/input1_prewitt_v.tiff test-data/input1_sobel_h.tif test-data/input1_sobel_h.tiff test-data/input1_sobel_v.tif test-data/input1_sobel_v.tiff test-data/input1_uint8.tif test-data/input1_uint8.tiff test-data/input2_float.tif test-data/input2_float.tiff test-data/input2_gaussian.tif test-data/input2_gaussian.tiff test-data/input2_uniform.tiff test-data/retina_gaussian_order0.tiff test-data/retina_gaussian_order2_axis0.tiff test-data/scikit-image/LICENSE.txt test-data/scikit-image/retina.png |
| diffstat | 27 files changed, 326 insertions(+), 84 deletions(-) [+] |
line wrap: on
line diff
--- a/creators.xml Wed Apr 24 08:12:03 2024 +0000 +++ b/creators.xml Fri Dec 12 21:18:21 2025 +0000 @@ -5,6 +5,16 @@ <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/> @@ -19,5 +29,15 @@ <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:21 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 Wed Apr 24 08:12:03 2024 +0000 +++ b/filter.xml Fri Dec 12 21:18:21 2025 +0000 @@ -1,81 +1,136 @@ -<tool id="ip_filter_standard" name="Filter 2-D image" version="@TOOL_VERSION@+galaxy@VERSION_SUFFIX@" profile="20.05"> +<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.12.0</token> - <token name="@VERSION_SUFFIX@">1</token> + <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/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="@TOOL_VERSION@">scipy</requirement> - <requirement type="package" version="1.26.4">numpy</requirement> - <requirement type="package" version="0.22.0">scikit-image</requirement> - <requirement type="package" version="2024.2.12">tifffile</requirement> - <requirement type="package" version="0.1">giatools</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' + python '$__tool_directory__/filter.py' '$input' '$output' - - $filter_type - $size + '$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="input" type="data" format="tiff,png" label="Input image" /> + <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_h">Prewitt horizontal</option> - <option value="prewitt_v">Prewitt vertical</option> - <option value="sobel_h">Sobel horizontal</option> - <option value="sobel_v">Sobel vertical</option> + <option value="prewitt">Prewitt</option> + <option value="sobel">Sobel</option> </param> <when value="gaussian"> - <param name="size" type="float" value="3" min="0.1" label="Sigma" help="The half width of the Gaussian bell (in pixels)." /> + <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="size" type="integer" value="3" label="Radius" help="Radius of the neighborhood (in pixels)." /> - </when> - <when value="prewitt_h"> - <param name="size" type="hidden" value="0" /> + <param name="radius" type="integer" min="2" value="3" label="Radius" + help="Radius of the neighborhood (circle, in pixels)." /> </when> - <when value="prewitt_v"> - <param name="size" type="hidden" value="0" /> + <when value="prewitt"> + <expand macro="select_axis"/> </when> - <when value="sobel_h"> - <param name="size" type="hidden" value="0" /> - </when> - <when value="sobel_v"> - <param name="size" type="hidden" value="0" /> + <when value="sobel"> + <expand macro="select_axis"/> </when> </conditional> </inputs> <outputs> <data format="tiff" name="output" /> - </outputs> + </outputs> <tests> <!-- Tests with uint8 TIFF input image --> <test> - <param name="input" value="input1_uint8.tif" /> - <param name="filter_type" value="gaussian" /> - <expand macro="tests/intensity_image_diff" name="output" value="input1_gaussian.tif" ftype="tiff"> + <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.tif` has values ranging between 23 and 254, with a mean value of 63.67. + 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 @@ -88,48 +143,106 @@ </expand> </test> <test> - <param name="input" value="input1_uint8.tif" /> - <param name="filter_type" value="median" /> - <expand macro="tests/intensity_image_diff" name="output" value="input1_median.tif" ftype="tiff"> + <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.tif" /> - <param name="filter_type" value="prewitt_h" /> - <expand macro="tests/intensity_image_diff" name="output" value="input1_prewitt_h.tif" ftype="tiff"/> + <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.tif" /> - <param name="filter_type" value="prewitt_v" /> - <expand macro="tests/intensity_image_diff" name="output" value="input1_prewitt_v.tif" ftype="tiff"/> + <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.tif" /> - <param name="filter_type" value="sobel_h" /> - <expand macro="tests/intensity_image_diff" name="output" value="input1_sobel_h.tif" ftype="tiff"/> + <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.tif" /> - <param name="filter_type" value="sobel_v" /> - <expand macro="tests/intensity_image_diff" name="output" value="input1_sobel_v.tif" ftype="tiff"/> + <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.tif" /> - <param name="filter_type" value="gaussian" /> - <expand macro="tests/intensity_image_diff" name="output" value="input2_gaussian.tif" ftype="tiff"> + <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> - **Applies a standard filter to a single-channel 2-D image.** + **Applies a standard, general-purpose 2-D filter to an image.** + + Support for different image types: - Mean filters like the Gaussian filter or the median filter preserve both the brightness of the image, and the range of values. + - 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>
--- a/filter_image.py Wed Apr 24 08:12:03 2024 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -import argparse - -import giatools.io -import scipy.ndimage as ndi -import skimage.io -import skimage.util -from skimage.morphology import disk - - -filters = { - 'gaussian': lambda im, sigma: ndi.gaussian_filter(im, sigma), - 'median': lambda im, radius: ndi.median_filter(im, footprint=disk(radius)), - 'prewitt_h': lambda im, *args: ndi.prewitt(im, axis=1), - 'prewitt_v': lambda im, *args: ndi.prewitt(im, axis=0), - 'sobel_h': lambda im, *args: ndi.sobel(im, axis=1), - 'sobel_v': lambda im, *args: ndi.sobel(im, axis=0), -} - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument('input', type=argparse.FileType('r'), help='Input file') - parser.add_argument('output', type=argparse.FileType('w'), help='Output file (TIFF)') - parser.add_argument('filter', choices=filters.keys(), help='Filter to be used') - parser.add_argument('size', type=float, help='Size of the filter (e.g., radius, sigma)') - args = parser.parse_args() - - im = giatools.io.imread(args.input.name) - res = filters[args.filter](im, args.size) - skimage.io.imsave(args.output.name, res, plugin='tifffile')
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test-data/scikit-image/LICENSE.txt Fri Dec 12 21:18:21 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.
