├── .gitignore ├── dashboard.png ├── attention-mri.tif ├── environment.yml ├── README.md └── app.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints 2 | */.ipynb_checkpoints/* -------------------------------------------------------------------------------- /dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hdsingh/Brain-Explorer/HEAD/dashboard.png -------------------------------------------------------------------------------- /attention-mri.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hdsingh/Brain-Explorer/HEAD/attention-mri.tif -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: brain_env 2 | channels: 3 | - conda-forge 4 | - defaults 5 | dependencies: 6 | - bokeh/label/dev::bokeh 7 | - pyviz/label/dev::datashader 8 | - pyviz/label/dev::holoviews 9 | - pyviz/label/dev::hvplot 10 | - pyviz/label/dev::panel 11 | - pyviz/label/dev::param 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Brain-Explorer 2 | 3 | [](https://mybinder.org/v2/gh/hdsingh/Brain-Explorer/master) 4 | 5 | This notebook shows 3D visualization of Brain. 6 | 7 |  8 | 9 | ## Installation 10 | 11 | 1. git clone https://github.com/hdsingh/Brain-Explorer 12 | 13 | 2. cd Brain-Explorer 14 | 15 | 3. `conda env create -f environment.yml` or 16 | ``` 17 | conda create env -n brain_env 18 | conda activate brain_env 19 | conda install -c pyviz/label/dev -c bokeh/label/dev bokeh datashader holoviews hvplot param panel 20 | ``` 21 | 22 | ## Deployment 23 | 24 | Deploy this dashboard from the CLI using: 25 | 26 | $ panel serve app.ipynb 27 | -------------------------------------------------------------------------------- /app.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import panel as pn\n", 10 | "from bokeh.plotting import figure\n", 11 | "import holoviews as hv\n", 12 | "from holoviews.streams import Params\n", 13 | "import param\n", 14 | "hv.extension('bokeh')\n", 15 | "pn.extension()" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": null, 21 | "metadata": {}, 22 | "outputs": [], 23 | "source": [ 24 | "def make_volume():\n", 25 | " \"\"\"This function makes a 3D array of data, a brain scan\"\"\"\n", 26 | " from skimage import io\n", 27 | " vol = io.imread(\n", 28 | "# \"https://s3.amazonaws.com/assets.datacamp.com/blog_assets/\" # Since the file has been downloaded\n", 29 | " \"attention-mri.tif\")\n", 30 | " return vol\n", 31 | "\n", 32 | "\n", 33 | "TOOLTIPS = [\n", 34 | " (\"x\", \"$x\"),\n", 35 | " (\"y\", \"$y\"),\n", 36 | " (\"value\", \"@image\"),\n", 37 | "]\n", 38 | "shape = (157, 189, 68)\n", 39 | "volume = make_volume()\n", 40 | "inverted = False\n" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": null, 46 | "metadata": {}, 47 | "outputs": [], 48 | "source": [ 49 | "def make():\n", 50 | " \"\"\"Here we make a figure widget with a 2D image and put it in the layout\"\"\"\n", 51 | " global inverted\n", 52 | " p1 = figure(width=400, height=400, tools='hover,wheel_zoom',\n", 53 | " tooltips=TOOLTIPS,\n", 54 | " x_range=[0, 157], y_range=[0, 189])\n", 55 | " if inverted:\n", 56 | " p1.image(image=[volume[:, ::-1, 30]], x=[0], y=[0], dw=[157], dh=[189])\n", 57 | " else:\n", 58 | " p1.image(image=[volume[:, :, 30]], x=[0], y=[0], dw=[157], dh=[189])\n", 59 | " widgets[1] = p1\n", 60 | " inverted = not inverted" 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": null, 66 | "metadata": {}, 67 | "outputs": [], 68 | "source": [ 69 | "button = pn.widgets.Button(name='Invert')\n", 70 | "button.on_click(lambda *_: make())\n", 71 | "widgets = pn.Column(button, button)\n", 72 | "make()\n", 73 | "widgets\n" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": null, 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "volume.shape" 83 | ] 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "metadata": {}, 88 | "source": [ 89 | "## 1. Using simple Dynamic Map and Params" 90 | ] 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "metadata": {}, 95 | "source": [ 96 | "### Simple Image" 97 | ] 98 | }, 99 | { 100 | "cell_type": "code", 101 | "execution_count": null, 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "hv.Image(volume[:,:,0])" 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": null, 111 | "metadata": {}, 112 | "outputs": [], 113 | "source": [ 114 | "def get_brain_image(axes = 'XY',index = 0):\n", 115 | " if axes == 'XY':\n", 116 | " data = volume[:,:,index] \n", 117 | " elif axes == 'XZ':\n", 118 | " data = volume[:,index,:]\n", 119 | " elif axes == 'YZ': \n", 120 | " data = volume[index,:,:]\n", 121 | " \n", 122 | " image = hv.Image(data)\n", 123 | " return image" 124 | ] 125 | }, 126 | { 127 | "cell_type": "markdown", 128 | "metadata": {}, 129 | "source": [ 130 | "### Dmap for Index" 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": null, 136 | "metadata": {}, 137 | "outputs": [], 138 | "source": [ 139 | "dmap = hv.DynamicMap(get_brain_image, kdims=['index'])\n", 140 | "dmap.redim.range(index=(0,67))" 141 | ] 142 | }, 143 | { 144 | "cell_type": "markdown", 145 | "metadata": {}, 146 | "source": [ 147 | "### Dmap for both axes and Index" 148 | ] 149 | }, 150 | { 151 | "cell_type": "code", 152 | "execution_count": null, 153 | "metadata": {}, 154 | "outputs": [], 155 | "source": [ 156 | "axes_list = ['XY','XZ','YZ']\n", 157 | "dmap = hv.DynamicMap(get_brain_image, kdims=['axes','index'])\n", 158 | "dmap.redim.values(axes = axes_list,index = range(67))" 159 | ] 160 | }, 161 | { 162 | "cell_type": "markdown", 163 | "metadata": {}, 164 | "source": [ 165 | "### Brain Explorer" 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": null, 171 | "metadata": {}, 172 | "outputs": [], 173 | "source": [ 174 | "class BrainExplorer(param.Parameterized):\n", 175 | " \n", 176 | " cmap = param.ObjectSelector(default ='dimgray',objects=hv.plotting.list_cmaps() )\n", 177 | " \n", 178 | " axes_list = ['XY','XZ','YZ']\n", 179 | " axes = param.ObjectSelector(default = 'XY',objects=axes_list)\n", 180 | " \n", 181 | " index = param.Integer(default=10,bounds = (0,max(volume.shape)-1))\n", 182 | " \n", 183 | " @param.depends('axes','index','cmap')\n", 184 | " def get_brain_image(self):\n", 185 | " axes = self.axes\n", 186 | " index = self.index\n", 187 | " cmap = self.cmap\n", 188 | " if axes == 'XY':\n", 189 | " data = volume[:,:,index] \n", 190 | " elif axes == 'XZ':\n", 191 | " data = volume[:,index,:]\n", 192 | " elif axes == 'YZ':\n", 193 | " data = volume[index,:,:]\n", 194 | "\n", 195 | " return hv.Image(data).opts(cmap = cmap,\n", 196 | " tools = ['hover'],\n", 197 | " height = 500,\n", 198 | " width = 500,\n", 199 | " xlabel = axes[0]+'-Axis', ylabel = axes[1]+'-Axis')" 200 | ] 201 | }, 202 | { 203 | "cell_type": "code", 204 | "execution_count": null, 205 | "metadata": {}, 206 | "outputs": [], 207 | "source": [ 208 | "b = BrainExplorer()\n", 209 | "dmap = pn.Row(hv.DynamicMap(b.get_brain_image),b.param)\n", 210 | "# dmap" 211 | ] 212 | }, 213 | { 214 | "cell_type": "markdown", 215 | "metadata": {}, 216 | "source": [ 217 | "### Dashboard 1" 218 | ] 219 | }, 220 | { 221 | "cell_type": "code", 222 | "execution_count": null, 223 | "metadata": {}, 224 | "outputs": [], 225 | "source": [ 226 | "dashboard = pn.Column(\n", 227 | " pn.Column('#
Brain Explorer',\n",
228 | " pn.Spacer(height=50)),\n",
229 | " pn.Row(\n",
230 | " pn.Row(dmap[0],\n",
231 | " pn.Spacer(width = 50),\n",
232 | " pn.Column(\n",
233 | " dmap[1][1],\n",
234 | " pn.Spacer(height = 30),\n",
235 | " dmap[1][2],\n",
236 | " pn.Spacer(height = 30),\n",
237 | " dmap[1][3])\n",
238 | " )\n",
239 | " )\n",
240 | ")"
241 | ]
242 | },
243 | {
244 | "cell_type": "code",
245 | "execution_count": null,
246 | "metadata": {},
247 | "outputs": [],
248 | "source": [
249 | "dashboard"
250 | ]
251 | },
252 | {
253 | "cell_type": "markdown",
254 | "metadata": {},
255 | "source": [
256 | "-------\n",
257 | "-------\n",
258 | "------\n",
259 | "---"
260 | ]
261 | },
262 | {
263 | "cell_type": "markdown",
264 | "metadata": {},
265 | "source": [
266 | "## 2. Using watch/callback mechanism"
267 | ]
268 | },
269 | {
270 | "cell_type": "markdown",
271 | "metadata": {},
272 | "source": [
273 | "There is a problem with the above dashboard. Axes `XY`, `XZ`, `YZ` have **68, 189, 157** number of images.\n",
274 | "However Index slider above is constant, shows 189 indexes for every axes. For `XY` and `YZ` blank images are shown after index **68** and **157**. This has been solved by creating a custom callback function so that it:\n",
275 | "1. Resets index to 0 whenever axis is changed.\n",
276 | "2. Resets `end` of index slider according to number of images in that axes."
277 | ]
278 | },
279 | {
280 | "cell_type": "markdown",
281 | "metadata": {},
282 | "source": [
283 | "#### Image"
284 | ]
285 | },
286 | {
287 | "cell_type": "code",
288 | "execution_count": null,
289 | "metadata": {},
290 | "outputs": [],
291 | "source": [
292 | "def get_brain_image(axes,index,cmap):\n",
293 | " if axes == 'XY':\n",
294 | " data = volume[:,:,index] \n",
295 | " elif axes == 'XZ':\n",
296 | " data = volume[:,index,:]\n",
297 | " else: #YZ\n",
298 | " data = volume[index,:,:]\n",
299 | " return hv.Image(data).options(cmap = cmap,\n",
300 | " tools = ['hover'],\n",
301 | " height = 500,\n",
302 | " width = 500,\n",
303 | " xlabel = axes[0]+'-Axis', ylabel = axes[1]+'-Axis')\n",
304 | "\n",
305 | "# get_brain_image('XY',0,'dimgray')"
306 | ]
307 | },
308 | {
309 | "cell_type": "markdown",
310 | "metadata": {},
311 | "source": [
312 | "#### Streams"
313 | ]
314 | },
315 | {
316 | "cell_type": "code",
317 | "execution_count": null,
318 | "metadata": {},
319 | "outputs": [],
320 | "source": [
321 | "axes_list = ['XY','XZ','YZ']\n",
322 | "axes = pn.widgets.Select(name = 'axes',value = 'XY',options=axes_list)\n",
323 | "axes_stream = Params(axes,['value'],rename={'value': 'axes'})\n",
324 | "\n",
325 | "cmaps = pn.widgets.Select(name = 'Color Map', value = 'CMRmap',options=hv.plotting.list_cmaps() )\n",
326 | "cmap_stream = Params(cmaps,['value'],rename={'value': 'cmap'})\n",
327 | "\n",
328 | "index = pn.widgets.IntSlider(name = 'index',end = (volume.shape[2]-1))\n",
329 | "index_stream = Params(index,['value'],rename={'value': 'index'})"
330 | ]
331 | },
332 | {
333 | "cell_type": "markdown",
334 | "metadata": {},
335 | "source": [
336 | "#### Callback Function"
337 | ]
338 | },
339 | {
340 | "cell_type": "code",
341 | "execution_count": null,
342 | "metadata": {},
343 | "outputs": [],
344 | "source": [
345 | "def reset_index(event):\n",
346 | " index.value = 0\n",
347 | " axes_selected = ''.join(event.new)\n",
348 | " if axes_selected == 'XY':\n",
349 | " index.end = (volume.shape[2]-1)\n",
350 | " elif axes_selected == 'XZ':\n",
351 | " index.end = (volume.shape[1]-1)\n",
352 | " elif axes_selected == 'YZ':\n",
353 | " index.end = (volume.shape[0]-1)"
354 | ]
355 | },
356 | {
357 | "cell_type": "markdown",
358 | "metadata": {},
359 | "source": [
360 | "#### `watch` mechanism"
361 | ]
362 | },
363 | {
364 | "cell_type": "code",
365 | "execution_count": null,
366 | "metadata": {},
367 | "outputs": [],
368 | "source": [
369 | "axes.param.watch(reset_index,['value'])"
370 | ]
371 | },
372 | {
373 | "cell_type": "markdown",
374 | "metadata": {},
375 | "source": [
376 | "It keeps a watch on the axes parameters to adjust index values accordingly."
377 | ]
378 | },
379 | {
380 | "cell_type": "markdown",
381 | "metadata": {},
382 | "source": [
383 | "#### Dynamic Map"
384 | ]
385 | },
386 | {
387 | "cell_type": "code",
388 | "execution_count": null,
389 | "metadata": {},
390 | "outputs": [],
391 | "source": [
392 | "dmap = hv.DynamicMap(lambda axes,index,cmap: get_brain_image(axes,index,cmap),\n",
393 | " streams=[axes_stream,index_stream,cmap_stream])"
394 | ]
395 | },
396 | {
397 | "cell_type": "markdown",
398 | "metadata": {},
399 | "source": [
400 | "#### Dashboard"
401 | ]
402 | },
403 | {
404 | "cell_type": "code",
405 | "execution_count": null,
406 | "metadata": {},
407 | "outputs": [],
408 | "source": [
409 | "dashboard = pn.Column(\n",
410 | " pn.Column('# Brain Explorer',\n",
411 | " pn.Spacer(height=30)),\n",
412 | " pn.Row(\n",
413 | " pn.Row(dmap,\n",
414 | " pn.Spacer(width = 50),\n",
415 | " pn.Column(\n",
416 | " pn.Spacer(height = 50),\n",
417 | " cmaps,\n",
418 | " pn.Spacer(height = 30),\n",
419 | " axes,\n",
420 | " pn.Spacer(height = 30),\n",
421 | " index)\n",
422 | " )\n",
423 | " )\n",
424 | ")"
425 | ]
426 | },
427 | {
428 | "cell_type": "code",
429 | "execution_count": null,
430 | "metadata": {},
431 | "outputs": [],
432 | "source": [
433 | "dashboard.servable()"
434 | ]
435 | },
436 | {
437 | "cell_type": "markdown",
438 | "metadata": {},
439 | "source": [
440 | "Deploy this dashboard from the CLI using:\n",
441 | "\n",
442 | "```$ panel serve app.ipynb```"
443 | ]
444 | }
445 | ],
446 | "metadata": {
447 | "kernelspec": {
448 | "display_name": "Python 3",
449 | "language": "python",
450 | "name": "python3"
451 | },
452 | "language_info": {
453 | "codemirror_mode": {
454 | "name": "ipython",
455 | "version": 3
456 | },
457 | "file_extension": ".py",
458 | "mimetype": "text/x-python",
459 | "name": "python",
460 | "nbconvert_exporter": "python",
461 | "pygments_lexer": "ipython3",
462 | "version": "3.6.7"
463 | }
464 | },
465 | "nbformat": 4,
466 | "nbformat_minor": 2
467 | }
468 |
--------------------------------------------------------------------------------