# HG changeset patch
# User imgteam
# Date 1767451390 0
# Node ID 2ee04d2ebdcf0073ea6188a6eeece5de7350c82b
# Parent 50fa6150e34008e88dfed4aa3ef547b594b95736
planemo upload for repository https://github.com/BMCV/galaxy-image-analysis/tree/master/tools/2d_auto_threshold/ commit 71f7ecabba78de48147d4a5e6ea380b6b70b16e8
diff -r 50fa6150e340 -r 2ee04d2ebdcf auto_threshold.py
--- a/auto_threshold.py Sat Jun 07 18:38:31 2025 +0000
+++ b/auto_threshold.py Sat Jan 03 14:43:10 2026 +0000
@@ -1,45 +1,47 @@
"""
-Copyright 2017-2024 Biomedical Computer Vision Group, Heidelberg University.
+Copyright 2017-2025 Biomedical Computer Vision Group, Heidelberg University.
Distributed under the MIT license.
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
"""
-import argparse
-
+import giatools
import numpy as np
import skimage.filters
import skimage.util
-from giatools.image import Image
+
+# Fail early if an optional backend is not available
+giatools.require_backend('omezarr')
class DefaultThresholdingMethod:
- def __init__(self, thres, accept: list[str] | None = None, **kwargs):
+ def __init__(self, thres, **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)
+ thres = self.thres(image, *args, **(self.kwargs | kwargs))
return image > thres + offset
+ def __str__(self):
+ return self.thres.__name__
+
class ManualThresholding:
- def __call__(self, image, thres1: float, thres2: float | None, **kwargs):
- if thres2 is None:
- return image > thres1
+ def __call__(self, image, threshold1: float, threshold2: float | None, **kwargs):
+ if threshold2 is None:
+ return image > threshold1
else:
- thres1, thres2 = sorted((thres1, thres2))
- return skimage.filters.apply_hysteresis_threshold(image, thres1, thres2)
+ threshold1, threshold2 = sorted((threshold1, threshold2))
+ return skimage.filters.apply_hysteresis_threshold(image, threshold1, threshold2)
+
+ def __str__(self):
+ return 'Manual'
-th_methods = {
+methods = {
'manual': ManualThresholding(),
'otsu': DefaultThresholdingMethod(skimage.filters.threshold_otsu),
@@ -47,71 +49,37 @@
'yen': DefaultThresholdingMethod(skimage.filters.threshold_yen),
'isodata': DefaultThresholdingMethod(skimage.filters.threshold_isodata),
- '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'),
+ 'loc_gaussian': DefaultThresholdingMethod(skimage.filters.threshold_local, method='gaussian'),
+ 'loc_median': DefaultThresholdingMethod(skimage.filters.threshold_local, method='median'),
+ 'loc_mean': DefaultThresholdingMethod(skimage.filters.threshold_local, method='mean'),
}
-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}"'
+if __name__ == "__main__":
+ tool = giatools.ToolBaseplate()
+ tool.add_input_image('input')
+ tool.add_output_image('output')
+ tool.parse_args()
- # Load image
- img_in = Image.read(input_filepath)
+ # Retrieve general parameters
+ method = tool.args.params.pop('method')
+ invert = tool.args.params.pop('invert')
# Perform thresholding
- result = th_methods[th_method](
- image=img_in.data,
- block_size=block_size,
- offset=offset,
- thres1=threshold1,
- thres2=threshold2,
- )
- if invert_output:
- result = np.logical_not(result)
-
- # Convert to canonical representation for binary images
- result = (result * 255).astype(np.uint8)
-
- # Write result
- Image(
- data=skimage.util.img_as_ubyte(result),
- axes=img_in.axes,
- ).normalize_axes_like(
- img_in.original_axes,
- ).write(
- output_filepath,
+ method_impl = methods[method]
+ print(
+ 'Thresholding:',
+ str(method_impl),
+ 'with',
+ ', '.join(
+ f'{key}={repr(value)}' for key, value in (tool.args.params | dict(invert=invert)).items()
+ ),
)
-
-
-if __name__ == "__main__":
- parser = argparse.ArgumentParser(description='Automatic image thresholding')
- 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, 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.input,
- args.output,
- args.th_method,
- args.block_size,
- args.offset,
- args.threshold1,
- args.threshold2,
- args.invert_output,
- )
+ for section in tool.run('ZYX', output_dtype_hint='binary'):
+ section_output = method_impl(
+ image=np.asarray(section['input'].data), # some implementations have issues with Dask arrays
+ **tool.args.params,
+ )
+ if invert:
+ section_output = np.logical_not(section_output)
+ section['output'] = section_output
diff -r 50fa6150e340 -r 2ee04d2ebdcf auto_threshold.xml
--- a/auto_threshold.xml Sat Jun 07 18:38:31 2025 +0000
+++ b/auto_threshold.xml Sat Jan 03 14:43:10 2026 +0000
@@ -3,48 +3,78 @@
creators.xml
tests.xml
- 0.25.0
+ 0.25.2
0
-
+
+
operation_3443
+ galaxy_image_analysis
+ giatools
scikit-image
scikit-image
scikit-image
- giatools
+ giatools
+ ome-zarr
-
-
+
+
+
-
+ --output ./out.tiff
+ --params '$params'
+ --verbose
+
+ ]]>
+
+
+
-
+
@@ -57,55 +87,37 @@
-
-
-
-
+
+
+
-
-
-
-
+
-
-
-
-
+
-
-
-
-
+
-
-
-
-
+
-
-
-
-
+
+
-
-
-
-
+
+
-
-
-
-
+
+
-
+
@@ -113,73 +125,90 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
-
+
-
+
+
+
+
+
+
+
+
- **Applies a standard thresholding algorithm to a 2-D single-channel image. Yields a binary image.**
+ **Applies a standard thresholding algorithm to an image. Yields a binary image.**
- The thresholding algorithm automatically determines a threshold value (unless manual thresholding is used).
- The input image is then thresholded, by assigning white (pixel value 255) to image regions above the determined threshold,
- and black (pixel value 0) to image regions below or equal to the determined threshold.
+ The thresholding algorithm automatically determines a threshold value (unless manual thresholding is used). The input image is
+ then thresholded, by assigning white (pixel value 255) to image regions above the determined threshold, and black (pixel value 0)
+ to image regions below or equal to the determined threshold. For multi-channel images, each channel is processed separately,
+ which, for example, may also yield colors beyond black and white in case of RGB images.
- The assignment of black and white to image regions below and above the threshold is inverted, if the corresponding option is set.
+ The assignment of the pixel values 0 and 255 (i.e. black and white) to image regions below and above the threshold is inverted,
+ if the corresponding option is set.
diff -r 50fa6150e340 -r 2ee04d2ebdcf creators.xml
--- a/creators.xml Sat Jun 07 18:38:31 2025 +0000
+++ b/creators.xml Sat Jan 03 14:43:10 2026 +0000
@@ -5,6 +5,11 @@
+
+
+
+
+
@@ -30,4 +35,9 @@
+
+
+
+
+
diff -r 50fa6150e340 -r 2ee04d2ebdcf test-data/input/README.md
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/README.md Sat Jan 03 14:43:10 2026 +0000
@@ -0,0 +1,37 @@
+# Overview of the test images
+
+## `input5.jpg`:
+
+- axes: `YX`
+- resolution: `(10, 10, 3)`
+- dtype: `uint8`
+
+## `input8_zyx.zarr`:
+
+- axes: `ZYX`
+- resolution: `(2, 100, 100)`
+- dtype: `float64`
+- metadata:
+ - resolution: `(1.0, 1.0)`
+ - z-spacing: `1.0`
+ - unit: `um`
+
+## `rgb.png`:
+
+- axes: `YXC`
+- resolution: `(6, 6, 3)`
+- dtype: `uint8`
+
+## `sample.tiff`:
+
+- axes: `YX`
+- resolution: `(265, 329)`
+- dtype: `uint8`
+- metadata: *unknown*
+
+## `sample2.tiff`:
+
+- axes: `YX`
+- resolution: `(96, 97)`
+- dtype: `uint8`
+- metadata: *unknown*
diff -r 50fa6150e340 -r 2ee04d2ebdcf test-data/input/input5.jpg
Binary file test-data/input/input5.jpg has changed
diff -r 50fa6150e340 -r 2ee04d2ebdcf test-data/input/ome-zarr-examples/LICENSE
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/ome-zarr-examples/LICENSE Sat Jan 03 14:43:10 2026 +0000
@@ -0,0 +1,28 @@
+BSD 3-Clause License
+
+Copyright (c) 2023, Tommaso Comparin
+
+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 copyright holder 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 COPYRIGHT HOLDER 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.
diff -r 50fa6150e340 -r 2ee04d2ebdcf test-data/input/ome-zarr-examples/image-03.zarr/.zattrs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/ome-zarr-examples/image-03.zarr/.zattrs Sat Jan 03 14:43:10 2026 +0000
@@ -0,0 +1,40 @@
+{
+ "multiscales": [
+ {
+ "axes": [
+ {
+ "name": "z",
+ "type": "space",
+ "unit": "micrometer"
+ },
+ {
+ "name": "y",
+ "type": "space",
+ "unit": "micrometer"
+ },
+ {
+ "name": "x",
+ "type": "space",
+ "unit": "micrometer"
+ }
+ ],
+ "datasets": [
+ {
+ "coordinateTransformations": [
+ {
+ "scale": [
+ 1.0,
+ 1.0,
+ 1.0
+ ],
+ "type": "scale"
+ }
+ ],
+ "path": "0"
+ }
+ ],
+ "version": "0.4"
+ }
+ ],
+ "version": "0.4"
+}
\ No newline at end of file
diff -r 50fa6150e340 -r 2ee04d2ebdcf test-data/input/ome-zarr-examples/image-03.zarr/.zgroup
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/ome-zarr-examples/image-03.zarr/.zgroup Sat Jan 03 14:43:10 2026 +0000
@@ -0,0 +1,3 @@
+{
+ "zarr_format": 2
+}
\ No newline at end of file
diff -r 50fa6150e340 -r 2ee04d2ebdcf test-data/input/ome-zarr-examples/image-03.zarr/0/.zarray
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/ome-zarr-examples/image-03.zarr/0/.zarray Sat Jan 03 14:43:10 2026 +0000
@@ -0,0 +1,25 @@
+{
+ "chunks": [
+ 1,
+ 50,
+ 100
+ ],
+ "compressor": {
+ "blocksize": 0,
+ "clevel": 5,
+ "cname": "lz4",
+ "id": "blosc",
+ "shuffle": 1
+ },
+ "dimension_separator": "/",
+ "dtype": "