Repository '2d_feature_extraction'
hg clone https://toolshed.g2.bx.psu.edu/repos/imgteam/2d_feature_extraction

Changeset 8:0b5ddf7b2924 (2026-01-04)
Previous changeset 7:6a2b26c297a4 (2025-12-17) Next changeset 9:45076fd788c9 (2026-01-05)
Commit message:
planemo upload for repository https://github.com/BMCV/galaxy-image-analysis/tree/master/tools/2d_feature_extraction/ commit bb2d58ed37d8eb09583b86e3cdd9f5d1b56c42a0
modified:
2d_feature_extraction.py
2d_feature_extraction.xml
added:
test-data/input/README.md
test-data/input/input10.zarr/0/c/0/0/0
test-data/input/input10.zarr/0/c/1/0/0
test-data/input/input10.zarr/0/zarr.json
test-data/input/input10.zarr/1/c/0/0/0
test-data/input/input10.zarr/1/c/1/0/0
test-data/input/input10.zarr/1/zarr.json
test-data/input/input10.zarr/2/c/0/0/0
test-data/input/input10.zarr/2/c/1/0/0
test-data/input/input10.zarr/2/zarr.json
test-data/input/input10.zarr/3/c/0/0/0
test-data/input/input10.zarr/3/c/1/0/0
test-data/input/input10.zarr/3/zarr.json
test-data/input/input10.zarr/4/c/1/0/0
test-data/input/input10.zarr/4/zarr.json
test-data/input/input10.zarr/zarr.json
test-data/input/input11.tiff
test-data/input/input12.png
test-data/input/input13.zarr/0/c/0/0
test-data/input/input13.zarr/0/zarr.json
test-data/input/input13.zarr/zarr.json
test-data/input/input1_uint8.tiff
test-data/input/input3_uint16.tiff
test-data/input/input9.zarr/0/c/0/0/0
test-data/input/input9.zarr/0/c/1/0/0
test-data/input/input9.zarr/0/zarr.json
test-data/input/input9.zarr/1/c/0/0/0
test-data/input/input9.zarr/1/c/1/0/0
test-data/input/input9.zarr/1/zarr.json
test-data/input/input9.zarr/2/c/0/0/0
test-data/input/input9.zarr/2/c/1/0/0
test-data/input/input9.zarr/2/zarr.json
test-data/input/input9.zarr/3/c/0/0/0
test-data/input/input9.zarr/3/c/1/0/0
test-data/input/input9.zarr/3/zarr.json
test-data/input/input9.zarr/4/c/0/0/0
test-data/input/input9.zarr/4/c/1/0/0
test-data/input/input9.zarr/4/zarr.json
test-data/input/input9.zarr/zarr.json
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/output/input11.tsv
test-data/output/input12.tsv
test-data/output/input13-area.tsv
test-data/output/input13-area_convex.tsv
test-data/output/input13-area_filled.tsv
test-data/output/input13-axis_major_length.tsv
test-data/output/input13-axis_minor_length.tsv
test-data/output/input13-bbox.tsv
test-data/output/input13-centroid.tsv
test-data/output/input13-eccentricity.tsv
test-data/output/input13-equivalent_diameter_area.tsv
test-data/output/input13-euler_number.tsv
test-data/output/input13-extent.tsv
test-data/output/input13-inertia_tensor_eigvals.tsv
test-data/output/input13-max_intensity.tsv
test-data/output/input13-mean_intensity.tsv
test-data/output/input13-min_intensity.tsv
test-data/output/input13-moments.tsv
test-data/output/input13-moments_hu.tsv
test-data/output/input13-orientation.tsv
test-data/output/input13-perimeter.tsv
test-data/output/input13-solidity.tsv
test-data/output/input9.tsv
validators.xml
removed:
test-data/input.tiff
test-data/out.tsv
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 2d_feature_extraction.py
--- a/2d_feature_extraction.py Wed Dec 17 11:23:18 2025 +0000
+++ b/2d_feature_extraction.py Sun Jan 04 20:56:49 2026 +0000
[
b'@@ -1,127 +1,115 @@\n-import argparse\n-\n-import giatools.io\n+import giatools\n import numpy as np\n import pandas as pd\n-import skimage.feature\n+import scipy.ndimage as ndi\n import skimage.measure\n-import skimage.morphology\n-import skimage.segmentation\n+\n+# Fail early if an optional backend is not available\n+giatools.require_backend(\'omezarr\')\n+\n+\n+def surface(labels: np.ndarray, label: int) -> int:\n+    """\n+    Ad-hoc implementation for computation of the "perimeter" of an object in 3D (that is a surface).\n+    """\n+    assert labels.ndim == 3  # sanity check\n+\n+    # Create 3-D structuring element with 4-connectivity\n+    selem = np.zeros((3, 3, 3), bool)\n+    for ijk in np.ndindex(*selem.shape):\n+        if (np.array(ijk) == 1).sum() >= 2:\n+            selem[*ijk] = True   # noqa: E999\n+    assert selem.sum() == 7  # sanity check\n+\n+    # Compute the area of the surface\n+    cc = (labels == label)\n+    cc_interior = ndi.binary_erosion(cc, selem)\n+    surface = np.logical_xor(cc, cc_interior)\n+    return surface.sum()  # number of voxels on the surface of the object\n+\n+\n+def compute_if_dask(obj):\n+    """\n+    Return the computed object or array if it is a Dask array or deferred computable Dask object.\n+    """\n+    return obj.compute() if hasattr(obj, \'compute\') else obj\n \n \n if __name__ == \'__main__\':\n+    tool = giatools.ToolBaseplate()\n+    tool.add_input_image(\'labels\')\n+    tool.add_input_image(\'intensities\', required=False)\n+    tool.parser.add_argument(\'--output\', type=str)\n+    tool.parse_args()\n \n-    parser = argparse.ArgumentParser(description=\'Extract image features\')\n+    # Validate the input image\n+    try:\n+        label_image = tool.args.input_images[\'labels\']\n+        if any(label_image.shape[label_image.axes.index(axis)] > 1 for axis in label_image.axes if axis not in \'ZYX\'):\n+            raise ValueError(f\'This tool is not applicable to images with {label_image.original_axes} axes.\')\n+\n+        # Extract the image features\n+        for section in tool.run(\'ZYX\'):  # the validation code above guarantees that we will have only a single iteration\n+            df = pd.DataFrame()\n \n-    # TODO create factory for boilerplate code\n-    features = parser.add_argument_group(\'compute features\')\n-    features.add_argument(\'--all\', dest=\'all_features\', action=\'store_true\')\n-    features.add_argument(\'--label\', dest=\'add_label\', action=\'store_true\')\n-    features.add_argument(\'--patches\', dest=\'add_roi_patches\', action=\'store_true\')\n-    features.add_argument(\'--max_intensity\', dest=\'max_intensity\', action=\'store_true\')\n-    features.add_argument(\'--mean_intensity\', dest=\'mean_intensity\', action=\'store_true\')\n-    features.add_argument(\'--min_intensity\', dest=\'min_intensity\', action=\'store_true\')\n-    features.add_argument(\'--moments_hu\', dest=\'moments_hu\', action=\'store_true\')\n-    features.add_argument(\'--centroid\', dest=\'centroid\', action=\'store_true\')\n-    features.add_argument(\'--bbox\', dest=\'bbox\', action=\'store_true\')\n-    features.add_argument(\'--area\', dest=\'area\', action=\'store_true\')\n-    features.add_argument(\'--filled_area\', dest=\'filled_area\', action=\'store_true\')\n-    features.add_argument(\'--convex_area\', dest=\'convex_area\', action=\'store_true\')\n-    features.add_argument(\'--perimeter\', dest=\'perimeter\', action=\'store_true\')\n-    features.add_argument(\'--extent\', dest=\'extent\', action=\'store_true\')\n-    features.add_argument(\'--eccentricity\', dest=\'eccentricity\', action=\'store_true\')\n-    features.add_argument(\'--equivalent_diameter\', dest=\'equivalent_diameter\', action=\'store_true\')\n-    features.add_argument(\'--euler_number\', dest=\'euler_number\', action=\'store_true\')\n-    features.add_argument(\'--inertia_tensor_eigvals\', dest=\'inertia_tensor_eigvals\', action=\'store_true\')\n-    features.add_argument(\'--major_axis_length\', dest=\'major_axis_length\', action=\'store_true\')\n-    features.add_argument(\'--minor_axis_length\', dest=\'minor_axis_length\', action=\'store_true\')\n-    features.add_argument(\'--orientati'..b'             # Add the object perimeter/surface\n+                elif feature_name == \'perimeter\' and labels_section_data.ndim == 3:\n+                    df[\'perimeter\'] = df[\'it\'].map(\n+                        lambda ait: surface(labels_section_data, regions[ait].label),  # `skimage.measure.regionprops` cannot compute perimeters for 3-D data\n+                    )\n \n-    if args.centroid or args.all_features:\n-        df[\'centroid\'] = df[\'it\'].map(lambda ait: regions[ait].centroid)\n-    if args.bbox or args.all_features:\n-        df[\'bbox\'] = df[\'it\'].map(lambda ait: regions[ait].bbox)\n-    if args.area or args.all_features:\n-        df[\'area\'] = df[\'it\'].map(lambda ait: regions[ait].area)\n-    if args.filled_area or args.all_features:\n-        df[\'filled_area\'] = df[\'it\'].map(lambda ait: regions[ait].filled_area)\n-    if args.convex_area or args.all_features:\n-        df[\'convex_area\'] = df[\'it\'].map(lambda ait: regions[ait].convex_area)\n-    if args.perimeter or args.all_features:\n-        df[\'perimeter\'] = df[\'it\'].map(lambda ait: regions[ait].perimeter)\n-    if args.extent or args.all_features:\n-        df[\'extent\'] = df[\'it\'].map(lambda ait: regions[ait].extent)\n-    if args.eccentricity or args.all_features:\n-        df[\'eccentricity\'] = df[\'it\'].map(lambda ait: regions[ait].eccentricity)\n-    if args.equivalent_diameter or args.all_features:\n-        df[\'equivalent_diameter\'] = df[\'it\'].map(lambda ait: regions[ait].equivalent_diameter)\n-    if args.euler_number or args.all_features:\n-        df[\'euler_number\'] = df[\'it\'].map(lambda ait: regions[ait].euler_number)\n-    if args.inertia_tensor_eigvals or args.all_features:\n-        df[\'inertia_tensor_eigvals\'] = df[\'it\'].map(lambda ait: regions[ait].inertia_tensor_eigvals)\n-    if args.major_axis_length or args.all_features:\n-        df[\'major_axis_length\'] = df[\'it\'].map(lambda ait: regions[ait].major_axis_length)\n-    if args.minor_axis_length or args.all_features:\n-        df[\'minor_axis_length\'] = df[\'it\'].map(lambda ait: regions[ait].minor_axis_length)\n-    if args.orientation or args.all_features:\n-        df[\'orientation\'] = df[\'it\'].map(lambda ait: regions[ait].orientation)\n-    if args.solidity or args.all_features:\n-        df[\'solidity\'] = df[\'it\'].map(lambda ait: regions[ait].solidity)\n-    if args.moments or args.all_features:\n-        df[\'moments\'] = df[\'it\'].map(lambda ait: regions[ait].moments)\n-    if args.convexity or args.all_features:\n-        perimeter = df[\'it\'].map(lambda ait: regions[ait].perimeter)\n-        area = df[\'it\'].map(lambda ait: regions[ait].area)\n-        df[\'convexity\'] = area / (perimeter * perimeter)\n+                # Skip features that are not available when processing 3-D images\n+                elif feature_name in (\'eccentricity\', \'moments_hu\', \'orientation\') and labels_section_data.ndim == 3:\n+                    print(f\'Skip feature that is not available for 3-D images: "{feature_name}"\')\n+\n+                # Add another feature from `regions` that was computed via `skimage.measure.regionprops`\n+                else:\n+                    try:\n+                        df[feature_name] = df[\'it\'].map(lambda ait: getattr(regions[ait], feature_name))\n+                    except TypeError:\n+                        raise ValueError(f\'Unknown feature: "{feature_name}"\')\n \n-    del df[\'it\']\n-    df.to_csv(out_file, sep=\'\\t\', lineterminator=\'\\n\', index=False)\n+            # Resolve any remaining Dask objects to the actual values (e.g., when processing Zarrs)\n+            df = df.map(compute_if_dask)\n+\n+            # Convert lists/tuples/arrays to lists of plain Python numbers (e.g., float instead of np.float64)\n+            df = df.map(\n+                lambda obj: np.asarray(obj).tolist() if type(obj) in (list, tuple, np.ndarray) else obj,\n+            )\n+\n+            del df[\'it\']\n+            df.to_csv(tool.args.raw_args.output, sep=\'\\t\', lineterminator=\'\\n\', index=False)\n+\n+    except ValueError as err:\n+        exit(err.args[0])\n'
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 2d_feature_extraction.xml
--- a/2d_feature_extraction.xml Wed Dec 17 11:23:18 2025 +0000
+++ b/2d_feature_extraction.xml Sun Jan 04 20:56:49 2026 +0000
[
b'@@ -2,84 +2,122 @@\n     <description>with scikit-image</description>\n     <macros>\n         <import>creators.xml</import>\n-        <token name="@TOOL_VERSION@">0.18.1</token>\n+        <import>validators.xml</import>\n+        <token name="@TOOL_VERSION@">0.25.2</token>\n         <token name="@VERSION_SUFFIX@">0</token>\n+        <xml name="features">\n+            <param name="features" type="select" label="Available features" multiple="true" display="checkboxes"\n+                   help="*) Features marked with an asterisk are only available for 2-D images (not for 3-D images).">\n+                <option value="label" selected="true">Label from the label map</option>\n+                <yield/>\n+                <option value="area">Area</option>\n+                <option value="area_convex">Convex area</option>\n+                <option value="area_filled">Filled area</option>\n+                <option value="axis_major_length">Major axis length</option>\n+                <option value="axis_minor_length">Minor axis length</option>\n+                <option value="bbox">Bounding box</option>\n+                <option value="centroid">Centroid</option>\n+                <option value="eccentricity">Eccentricity*</option>\n+                <option value="equivalent_diameter_area">Equivalent diameter</option>\n+                <option value="euler_number">Euler number</option>\n+                <option value="extent">Extent</option>\n+                <option value="inertia_tensor_eigvals">Inertia tensor eigenvalues</option>\n+                <option value="moments">Moments</option>\n+                <option value="moments_hu">Moments Hu*</option>\n+                <option value="orientation">Orientation*</option>\n+                <option value="perimeter">Perimeter</option>\n+                <option value="solidity">Solidity</option>\n+            </param>\n+        </xml>\n+        <xml name="zarr-isolated-feature-test" tokens="feature">\n+            <test>\n+                <param name="labels" value="input/input13.zarr"/>\n+                <conditional name="setup">\n+                    <param name="mode" value="with-intensities"/>\n+                    <param name="intensities" value="input/input1_uint8.tiff"/>\n+                    <param name="features" value="@FEATURE@"/>\n+                </conditional>\n+                <output name="output" ftype="tabular" value="output/input13-@FEATURE@.tsv"/>\n+            </test>\n+        </xml>\n     </macros>\n     <creator>\n         <expand macro="creators/bmcv"/>\n+        <expand macro="creators/kostrykin"/>\n     </creator>\n     <edam_operations>\n         <edam_operation>operation_3443</edam_operation>\n     </edam_operations>\n     <xrefs>\n         <xref type="bio.tools">scikit-image</xref>\n+        <xref type="bio.tools">giatools</xref>\n+        <xref type="bio.tools">galaxy_image_analysis</xref>\n         <xref type="biii">scikit-image</xref>\n     </xrefs>\n     <requirements>\n         <requirement type="package" version="@TOOL_VERSION@">scikit-image</requirement>\n         <requirement type="package" version="2.2.2">pandas</requirement>\n-        <requirement type="package" version="1.23.3">numpy</requirement>\n-        <requirement type="package" version="2020.10.1">tifffile</requirement>\n-        <requirement type="package" version="0.1">giatools</requirement>\n+        <requirement type="package" version="0.7.3">giatools</requirement>\n+        <requirement type="package" version="0.12.2">ome-zarr</requirement>\n     </requirements>\n-    <command detect_errors="aggressive">\n-    <![CDATA[\n-    python \'$__tool_directory__/2d_feature_extraction.py\'\n-    #if $feature_options[\'features\'] == \'all\'\n-      --all\n-    #else if $feature_options[\'features\'] == \'select\'\n-      ${\' \'.join(str( $feature_options[\'selected_features\'] ).split(\',\'))}\n-    #end if\n-    #if $use_raw_option[\'use_raw\'] == \'raw_image\'\n-      --raw \'$input_raw\'\n-    #end if\n-    \'$input_label\' \'$output\'\n-    ]]>\n-    </command>\n+    <required_files>\n+ '..b' value="label"/>\n+            </conditional>\n+            <assert_stderr>\n+                <!-- Rejected by validator -->\n+                <has_n_lines n="0"/>\n+            </assert_stderr>\n+            <assert_stdout>\n+                <!-- Rejected by validator -->\n+                <has_n_lines n="0"/>\n+            </assert_stdout>\n+        </test>\n+        <test expect_failure="true">\n+            <param name="labels" value="input/input10.zarr"/>\n+            <conditional name="setup">\n+                <param name="mode" value="without-intensities"/>\n+                <param name="features" value="label"/>\n+            </conditional>\n+            <assert_stderr>\n+                <!-- Rejected by py-script -->\n+                <has_text text="This tool is not applicable to images with CYX axes."/>\n+            </assert_stderr>\n         </test>\n     </tests>\n     <help>\n \n-        **Computes features of a label map.**\n+**Computes features of a label map.**\n+\n+The computed features are computed based solely on the properties of the objects in the label map, or, optionally, by also taking the intensities from a corresponding intensity image into account. Labels with value 0 are ignored.\n+\n+Both images must be 2-D or 3-D single-channel images.\n+\n+Supported Features\n+==================\n+\n+**Area:** Area of the region i.e. number of pixels of the region scaled by pixel-area.\n+\n+**Convex area:** Area of the convex hull image, which is the smallest convex polygon that encloses the region.\n+\n+**Filled area:** Area of the region with all the holes filled in.\n+\n+**Major axis length:** The length of the major axis of the ellipse that has the same normalized second central moments as the region.\n+\n+**Minor axis length:** The length of the minor axis of the ellipse that has the same normalized second central moments as the region.\n+\n+**Bounding box:** Bounding box `(min_row, min_col, max_row, max_col)`. Pixels belonging to the bounding box are in the half-open interval `[min_row; max_row)` and `[min_col; max_col)`.\n \n-        The computed features are computed based solely on the properties of the labels in the label map,\n-        or, optionally, by also taking the intensities from a corresponding intensity image into account.\n+**Centroid:** Centroid coordinate tuple `(row, col)`.\n+\n+**Eccentricity:** Eccentricity of the ellipse that has the same second-moments as the region. The eccentricity is the ratio of the focal distance (distance between focal points) over the major axis length. The value is in the interval [0, 1). When it is 0, the ellipse becomes a circle.\n+\n+**Equivalent diameter:** The diameter of a circle with the same area as the region.\n+\n+**Euler number:** Euler characteristic of the set of non-zero pixels. Computed as number of connected components subtracted by number of holes (input.ndim connectivity). In 3-D, number of connected components plus number of holes subtracted by number of tunnels.\n+\n+**Extent:** Ratio of pixels in the region to pixels in the total bounding box. Computed as `area / (rows * cols)`.\n \n-        The label map must be a 2-D or 3-D single-channel image.\n+**Inertia tensor eigenvalues:** The eigenvalues of the inertia tensor in decreasing order.\n+\n+**Moments:** Spatial moments up to 3rd order: `m_ij = sum{ array(row, col) * row^i * col^j }` where the sum is over the row, col coordinates of the region.\n+\n+**Moments Hu:** Hu moments (translation, scale and rotation invariant).\n+\n+**Orientation:** Angle between the vertical axis (rows) and the major axis of the ellipse that has the same second moments as the region, ranging from `-pi/2` to `pi/2` counter-clockwise.\n+\n+**Perimeter:** Perimeter of object which approximates the contour as a line through the centers of border pixels using a 4-connectivity. Generalizes to the surface of an object in 3-D, which uses a different approximation.\n+\n+**Solidity:** Ratio of pixels in the region to pixels of the convex hull image.\n     \n     </help>\n     <citations>\n'
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input.tiff
b
Binary file test-data/input.tiff has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/README.md
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/README.md Sun Jan 04 20:56:49 2026 +0000
b
@@ -0,0 +1,73 @@
+# Overview of the test images
+
+## Label maps
+
+### `input9.zarr`:
+
+- axes: `ZYX`
+- resolution: `(2, 100, 100)`
+- dtype: `bool`
+- binary image
+- metadata:
+  - resolution: `(1.0, 1.0)`
+  - z-spacing: `1.0`
+  - unit: `um`
+
+### `input11.tiff`
+
+- axes: `YX`
+- resolution: `(265, 329)`
+- dtype: `uint16`
+- binary image
+
+### `input12.png`
+
+- axes: `YX`
+- resolution: `(58, 64)`
+- dtype: `uint8`
+- labels: `0...24`
+
+### `input13.zarr`
+
+- axes: `YX`
+- resolution: `(265, 329)`
+- dtype: `uint8`
+- labels: `0...2`
+
+## Intensity images
+
+### `input1_uint8.tiff`:
+
+- axes: `YX`
+- resolution: `(265, 329)`
+- dtype: `uint8`
+- metadata: none
+
+### `input3_uint16.tiff`:
+
+- axes: `YXC`
+- resolution: `(58, 64, 3)`
+- dtype: `uint16`
+- metadata:
+  - resolution: `(2.0, 1.0)`
+  - unit: `mm`
+
+### `input8_zyx.zarr`:
+
+- axes: `ZYX`
+- resolution: `(2, 100, 100)`
+- dtype: `float64`
+- 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`
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input10.zarr/0/c/0/0/0
b
Binary file test-data/input/input10.zarr/0/c/0/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input10.zarr/0/c/1/0/0
b
Binary file test-data/input/input10.zarr/0/c/1/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input10.zarr/0/zarr.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/input10.zarr/0/zarr.json Sun Jan 04 20:56:49 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
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input10.zarr/1/c/0/0/0
b
Binary file test-data/input/input10.zarr/1/c/0/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input10.zarr/1/c/1/0/0
b
Binary file test-data/input/input10.zarr/1/c/1/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input10.zarr/1/zarr.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/input10.zarr/1/zarr.json Sun Jan 04 20:56:49 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
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input10.zarr/2/c/0/0/0
b
Binary file test-data/input/input10.zarr/2/c/0/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input10.zarr/2/c/1/0/0
b
Binary file test-data/input/input10.zarr/2/c/1/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input10.zarr/2/zarr.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/input10.zarr/2/zarr.json Sun Jan 04 20:56:49 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
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input10.zarr/3/c/0/0/0
b
Binary file test-data/input/input10.zarr/3/c/0/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input10.zarr/3/c/1/0/0
b
Binary file test-data/input/input10.zarr/3/c/1/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input10.zarr/3/zarr.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/input10.zarr/3/zarr.json Sun Jan 04 20:56:49 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
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input10.zarr/4/c/1/0/0
b
Binary file test-data/input/input10.zarr/4/c/1/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input10.zarr/4/zarr.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/input10.zarr/4/zarr.json Sun Jan 04 20:56:49 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
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input10.zarr/zarr.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/input10.zarr/zarr.json Sun Jan 04 20:56:49 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
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input11.tiff
b
Binary file test-data/input/input11.tiff has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input12.png
b
Binary file test-data/input/input12.png has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input13.zarr/0/c/0/0
b
Binary file test-data/input/input13.zarr/0/c/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input13.zarr/0/zarr.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/input13.zarr/0/zarr.json Sun Jan 04 20:56:49 2026 +0000
[
@@ -0,0 +1,43 @@
+{
+  "shape": [
+    265,
+    329
+  ],
+  "data_type": "uint8",
+  "chunk_grid": {
+    "name": "regular",
+    "configuration": {
+      "chunk_shape": [
+        265,
+        329
+      ]
+    }
+  },
+  "chunk_key_encoding": {
+    "name": "default",
+    "configuration": {
+      "separator": "/"
+    }
+  },
+  "fill_value": 0,
+  "codecs": [
+    {
+      "name": "bytes"
+    },
+    {
+      "name": "zstd",
+      "configuration": {
+        "level": 0,
+        "checksum": false
+      }
+    }
+  ],
+  "attributes": {},
+  "dimension_names": [
+    "Y",
+    "X"
+  ],
+  "zarr_format": 3,
+  "node_type": "array",
+  "storage_transformers": []
+}
\ No newline at end of file
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input13.zarr/zarr.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/input13.zarr/zarr.json Sun Jan 04 20:56:49 2026 +0000
[
@@ -0,0 +1,38 @@
+{
+  "attributes": {
+    "ome": {
+      "version": "0.5",
+      "multiscales": [
+        {
+          "datasets": [
+            {
+              "path": "0",
+              "coordinateTransformations": [
+                {
+                  "type": "scale",
+                  "scale": [
+                    1.0,
+                    1.0
+                  ]
+                }
+              ]
+            }
+          ],
+          "name": "/",
+          "axes": [
+            {
+              "name": "Y",
+              "type": "space"
+            },
+            {
+              "name": "X",
+              "type": "space"
+            }
+          ]
+        }
+      ]
+    }
+  },
+  "zarr_format": 3,
+  "node_type": "group"
+}
\ No newline at end of file
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input1_uint8.tiff
b
Binary file test-data/input/input1_uint8.tiff has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input3_uint16.tiff
b
Binary file test-data/input/input3_uint16.tiff has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input9.zarr/0/c/0/0/0
b
Binary file test-data/input/input9.zarr/0/c/0/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input9.zarr/0/c/1/0/0
b
Binary file test-data/input/input9.zarr/0/c/1/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input9.zarr/0/zarr.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/input9.zarr/0/zarr.json Sun Jan 04 20:56:49 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
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input9.zarr/1/c/0/0/0
b
Binary file test-data/input/input9.zarr/1/c/0/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input9.zarr/1/c/1/0/0
b
Binary file test-data/input/input9.zarr/1/c/1/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input9.zarr/1/zarr.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/input9.zarr/1/zarr.json Sun Jan 04 20:56:49 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
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input9.zarr/2/c/0/0/0
b
Binary file test-data/input/input9.zarr/2/c/0/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input9.zarr/2/c/1/0/0
b
Binary file test-data/input/input9.zarr/2/c/1/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input9.zarr/2/zarr.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/input9.zarr/2/zarr.json Sun Jan 04 20:56:49 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
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input9.zarr/3/c/0/0/0
b
Binary file test-data/input/input9.zarr/3/c/0/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input9.zarr/3/c/1/0/0
b
Binary file test-data/input/input9.zarr/3/c/1/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input9.zarr/3/zarr.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/input9.zarr/3/zarr.json Sun Jan 04 20:56:49 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
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input9.zarr/4/c/0/0/0
b
Binary file test-data/input/input9.zarr/4/c/0/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input9.zarr/4/c/1/0/0
b
Binary file test-data/input/input9.zarr/4/c/1/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input9.zarr/4/zarr.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/input9.zarr/4/zarr.json Sun Jan 04 20:56:49 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
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/input9.zarr/zarr.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/input9.zarr/zarr.json Sun Jan 04 20:56:49 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"
+}
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 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 Sun Jan 04 20:56:49 2026 +0000
b
@@ -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.
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 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 Sun Jan 04 20:56:49 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
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 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 Sun Jan 04 20:56:49 2026 +0000
b
@@ -0,0 +1,3 @@
+{
+    "zarr_format": 2
+}
\ No newline at end of file
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 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 Sun Jan 04 20:56:49 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
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/ome-zarr-examples/image-03.zarr/0/0/0/0
b
Binary file test-data/input/ome-zarr-examples/image-03.zarr/0/0/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/ome-zarr-examples/image-03.zarr/0/0/1/0
b
Binary file test-data/input/ome-zarr-examples/image-03.zarr/0/0/1/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/ome-zarr-examples/image-03.zarr/0/1/0/0
b
Binary file test-data/input/ome-zarr-examples/image-03.zarr/0/1/0/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/ome-zarr-examples/image-03.zarr/0/1/1/0
b
Binary file test-data/input/ome-zarr-examples/image-03.zarr/0/1/1/0 has changed
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/ome-zarr-examples/image-03.zarr/labels/.zattrs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/ome-zarr-examples/image-03.zarr/labels/.zattrs Sun Jan 04 20:56:49 2026 +0000
[
@@ -0,0 +1,3 @@
+{
+    "labels": []
+}
\ No newline at end of file
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/input/ome-zarr-examples/image-03.zarr/labels/.zgroup
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/ome-zarr-examples/image-03.zarr/labels/.zgroup Sun Jan 04 20:56:49 2026 +0000
b
@@ -0,0 +1,3 @@
+{
+    "zarr_format": 2
+}
\ No newline at end of file
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/out.tsv
--- a/test-data/out.tsv Wed Dec 17 11:23:18 2025 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
b
@@ -1,3 +0,0 @@
-label area
-1 8
-2 9
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input11.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input11.tsv Sun Jan 04 20:56:49 2026 +0000
b
@@ -0,0 +1,2 @@
+area mean_intensity perimeter
+20825.0 129.36364945978391 590.399061660723
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input12.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input12.tsv Sun Jan 04 20:56:49 2026 +0000
b
@@ -0,0 +1,25 @@
+label area area_convex solidity
+1 28.0 29.0 0.9655172413793104
+2 17.0 17.0 1.0
+3 9.0 9.0 1.0
+4 15.0 15.0 1.0
+5 23.0 26.0 0.8846153846153846
+6 21.0 21.0 1.0
+7 11.0 11.0 1.0
+8 60.0 80.0 0.75
+9 58.0 86.0 0.6744186046511628
+10 22.0 22.0 1.0
+11 2.0 2.0 1.0
+12 41.0 42.0 0.9761904761904762
+13 74.0 102.0 0.7254901960784313
+14 9.0 9.0 1.0
+15 8.0 8.0 1.0
+16 27.0 28.0 0.9642857142857143
+17 6.0 6.0 1.0
+18 61.0 73.0 0.8356164383561644
+19 55.0 72.0 0.7638888888888888
+20 18.0 23.0 0.782608695652174
+21 1.0 1.0 1.0
+22 4.0 4.0 1.0
+23 10.0 10.0 1.0
+24 8.0 8.0 1.0
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input13-area.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input13-area.tsv Sun Jan 04 20:56:49 2026 +0000
b
@@ -0,0 +1,3 @@
+area
+2500.0
+2500.0
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input13-area_convex.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input13-area_convex.tsv Sun Jan 04 20:56:49 2026 +0000
b
@@ -0,0 +1,3 @@
+area_convex
+2500.0
+2500.0
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input13-area_filled.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input13-area_filled.tsv Sun Jan 04 20:56:49 2026 +0000
b
@@ -0,0 +1,3 @@
+area_filled
+2500.0
+2500.0
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input13-axis_major_length.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input13-axis_major_length.tsv Sun Jan 04 20:56:49 2026 +0000
b
@@ -0,0 +1,3 @@
+axis_major_length
+57.723478758647246
+57.723478758647246
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input13-axis_minor_length.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input13-axis_minor_length.tsv Sun Jan 04 20:56:49 2026 +0000
b
@@ -0,0 +1,3 @@
+axis_minor_length
+57.723478758647246
+57.723478758647246
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input13-bbox.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input13-bbox.tsv Sun Jan 04 20:56:49 2026 +0000
[
@@ -0,0 +1,3 @@
+bbox
+[50, 60, 100, 110]
+[150, 160, 200, 210]
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input13-centroid.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input13-centroid.tsv Sun Jan 04 20:56:49 2026 +0000
[
@@ -0,0 +1,3 @@
+centroid
+[74.5, 84.5]
+[174.5, 184.5]
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input13-eccentricity.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input13-eccentricity.tsv Sun Jan 04 20:56:49 2026 +0000
b
@@ -0,0 +1,3 @@
+eccentricity
+0.0
+0.0
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input13-equivalent_diameter_area.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input13-equivalent_diameter_area.tsv Sun Jan 04 20:56:49 2026 +0000
b
@@ -0,0 +1,3 @@
+equivalent_diameter_area
+56.41895835477563
+56.41895835477563
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input13-euler_number.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input13-euler_number.tsv Sun Jan 04 20:56:49 2026 +0000
b
@@ -0,0 +1,3 @@
+euler_number
+1
+1
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input13-extent.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input13-extent.tsv Sun Jan 04 20:56:49 2026 +0000
b
@@ -0,0 +1,3 @@
+extent
+1.0
+1.0
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input13-inertia_tensor_eigvals.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input13-inertia_tensor_eigvals.tsv Sun Jan 04 20:56:49 2026 +0000
[
@@ -0,0 +1,3 @@
+inertia_tensor_eigvals
+[208.25, 208.25]
+[208.25, 208.25]
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input13-max_intensity.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input13-max_intensity.tsv Sun Jan 04 20:56:49 2026 +0000
b
@@ -0,0 +1,3 @@
+max_intensity
+205.0
+195.0
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input13-mean_intensity.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input13-mean_intensity.tsv Sun Jan 04 20:56:49 2026 +0000
b
@@ -0,0 +1,3 @@
+mean_intensity
+130.1204
+131.9028
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input13-min_intensity.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input13-min_intensity.tsv Sun Jan 04 20:56:49 2026 +0000
b
@@ -0,0 +1,3 @@
+min_intensity
+27.0
+26.0
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input13-moments.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input13-moments.tsv Sun Jan 04 20:56:49 2026 +0000
[
@@ -0,0 +1,3 @@
+moments
+[[2500.0, 61250.0, 2021250.0, 75031250.0], [61250.0, 1500625.0, 49520625.0, 1838265625.0], [2021250.0, 49520625.0, 1634180625.0, 60662765625.0], [75031250.0, 1838265625.0, 60662765625.0, 2251875390625.0]]
+[[2500.0, 61250.0, 2021250.0, 75031250.0], [61250.0, 1500625.0, 49520625.0, 1838265625.0], [2021250.0, 49520625.0, 1634180625.0, 60662765625.0], [75031250.0, 1838265625.0, 60662765625.0, 2251875390625.0]]
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input13-moments_hu.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input13-moments_hu.tsv Sun Jan 04 20:56:49 2026 +0000
[
@@ -0,0 +1,3 @@
+moments_hu
+[0.1666, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
+[0.1666, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input13-orientation.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input13-orientation.tsv Sun Jan 04 20:56:49 2026 +0000
b
@@ -0,0 +1,3 @@
+orientation
+-0.7853981633974483
+-0.7853981633974483
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input13-perimeter.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input13-perimeter.tsv Sun Jan 04 20:56:49 2026 +0000
b
@@ -0,0 +1,3 @@
+perimeter
+196.0
+196.0
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input13-solidity.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input13-solidity.tsv Sun Jan 04 20:56:49 2026 +0000
b
@@ -0,0 +1,3 @@
+solidity
+1.0
+1.0
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 test-data/output/input9.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/input9.tsv Sun Jan 04 20:56:49 2026 +0000
[
@@ -0,0 +1,2 @@
+area area_convex area_filled axis_major_length axis_minor_length bbox centroid equivalent_diameter_area euler_number extent inertia_tensor_eigvals moments perimeter solidity max_intensity mean_intensity min_intensity
+10076.0 19452.0 10076.0 141.5372589111894 2.2275554354875524 [0, 0, 0, 2, 100, 100] [0.4791583961889639, 51.02153632393807, 55.51121476776498] 26.797637758267182 -1 0.5038 [1633.5242978752312, 1001.887883165562, 632.1326150314862] [[[10076.0, 559331.0, 39680985.0, 3075710795.0], [514093.0, 26719026.0, 1828782204.0, 137703070104.0], [34057349.0, 1680671976.0, 112117199682.0, 8307987945522.0], [2545051621.0, 120295106826.0, 7853098605480.0, 574813269790152.0]], [[4828.0, 278845.0, 20126387.0, 1574048161.0], [246982.0, 13386366.0, 940613844.0, 72136219614.0], [16531236.0, 855530424.0, 59225271630.0, 4513705336536.0], [1248767206.0, 62249971170.0, 4259056106556.0, 323493844156146.0]], [[4828.0, 278845.0, 20126387.0, 1574048161.0], [246982.0, 13386366.0, 940613844.0, 72136219614.0], [16531236.0, 855530424.0, 59225271630.0, 4513705336536.0], [1248767206.0, 62249971170.0, 4259056106556.0, 323493844156146.0]], [[4828.0, 278845.0, 20126387.0, 1574048161.0], [246982.0, 13386366.0, 940613844.0, 72136219614.0], [16531236.0, 855530424.0, 59225271630.0, 4513705336536.0], [1248767206.0, 62249971170.0, 4259056106556.0, 323493844156146.0]]] 10076 0.5179930084310097 999.8037382034536 523.4422397609023 0.11458563869848337
b
diff -r 6a2b26c297a4 -r 0b5ddf7b2924 validators.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/validators.xml Sun Jan 04 20:56:49 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>