Mercurial > repos > imgteam > ridge_filter
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>
--- /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.
--- /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>
