# HG changeset patch
# User imgteam
# Date 1767970489 0
# Node ID 457514bb6750f4340753d28d69b9ce91d3295e4d
# Parent f8bfa85cac4cb007315ab1838cb417ea29de6e78
planemo upload for repository https://github.com/BMCV/galaxy-image-analysis/tree/master/tools/crop_image/ commit 52a95105291e38f3410e347ed3b60d6acd6d5daa
diff -r f8bfa85cac4c -r 457514bb6750 creators.xml
--- a/creators.xml Fri Jun 06 12:46:50 2025 +0000
+++ b/creators.xml Fri Jan 09 14:54:49 2026 +0000
@@ -5,6 +5,11 @@
+
+
+
+
+
@@ -30,4 +35,9 @@
+
+
+
+
+
diff -r f8bfa85cac4c -r 457514bb6750 crop_image.py
--- a/crop_image.py Fri Jun 06 12:46:50 2025 +0000
+++ b/crop_image.py Fri Jan 09 14:54:49 2026 +0000
@@ -1,8 +1,13 @@
import argparse
import os
+import dask.array as da
+import giatools
+import giatools.image
import numpy as np
-from giatools.image import Image
+
+# Fail early if an optional backend is not available
+giatools.require_backend('omezarr')
def crop_image(
@@ -12,21 +17,39 @@
output_dir: str,
skip_labels: frozenset[int],
):
- image = Image.read(image_filepath)
- labelmap = Image.read(labelmap_filepath)
+ axes = giatools.default_normalized_axes
+ image = giatools.Image.read(image_filepath, normalize_axes=axes)
+ labelmap = giatools.Image.read(labelmap_filepath, normalize_axes=axes)
+
+ # Establish compatibility of multi-channel/frame/etc. images with single-channel/frame/etc. label maps
+ original_labelmap_shape = labelmap.shape
+ for image_s, labelmap_s, (axis_idx, axis) in zip(image.shape, labelmap.shape, enumerate(axes)):
+ if image_s > 1 and labelmap_s == 1 and axis not in 'YX':
+ target_shape = list(labelmap.shape)
+ target_shape[axis_idx] = image_s
- if image.axes != labelmap.axes:
- raise ValueError(f'Axes mismatch between image ({image.axes}) and label map ({labelmap.axes}).')
+ # Broadcast the labelmap data to the target shape without copying
+ if hasattr(labelmap.data, 'compute'):
+ labelmap.data = da.broadcast_to(labelmap.data, target_shape) # `data` is Dask array
+ else:
+ labelmap.data = np.broadcast_to(labelmap.data, target_shape, subok=True) # `data` is NumPy array
- if image.data.shape != labelmap.data.shape:
- raise ValueError(f'Shape mismatch between image ({image.data.shape}) and label map ({labelmap.data.shape}).')
+ # Validate that the shapes of the images are compatible
+ if image.shape != labelmap.shape:
+ labelmap_shape_str = str(original_labelmap_shape)
+ if labelmap.shape != original_labelmap_shape:
+ labelmap_shape_str = f'{labelmap_shape_str}, broadcasted to {labelmap.shape}'
+ raise ValueError(
+ f'Shape mismatch between image {image.shape} and label map {labelmap_shape_str}, with {axes} axes.',
+ )
- for label in np.unique(labelmap.data):
+ # Extract the image crops
+ for label in giatools.image._unique(labelmap.data):
if label in skip_labels:
continue
roi_mask = (labelmap.data == label)
roi = crop_image_to_mask(image.data, roi_mask)
- roi_image = Image(roi, image.axes).normalize_axes_like(image.original_axes)
+ roi_image = giatools.Image(roi, image.axes).normalize_axes_like(image.original_axes)
roi_image.write(os.path.join(output_dir, f'{label}.{output_ext}'))
@@ -42,8 +65,18 @@
for dim in range(data.ndim):
mask1d = mask.any(axis=tuple(i for i in range(mask.ndim) if i != dim))
mask1d_indices = np.where(mask1d)[0]
+
+ # Convert `mask1d_indices` to a NumPy array if it is a Dask array
+ if hasattr(mask1d_indices, 'compute'):
+ mask1d_indices = mask1d_indices.compute()
+
mask1d_indices_cvxhull = np.arange(min(mask1d_indices), max(mask1d_indices) + 1)
- data = data.take(axis=dim, indices=mask1d_indices_cvxhull)
+
+ # Crop the `data` to the minimal bounding box
+ if hasattr(data, 'compute'):
+ data = da.take(data, axis=dim, indices=mask1d_indices_cvxhull) # `data` is a Dask array
+ else:
+ data = data.take(axis=dim, indices=mask1d_indices_cvxhull) # `data` is a NumPy array
return data
diff -r f8bfa85cac4c -r 457514bb6750 crop_image.xml
--- a/crop_image.xml Fri Jun 06 12:46:50 2025 +0000
+++ b/crop_image.xml Fri Jan 09 14:54:49 2026 +0000
@@ -3,16 +3,18 @@
creators.xml
tests.xml
- 0.4.1
+ 0.7.3
0
-
+
+
operation_3443
+ galaxy_image_analysis
giatools
@@ -23,17 +25,34 @@
mkdir ./output &&
python '$__tool_directory__/crop_image.py'
- '$image'
- '$labelmap'
+ #if $image.extension == "zarr"
+ '$image.extra_files_path/$image.metadata.store_root'
+ #else
+ '$image'
+ #end if
+
+ #if $labelmap.extension == "zarr"
+ '$labelmap.extra_files_path/$labelmap.metadata.store_root'
+ #else
+ '$labelmap'
+ #end if
+
'$skip_labels'
- '${image.ext}'
+
+ #if str($image.ext).lower() == 'png'
+ 'png'
+ #else
+ 'tiff'
+ #end if
./output
]]>
-
-
+
+
^\d+(,\d+)*$|^$
@@ -46,7 +65,7 @@
-
+
@@ -55,7 +74,7 @@
-
+
@@ -64,7 +83,7 @@
-
+
@@ -75,18 +94,55 @@
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
+
+
+
+
+
+
@@ -94,9 +150,12 @@
**Crops an image using one or more regions of interest.**
- The image is cropped using a label map that identifies individual regions of interest. The image and the label map must be of equal size.
+ The image is cropped using a label map that identifies individual regions of interest. The image and the label map must be of
+ compatible size. The sizes are compatible if they are equal, or, if the label map can be broadcasted to the size of the image
+ (e.g., if the image is a multi-channel image and the label map is single-channel but has identical width and height).
- This operation preserves the file type of the image, the brightness, and the range of values.
+ This operation preserves the brightness and the range of values of the input image. The file format is also preserved, unless
+ the input image is a Zarr, for which the output image file format is TIFF.
diff -r f8bfa85cac4c -r 457514bb6750 test-data/cyx_uint8.zarr/0/c/0/0/0
Binary file test-data/cyx_uint8.zarr/0/c/0/0/0 has changed
diff -r f8bfa85cac4c -r 457514bb6750 test-data/cyx_uint8.zarr/0/c/1/0/0
Binary file test-data/cyx_uint8.zarr/0/c/1/0/0 has changed
diff -r f8bfa85cac4c -r 457514bb6750 test-data/cyx_uint8.zarr/0/c/2/0/0
Binary file test-data/cyx_uint8.zarr/0/c/2/0/0 has changed
diff -r f8bfa85cac4c -r 457514bb6750 test-data/cyx_uint8.zarr/0/zarr.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/cyx_uint8.zarr/0/zarr.json Fri Jan 09 14:54:49 2026 +0000
@@ -0,0 +1,46 @@
+{
+ "shape": [
+ 3,
+ 16,
+ 20
+ ],
+ "data_type": "uint8",
+ "chunk_grid": {
+ "name": "regular",
+ "configuration": {
+ "chunk_shape": [
+ 1,
+ 16,
+ 20
+ ]
+ }
+ },
+ "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
diff -r f8bfa85cac4c -r 457514bb6750 test-data/cyx_uint8.zarr/zarr.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/cyx_uint8.zarr/zarr.json Fri Jan 09 14:54:49 2026 +0000
@@ -0,0 +1,43 @@
+{
+ "attributes": {
+ "ome": {
+ "version": "0.5",
+ "multiscales": [
+ {
+ "datasets": [
+ {
+ "path": "0",
+ "coordinateTransformations": [
+ {
+ "type": "scale",
+ "scale": [
+ 1.0,
+ 1.0,
+ 1.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
diff -r f8bfa85cac4c -r 457514bb6750 test-data/cyx_uint8_uint8.tiff
Binary file test-data/cyx_uint8_uint8.tiff has changed
diff -r f8bfa85cac4c -r 457514bb6750 test-data/yx_uint8_mask.tiff
Binary file test-data/yx_uint8_mask.tiff has changed
diff -r f8bfa85cac4c -r 457514bb6750 test-data/yx_uint8_mask.zarr/0/c/0/0
Binary file test-data/yx_uint8_mask.zarr/0/c/0/0 has changed
diff -r f8bfa85cac4c -r 457514bb6750 test-data/yx_uint8_mask.zarr/0/zarr.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/yx_uint8_mask.zarr/0/zarr.json Fri Jan 09 14:54:49 2026 +0000
@@ -0,0 +1,43 @@
+{
+ "shape": [
+ 16,
+ 20
+ ],
+ "data_type": "uint8",
+ "chunk_grid": {
+ "name": "regular",
+ "configuration": {
+ "chunk_shape": [
+ 16,
+ 20
+ ]
+ }
+ },
+ "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
diff -r f8bfa85cac4c -r 457514bb6750 test-data/yx_uint8_mask.zarr/zarr.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/yx_uint8_mask.zarr/zarr.json Fri Jan 09 14:54: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