├── .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 | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/hdsingh/Brain-Explorer/master) 4 | 5 | This notebook shows 3D visualization of Brain. 6 | 7 | ![Dashboard](dashboard.png) 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 | 


--------------------------------------------------------------------------------