# HG changeset patch
# User imgteam
# Date 1767643828 0
# Node ID 7d6be2b7e1e2c1e4fd2557a98bcc40a193b7cfb6
planemo upload for repository https://github.com/BMCV/galaxy-image-analysis/tree/master/tools/libcarna/ commit 20db59ffe2a97f25d82ba02e451bf73f93ef84ee
diff -r 000000000000 -r 7d6be2b7e1e2 README.md
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/README.md Mon Jan 05 20:10:28 2026 +0000
@@ -0,0 +1,19 @@
+# libcarna
+
+## HTML output
+
+The tool produces HTML output that needs to be added to the allow list.
+
+## GPU support
+
+The following extra Docker parameters are required to run this tool with GPU support:
+```bash
+--gpus all -e NVIDIA_VISIBLE_DEVICES=all -e NVIDIA_DRIVER_CAPABILITIES=graphics,compute
+```
+Otherwise, the tool runs with software rendering, whuch is much slower.
+
+When using `planemo test`, the full command line is:
+```bash
+planemo test --docker --docker_run_extra_arguments \
+ "--gpus all -e NVIDIA_VISIBLE_DEVICES=all -e NVIDIA_DRIVER_CAPABILITIES=graphics,compute"
+```
diff -r 000000000000 -r 7d6be2b7e1e2 colormaps.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/colormaps.xml Mon Jan 05 20:10:28 2026 +0000
@@ -0,0 +1,320 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff -r 000000000000 -r 7d6be2b7e1e2 creators.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/creators.xml Mon Jan 05 20:10:28 2026 +0000
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff -r 000000000000 -r 7d6be2b7e1e2 render.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/render.py Mon Jan 05 20:10:28 2026 +0000
@@ -0,0 +1,143 @@
+import giatools
+import libcarna
+import libcarna._imshow
+import pandas as pd
+
+# Fail early if an optional backend is not available
+giatools.require_backend('omezarr')
+
+# Patch `libcarna._imshow` to return plain HTML
+libcarna._imshow.IPythonHTML = lambda html: html
+
+
+GEOMETRY_TYPE_INTENSITIES = 0
+GEOMETRY_TYPE_MASK = 1
+
+
+def wrap_color(params: dict) -> dict:
+ """
+ Return the `params` dictionary but wrap values for `color` with `libcarna.color`.
+ """
+ result = dict()
+ for key, value in params.items():
+ if key == 'color':
+ value = libcarna.color(value)
+ result[key] = value
+ return result
+
+
+if __name__ == "__main__":
+ tool = giatools.ToolBaseplate()
+ tool.add_input_image('intensities')
+ tool.add_input_image('mask', required=False)
+ tool.parser.add_argument('--colormap', type=str)
+ tool.parser.add_argument('--html', type=str)
+ tool.parse_args()
+
+ # Load custom colormap
+ if tool.args.raw_args.colormap:
+ df_colormap = pd.read_csv(tool.args.raw_args.colormap, delimiter='\t')
+
+ # Validate the input image(s)
+ try:
+ for image in tool.args.input_images.values():
+ if any(image.shape[image.axes.index(axis)] > 1 for axis in image.axes if axis not in 'ZYX'):
+ raise ValueError(f'This tool is not applicable to images with {image.original_axes} axes.')
+
+ # Create and configure frame renderer
+ print('Sample rate:', tool.args.params['sample_rate'])
+ mode = getattr(libcarna, tool.args.params['mode'])(
+ GEOMETRY_TYPE_INTENSITIES,
+ sr=tool.args.params['sample_rate'],
+ **tool.args.params['mode_kwargs']
+ )
+ mask_renderer = libcarna.mask_renderer(
+ GEOMETRY_TYPE_MASK,
+ sr=tool.args.params['sample_rate'],
+ **wrap_color(tool.args.params['mask_renderer_kwargs']),
+ )
+ r = libcarna.renderer(
+ tool.args.params['width'],
+ tool.args.params['height'],
+ [mode, mask_renderer],
+ )
+ print('EGL Vendor:', r.gl_context.vendor)
+
+ # Build the scene graph
+ root = libcarna.node()
+ intensities = tool.args.input_images['intensities']
+ intensities_volume = libcarna.volume(
+ GEOMETRY_TYPE_INTENSITIES,
+ intensities.normalize_axes_like(tool.args.params['axes']).data,
+ parent=root,
+ spacing=[
+ {
+ 'X': intensities.metadata.pixel_size[0] or 1,
+ 'Y': intensities.metadata.pixel_size[1] or 1,
+ 'Z': intensities.metadata.z_spacing or 1,
+ }
+ [axis] for axis in tool.args.params['axes']
+ ],
+ normals=(tool.args.params['mode'] == 'dvr'),
+ )
+ camera = libcarna.camera(
+ parent=root,
+ ).frustum(
+ **tool.args.params['camera']['kwargs'],
+ ).translate(
+ z=tool.args.params['camera']['distance'],
+ )
+ if (mask := tool.args.input_images.get('mask')):
+ libcarna.volume(
+ GEOMETRY_TYPE_MASK,
+ mask.normalize_axes_like(tool.args.params['axes']).data,
+ parent=intensities_volume,
+ spacing=intensities_volume.spacing,
+ )
+
+ # Apply colormap
+ if tool.args.params['colormap'] == 'custom':
+ mode.cmap.clear()
+ i0, color0 = None, None
+ for row in df_colormap.to_dict(orient='records'):
+ match row['type']:
+ case 'relative':
+ i1 = row['intensity']
+ case 'absolute':
+ i1 = intensities_volume.normalized(row['intensity'])
+ case _:
+ raise ValueError('Unknown intensity type: "{}"'.format(row['type']))
+ color1 = libcarna.color(row['color'])
+ if i0 is not None:
+ mode.cmap.linear_segment(i0, i1, color0, color1)
+ i0, color0 = i1, color1
+ else:
+ cmap_kwargs = dict()
+ if (ramp_params := tool.args.params['ramp']):
+ ramp_values = list()
+ for val_type, value in (
+ (ramp_params['start_type'], ramp_params['start_value']),
+ (ramp_params['end_type'], ramp_params['end_value']),
+ ):
+ ramp_values.append(
+ value if val_type == 'relative' else intensities_volume.normalized(value),
+ )
+ cmap_kwargs['ramp'] = tuple(ramp_values)
+ mode.cmap(tool.args.params['colormap'], **cmap_kwargs)
+
+ # Render
+ html = libcarna.imshow(
+ libcarna.animate(
+ libcarna.animate.rotate_local(camera),
+ n_frames=tool.args.params['video']['frames'],
+ ).render(r, camera),
+ mode.cmap.bar(intensities_volume),
+ fps=tool.args.params['video']['fps'],
+ )
+
+ # Write the result
+ with open(tool.args.raw_args.html, 'w') as fhtml:
+ fhtml.write(html)
+
+ except ValueError as err:
+ exit(err.args[0])
diff -r 000000000000 -r 7d6be2b7e1e2 render.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/render.xml Mon Jan 05 20:10:28 2026 +0000
@@ -0,0 +1,339 @@
+
+ with LibCarna
+
+ creators.xml
+ validators.xml
+ colormaps.xml
+ 0.2.0
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ operation_3443
+
+
+ galaxy_image_analysis
+ giatools
+
+
+
+ docker.io/kostrykin/libcarna-python:@TOOL_VERSION@-0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+**Renders videos for visualization of 3-D image data.**
+
+The image data is rotated to facilitate grasping visual information from different angles.
+
+LibCarna employs a volume rendering technique called *ray marching* and permits different visualization modes, including *maximum intensity projections* (MIP) and *direct volume rendering* (DVR). In addition, the renderings can be augmented with binary or label masks (e.g., segmentation overlays).
+
+.. image:: dvr.gif
+ :width: 438px
+ :scale: 100%
+
+An overview of the available colormaps is available at `matplotlib.org`_.
+
+.. _matplotlib.org: https://matplotlib.org/stable/users/explain/colors/colormaps.html
+
+When using custom colormaps, a tabular file with at least 3 columns must be used (`intensity`, `type`, `color`). Each pair of consecutive rows defines a linear segment of the colormap. The `intensity` values can be given either as absolute intensity values, or as relative values where 0 and 1 correspond to the minimum and maximum intensities of the image data, respectively. The `type` column indicates whether the corresponding `intensity` value is `absolute` or `relative`. The `color` must be given in hexadecimal notation with a ``#`` prefix and must be either 6 or 8 digits long (6 for RGB and 8 for RGBA). An example is given below.
+
++-----------+------------+----------+
+| color | intensity | type |
++-----------+------------+----------+
+| #ff00007f | 0 | absolute |
++-----------+------------+----------+
+| #ffff00ff | 0.5 | absolute |
++-----------+------------+----------+
+| #00ffffff | 1.0 | relative |
++-----------+------------+----------+
+
+
+
+ 10.1016/j.jbiotec.2017.07.019
+
+
diff -r 000000000000 -r 7d6be2b7e1e2 static/images/dvr.gif
Binary file static/images/dvr.gif has changed
diff -r 000000000000 -r 7d6be2b7e1e2 test-data/input/colormap.tsv
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/colormap.tsv Mon Jan 05 20:10:28 2026 +0000
@@ -0,0 +1,4 @@
+color intensity type
+#ff00007f 0 absolute
+#ffff00ff 0.5 absolute
+#00ffffff 1.0 relative
diff -r 000000000000 -r 7d6be2b7e1e2 test-data/input/float32_10z_32y_40x.tiff
Binary file test-data/input/float32_10z_32y_40x.tiff has changed
diff -r 000000000000 -r 7d6be2b7e1e2 test-data/input/float32_5z_16y_20x-no_z_spacing.zarr/0/c/0/0/0
Binary file test-data/input/float32_5z_16y_20x-no_z_spacing.zarr/0/c/0/0/0 has changed
diff -r 000000000000 -r 7d6be2b7e1e2 test-data/input/float32_5z_16y_20x-no_z_spacing.zarr/0/c/1/0/0
Binary file test-data/input/float32_5z_16y_20x-no_z_spacing.zarr/0/c/1/0/0 has changed
diff -r 000000000000 -r 7d6be2b7e1e2 test-data/input/float32_5z_16y_20x-no_z_spacing.zarr/0/c/2/0/0
Binary file test-data/input/float32_5z_16y_20x-no_z_spacing.zarr/0/c/2/0/0 has changed
diff -r 000000000000 -r 7d6be2b7e1e2 test-data/input/float32_5z_16y_20x-no_z_spacing.zarr/0/c/3/0/0
Binary file test-data/input/float32_5z_16y_20x-no_z_spacing.zarr/0/c/3/0/0 has changed
diff -r 000000000000 -r 7d6be2b7e1e2 test-data/input/float32_5z_16y_20x-no_z_spacing.zarr/0/c/4/0/0
Binary file test-data/input/float32_5z_16y_20x-no_z_spacing.zarr/0/c/4/0/0 has changed
diff -r 000000000000 -r 7d6be2b7e1e2 test-data/input/float32_5z_16y_20x-no_z_spacing.zarr/0/zarr.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/float32_5z_16y_20x-no_z_spacing.zarr/0/zarr.json Mon Jan 05 20:10:28 2026 +0000
@@ -0,0 +1,49 @@
+{
+ "shape": [
+ 5,
+ 16,
+ 20
+ ],
+ "data_type": "float32",
+ "chunk_grid": {
+ "name": "regular",
+ "configuration": {
+ "chunk_shape": [
+ 1,
+ 16,
+ 20
+ ]
+ }
+ },
+ "chunk_key_encoding": {
+ "name": "default",
+ "configuration": {
+ "separator": "/"
+ }
+ },
+ "fill_value": 0.0,
+ "codecs": [
+ {
+ "name": "bytes",
+ "configuration": {
+ "endian": "little"
+ }
+ },
+ {
+ "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
diff -r 000000000000 -r 7d6be2b7e1e2 test-data/input/float32_5z_16y_20x-no_z_spacing.zarr/zarr.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/input/float32_5z_16y_20x-no_z_spacing.zarr/zarr.json Mon Jan 05 20:10:28 2026 +0000
@@ -0,0 +1,46 @@
+{
+ "attributes": {
+ "ome": {
+ "version": "0.5",
+ "multiscales": [
+ {
+ "datasets": [
+ {
+ "path": "0",
+ "coordinateTransformations": [
+ {
+ "type": "scale",
+ "scale": [
+ 1.0,
+ 1.0,
+ 1.0
+ ]
+ }
+ ]
+ }
+ ],
+ "name": "/",
+ "axes": [
+ {
+ "name": "Z",
+ "type": "space",
+ "unit": "mm"
+ },
+ {
+ "name": "Y",
+ "type": "space",
+ "unit": "mm"
+ },
+ {
+ "name": "X",
+ "type": "space",
+ "unit": "mm"
+ }
+ ]
+ }
+ ]
+ }
+ },
+ "zarr_format": 3,
+ "node_type": "group"
+}
\ No newline at end of file
diff -r 000000000000 -r 7d6be2b7e1e2 test-data/input/uint8_10z_32y_40x.tiff
Binary file test-data/input/uint8_10z_32y_40x.tiff has changed
diff -r 000000000000 -r 7d6be2b7e1e2 test-data/output/float32_10z_32y_40x-colormap.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/float32_10z_32y_40x-colormap.html Mon Jan 05 20:10:28 2026 +0000
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.542202
+
+
+
+
+ 0.523496
+
+
+
+
+ 0.504791
+
+
+
+
+ 0.486085
+
+
+
+
+ 0.46738
+
+
+
+
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 7d6be2b7e1e2 test-data/output/float32_10z_32y_40x-dvr-mask.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/float32_10z_32y_40x-dvr-mask.html Mon Jan 05 20:10:28 2026 +0000
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.542202
+
+
+
+
+ 0.523496
+
+
+
+
+ 0.504791
+
+
+
+
+ 0.486085
+
+
+
+
+ 0.46738
+
+
+
+
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 7d6be2b7e1e2 test-data/output/float32_10z_32y_40x-mip.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/float32_10z_32y_40x-mip.html Mon Jan 05 20:10:28 2026 +0000
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.542202
+
+
+
+
+ 0.523496
+
+
+
+
+ 0.504791
+
+
+
+
+ 0.486085
+
+
+
+
+ 0.46738
+
+
+
+
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 7d6be2b7e1e2 test-data/output/float32_5z_16y_20x.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/output/float32_5z_16y_20x.html Mon Jan 05 20:10:28 2026 +0000
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 0.51775
+
+
+
+
+ 0.507163
+
+
+
+
+ 0.496576
+
+
+
+
+ 0.485989
+
+
+
+
+ 0.475402
+
+
+
+
+
+
+
+
\ No newline at end of file
diff -r 000000000000 -r 7d6be2b7e1e2 validators.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/validators.xml Mon Jan 05 20:10:28 2026 +0000
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = 2]]>
+
+
+
+
+
+
+
+