Skip to content

Commit 27e76b6

Browse files
committed
Connectomes (#221)
1 parent 69b8d57 commit 27e76b6

File tree

4 files changed

+141
-8
lines changed

4 files changed

+141
-8
lines changed

examples/basic.vox.connect.ipynb

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "ca4291ef-8a32-4da2-a630-43f6a9b3e0e0",
6+
"metadata": {},
7+
"source": [
8+
"# Voxels, meshes, streamlines and connectomes\n",
9+
"\n",
10+
"Voxel-based volumes and triangle-based meshes can exist together in the same scene. NiiVue considers connectomes as a special class of meshes. "
11+
]
12+
},
13+
{
14+
"cell_type": "code",
15+
"execution_count": null,
16+
"id": "ee278b27-372d-4b13-b85c-2965ce0ec671",
17+
"metadata": {
18+
"scrolled": true
19+
},
20+
"outputs": [],
21+
"source": [
22+
"from ipyniivue import NiiVue\n",
23+
"import ipywidgets as widgets\n",
24+
"from IPython.display import display\n",
25+
"\n",
26+
"nv = NiiVue()\n",
27+
"\n",
28+
"nv.load_volumes([{\"path\": \"./images/mni152.nii.gz\"}])\n",
29+
"nv.load_meshes([\n",
30+
" {\"path\": \"./images/BrainMesh_ICBM152.lh.mz3\", \"rgba255\": [255, 64, 32, 255]}, \n",
31+
" {\"path\": \"./images/dpsv.trx\"},\n",
32+
" {\"path\": \"./images/connectome.jcon\"},\n",
33+
" ])\n",
34+
"nv.set_mesh_shader(nv.meshes[0].id, 'crosscut')\n",
35+
"\n",
36+
"nv.set_clip_plane(-0.1, 270, 0)\n",
37+
"nv.opts.mesh_xray = 0.02\n",
38+
"\n",
39+
"# UI\n",
40+
"edge_slider = widgets.IntSlider(\n",
41+
" value=10,\n",
42+
" min=5,\n",
43+
" max=30,\n",
44+
" description=\"Edge\",\n",
45+
" readout=False\n",
46+
")\n",
47+
"\n",
48+
"def on_edge_change(change):\n",
49+
" \"\"\"Set edge diametter.\"\"\"\n",
50+
" nv.set_mesh_property(nv.meshes[2].id, \"edge_scale\", change[\"new\"] * 0.1)\n",
51+
"\n",
52+
"edge_slider.observe(on_edge_change, names=\"value\")\n",
53+
"\n",
54+
"node_slider = widgets.IntSlider(\n",
55+
" value=10,\n",
56+
" min=5,\n",
57+
" max=30,\n",
58+
" description=\"Node\",\n",
59+
" readout=False\n",
60+
")\n",
61+
"\n",
62+
"def on_node_change(change):\n",
63+
" \"\"\"Set node diametter.\"\"\"\n",
64+
" nv.set_mesh_property(nv.meshes[2].id, \"node_scale\", change[\"new\"] * 0.1)\n",
65+
"\n",
66+
"node_slider.observe(on_node_change, names=\"value\")\n",
67+
"\n",
68+
"controls = widgets.HBox([edge_slider, node_slider])\n",
69+
"\n",
70+
"display(controls)\n",
71+
"display(nv)"
72+
]
73+
},
74+
{
75+
"cell_type": "code",
76+
"execution_count": null,
77+
"id": "212a98b7-cd67-43fc-9e50-bf919627ddeb",
78+
"metadata": {},
79+
"outputs": [],
80+
"source": []
81+
},
82+
{
83+
"cell_type": "code",
84+
"execution_count": null,
85+
"id": "00695288-2dd3-458f-b819-40330d490975",
86+
"metadata": {},
87+
"outputs": [],
88+
"source": []
89+
},
90+
{
91+
"cell_type": "code",
92+
"execution_count": null,
93+
"id": "528f9a15-039a-4125-b147-76b5fd9487ce",
94+
"metadata": {},
95+
"outputs": [],
96+
"source": []
97+
}
98+
],
99+
"metadata": {},
100+
"nbformat": 4,
101+
"nbformat_minor": 5
102+
}

js/mesh.ts

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,18 @@ function setup_mesh_property_listeners(
166166
nv.onMeshShaderChanged(meshIndex, mesh.meshShaderIndex);
167167
}
168168

