Mercurial > repos > imgteam > 2d_auto_threshold
changeset 9:50fa6150e340 draft default tip
planemo upload for repository https://github.com/BMCV/galaxy-image-analysis/tree/master/tools/2d_auto_threshold/ commit 01343602708de3cc7fa4986af9000adc36dd0651
author | imgteam |
---|---|
date | Sat, 07 Jun 2025 18:38:31 +0000 |
parents | 699a5e9146b3 |
children | |
files | auto_threshold.py auto_threshold.xml creators.xml test-data/out1.tif test-data/out1.tiff test-data/out2.tif test-data/out2.tiff test-data/out3.tif test-data/out3.tiff test-data/out4.tif test-data/out4.tiff test-data/out5.tif test-data/out5.tiff test-data/rgb_otsu.tiff test-data/sample.tif test-data/sample.tiff test-data/sample2.tif test-data/sample2.tiff test-data/sample_manual_180_240.tiff |
diffstat | 19 files changed, 182 insertions(+), 76 deletions(-) [+] |
line wrap: on
line diff
--- a/auto_threshold.py Wed Apr 24 08:11:33 2024 +0000 +++ b/auto_threshold.py Sat Jun 07 18:38:31 2025 +0000 @@ -7,49 +7,111 @@ import argparse -import giatools.io import numpy as np import skimage.filters import skimage.util -import tifffile +from giatools.image import Image + + +class DefaultThresholdingMethod: + + def __init__(self, thres, accept: list[str] | None = None, **kwargs): + self.thres = thres + self.accept = accept if accept else [] + self.kwargs = kwargs + + def __call__(self, image, *args, offset=0, **kwargs): + accepted_kwargs = self.kwargs.copy() + for key, val in kwargs.items(): + if key in self.accept: + accepted_kwargs[key] = val + thres = self.thres(image, *args, **accepted_kwargs) + return image > thres + offset + + +class ManualThresholding: + + def __call__(self, image, thres1: float, thres2: float | None, **kwargs): + if thres2 is None: + return image > thres1 + else: + thres1, thres2 = sorted((thres1, thres2)) + return skimage.filters.apply_hysteresis_threshold(image, thres1, thres2) th_methods = { - 'manual': lambda thres, **kwargs: thres, + 'manual': ManualThresholding(), - 'otsu': lambda img_raw, **kwargs: skimage.filters.threshold_otsu(img_raw), - 'li': lambda img_raw, **kwargs: skimage.filters.threshold_li(img_raw), - 'yen': lambda img_raw, **kwargs: skimage.filters.threshold_yen(img_raw), - 'isodata': lambda img_raw, **kwargs: skimage.filters.threshold_isodata(img_raw), + 'otsu': DefaultThresholdingMethod(skimage.filters.threshold_otsu), + 'li': DefaultThresholdingMethod(skimage.filters.threshold_li), + 'yen': DefaultThresholdingMethod(skimage.filters.threshold_yen), + 'isodata': DefaultThresholdingMethod(skimage.filters.threshold_isodata), - 'loc_gaussian': lambda img_raw, bz, **kwargs: skimage.filters.threshold_local(img_raw, bz, method='gaussian'), - 'loc_median': lambda img_raw, bz, **kwargs: skimage.filters.threshold_local(img_raw, bz, method='median'), - 'loc_mean': lambda img_raw, bz, **kwargs: skimage.filters.threshold_local(img_raw, bz, method='mean') + 'loc_gaussian': DefaultThresholdingMethod(skimage.filters.threshold_local, accept=['block_size'], method='gaussian'), + 'loc_median': DefaultThresholdingMethod(skimage.filters.threshold_local, accept=['block_size'], method='median'), + 'loc_mean': DefaultThresholdingMethod(skimage.filters.threshold_local, accept=['block_size'], method='mean'), } -def do_thresholding(in_fn, out_fn, th_method, block_size, offset, threshold, invert_output=False): - img = giatools.io.imread(in_fn) - img = np.squeeze(img) - assert img.ndim == 2 +def do_thresholding( + input_filepath: str, + output_filepath: str, + th_method: str, + block_size: int, + offset: float, + threshold1: float, + threshold2: float | None, + invert_output: bool, +): + assert th_method in th_methods, f'Unknown method "{th_method}"' + + # Load image + img_in = Image.read(input_filepath) - th = offset + th_methods[th_method](img_raw=img, bz=block_size, thres=threshold) - res = img > th + # Perform thresholding + result = th_methods[th_method]( + image=img_in.data, + block_size=block_size, + offset=offset, + thres1=threshold1, + thres2=threshold2, + ) if invert_output: - res = np.logical_not(res) + result = np.logical_not(result) + + # Convert to canonical representation for binary images + result = (result * 255).astype(np.uint8) - tifffile.imwrite(out_fn, skimage.util.img_as_ubyte(res)) + # Write result + Image( + data=skimage.util.img_as_ubyte(result), + axes=img_in.axes, + ).normalize_axes_like( + img_in.original_axes, + ).write( + output_filepath, + ) if __name__ == "__main__": parser = argparse.ArgumentParser(description='Automatic image thresholding') - parser.add_argument('im_in', help='Path to the input image') - parser.add_argument('im_out', help='Path to the output image (uint8)') + parser.add_argument('input', type=str, help='Path to the input image') + parser.add_argument('output', type=str, help='Path to the output image (uint8)') parser.add_argument('th_method', choices=th_methods.keys(), help='Thresholding method') - parser.add_argument('block_size', type=int, default=5, help='Odd size of pixel neighborhood for calculating the threshold') - parser.add_argument('offset', type=float, default=0, help='Offset of automatically determined threshold value') - parser.add_argument('threshold', type=float, default=0, help='Manual threshold value') + parser.add_argument('block_size', type=int, help='Odd size of pixel neighborhood for calculating the threshold') + parser.add_argument('offset', type=float, help='Offset of automatically determined threshold value') + parser.add_argument('threshold1', type=float, help='Manual threshold value') + parser.add_argument('--threshold2', type=float, help='Second manual threshold value (for hysteresis thresholding)') parser.add_argument('--invert_output', default=False, action='store_true', help='Values below/above the threshold are labeled with 0/255 by default, and with 255/0 if this argument is used') args = parser.parse_args() - do_thresholding(args.im_in, args.im_out, args.th_method, args.block_size, args.offset, args.threshold, args.invert_output) + do_thresholding( + args.input, + args.output, + args.th_method, + args.block_size, + args.offset, + args.threshold1, + args.threshold2, + args.invert_output, + )
--- a/auto_threshold.xml Wed Apr 24 08:11:33 2024 +0000 +++ b/auto_threshold.xml Sat Jun 07 18:38:31 2025 +0000 @@ -3,8 +3,8 @@ <macros> <import>creators.xml</import> <import>tests.xml</import> - <token name="@TOOL_VERSION@">0.18.1</token> - <token name="@VERSION_SUFFIX@">3</token> + <token name="@TOOL_VERSION@">0.25.0</token> + <token name="@VERSION_SUFFIX@">0</token> <xml name="inputs/offset"> <param name="offset" type="float" value="0" label="Offset" help="Offset to be added to the automatically determined threshold value. Positive values will increase the threshold (and thus reduce the amount of values above the threshold)." /> </xml> @@ -19,21 +19,28 @@ <xref type="bio.tools">scikit-image</xref> <xref type="biii">scikit-image</xref> </xrefs> - <requirements> + <requirements> <requirement type="package" version="@TOOL_VERSION@">scikit-image</requirement> - <requirement type="package" version="2020.10.1">tifffile</requirement> - <requirement type="package" version="0.1">giatools</requirement> + <requirement type="package" version="0.4.1">giatools</requirement> </requirements> <command detect_errors="aggressive"> <![CDATA[ - python '$__tool_directory__/auto_threshold.py' - '$input' - ./out.tif - '$th_method.method_id' - '$th_method.block_size' - '$th_method.offset' - '$th_method.threshold' - $invert_output + + python '$__tool_directory__/auto_threshold.py' + + '$input' + ./out.tiff + + '$th_method.method_id' + '$th_method.block_size' + '$th_method.offset' + '$th_method.threshold1' + $invert_output + + #if str($th_method.threshold2) != '': + --threshold2 '$th_method.threshold2' + #end if + ]]> </command> <inputs> @@ -50,42 +57,50 @@ <option value="loc_mean">Locally adaptive / Mean</option> </param> <when value="manual"> - <param name="threshold" type="float" value="0" label="Threshold value" /> + <param name="threshold1" type="float" value="0" label="Threshold value" /> + <param name="threshold2" type="float" value="" optional="true" label="Second threshold value for hysteresis thresholding" /> <param name="block_size" type="hidden" value="0" /> <param name="offset" type="hidden" value="0" /> </when> <when value="otsu"> - <param name="threshold" type="hidden" value="0" /> + <param name="threshold1" type="hidden" value="0" /> + <param name="threshold2" type="hidden" value="" /> <param name="block_size" type="hidden" value="0" /> <expand macro="inputs/offset" /> </when> <when value="li"> - <param name="threshold" type="hidden" value="0" /> + <param name="threshold1" type="hidden" value="0" /> + <param name="threshold2" type="hidden" value="" /> <param name="block_size" type="hidden" value="0" /> <expand macro="inputs/offset" /> </when> <when value="isodata"> - <param name="threshold" type="hidden" value="0" /> + <param name="threshold1" type="hidden" value="0" /> + <param name="threshold2" type="hidden" value="" /> <param name="block_size" type="hidden" value="0" /> <expand macro="inputs/offset" /> </when> <when value="yen"> - <param name="threshold" type="hidden" value="0" /> + <param name="threshold1" type="hidden" value="0" /> + <param name="threshold2" type="hidden" value="" /> <param name="block_size" type="hidden" value="0" /> <expand macro="inputs/offset" /> </when> <when value="loc_gaussian"> - <param name="threshold" type="hidden" value="0" /> + <param name="threshold1" type="hidden" value="0" /> + <param name="threshold2" type="hidden" value="" /> <param name="block_size" type="integer" value="5" label="Odd size of pixel neighborhood for determining the threshold" /> <expand macro="inputs/offset" /> </when> <when value="loc_median"> - <param name="threshold" type="hidden" value="0" /> + <param name="threshold1" type="hidden" value="0" /> + <param name="threshold2" type="hidden" value="" /> <param name="block_size" type="integer" value="5" label="Odd size of pixel neighborhood for determining the threshold" /> <expand macro="inputs/offset" /> </when> <when value="loc_mean"> - <param name="threshold" type="hidden" value="0" /> + <param name="threshold1" type="hidden" value="0" /> + <param name="threshold2" type="hidden" value="" /> <param name="block_size" type="integer" value="5" label="Odd size of pixel neighborhood for determining the threshold" /> <expand macro="inputs/offset" /> </when> @@ -93,48 +108,67 @@ <param name="invert_output" type="boolean" checked="false" truevalue="--invert_output" falsevalue="" label="Invert output labels" help="Pixels are usually assigned the label 0 if the pixel value is below (or equal to) the threshold, and 255 if it is above the threshold. If this option is activated, pixels are assigned the label 255 if the pixel value is below (or equal to) the threshold, and 0 if it is above the threshold." /> </inputs> <outputs> - <data format="tiff" name="output" from_work_dir="out.tif" /> + <data format="tiff" name="output" from_work_dir="out.tiff" /> </outputs> <tests> - <!-- Tests for single-channel images --> + <!-- Tests for single-channel images (TIFF) --> <test> - <param name="input" value="sample.tif"/> - <param name="method_id" value="loc_gaussian"/> - <param name="block_size" value="51"/> - <param name="invert_output" value="False"/> - <expand macro="tests/binary_image_diff" name="output" value="out1.tif" ftype="tiff"/> + <param name="input" value="sample.tiff"/> + <conditional name="th_method"> + <param name="method_id" value="loc_gaussian"/> + <param name="block_size" value="51"/> + </conditional> + <expand macro="tests/binary_image_diff" name="output" value="out1.tiff" ftype="tiff"/> + </test> + <test> + <param name="input" value="sample.tiff"/> + <conditional name="th_method"> + <param name="method_id" value="loc_gaussian"/> + <param name="block_size" value="51"/> + <param name="offset" value="1"/> + </conditional> + <expand macro="tests/binary_image_diff" name="output" value="out2.tiff" ftype="tiff"/> </test> <test> - <param name="input" value="sample.tif"/> - <param name="method_id" value="loc_gaussian"/> - <param name="block_size" value="51"/> - <param name="offset" value="1"/> - <param name="invert_output" value="False"/> - <expand macro="tests/binary_image_diff" name="output" value="out2.tif" ftype="tiff"/> + <param name="input" value="sample.tiff"/> + <conditional name="th_method"> + <param name="method_id" value="otsu"/> + </conditional> + <expand macro="tests/binary_image_diff" name="output" value="out3.tiff" ftype="tiff"/> + </test> + <test> + <param name="input" value="sample.tiff"/> + <param name="invert_output" value="True"/> + <conditional name="th_method"> + <param name="method_id" value="manual"/> + <param name="threshold1" value="64"/> + </conditional> + <expand macro="tests/binary_image_diff" name="output" value="out4.tiff" ftype="tiff"/> </test> <test> - <param name="input" value="sample.tif"/> - <param name="method_id" value="otsu"/> - <param name="invert_output" value="False"/> - <expand macro="tests/binary_image_diff" name="output" value="out3.tif" ftype="tiff"/> + <param name="input" value="sample.tiff"/> + <conditional name="th_method"> + <param name="method_id" value="manual"/> + <param name="threshold1" value="180"/> + <param name="threshold2" value="240"/> + </conditional> + <expand macro="tests/binary_image_diff" name="output" value="sample_manual_180_240.tiff" ftype="tiff"/> </test> + <!-- Tests for multi-channel images (PNG) --> <test> - <param name="input" value="sample.tif"/> - <param name="method_id" value="manual"/> - <param name="threshold" value="64"/> - <param name="invert_output" value="True"/> - <expand macro="tests/binary_image_diff" name="output" value="out4.tif" ftype="tiff"/> + <param name="input" value="rgb.png"/> + <conditional name="th_method"> + <param name="method_id" value="otsu"/> + </conditional> + <expand macro="tests/binary_image_diff" name="output" value="rgb_otsu.tiff" ftype="tiff"/> </test> <!-- Tests for irregular files --> <test> - <param name="input" value="sample2.tif"/> - <param name="method_id" value="otsu"/> - <param name="invert_output" value="False"/> - <expand macro="tests/binary_image_diff" name="output" value="out5.tif" ftype="tiff"/> - </test> - <!-- Tests for multi-channel images --> - <test expect_failure="true"> - <param name="input" value="rgb.png"/> + <param name="input" value="sample2.tiff"/> + <conditional name="th_method"> + <param name="method_id" value="otsu"/> + </conditional> + <expand macro="tests/binary_image_diff" name="output" value="out5.tiff" ftype="tiff"/> </test> </tests> <help>
--- a/creators.xml Wed Apr 24 08:11:33 2024 +0000 +++ b/creators.xml Sat Jun 07 18:38:31 2025 +0000 @@ -5,6 +5,11 @@ <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 +24,10 @@ <person givenName="Till" familyName="Korten"/> <yield/> </xml> - + + <xml name="creators/pavanvidem"> + <person givenName="Pavan" familyName="Videm"/> + <yield/> + </xml> + </macros>