changeset 5:1ae14e703e5b draft default tip

planemo upload for repository https://github.com/BMCV/galaxy-image-analysis/tree/master/tools/2d_auto_threshold/ commit 71f7ecabba78de48147d4a5e6ea380b6b70b16e8
author imgteam
date Sat, 03 Jan 2026 14:42:51 +0000
parents 7d80eb2411fb
children
files auto_threshold.py auto_threshold.xml creators.xml test-data/input/README.md test-data/input/input5.jpg test-data/input/ome-zarr-examples/LICENSE test-data/input/ome-zarr-examples/image-03.zarr/.zattrs test-data/input/ome-zarr-examples/image-03.zarr/.zgroup test-data/input/ome-zarr-examples/image-03.zarr/0/.zarray test-data/input/ome-zarr-examples/image-03.zarr/0/0/0/0 test-data/input/ome-zarr-examples/image-03.zarr/0/0/1/0 test-data/input/ome-zarr-examples/image-03.zarr/0/1/0/0 test-data/input/ome-zarr-examples/image-03.zarr/0/1/1/0 test-data/input/ome-zarr-examples/image-03.zarr/labels/.zattrs test-data/input/ome-zarr-examples/image-03.zarr/labels/.zgroup test-data/input/rgb.png test-data/input/sample.tiff test-data/input/sample2.tiff test-data/out1.tiff test-data/out2.tiff test-data/out3.tiff test-data/out4.tiff test-data/out5.tiff test-data/output/input5_li.tiff test-data/output/input8_yen.tiff test-data/output/out1.tiff test-data/output/out2.tiff test-data/output/out3.tiff test-data/output/out4.tiff test-data/output/out5.tiff test-data/output/rgb_otsu.tiff test-data/output/sample_manual_180_240.tiff test-data/rgb.png test-data/rgb_otsu.tiff test-data/sample.tiff test-data/sample2.tiff test-data/sample_manual_180_240.tiff
diffstat 37 files changed, 297 insertions(+), 151 deletions(-) [+]
line wrap: on
line diff
--- a/auto_threshold.py	Sat Jun 07 18:38:16 2025 +0000
+++ b/auto_threshold.py	Sat Jan 03 14:42:51 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
--- a/auto_threshold.xml	Sat Jun 07 18:38:16 2025 +0000
+++ b/auto_threshold.xml	Sat Jan 03 14:42:51 2026 +0000
@@ -3,48 +3,78 @@
     <macros>
         <import>creators.xml</import>
         <import>tests.xml</import>
-        <token name="@TOOL_VERSION@">0.25.0</token>
+        <token name="@TOOL_VERSION@">0.25.2</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)." />
+            <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>
     </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">giatools</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.4.1">giatools</requirement>
+        <requirement type="package" version="0.7.3">giatools</requirement>
+        <requirement type="package" version="0.12.2">ome-zarr</requirement>
     </requirements>
-    <command detect_errors="aggressive">
-    <![CDATA[
+    <required_files>
+        <include type="literal" path="auto_threshold.py"/>
+    </required_files>
+    <command detect_errors="aggressive"><![CDATA[
 
         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'
+        #if $input.extension == "zarr"
+            --input '$input.extra_files_path/$input.metadata.store_root'
+        #else
+            --input '$input'
         #end if
 
-    ]]>
-    </command>
+        --output ./out.tiff
+        --params '$params'
+        --verbose
+
+    ]]></command>
+    <configfiles>
+        <configfile name="params"><![CDATA[
+            {
+
+            #if str($th_method.method_id).startswith('loc_')
+                "block_size": $th_method.block_size,
+            #end if
+
+            #if $th_method.method_id != "manual"
+                "offset": $th_method.offset,
+
+            #else
+                "threshold1": $th_method.threshold1,
+                #if str($th_method.threshold2) != ""
+                    "threshold2": $th_method.threshold2,
+                #else
+                    "threshold2": null,
+                #end if
+
+            #end if
+
+                "method": "$th_method.method_id",
+                "invert": $invert_output
+
+            }
+        ]]></configfile>
+    </configfiles>
     <inputs>