169+
function node_scale_changed() {
170+
mesh.nodeScale = mmodel.get("node_scale");
171+
mesh.updateMesh(nv.gl);
172+
nv.updateGLVolume();
173+
}
174+
175+
function edge_scale_changed() {
176+
mesh.edgeScale = mmodel.get("edge_scale");
177+
mesh.updateMesh(nv.gl);
178+
nv.updateGLVolume();
179+
}
180+
169181
function fiber_radius_changed() {
170182
mesh.fiberRadius = mmodel.get("fiber_radius");
171183
mesh.updateMesh(nv.gl);
@@ -221,6 +233,8 @@ function setup_mesh_property_listeners(
221233
colormap_invert_changed();
222234
colorbar_visible_changed();
223235
mesh_shader_index_changed();
236+
edge_scale_changed();
237+
node_scale_changed();
224238
fiber_radius_changed();
225239
fiber_length_changed();
226240
fiber_dither_changed();
@@ -234,6 +248,8 @@ function setup_mesh_property_listeners(
234248
mmodel.on("change:colormap_invert", colormap_invert_changed);
235249
mmodel.on("change:colorbar_visible", colorbar_visible_changed);
236250
mmodel.on("change:mesh_shader_index", mesh_shader_index_changed);
251+
mmodel.on("change:edge_scale", edge_scale_changed);
252+
mmodel.on("change:node_scale", node_scale_changed);
237253
mmodel.on("change:fiber_radius", fiber_radius_changed);
238254
mmodel.on("change:fiber_length", fiber_length_changed);
239255
mmodel.on("change:fiber_dither", fiber_dither_changed);
@@ -252,6 +268,8 @@ function setup_mesh_property_listeners(
252268
mmodel.off("change:colormap_invert", colormap_invert_changed);
253269
mmodel.off("change:colorbar_visible", colorbar_visible_changed);
254270
mmodel.off("change:mesh_shader_index", mesh_shader_index_changed);
271+
mmodel.off("change:edge_scale", edge_scale_changed);
272+
mmodel.off("change:node_scale", node_scale_changed);
255273
mmodel.off("change:fiber_radius", fiber_radius_changed);
256274
mmodel.off("change:fiber_length", fiber_length_changed);
257275
mmodel.off("change:fiber_dither", fiber_dither_changed);
@@ -290,14 +308,23 @@ export async function create_mesh(
290308
} else if (path || data) {
291309
const dataBuffer = path?.data?.buffer || data?.buffer;
292310
const name = path?.name || mmodel.get("name");
293-
mesh = await niivue.NVMesh.readMesh(
294-
dataBuffer as ArrayBuffer,
295-
name,
296-
nv.gl,
297-
mmodel.get("opacity"),
298-
new Uint8Array(mmodel.get("rgba255")),
299-
mmodel.get("visible"),
300-
);
311+
if (typeof name === "string" && name.toLowerCase().endsWith(".jcon")) {
312+
const decoder = new TextDecoder("utf-8");
313+
const jsonText = decoder.decode(dataBuffer);
314+
const jsonObj = JSON.parse(jsonText);
315+
mesh = nv.loadConnectomeAsMesh(jsonObj);
316+
mmodel.set("node_scale", mesh.nodeScale);
317+
mmodel.set("edge_scale", mesh.edgeScale);
318+
} else {
319+
mesh = await niivue.NVMesh.readMesh(
320+
dataBuffer as ArrayBuffer,
321+
name,
322+
nv.gl,
323+
mmodel.get("opacity"),
324+
new Uint8Array(mmodel.get("rgba255")),
325+
mmodel.get("visible"),
326+
);
327+
}
301328
mesh.id = backendId;
302329
} else if (url) {
303330
mesh = await niivue.NVMesh.loadFromUrl({

js/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ export type MeshModel = AnyModel<{
130130
colormap_invert: boolean;
131131
colorbar_visible: boolean;
132132
mesh_shader_index: number;
133+
edge_scale: number;
134+
node_scale: number;
133135
fiber_radius: number;
134136
fiber_length: number;
135137
fiber_dither: number;

src/ipyniivue/widget.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,8 @@ class Mesh(BaseAnyWidget):
329329
colormap_invert = t.Bool(False).tag(sync=True)
330330
colorbar_visible = t.Bool(True).tag(sync=True)
331331
mesh_shader_index = t.Int(default_value=0).tag(sync=True)
332+
edge_scale = t.Float(1.0).tag(sync=True)
333+
node_scale = t.Float(1.0).tag(sync=True)
332334
fiber_radius = t.Float(0.0).tag(sync=True)
333335
fiber_length = t.Float(2.0).tag(sync=True)
334336
fiber_dither = t.Float(0.1).tag(sync=True)

0 commit comments

Comments
 (0)