Mercurial > repos > imgteam > binary2labelimage
changeset 6:364e235bf378 draft default tip
planemo upload for repository https://github.com/BMCV/galaxy-image-analysis/tree/master/tools/binary2labelimage/ commit f5a4de7535e433e3b0e96e0694e481b6643a54f8
line wrap: on
line diff
--- a/2d_split_binaryimage_by_watershed.py Mon May 12 08:15:44 2025 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -import argparse -import sys - -import numpy as np -import skimage.io -import skimage.util -from scipy import ndimage as ndi -from skimage.feature import peak_local_max -from skimage.segmentation import watershed - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description='Split binaryimage by watershed') - parser.add_argument('input_file', type=argparse.FileType('r'), default=sys.stdin, help='input file') - parser.add_argument('out_file', type=argparse.FileType('w'), default=sys.stdin, help='out file (TIFF)') - parser.add_argument('min_distance', type=int, default=100, help='Minimum distance to next object') - args = parser.parse_args() - - img_in = skimage.io.imread(args.input_file.name) - distance = ndi.distance_transform_edt(img_in) - - local_max_indices = peak_local_max( - distance, - min_distance=args.min_distance, - labels=img_in, - ) - local_max_mask = np.zeros(img_in.shape, dtype=bool) - local_max_mask[tuple(local_max_indices.T)] = True - markers = ndi.label(local_max_mask)[0] - res = watershed(-distance, markers, mask=img_in) - - res = skimage.util.img_as_uint(res) - skimage.io.imsave(args.out_file.name, res, plugin="tifffile")
--- a/binary2label.py Mon May 12 08:15:44 2025 +0000 +++ b/binary2label.py Sat Jan 03 14:14:28 2026 +0000 @@ -1,27 +1,70 @@ -import argparse +import giatools +import numpy as np +import scipy.ndimage as ndi + +# Fail early if an optional backend is not available +giatools.require_backend('omezarr') + -import giatools -import scipy.ndimage as ndi -import tifffile +def label_watershed(arr: np.ndarray, **kwargs) -> np.ndarray: + import skimage.util + from skimage.feature import peak_local_max + from skimage.segmentation import watershed + distance = ndi.distance_transform_edt(arr) + local_max_indices = peak_local_max( + distance, + labels=arr, + **kwargs, + ) + local_max_mask = np.zeros(arr.shape, dtype=bool) + local_max_mask[tuple(local_max_indices.T)] = True + markers = ndi.label(local_max_mask)[0] + res = watershed(-distance, markers, mask=arr) + return skimage.util.img_as_uint(res) # converts to uint16 -# Parse CLI parameters -parser = argparse.ArgumentParser() -parser.add_argument('input', type=str, help='input file') -parser.add_argument('output', type=str, help='output file (TIFF)') -args = parser.parse_args() +if __name__ == '__main__': + + tool = giatools.ToolBaseplate() + tool.add_input_image('input') + tool.add_output_image('output') + tool.parse_args() + + # Validate the input image and the selected method + try: + input_image = tool.args.input_images['input'] + if (method := tool.args.params.pop('method')) == 'watershed' and input_image.shape[input_image.axes.index('Z')] > 1: + raise ValueError(f'Method "{method}" is not applicable to 3-D images.') + + elif input_image.shape[input_image.axes.index('C')] > 1: + raise ValueError('Multi-channel images are forbidden to avoid confusion with multi-channel labels (e.g., RGB labels).') + + else: + + # Choose the requested labeling method + match method: -# Read the input image with the original axes -img = giatools.Image.read(args.input) -img = img.normalize_axes_like( - img.original_axes, -) + case 'cca': + joint_axes = 'ZYX' + label = lambda input_section_bin: ( # noqa: E731 + ndi.label(input_section_bin, **tool.args.params)[0].astype(np.uint16) + ) + + case 'watershed': + joint_axes = 'YX' + label = lambda input_section_bin: ( # noqa: E731 + label_watershed(input_section_bin, **tool.args.params) # already uint16 + ) -# Make sure the image is truly binary -img_arr_bin = (img.data > 0) + case _: + raise ValueError(f'Unknown method: "{method}"') -# Perform the labeling -img.data = ndi.label(img_arr_bin)[0] + # Perform the labeling + for section in tool.run(joint_axes): + section['output'] = label( + section['input'].data > 0, # ensure that the input data is truly binary + ) -# Write the result image (same axes as input image) -tifffile.imwrite(args.output, img.data, metadata=dict(axes=img.axes)) + # Exit and print error to stderr + except ValueError as err: + exit(err.args[0])
--- a/binary2label.xml Mon May 12 08:15:44 2025 +0000 +++ b/binary2label.xml Sat Jan 03 14:14:28 2026 +0000 @@ -1,83 +1,209 @@ <tool id="ip_binary_to_labelimage" name="Convert binary image to label map" version="@TOOL_VERSION@+galaxy@VERSION_SUFFIX@"> - <description></description> + <description>with giatools</description> <macros> <import>creators.xml</import> - <token name="@TOOL_VERSION@">0.6</token> + <import>tests.xml</import> + <import>validators.xml</import> + <token name="@TOOL_VERSION@">0.7.3</token> <token name="@VERSION_SUFFIX@">0</token> + <xml name="input"> + <!-- JPEG is not allowed because it is a lossy compression that has no strictly constant labels --> + <param name="input" type="data" format="tiff,zarr,png" label="Binary image"> + <expand macro="validators/is_binary"/> + <yield/> + </param> + </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> </xrefs> <requirements> - <requirement type="package" version="0.4.0">giatools</requirement> - <requirement type="package" version="1.12.0">scipy</requirement> + <requirement type="package" version="@TOOL_VERSION@">giatools</requirement> + <requirement type="package" version="1.16.3">scipy</requirement> + <requirement type="package" version="0.12.2">ome-zarr</requirement> </requirements> - <command detect_errors="aggressive"> - <![CDATA[ - #if str($mode.mode_selector) == 'cca': - python '$__tool_directory__/binary2label.py' '$input' '$output' - #elif str($mode.mode_selector) == 'watershed': - python '$__tool_directory__/2d_split_binaryimage_by_watershed.py' '$input' '$output' $min_distance + <required_files> + <include type="literal" path="binary2label.py"/> + </required_files> + <command detect_errors="aggressive"><![CDATA[ + + python '$__tool_directory__/binary2label.py' + + #if $setup.input.extension == "zarr" + --input '$setup.input.extra_files_path/$setup.input.metadata.store_root' + #else + --input '$setup.input' #end if - ]]> - </command> + + --output 'output.tiff' + --params '$params' + --verbose + + ]]></command> + <configfiles> + <configfile name="params"><![CDATA[ + { + + #if str($setup.method) == "watershed" + "min_distance": $setup.min_distance, + #end if + + "method": "$setup.method" + + } + ]]></configfile> + </configfiles> <inputs> - <param name="input" type="data" format="tiff,png,jpg,bmp" label="Binary image"/> - <conditional name="mode"> - <param name="mode_selector" type="select" label="Mode"> + <conditional name="setup"> + <param name="method" type="select" label="Mode" + help="Connected component analysis assigns unique labels to objects that are separated by 1 pixel or more. Watershed transform can also separate partially overlapping objects, but is only applicable to 2-D image data."> <option value="cca" selected="true">Connected component analysis</option> <option value="watershed">Watershed transform</option> </param> <when value="cca"> + <expand macro="input"/> </when> <when value="watershed"> - <param name="min_distance" type="integer" min="0" value="5" label="Minimum distance between two objects" /> + <expand macro="input"> + <expand macro="validators/is_2d"/> + </expand> + <param name="min_distance" type="integer" min="0" value="5" label="Minimum distance between two objects"/> </when> </conditional> </inputs> <outputs> - <data format="tiff" name="output"/> + <data format="tiff" name="output" from_work_dir="output.tiff"/> </outputs> <tests> + <!-- Tests for 2-D --> <test> - <param name="input" value="galaxyIcon_noText.tiff" /> - <conditional name="mode"> - <param name="mode_selector" value="cca" /> + <conditional name="setup"> + <param name="method" value="cca"/> + <param name="input" value="input/input11.tiff"/> </conditional> - <output name="output" value="label.tiff" ftype="tiff" compare="image_diff"/> + <expand macro="tests/label_image_diff" name="output" value="output/input11-cca.tiff" ftype="tiff"/> + <assert_stdout> + <has_line line="[input] Input image axes: YX"/> + <has_line line="[input] Input image shape: (265, 329)"/> + <has_line line="[input] Input image dtype: uint16"/> + <has_line line="[output] Output image axes: YX"/> + <has_line line="[output] Output image shape: (265, 329)"/> + <has_line line="[output] Output image dtype: uint16"/> + </assert_stdout> </test> <test> - <param name="input" value="in.tiff"/> - <conditional name="mode"> - <param name="mode_selector" value="watershed" /> - <param name="min_distance" value="10" /> + <conditional name="setup"> + <param name="method" value="watershed"/> + <param name="input" value="input/input11.tiff"/> + <param name="min_distance" value="10"/> </conditional> - <output name="output" value="out.tiff" ftype="tiff" compare="image_diff"/> + <expand macro="tests/label_image_diff" name="output" value="output/input11-watershed.tiff" ftype="tiff"/> + <assert_stdout> + <has_line line="[input] Input image axes: YX"/> + <has_line line="[input] Input image shape: (265, 329)"/> + <has_line line="[input] Input image dtype: uint16"/> + <has_line line="[output] Output image axes: YX"/> + <has_line line="[output] Output image shape: (265, 329)"/> + <has_line line="[output] Output image dtype: uint16"/> + </assert_stdout> </test> + <!-- Tests for 3-D --> <test> - <param name="input" value="uint8_z12_x11_y10.tiff"/> - <conditional name="mode"> - <param name="mode_selector" value="cca" /> + <conditional name="setup"> + <param name="method" value="cca"/> + <param name="input" value="input/input9.zarr"/> + </conditional> + <!-- `label_image_diff` currently does not support 3-D images: https://github.com/galaxyproject/galaxy/pull/21455 --> + <expand macro="tests/intensity_image_diff" name="output" value="output/input9-cca.tiff" ftype="tiff"/> + <assert_stdout> + <has_line line="[input] Input image axes: ZYX"/> + <has_line line="[input] Input image shape: (2, 100, 100)"/> + <has_line line="[input] Input image dtype: bool"/> + <has_line line="[input] Input image resolution=(1.0, 1.0), unit='um', z_spacing=1.0"/> + <has_line line="[output] Output image axes: ZYX"/> + <has_line line="[output] Output image shape: (2, 100, 100)"/> + <has_line line="[output] Output image dtype: uint16"/> + <has_line line="[output] Output image resolution=(1.0, 1.0), unit='um', z_spacing=1.0"/> + </assert_stdout> + </test> + <test expect_failure="true"> + <conditional name="setup"> + <param name="method" value="watershed"/> + <param name="input" value="input/input9.zarr"/> + </conditional> + <assert_stderr> + <!-- Rejected by py-script --> + <has_text text='Method "watershed" is not applicable to 3-D images.'/> + </assert_stderr> + <assert_stdout> + <has_line line="[input] Input image axes: ZYX"/> + <has_line line="[input] Input image shape: (2, 100, 100)"/> + <has_line line="[input] Input image dtype: bool"/> + <has_line line="[input] Input image resolution=(1.0, 1.0), unit='um', z_spacing=1.0"/> + </assert_stdout> + </test> + <test expect_failure="true"> + <conditional name="setup"> + <param name="method" value="watershed"/> + <param name="input" value="input/input9.tiff"/> </conditional> - <output name="output" value="uint8_z12_x11_y10-output.tiff" ftype="tiff" compare="image_diff"> - <assert_contents> - <has_image_width width="11"/> - <has_image_height height="10"/> - <has_image_depth depth="12"/> - </assert_contents> - </output> + <assert_stderr> + <!-- Rejected by validator --> + <has_n_lines n="0"/> + </assert_stderr> + <assert_stdout> + <!-- Rejected by validator --> + <has_n_lines n="0"/> + </assert_stdout> + </test> + <!-- Tests for multi-channel images --> + <test expect_failure="true"> + <conditional name="setup"> + <param name="method" value="cca"/> + <param name="input" value="input/input10.zarr"/> + </conditional> + <assert_stderr> + <!-- Rejected by py-script --> + <has_text text='Multi-channel images are forbidden to avoid confusion with multi-channel labels (e.g., RGB labels).'/> + </assert_stderr> + <assert_stdout> + <has_line line="[input] Input image axes: CYX"/> + <has_line line="[input] Input image shape: (2, 64, 64)"/> + <has_line line="[input] Input image dtype: uint8"/> + <has_line line="[input] Input image resolution=(1.0, 1.0)"/> + </assert_stdout> + </test> + <test expect_failure="true"> + <conditional name="setup"> + <param name="method" value="cca"/> + <param name="input" value="input/rgb.png"/> + </conditional> + <assert_stderr> + <!-- Rejected by validator --> + <has_n_lines n="0"/> + </assert_stderr> + <assert_stdout> + <!-- Rejected by validator --> + <has_n_lines n="0"/> + </assert_stdout> </test> </tests> <help> - This tool assigns each object a unique label. + + **Converts a binary image to a label map.** - Individual objects are determined using connected component analysis, or distance transform and watershed. + This tool assigns each object a unique label. + + Individual objects are determined using connected component analysis, or distance transform and watershed. + </help> <citations> <citation type="doi">10.1016/j.jbiotec.2017.07.019</citation>
--- a/creators.xml Mon May 12 08:15:44 2025 +0000 +++ b/creators.xml Sat Jan 03 14:14:28 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/> @@ -24,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/test-data/input/README.md Sat Jan 03 14:14:28 2026 +0000 @@ -0,0 +1,46 @@ +# Overview of the test images + +## `input9.tiff`: + +- axes: `ZYX` +- resolution: `(2, 100, 100)` +- dtype: `bool` +- binary image +- metadata: + - resolution: `(1.0, 1.0)` + - z-spacing: `1.0` + - unit: `um` + +## `input9.zarr`: + +- axes: `ZYX` +- resolution: `(2, 100, 100)` +- dtype: `bool` +- binary image +- metadata: + - resolution: `(1.0, 1.0)` + - z-spacing: `1.0` + - unit: `um` + +## `input10.zarr`: + +- axes: `CYX` +- resolution: `(2, 64, 64)` +- dtype: `uint8` +- metadata: + - resolution: `(1.0, 1.0)` + - z-spacing: `1.0` + - unit: `um` + +## `input11.tiff` + +- axes: `YX` +- resolution: `(265, 329)` +- dtype: `uint16` +- binary image + +## `rgb.png`: + +- axes: `YXC` +- resolution: `(6, 6, 3)` +- dtype: `uint8`
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test-data/input/input10.zarr/0/zarr.json Sat Jan 03 14:14:28 2026 +0000 @@ -0,0 +1,46 @@ +{ + "shape": [ + 2, + 64, + 64 + ], + "data_type": "uint8", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1, + 100, + 100 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes" + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "dimension_names": [ + "c", + "y", + "x" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test-data/input/input10.zarr/1/zarr.json Sat Jan 03 14:14:28 2026 +0000 @@ -0,0 +1,46 @@ +{ + "shape": [ + 2, + 32, + 32 + ], + "data_type": "uint8", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1, + 100, + 100 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes" + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "dimension_names": [ + "c", + "y", + "x" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test-data/input/input10.zarr/2/zarr.json Sat Jan 03 14:14:28 2026 +0000 @@ -0,0 +1,46 @@ +{ + "shape": [ + 2, + 16, + 16 + ], + "data_type": "uint8", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1, + 100, + 100 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes" + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "dimension_names": [ + "c", + "y", + "x" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test-data/input/input10.zarr/3/zarr.json Sat Jan 03 14:14:28 2026 +0000 @@ -0,0 +1,46 @@ +{ + "shape": [ + 2, + 8, + 8 + ], + "data_type": "uint8", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1, + 100, + 100 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes" + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "dimension_names": [ + "c", + "y", + "x" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test-data/input/input10.zarr/4/zarr.json Sat Jan 03 14:14:28 2026 +0000 @@ -0,0 +1,46 @@ +{ + "shape": [ + 2, + 4, + 4 + ], + "data_type": "uint8", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1, + 100, + 100 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": 0, + "codecs": [ + { + "name": "bytes" + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "dimension_names": [ + "c", + "y", + "x" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test-data/input/input10.zarr/zarr.json Sat Jan 03 14:14:28 2026 +0000 @@ -0,0 +1,95 @@ +{ + "attributes": { + "ome": { + "version": "0.5", + "multiscales": [ + { + "datasets": [ + { + "path": "0", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 1.0, + 1.0 + ] + } + ] + }, + { + "path": "1", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 2.0, + 2.0 + ] + } + ] + }, + { + "path": "2", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 4.0, + 4.0 + ] + } + ] + }, + { + "path": "3", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 8.0, + 8.0 + ] + } + ] + }, + { + "path": "4", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 16.0, + 16.0 + ] + } + ] + } + ], + "name": "/", + "axes": [ + { + "name": "c", + "type": "channel" + }, + { + "name": "y", + "type": "space" + }, + { + "name": "x", + "type": "space" + } + ] + } + ] + } + }, + "zarr_format": 3, + "node_type": "group" +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test-data/input/input9.zarr/0/zarr.json Sat Jan 03 14:14:28 2026 +0000 @@ -0,0 +1,46 @@ +{ + "shape": [ + 2, + 100, + 100 + ], + "data_type": "bool", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1, + 100, + 100 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": false, + "codecs": [ + { + "name": "bytes" + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "dimension_names": [ + "z", + "y", + "x" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test-data/input/input9.zarr/1/zarr.json Sat Jan 03 14:14:28 2026 +0000 @@ -0,0 +1,46 @@ +{ + "shape": [ + 2, + 50, + 50 + ], + "data_type": "bool", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1, + 100, + 100 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": false, + "codecs": [ + { + "name": "bytes" + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "dimension_names": [ + "z", + "y", + "x" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test-data/input/input9.zarr/2/zarr.json Sat Jan 03 14:14:28 2026 +0000 @@ -0,0 +1,46 @@ +{ + "shape": [ + 2, + 25, + 25 + ], + "data_type": "bool", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1, + 100, + 100 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": false, + "codecs": [ + { + "name": "bytes" + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "dimension_names": [ + "z", + "y", + "x" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test-data/input/input9.zarr/3/zarr.json Sat Jan 03 14:14:28 2026 +0000 @@ -0,0 +1,46 @@ +{ + "shape": [ + 2, + 12, + 12 + ], + "data_type": "bool", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1, + 100, + 100 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": false, + "codecs": [ + { + "name": "bytes" + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "dimension_names": [ + "z", + "y", + "x" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test-data/input/input9.zarr/4/zarr.json Sat Jan 03 14:14:28 2026 +0000 @@ -0,0 +1,46 @@ +{ + "shape": [ + 2, + 6, + 6 + ], + "data_type": "bool", + "chunk_grid": { + "name": "regular", + "configuration": { + "chunk_shape": [ + 1, + 100, + 100 + ] + } + }, + "chunk_key_encoding": { + "name": "default", + "configuration": { + "separator": "/" + } + }, + "fill_value": false, + "codecs": [ + { + "name": "bytes" + }, + { + "name": "zstd", + "configuration": { + "level": 0, + "checksum": false + } + } + ], + "attributes": {}, + "dimension_names": [ + "z", + "y", + "x" + ], + "zarr_format": 3, + "node_type": "array", + "storage_transformers": [] +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test-data/input/input9.zarr/zarr.json Sat Jan 03 14:14:28 2026 +0000 @@ -0,0 +1,98 @@ +{ + "attributes": { + "ome": { + "version": "0.5", + "multiscales": [ + { + "datasets": [ + { + "path": "0", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 1.0, + 1.0 + ] + } + ] + }, + { + "path": "1", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 2.0, + 2.0 + ] + } + ] + }, + { + "path": "2", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 4.0, + 4.0 + ] + } + ] + }, + { + "path": "3", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 8.333333333333334, + 8.333333333333334 + ] + } + ] + }, + { + "path": "4", + "coordinateTransformations": [ + { + "type": "scale", + "scale": [ + 1.0, + 16.666666666666668, + 16.666666666666668 + ] + } + ] + } + ], + "name": "/", + "axes": [ + { + "name": "z", + "type": "space", + "unit": "micrometer" + }, + { + "name": "y", + "type": "space", + "unit": "micrometer" + }, + { + "name": "x", + "type": "space", + "unit": "micrometer" + } + ] + } + ] + } + }, + "zarr_format": 3, + "node_type": "group" +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests.xml Sat Jan 03 14:14:28 2026 +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>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/validators.xml Sat Jan 03 14:14:28 2026 +0000 @@ -0,0 +1,45 @@ +<macros> + + <!-- Macros for validation of inputs --> + + <xml name="validators/is_single_channel"> + <!-- + The OME-Zarr datatype in Galaxy is currently not derived from the Image datatype, and it does + hence not inherit the metadata fields like `channels`. To cope with that, we allow all datasets + except those where we *know* that they are *not* single-channel. + --> + <validator type="expression" message="Dataset is a multi-channel image" + ><![CDATA[getattr(value.metadata, "channels", None) in (None, '') or int(value.metadata.channels) < 2]]></validator> + </xml> + + <xml name="validators/is_single_frame"> + <!-- + The OME-Zarr datatype in Galaxy is currently not derived from the Image datatype, and it does + hence not inherit the metadata fields like `frames`. To cope with that, we allow all datasets + except those where we *know* that they are *not* single-frame. + --> + <validator type="expression" message="Dataset is a multi-frame image" + ><![CDATA[getattr(value.metadata, "frames", None) in (None, '') or int(value.metadata.frames) < 2]]></validator> + </xml> + + <xml name="validators/is_2d"> + <!-- + The OME-Zarr datatype in Galaxy is currently not derived from the Image datatype, and it does + hence not inherit the metadata fields like `depth`. To cope with that, we allow all datasets + except those where we *know* that they are *not* 2-D. + --> + <validator type="expression" message="Dataset is a 3-D image" + ><![CDATA[getattr(value.metadata, "depth", None) in (None, '') or int(value.metadata.depth) < 2]]></validator> + </xml> + + <xml name="validators/is_binary"> + <!-- + The OME-Zarr datatype in Galaxy is currently not derived from the Image datatype, and it does + hence not inherit the metadata fields like `num_unique_values`. To cope with that, we allow all + datasets except those where we *know* that they are *not* binary. + --> + <validator type="expression" message="Dataset is not a binary image" + ><![CDATA[getattr(value.metadata, "num_unique_values", None) in (None, '') or int(value.metadata.num_unique_values) <= 2]]></validator> + </xml> + +</macros>