-        <param name="input" type="data" format="tiff,png" label="Input image" />
+        <param name="input" type="data" format="tiff,zarr,png,jpg" label="Input image"/>
         <conditional name="th_method">
             <param name="method_id" type="select" label="Thresholding method">
                 <option value="manual">Manual</option>
@@ -57,55 +87,37 @@
                 <option value="loc_mean">Locally adaptive / Mean</option>
             </param>
             <when value="manual">
-                <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" />
+                <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="offset" type="hidden" value="0"/>
             </when>
             <when value="otsu">
-                <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" />
+                <expand macro="inputs/offset"/>
             </when>
             <when value="li">
-                <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" />
+                <expand macro="inputs/offset"/>
             </when>
             <when value="isodata">
-                <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" />
+                <expand macro="inputs/offset"/>
             </when>
             <when value="yen">
-                <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" />
+                <expand macro="inputs/offset"/>
             </when>
             <when value="loc_gaussian">
-                <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" />
+                <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="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" />
+                <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="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" />
+                <param name="block_size" type="integer" value="5" label="Odd size of pixel neighborhood for determining the threshold"/>
+                <expand macro="inputs/offset"/>
             </when>
         </conditional>
-        <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." />
+        <param name="invert_output" type="boolean" checked="false" 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.tiff" />
@@ -113,73 +125,90 @@
     <tests>
         <!-- Tests for single-channel images (TIFF) -->
         <test>
-            <param name="input" value="sample.tiff"/>
+            <param name="input" value="input/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"/>
+            <expand macro="tests/binary_image_diff" name="output" value="output/out1.tiff" ftype="tiff"/>
         </test>
         <test>
-            <param name="input" value="sample.tiff"/>
+            <param name="input" value="input/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"/>
+            <expand macro="tests/binary_image_diff" name="output" value="output/out2.tiff" ftype="tiff"/>
         </test>
         <test>
-            <param name="input" value="sample.tiff"/>
+            <param name="input" value="input/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"/>
+            <expand macro="tests/binary_image_diff" name="output" value="output/out3.tiff" ftype="tiff"/>
         </test>
         <test>
-            <param name="input" value="sample.tiff"/>
+            <param name="input" value="input/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"/>
+            <expand macro="tests/binary_image_diff" name="output" value="output/out4.tiff" ftype="tiff"/>
         </test>
         <test>
-            <param name="input" value="sample.tiff"/>
+            <param name="input" value="input/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"/>
+            <expand macro="tests/binary_image_diff" name="output" value="output/sample_manual_180_240.tiff" ftype="tiff"/>
         </test>
         <!-- Tests for multi-channel images (PNG) -->
         <test>
-            <param name="input" value="rgb.png"/>
+            <param name="input" value="input/rgb.png"/>
+            <conditional name="th_method">
+                <param name="method_id" value="otsu"/>
+            </conditional>
+            <expand macro="tests/binary_image_diff" name="output" value="output/rgb_otsu.tiff" ftype="tiff"/>
+        </test>
+        <!-- Tests for irregular files (TODO: clear up what this means) -->
+        <test>
+            <param name="input" value="input/sample2.tiff"/>
             <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"/>
+            <expand macro="tests/binary_image_diff" name="output" value="output/out5.tiff" ftype="tiff"/>
         </test>
-        <!-- Tests for irregular files -->
+        <!-- Tests for exotic files -->
         <test>
-            <param name="input" value="sample2.tiff"/>
+            <param name="input" value="input/input5.jpg"/>
             <conditional name="th_method">
-                <param name="method_id" value="otsu"/>
+                <param name="method_id" value="li"/>
             </conditional>
