Mercurial > repos > imgteam > libcarna_render
comparison render.py @ 0:7d6be2b7e1e2 draft
planemo upload for repository https://github.com/BMCV/galaxy-image-analysis/tree/master/tools/libcarna/ commit 20db59ffe2a97f25d82ba02e451bf73f93ef84ee
| author | imgteam |
|---|---|
| date | Mon, 05 Jan 2026 20:10:28 +0000 |
| parents | |
| children | 31a2e1909ae5 |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:7d6be2b7e1e2 |
|---|---|
| 1 import giatools | |
| 2 import libcarna | |
| 3 import libcarna._imshow | |
| 4 import pandas as pd | |
| 5 | |
| 6 # Fail early if an optional backend is not available | |
| 7 giatools.require_backend('omezarr') | |
| 8 | |
| 9 # Patch `libcarna._imshow` to return plain HTML | |
| 10 libcarna._imshow.IPythonHTML = lambda html: html | |
| 11 | |
| 12 | |
| 13 GEOMETRY_TYPE_INTENSITIES = 0 | |
| 14 GEOMETRY_TYPE_MASK = 1 | |
| 15 | |
| 16 | |
| 17 def wrap_color(params: dict) -> dict: | |
| 18 """ | |
| 19 Return the `params` dictionary but wrap values for `color` with `libcarna.color`. | |
| 20 """ | |
| 21 result = dict() | |
| 22 for key, value in params.items(): | |
| 23 if key == 'color': | |
| 24 value = libcarna.color(value) | |
| 25 result[key] = value | |
| 26 return result | |
| 27 | |
| 28 | |
| 29 if __name__ == "__main__": | |
| 30 tool = giatools.ToolBaseplate() | |
| 31 tool.add_input_image('intensities') | |
| 32 tool.add_input_image('mask', required=False) | |
| 33 tool.parser.add_argument('--colormap', type=str) | |
| 34 tool.parser.add_argument('--html', type=str) | |
| 35 tool.parse_args() | |
| 36 | |
| 37 # Load custom colormap | |
| 38 if tool.args.raw_args.colormap: | |
| 39 df_colormap = pd.read_csv(tool.args.raw_args.colormap, delimiter='\t') | |
| 40 | |
| 41 # Validate the input image(s) | |
| 42 try: | |
| 43 for image in tool.args.input_images.values(): | |
| 44 if any(image.shape[image.axes.index(axis)] > 1 for axis in image.axes if axis not in 'ZYX'): | |
| 45 raise ValueError(f'This tool is not applicable to images with {image.original_axes} axes.') | |
| 46 | |
| 47 # Create and configure frame renderer | |
| 48 print('Sample rate:', tool.args.params['sample_rate']) | |
| 49 mode = getattr(libcarna, tool.args.params['mode'])( | |
| 50 GEOMETRY_TYPE_INTENSITIES, | |
| 51 sr=tool.args.params['sample_rate'], | |
| 52 **tool.args.params['mode_kwargs'] | |
| 53 ) | |
| 54 mask_renderer = libcarna.mask_renderer( | |
| 55 GEOMETRY_TYPE_MASK, | |
| 56 sr=tool.args.params['sample_rate'], | |
| 57 **wrap_color(tool.args.params['mask_renderer_kwargs']), | |
| 58 ) | |
| 59 r = libcarna.renderer( | |
| 60 tool.args.params['width'], | |
| 61 tool.args.params['height'], | |
| 62 [mode, mask_renderer], | |
| 63 ) | |
| 64 print('EGL Vendor:', r.gl_context.vendor) | |
| 65 | |
| 66 # Build the scene graph | |
| 67 root = libcarna.node() | |
| 68 intensities = tool.args.input_images['intensities'] | |
| 69 intensities_volume = libcarna.volume( | |
| 70 GEOMETRY_TYPE_INTENSITIES, | |
| 71 intensities.normalize_axes_like(tool.args.params['axes']).data, | |
| 72 parent=root, | |
| 73 spacing=[ | |
| 74 { | |
| 75 'X': intensities.metadata.pixel_size[0] or 1, | |
| 76 'Y': intensities.metadata.pixel_size[1] or 1, | |
| 77 'Z': intensities.metadata.z_spacing or 1, | |
| 78 } | |
| 79 [axis] for axis in tool.args.params['axes'] | |
| 80 ], | |
| 81 normals=(tool.args.params['mode'] == 'dvr'), | |
| 82 ) | |
| 83 camera = libcarna.camera( | |
| 84 parent=root, | |
| 85 ).frustum( | |
| 86 **tool.args.params['camera']['kwargs'], | |
| 87 ).translate( | |
| 88 z=tool.args.params['camera']['distance'], | |
| 89 ) | |
| 90 if (mask := tool.args.input_images.get('mask')): | |
| 91 libcarna.volume( | |
| 92 GEOMETRY_TYPE_MASK, | |
| 93 mask.normalize_axes_like(tool.args.params['axes']).data, | |
| 94 parent=intensities_volume, | |
| 95 spacing=intensities_volume.spacing, | |
| 96 ) | |
| 97 | |
| 98 # Apply colormap | |
| 99 if tool.args.params['colormap'] == 'custom': | |
| 100 mode.cmap.clear() | |
| 101 i0, color0 = None, None | |
| 102 for row in df_colormap.to_dict(orient='records'): | |
| 103 match row['type']: | |
| 104 case 'relative': | |
| 105 i1 = row['intensity'] | |
| 106 case 'absolute': | |
| 107 i1 = intensities_volume.normalized(row['intensity']) | |
| 108 case _: | |
| 109 raise ValueError('Unknown intensity type: "{}"'.format(row['type'])) | |
| 110 color1 = libcarna.color(row['color']) | |
| 111 if i0 is not None: | |
| 112 mode.cmap.linear_segment(i0, i1, color0, color1) | |
| 113 i0, color0 = i1, color1 | |
| 114 else: | |
| 115 cmap_kwargs = dict() | |
| 116 if (ramp_params := tool.args.params['ramp']): | |
| 117 ramp_values = list() | |
| 118 for val_type, value in ( | |
| 119 (ramp_params['start_type'], ramp_params['start_value']), | |
| 120 (ramp_params['end_type'], ramp_params['end_value']), | |
| 121 ): | |
| 122 ramp_values.append( | |
| 123 value if val_type == 'relative' else intensities_volume.normalized(value), | |
| 124 ) | |
| 125 cmap_kwargs['ramp'] = tuple(ramp_values) | |
| 126 mode.cmap(tool.args.params['colormap'], **cmap_kwargs) | |
| 127 | |
| 128 # Render | |
| 129 html = libcarna.imshow( | |
| 130 libcarna.animate( | |
| 131 libcarna.animate.rotate_local(camera), | |
| 132 n_frames=tool.args.params['video']['frames'], | |
| 133 ).render(r, camera), | |
| 134 mode.cmap.bar(intensities_volume), | |
| 135 fps=tool.args.params['video']['fps'], | |
| 136 ) | |
| 137 | |
| 138 # Write the result | |
| 139 with open(tool.args.raw_args.html, 'w') as fhtml: | |
| 140 fhtml.write(html) | |
| 141 | |
| 142 except ValueError as err: | |
| 143 exit(err.args[0]) |
