Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow clipping of volumetric renderings #437

Merged
merged 4 commits into from
Jul 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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']
2 changes: 2 additions & 0 deletions docs/source/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Examples
examples/lighting
examples/popup
examples/slice
examples/slice
examples/volume-clipping

Feel free to contribute new examples:

Expand Down
Binary file added docs/source/examples/screenshot/volume-clip.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
90 changes: 90 additions & 0 deletions docs/source/examples/volume-clipping.ipynb
Original file line number Diff line number Diff line change
@@ -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
}
4 changes: 3 additions & 1 deletion ipyvolume/vue/container.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
<v-expansion-panel>
<v-expansion-panel-header>Misc</v-expansion-panel-header>
<v-expansion-panel-content>
<jupyter-widget v-for="child in children" :key="child" :widget="child"></jupyter-widget>
<div>
<jupyter-widget v-for="child in children" :key="child" :widget="child"></jupyter-widget>
</div>
</v-expansion-panel-content>
</v-expansion-panel>
<v-expansion-panel>
Expand Down
7 changes: 7 additions & 0 deletions ipyvolume/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
41 changes: 30 additions & 11 deletions js/src/volume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -168,6 +171,7 @@ class VolumeModel extends widgets.WidgetModel {
};

volume: any;
scales?: any;
texture_volume: THREE.DataTexture;
uniform_volumes_values: {
data_range?: any,
Expand Down Expand Up @@ -321,27 +325,42 @@ 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]);
const y1n = sy(extent[1][1]);
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
Expand Down
Binary file modified ui-tests/reference-output/screenshots/lighting_ipynb_cell_0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified ui-tests/reference-output/screenshots/lighting_ipynb_cell_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified ui-tests/reference-output/screenshots/lighting_ipynb_cell_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified ui-tests/reference-output/screenshots/lighting_ipynb_cell_3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified ui-tests/reference-output/screenshots/lighting_ipynb_cell_4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified ui-tests/reference-output/screenshots/lighting_ipynb_cell_5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified ui-tests/reference-output/screenshots/lighting_ipynb_cell_6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified ui-tests/reference-output/screenshots/scatter_ipynb_cell_0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.