# 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]]> + + + + + + + +