-            <expand macro="tests/binary_image_diff" name="output" value="out5.tiff" ftype="tiff"/>
+            <expand macro="tests/binary_image_diff" name="output" value="output/input5_li.tiff" ftype="tiff"/>
+        </test>
+        <test>
+            <param name="input" value="input/input8_zyx.zarr"/>
+            <conditional name="th_method">
+                <param name="method_id" value="yen"/>
+            </conditional>
+            <expand macro="tests/binary_image_diff" name="output" value="output/input8_yen.tiff" ftype="tiff"/>
         </test>
     </tests>
     <help>
 
-        **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.
 
     </help>
     <citations>
--- a/creators.xml	Sat Jun 07 18:38:16 2025 +0000
+++ b/creators.xml	Sat Jan 03 14:42:51 2026 +0000
@@ -5,6 +5,11 @@
         <yield />
     </xml>
 
+    <xml name="creators/kostrykin">
+        <person givenName="Leonid" familyName="Kostrykin"/>
+        <yield/>
+    </xml>
+
     <xml name="creators/rmassei">
         <person givenName="Riccardo" familyName="Massei"/>
         <yield/>
@@ -30,4 +35,9 @@
         <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/test-data/input/README.md	Sat Jan 03 14:42:51 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*
Binary file test-data/input/input5.jpg has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/ome-zarr-examples/LICENSE	Sat Jan 03 14:42:51 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.
--- /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:42:51 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
--- /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:42:51 2026 +0000
@@ -0,0 +1,3 @@
+{
+    "zarr_format": 2
+}
\ No newline at end of file
--- /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:42:51 2026 +0000
@@ -0,0 +1,25 @@
+{
+    "chunks": [
+        1,
+        50,
+        100
+    ],
+    "compressor": {
+        "blocksize": 0,
+        "clevel": 5,
+        "cname": "lz4",
+        "id": "blosc",
+        "shuffle": 1
+    },
+    "dimension_separator": "/",
+    "dtype": "<f8",
+    "fill_value": 0.0,
+    "filters": null,
+    "order": "C",
+    "shape": [
+        2,
+        100,
+        100
+    ],
+    "zarr_format": 2
+}
\ No newline at end of file
Binary file test-data/input/ome-zarr-examples/image-03.zarr/0/0/0/0 has changed
Binary file test-data/input/ome-zarr-examples/image-03.zarr/0/0/1/0 has changed
Binary file test-data/input/ome-zarr-examples/image-03.zarr/0/1/0/0 has changed
Binary file test-data/input/ome-zarr-examples/image-03.zarr/0/1/1/0 has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/ome-zarr-examples/image-03.zarr/labels/.zattrs	Sat Jan 03 14:42:51 2026 +0000
@@ -0,0 +1,3 @@
+{
+    "labels": []
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/ome-zarr-examples/image-03.zarr/labels/.zgroup	Sat Jan 03 14:42:51 2026 +0000
@@ -0,0 +1,3 @@
+{
+    "zarr_format": 2
+}
\ No newline at end of file
Binary file test-data/input/rgb.png has changed
Binary file test-data/input/sample.tiff has changed
Binary file test-data/input/sample2.tiff has changed
Binary file test-data/out1.tiff has changed
Binary file test-data/out2.tiff has changed
Binary file test-data/out3.tiff has changed
Binary file test-data/out4.tiff has changed
Binary file test-data/out5.tiff has changed
Binary file test-data/output/input5_li.tiff has changed
Binary file test-data/output/input8_yen.tiff has changed
Binary file test-data/output/out1.tiff has changed
Binary file test-data/output/out2.tiff has changed
Binary file test-data/output/out3.tiff has changed
Binary file test-data/output/out4.tiff has changed
Binary file test-data/output/out5.tiff has changed
Binary file test-data/output/rgb_otsu.tiff has changed
Binary file test-data/output/sample_manual_180_240.tiff has changed
Binary file test-data/rgb.png has changed
Binary file test-data/rgb_otsu.tiff has changed
Binary file test-data/sample.tiff has changed
Binary file test-data/sample2.tiff has changed
Binary file test-data/sample_manual_180_240.tiff has changed