Skip to content

Commit 35b4bd0

Browse files
Merge pull request #437 from widgetti/feat_clip_volume
feat: allow clipping of volumetric renderings
2 parents 0740fc6 + f220660 commit 35b4bd0

File tree

15 files changed

+134
-13
lines changed

15 files changed

+134
-13
lines changed

docs/source/conf.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@
8181
# from https://github.com/ipython/ipywidgets/blob/master/docs/source/conf.py
8282
_release = {}
8383
exec(compile(open('../../ipyvolume/_version.py').read(), '../../ipyvolume/_version.py', 'exec'), _release)
84-
version = '.'.join(map(str, _release['__version_tuple__'][:2]))
8584
release = _release['__version__']
85+
version = ".".join(_release['__version__'].split(".")[:2])
8686

8787
# The language for content autogenerated by Sphinx. Refer to documentation
8888
# for a list of supported languages.
@@ -386,5 +386,6 @@
386386
'examples/popup': 'examples/screenshot/ipyvolume-popup-legend-iris.gif',
387387
'examples/lighting': 'examples/screenshot/volume-rendering-specular.gif',
388388
'examples/slice': 'examples/screenshot/ipyvolume-slice-head.gif',
389+
'examples/volume-clipping': 'examples/screenshot/volume-clip.gif',
389390
}
390391
exclude_patterns = ['**.ipynb_checkpoints']

docs/source/examples.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Examples
1515
examples/lighting
1616
examples/popup
1717
examples/slice
18+
examples/slice
19+
examples/volume-clipping
1820

1921
Feel free to contribute new examples:
2022

1.99 MB
Loading
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "580c6579",
6+
"metadata": {},
7+
"source": [
8+
"# Clipping of volume\n",
9+
"\n",
10+
"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."
11+
]
12+
},
13+
{
14+
"cell_type": "code",
15+
"execution_count": null,
16+
"id": "c7e47337",
17+
"metadata": {},
18+
"outputs": [],
19+
"source": [
20+
"import ipyvolume as ipv\n",
21+
"fig = ipv.figure()\n",
22+
"volume = ipv.examples.head(show=False, description=\"Patient X\")\n",
23+
"ipv.show()"
24+
]
25+
},
26+
{
27+
"cell_type": "code",
28+
"execution_count": null,
29+
"id": "34ea1395",
30+
"metadata": {},
31+
"outputs": [],
32+
"source": [
33+
"import ipywidgets as w\n",
34+
"clip_min = w.IntSlider(description=\"xmin\", min=0, max=128, value=50)\n",
35+
"clip_max = w.IntSlider(description=\"xmax\", min=0, max=128, value=100)\n",
36+
"w.jslink((clip_min, 'value'), (volume, 'clip_x_min'))\n",
37+
"w.jslink((clip_max, 'value'), (volume, 'clip_x_max'))\n",
38+
"container = ipv.gcc()\n",
39+
"container.children = container.children + [clip_min, clip_max]\n"
40+
]
41+
},
42+
{
43+
"cell_type": "markdown",
44+
"id": "81702c3d",
45+
"metadata": {},
46+
"source": [
47+
"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."
48+
]
49+
},
50+
{
51+
"cell_type": "code",
52+
"execution_count": null,
53+
"id": "68b54115",
54+
"metadata": {},
55+
"outputs": [],
56+
"source": [
57+
"w.jslink((fig, 'slice_x'), (volume, 'clip_x_max'));"
58+
]
59+
},
60+
{
61+
"cell_type": "markdown",
62+
"id": "22f48b7f",
63+
"metadata": {},
64+
"source": [
65+
"[screencapture](screenshot/volume-clip.gif)"
66+
]
67+
}
68+
],
69+
"metadata": {
70+
"kernelspec": {
71+
"display_name": "Python 3 (ipykernel)",
72+
"language": "python",
73+
"name": "python3"
74+
},
75+
"language_info": {
76+
"codemirror_mode": {
77+
"name": "ipython",
78+
"version": 3
79+
},
80+
"file_extension": ".py",
81+
"mimetype": "text/x-python",
82+
"name": "python",
83+
"nbconvert_exporter": "python",
84+
"pygments_lexer": "ipython3",
85+
"version": "3.9.16"
86+
}
87+
},
88+
"nbformat": 4,
89+
"nbformat_minor": 5
90+
}

