diff --git a/docs/source/conf.py b/docs/source/conf.py index 8973b1e9..4b168a31 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -81,8 +81,8 @@ # from https://github.com/ipython/ipywidgets/blob/master/docs/source/conf.py _release = {} exec(compile(open('../../ipyvolume/_version.py').read(), '../../ipyvolume/_version.py', 'exec'), _release) -version = '.'.join(map(str, _release['__version_tuple__'][:2])) release = _release['__version__'] +version = ".".join(_release['__version__'].split(".")[:2]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -386,5 +386,6 @@ 'examples/popup': 'examples/screenshot/ipyvolume-popup-legend-iris.gif', 'examples/lighting': 'examples/screenshot/volume-rendering-specular.gif', 'examples/slice': 'examples/screenshot/ipyvolume-slice-head.gif', + 'examples/volume-clipping': 'examples/screenshot/volume-clip.gif', } exclude_patterns = ['**.ipynb_checkpoints'] diff --git a/docs/source/examples.rst b/docs/source/examples.rst index 62f5f943..374ebf38 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -15,6 +15,8 @@ Examples examples/lighting examples/popup examples/slice + examples/slice + examples/volume-clipping Feel free to contribute new examples: diff --git a/docs/source/examples/screenshot/volume-clip.gif b/docs/source/examples/screenshot/volume-clip.gif new file mode 100644 index 00000000..b6b31740 Binary files /dev/null and b/docs/source/examples/screenshot/volume-clip.gif differ diff --git a/docs/source/examples/volume-clipping.ipynb b/docs/source/examples/volume-clipping.ipynb new file mode 100644 index 00000000..7b81c929 --- /dev/null +++ b/docs/source/examples/volume-clipping.ipynb @@ -0,0 +1,90 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "580c6579", + "metadata": {}, + "source": [ + "# Clipping of volume\n", + "\n", + "In order to inspect volumetric renderings, it may be useful to remove parts of it. Using the `clip_x_min`, `clip_x_max`, `clip_y_min`, `clip_y_max`, `clip_z_min` and `clip_z_max` traits, we can control which part of the volume is rendered. In the example below you can use the sliders labels 'xmin' and 'xmax' to inspect regions inside the volume." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c7e47337", + "metadata": {}, + "outputs": [], + "source": [ + "import ipyvolume as ipv\n", + "fig = ipv.figure()\n", + "volume = ipv.examples.head(show=False, description=\"Patient X\")\n", + "ipv.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34ea1395", + "metadata": {}, + "outputs": [], + "source": [ + "import ipywidgets as w\n", + "clip_min = w.IntSlider(description=\"xmin\", min=0, max=128, value=50)\n", + "clip_max = w.IntSlider(description=\"xmax\", min=0, max=128, value=100)\n", + "w.jslink((clip_min, 'value'), (volume, 'clip_x_min'))\n", + "w.jslink((clip_max, 'value'), (volume, 'clip_x_max'))\n", + "container = ipv.gcc()\n", + "container.children = container.children + [clip_min, clip_max]\n" + ] + }, + { + "cell_type": "markdown", + "id": "81702c3d", + "metadata": {}, + "source": [ + "Note that you can also link the instance the `slice_x` trait of the figure to the `clip_x_max` trait. Now we can hold the shift key and the clip plane will follow the mouse cursor." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "68b54115", + "metadata": {}, + "outputs": [], + "source": [ + "w.jslink((fig, 'slice_x'), (volume, 'clip_x_max'));" + ] + }, + { + "cell_type": "markdown", + "id": "22f48b7f", + "metadata": {}, + "source": [ + "[screencapture](screenshot/volume-clip.gif)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/ipyvolume/vue/container.vue b/ipyvolume/vue/container.vue index f31f517f..25c1097c 100644 --- a/ipyvolume/vue/container.vue +++ b/ipyvolume/vue/container.vue @@ -12,7 +12,9 @@ Misc - +
+ +
diff --git a/ipyvolume/widgets.py b/ipyvolume/widgets.py index 6f2eb07a..b6b512bb 100644 --- a/ipyvolume/widgets.py +++ b/ipyvolume/widgets.py @@ -193,6 +193,13 @@ class Volume(widgets.Widget, LegendData): extent = traitlets.Any().tag(sync=True) extent_original = traitlets.Any() + clip_x_min = traitlets.CFloat(None, allow_none=True).tag(sync=True) + clip_x_max = traitlets.CFloat(None, allow_none=True).tag(sync=True) + clip_y_min = traitlets.CFloat(None, allow_none=True).tag(sync=True) + clip_y_max = traitlets.CFloat(None, allow_none=True).tag(sync=True) + clip_z_min = traitlets.CFloat(None, allow_none=True).tag(sync=True) + clip_z_max = traitlets.CFloat(None, allow_none=True).tag(sync=True) + def __init__(self, **kwargs): super(Volume, self).__init__(**kwargs) self._update_data() diff --git a/js/src/volume.ts b/js/src/volume.ts index 873abd0d..8f8e0ea7 100644 --- a/js/src/volume.ts +++ b/js/src/volume.ts @@ -118,6 +118,9 @@ class VolumeView extends widgets.WidgetView { this.renderer.rebuild_multivolume_rendering_material(); this.renderer.update(); }); + this.model.on("change:clip_x_min change:clip_x_max change:clip_y_min change:clip_y_max change:clip_z_min change:clip_z_max", () => { + this.renderer.update(); + }); (window as any).last_volume = this; // for debugging purposes @@ -168,6 +171,7 @@ class VolumeModel extends widgets.WidgetModel { }; volume: any; + scales?: any; texture_volume: THREE.DataTexture; uniform_volumes_values: { data_range?: any, @@ -321,13 +325,26 @@ class VolumeModel extends widgets.WidgetModel { return this.get("rendering_method") === "NORMAL"; } set_scales(scales) { - const sx = createD3Scale(scales.x).range([0, 1]); - const sy = createD3Scale(scales.y).range([0, 1]); - const sz = createD3Scale(scales.z).range([0, 1]); + this.scales = scales; + this.update_geometry(); + } + update_geometry() { + const sx = createD3Scale(this.scales.x).range([0, 1]); + const sy = createD3Scale(this.scales.y).range([0, 1]); + const sz = createD3Scale(this.scales.z).range([0, 1]); const extent = this.get("extent"); - // normalized coordinates of the corners of the box + // return v, of the clipped value + const or_clip = (v, name) => { + const clip_value = this.get("clip_" + name); + if ((clip_value === undefined) || (clip_value === null)) { + return v; + } + return clip_value; + } + + // normalized coordinates of the corners of the box const x0n = sx(extent[0][0]); const x1n = sx(extent[0][1]); const y0n = sy(extent[1][0]); @@ -335,13 +352,15 @@ class VolumeModel extends widgets.WidgetModel { const z0n = sz(extent[2][0]); const z1n = sz(extent[2][1]); - // clipped coordinates - const cx0 = Math.max(x0n, 0); - const cx1 = Math.min(x1n, 1); - const cy0 = Math.max(y0n, 0); - const cy1 = Math.min(y1n, 1); - const cz0 = Math.max(z0n, 0); - const cz1 = Math.min(z1n, 1); + // normalized coordinates of the corners of the box + // including the custom clipping, and viewport clipping + const cx0 = Math.max(sx(or_clip(extent[0][0], "x_min")), 0); + const cx1 = Math.min(sx(or_clip(extent[0][1], "x_max")), 1); + const cy0 = Math.max(sy(or_clip(extent[1][0], "y_min")), 0); + const cy1 = Math.min(sy(or_clip(extent[1][1], "y_max")), 1); + const cz0 = Math.max(sz(or_clip(extent[2][0], "z_min")), 0); + const cz1 = Math.min(sz(or_clip(extent[2][1], "z_max")), 1); + // the clipped coordinates back to world space, then normalized to extend // these are example calculations, the transform goes into scale and offset uniforms below diff --git a/ui-tests/reference-output/screenshots/lighting_ipynb_cell_0.png b/ui-tests/reference-output/screenshots/lighting_ipynb_cell_0.png index a8c2efe7..852c456c 100644 Binary files a/ui-tests/reference-output/screenshots/lighting_ipynb_cell_0.png and b/ui-tests/reference-output/screenshots/lighting_ipynb_cell_0.png differ diff --git a/ui-tests/reference-output/screenshots/lighting_ipynb_cell_1.png b/ui-tests/reference-output/screenshots/lighting_ipynb_cell_1.png index a8c2efe7..852c456c 100644 Binary files a/ui-tests/reference-output/screenshots/lighting_ipynb_cell_1.png and b/ui-tests/reference-output/screenshots/lighting_ipynb_cell_1.png differ diff --git a/ui-tests/reference-output/screenshots/lighting_ipynb_cell_2.png b/ui-tests/reference-output/screenshots/lighting_ipynb_cell_2.png index 9e66a8c8..98efe8d9 100644 Binary files a/ui-tests/reference-output/screenshots/lighting_ipynb_cell_2.png and b/ui-tests/reference-output/screenshots/lighting_ipynb_cell_2.png differ diff --git a/ui-tests/reference-output/screenshots/lighting_ipynb_cell_3.png b/ui-tests/reference-output/screenshots/lighting_ipynb_cell_3.png index 214e8155..47c2b1c8 100644 Binary files a/ui-tests/reference-output/screenshots/lighting_ipynb_cell_3.png and b/ui-tests/reference-output/screenshots/lighting_ipynb_cell_3.png differ diff --git a/ui-tests/reference-output/screenshots/lighting_ipynb_cell_4.png b/ui-tests/reference-output/screenshots/lighting_ipynb_cell_4.png index 2056eb44..c070d9b0 100644 Binary files a/ui-tests/reference-output/screenshots/lighting_ipynb_cell_4.png and b/ui-tests/reference-output/screenshots/lighting_ipynb_cell_4.png differ diff --git a/ui-tests/reference-output/screenshots/lighting_ipynb_cell_5.png b/ui-tests/reference-output/screenshots/lighting_ipynb_cell_5.png index 0c417f27..526ad857 100644 Binary files a/ui-tests/reference-output/screenshots/lighting_ipynb_cell_5.png and b/ui-tests/reference-output/screenshots/lighting_ipynb_cell_5.png differ diff --git a/ui-tests/reference-output/screenshots/lighting_ipynb_cell_6.png b/ui-tests/reference-output/screenshots/lighting_ipynb_cell_6.png index b9f092a1..acd04d2d 100644 Binary files a/ui-tests/reference-output/screenshots/lighting_ipynb_cell_6.png and b/ui-tests/reference-output/screenshots/lighting_ipynb_cell_6.png differ diff --git a/ui-tests/reference-output/screenshots/scatter_ipynb_cell_0.png b/ui-tests/reference-output/screenshots/scatter_ipynb_cell_0.png index 49689576..62fc8838 100644 Binary files a/ui-tests/reference-output/screenshots/scatter_ipynb_cell_0.png and b/ui-tests/reference-output/screenshots/scatter_ipynb_cell_0.png differ