ipyvolume/vue/container.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
<v-expansion-panel>
1313
<v-expansion-panel-header>Misc</v-expansion-panel-header>
1414
<v-expansion-panel-content>
15-
<jupyter-widget v-for="child in children" :key="child" :widget="child"></jupyter-widget>
15+
<div>
16+
<jupyter-widget v-for="child in children" :key="child" :widget="child"></jupyter-widget>
17+
</div>
1618
</v-expansion-panel-content>
1719
</v-expansion-panel>
1820
<v-expansion-panel>

ipyvolume/widgets.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,13 @@ class Volume(widgets.Widget, LegendData):
193193
extent = traitlets.Any().tag(sync=True)
194194
extent_original = traitlets.Any()
195195

196+
clip_x_min = traitlets.CFloat(None, allow_none=True).tag(sync=True)
197+
clip_x_max = traitlets.CFloat(None, allow_none=True).tag(sync=True)
198+
clip_y_min = traitlets.CFloat(None, allow_none=True).tag(sync=True)
199+
clip_y_max = traitlets.CFloat(None, allow_none=True).tag(sync=True)
200+
clip_z_min = traitlets.CFloat(None, allow_none=True).tag(sync=True)
201+
clip_z_max = traitlets.CFloat(None, allow_none=True).tag(sync=True)
202+
196203
def __init__(self, **kwargs):
197204
super(Volume, self).__init__(**kwargs)
198205
self._update_data()

js/src/volume.ts

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ class VolumeView extends widgets.WidgetView {
118118
this.renderer.rebuild_multivolume_rendering_material();
119119
this.renderer.update();
120120
});
121+
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", () => {
122+
this.renderer.update();
123+
});
121124

122125
(window as any).last_volume = this; // for debugging purposes
123126

@@ -168,6 +171,7 @@ class VolumeModel extends widgets.WidgetModel {
168171
};
169172

170173
volume: any;
174+
scales?: any;
171175
texture_volume: THREE.DataTexture;
172176
uniform_volumes_values: {
173177
data_range?: any,
@@ -321,27 +325,42 @@ class VolumeModel extends widgets.WidgetModel {
321325
return this.get("rendering_method") === "NORMAL";
322326
}
323327
set_scales(scales) {
324-
const sx = createD3Scale(scales.x).range([0, 1]);
325-
const sy = createD3Scale(scales.y).range([0, 1]);
326-
const sz = createD3Scale(scales.z).range([0, 1]);
328+
this.scales = scales;
329+
this.update_geometry();
330+
}
331+
update_geometry() {
332+
const sx = createD3Scale(this.scales.x).range([0, 1]);
333+
const sy = createD3Scale(this.scales.y).range([0, 1]);
334+
const sz = createD3Scale(this.scales.z).range([0, 1]);
327335

328336
const extent = this.get("extent");
329337

330-
// normalized coordinates of the corners of the box
338+
// return v, of the clipped value
339+
const or_clip = (v, name) => {
340+
const clip_value = this.get("clip_" + name);
341+
if ((clip_value === undefined) || (clip_value === null)) {
342+
return v;
343+
}
344+
return clip_value;
345+
}
346+
347+
// normalized coordinates of the corners of the box
331348
const x0n = sx(extent[0][0]);
332349
const x1n = sx(extent[0][1]);
333350
const y0n = sy(extent[1][0]);
334351
const y1n = sy(extent[1][1]);
335352
const z0n = sz(extent[2][0]);
336353
const z1n = sz(extent[2][1]);
337354

338-
// clipped coordinates
339-
const cx0 = Math.max(x0n, 0);
340-
const cx1 = Math.min(x1n, 1);
341-
const cy0 = Math.max(y0n, 0);
342-
const cy1 = Math.min(y1n, 1);
343-
const cz0 = Math.max(z0n, 0);
344-
const cz1 = Math.min(z1n, 1);
355+
// normalized coordinates of the corners of the box
356+
// including the custom clipping, and viewport clipping
357+
const cx0 = Math.max(sx(or_clip(extent[0][0], "x_min")), 0);
358+
const cx1 = Math.min(sx(or_clip(extent[0][1], "x_max")), 1);
359+
const cy0 = Math.max(sy(or_clip(extent[1][0], "y_min")), 0);
360+
const cy1 = Math.min(sy(or_clip(extent[1][1], "y_max")), 1);
361+
const cz0 = Math.max(sz(or_clip(extent[2][0], "z_min")), 0);
362+
const cz1 = Math.min(sz(or_clip(extent[2][1], "z_max")), 1);
363+
345364

346365
// the clipped coordinates back to world space, then normalized to extend
347366
// these are example calculations, the transform goes into scale and offset uniforms below
-28 Bytes
Loading
-28 Bytes
Loading
-22 Bytes
Loading

0 commit comments

Comments
 (0)