├── nbpapaya ├── __init__.py ├── brain_view.py └── base.py ├── .gitmodules ├── setup.py ├── README.md └── example.ipynb /nbpapaya/__init__.py: -------------------------------------------------------------------------------- 1 | from .brain_view import clear_brain, open_brains 2 | from .base import Brain, Surface, Overlay 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "nbpapaya/three.js"] 2 | path = nbpapaya/three.js 3 | url = https://github.com/mrdoob/three.js 4 | [submodule "nbpapaya/Papaya"] 5 | path = nbpapaya/Papaya 6 | url = https://github.com/rii-mango/Papaya 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | setup(name='nbpapaya', 4 | version='1.0', 5 | description='Papaya viewer in the IPython notebook', 6 | author='Anisha', 7 | author_email='akeshavan@ucla.edu', 8 | url=None, 9 | packages=['nbpapaya'], 10 | package_data={"nbpapaya":["Papaya/release/current/standard/papaya.js", 11 | "Papaya/release/current/standard/papaya.css", 12 | "three.js/build/*", 13 | "three.js/libs/stats.min.js", 14 | "three.js/examples/js/controls/TrackballControls.js", 15 | "three.js/examples/js/loaders/VTKLoader.js", 16 | ]} 17 | ) 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UPDATE 2 | 3 | 4 | This is old now. Use nilearn's plotting library for this!! 5 | 6 | ```python 7 | from nilearn import plotting 8 | plotting.view_stat_map("/path/to/file") 9 | ``` 10 | 11 | 12 | # README 13 | 14 | 15 | Want to see 3D brain data in the IPython_ notebook? Now you can by using Papaya_, a Javascript library for viewing medical images in the browser, and this little bit of code. 16 | 17 | ## Install 18 | 19 | 20 | Clone this repository to your computer, 21 | 22 | 23 | >>> git clone https://github.com/akeshavan/nbpapaya 24 | 25 | 26 | then add submodules 27 | 28 | 29 | >>> git submodule update --init --recursive 30 | 31 | 32 | and run the setup script. 33 | 34 | >>> python setup.py install 35 | 36 | 37 | Use 38 | --- 39 | 40 | Open a new IPython_ notebook 41 | 42 | from nbpapaya import Brain, clear_brain 43 | 44 | Then show a brain: 45 | 46 | Brain("/path/to/your/brain.nii",port=) 47 | 48 | Or show overlaid brains 49 | 50 | Brain(["/path/to/brain1.nii","/path/to/brain2.nii"],8888) 51 | 52 | You can play around with the color maps and intensity ranges on the Papaya_ javascript interface. 53 | 54 | Also, some files are created in the directory of your notebook to make it work. When you're done, clean up: 55 | 56 | clear_brain() 57 | 58 | The file papaya_viewer.html and the papaya_data folder are deleted. 59 | 60 | 61 | Troubleshoot 62 | ------------ 63 | 64 | Things might go wrong because I wrote this quickly. If you don't see anything, make sure you called Brain with the correct port. If you did, make sure that papaya.js and papaya.css exist in ~/.ipython/profile_default/static/custom/. If nothing is there, move the files in there. Also email me: akeshavan@ucla.edu. 65 | 66 | 67 | 68 | .. _IPython: http://ipython.org/notebook.html 69 | .. _Papaya: https://github.com/rii-mango/Papaya/ 70 | -------------------------------------------------------------------------------- /nbpapaya/brain_view.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import weakref 4 | from json import dumps as json 5 | from tempfile import mktemp, NamedTemporaryFile 6 | from warnings import warn 7 | #from nipype.utils.filemanip import split_filename 8 | 9 | def split_filename(fname): 10 | """Split a filename into parts: path, base filename and extension. 11 | 12 | Parameters 13 | ---------- 14 | fname : str 15 | file or path name 16 | 17 | Returns 18 | ------- 19 | pth : str 20 | base path from fname 21 | fname : str 22 | filename from fname, without extension 23 | ext : str 24 | file extension from fname 25 | 26 | Examples 27 | -------- 28 | >>> from nipype.utils.filemanip import split_filename 29 | >>> pth, fname, ext = split_filename('/home/data/subject.nii.gz') 30 | >>> pth 31 | '/home/data' 32 | 33 | >>> fname 34 | 'subject' 35 | 36 | >>> ext 37 | '.nii.gz' 38 | 39 | """ 40 | 41 | special_extensions = [".nii.gz"] 42 | 43 | pth, fname = os.path.split(fname) 44 | 45 | ext = None 46 | for special_ext in special_extensions: 47 | ext_len = len(special_ext) 48 | if len(fname) > ext_len and fname[-ext_len:].lower() == special_ext.lower(): 49 | ext = fname[-ext_len:] 50 | fname = fname[:-ext_len] 51 | break 52 | if not ext: 53 | fname, ext = os.path.splitext(fname) 54 | 55 | return pth, fname, ext 56 | 57 | 58 | open_brains = weakref.WeakValueDictionary() 59 | 60 | def _parse_options(file_names, options, image_options): 61 | if options is None: 62 | options_json = ""; 63 | else: 64 | opt = [] 65 | for option_name, value in options.items(): 66 | line = "params['{}'] = {};".format(option_name, json(value)) 67 | opt.append(line) 68 | options_json = "\n".join(opt) 69 | 70 | if image_options is None: 71 | image_options_json = "" 72 | else: 73 | opt = [] 74 | for file, image_options in zip(sorted(file_names), image_options): 75 | line = "params['{}'] = {};".format(file, json(image_options)) 76 | opt.append(line) 77 | image_options_json = "\n".join(opt) 78 | return options_json, image_options_json 79 | 80 | def clear_brain(): 81 | """Remove all the files the Brain object made to show you things. 82 | You must re-run all cells to reload the data""" 83 | 84 | if os.path.exists(os.path.abspath("papaya_data")): 85 | shutil.rmtree(os.path.abspath("papaya_data")) 86 | 87 | def get_example_data(): 88 | vtk = "http://roygbiv.mindboggle.info/data/mindboggled/Twins-2-1/labels/left_cortical_surface/freesurfer_cortex_labels.vtk" 89 | nifti = "" 90 | from subprocess import check_call 91 | import os 92 | 93 | folder = os.path.abspath("nppapaya_example_data") 94 | if not os.path.exists(folder): 95 | os.makedirs(folder) 96 | cmd = ["wget", vtk] 97 | check_call(cmd, cwd=folder) 98 | -------------------------------------------------------------------------------- /example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 3D Visualization in the Notebook w/ three.js" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "### In the Notebook: https://github.com/akeshavan/nbpapaya" 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "### don't forget the submodules!\n", 22 | "\n", 23 | "```\n", 24 | "git submodule update --init --recursive\n", 25 | "```" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "metadata": {}, 31 | "source": [ 32 | "### Outside the Notebook: https://github.com/akeshavan/overlay\n", 33 | "\n", 34 | "### Demo: http://akeshavan.github.io/overlay" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "metadata": {}, 40 | "source": [ 41 | " import stuff" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 2, 47 | "metadata": { 48 | "collapsed": false 49 | }, 50 | "outputs": [], 51 | "source": [ 52 | "from nbpapaya import Brain, clear_brain, Brain, Surface, get_example_data, Overlay\n", 53 | "import os" 54 | ] 55 | }, 56 | { 57 | "cell_type": "markdown", 58 | "metadata": {}, 59 | "source": [ 60 | "define a dictionary" 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": 4, 66 | "metadata": { 67 | "collapsed": false 68 | }, 69 | "outputs": [], 70 | "source": [ 71 | "MeshOpts = { os.path.abspath(\"nbpapaya_example_data/freesurfer_curvature.vtk\"):{ \n", 72 | " \"filename\": os.path.abspath(\"nbpapaya_example_data/vertices.csv\"),\n", 73 | " \"colormin\": \"#0000FF\", \n", 74 | " \"colormax\": \"#FF0000\",\n", 75 | " \"vmin\": 2,\n", 76 | " \"vmax\": 5,\n", 77 | " \"key\": \"freesurfer thickness\",\n", 78 | " \"key_options\": [\"freesurfer thickness\", \"freesurfer curvature\", \n", 79 | " \"area\", \"freesurfer convexity (sulc)\", \"geodesic depth\", \n", 80 | " \"mean curvature\", \"travel depth\"], \n", 81 | " \"threshold\": 2.4,\n", 82 | " \"mesh_transparency\": 1,\n", 83 | " \"mesh_visible\": True,\n", 84 | " \"overlay_transparency\": 1\n", 85 | " }\n", 86 | " \n", 87 | " }" 88 | ] 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "metadata": {}, 93 | "source": [ 94 | "plot the brain" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": 3, 100 | "metadata": { 101 | "collapsed": false, 102 | "scrolled": false 103 | }, 104 | "outputs": [ 105 | { 106 | "name": "stdout", 107 | "output_type": "stream", 108 | "text": [ 109 | "doing checks /Users/keshavan/.jupyter/custom/\n" 110 | ] 111 | }, 112 | { 113 | "data": { 114 | "text/html": [ 115 | "" 121 | ], 122 | "text/plain": [ 123 | "" 124 | ] 125 | }, 126 | "execution_count": 4, 127 | "metadata": {}, 128 | "output_type": "execute_result" 129 | } 130 | ], 131 | "source": [ 132 | "Surface(\"/Users/keshavan/Dropbox/brainthing/freesurfer_cortex_labels.vtk\", port=8889)" 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": 6, 138 | "metadata": { 139 | "collapsed": false 140 | }, 141 | "outputs": [ 142 | { 143 | "name": "stdout", 144 | "output_type": "stream", 145 | "text": [ 146 | "doing checks /Users/keshavan/.ipython/profile_default/static/custom/\n" 147 | ] 148 | }, 149 | { 150 | "data": { 151 | "text/html": [ 152 | "" 158 | ], 159 | "text/plain": [ 160 | "" 161 | ] 162 | }, 163 | "execution_count": 6, 164 | "metadata": {}, 165 | "output_type": "execute_result" 166 | } 167 | ], 168 | "source": [ 169 | "Brain(\"/Users/keshavan/Dropbox/brainthing/ds008/sub001/anatomy/highres001.nii.gz\",port=8889)" 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": null, 175 | "metadata": { 176 | "collapsed": true 177 | }, 178 | "outputs": [], 179 | "source": [] 180 | } 181 | ], 182 | "metadata": { 183 | "kernelspec": { 184 | "display_name": "Python 2", 185 | "language": "python", 186 | "name": "python2" 187 | }, 188 | "language_info": { 189 | "codemirror_mode": { 190 | "name": "ipython", 191 | "version": 2 192 | }, 193 | "file_extension": ".py", 194 | "mimetype": "text/x-python", 195 | "name": "python", 196 | "nbconvert_exporter": "python", 197 | "pygments_lexer": "ipython2", 198 | "version": "2.7.11" 199 | } 200 | }, 201 | "nbformat": 4, 202 | "nbformat_minor": 0 203 | } 204 | -------------------------------------------------------------------------------- /nbpapaya/base.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import weakref 4 | from json import dumps as json 5 | from tempfile import mktemp, NamedTemporaryFile 6 | from warnings import warn 7 | from .brain_view import split_filename, _parse_options, open_brains 8 | 9 | class ViewerBase(object): 10 | 11 | def _repr_html_(self): 12 | return """ 13 | 20 | """.format(objid=self.objid, width=self.width, height=self.height) 25 | 26 | def _do_checks(self): 27 | print("doing checks", self.home_dir) 28 | if not os.path.exists(os.path.join(self.home_dir,"papaya.js")): 29 | shutil.copyfile(os.path.join(os.path.split(__file__)[0],"Papaya/release/current/standard/papaya.js"), 30 | os.path.join(self.home_dir,"papaya.js")) 31 | 32 | if not os.path.exists(os.path.join(self.home_dir,"papaya.css")): 33 | shutil.copyfile(os.path.join(os.path.split(__file__)[0],"Papaya/release/current/standard/papaya.css"), 34 | os.path.join(self.home_dir,"papaya.css")) 35 | 36 | if not os.path.exists(os.path.abspath("./papaya_data")): 37 | os.mkdir(os.path.abspath("./papaya_data")) 38 | 39 | if not os.path.exists(os.path.join(self.home_dir,"three.min.js")): 40 | shutil.copyfile(os.path.join(os.path.split(__file__)[0],"three.js/build/three.min.js"), 41 | os.path.join(self.home_dir,"three.min.js")) 42 | 43 | if not os.path.exists(os.path.join(self.home_dir,"VTKLoader.js")): 44 | shutil.copyfile(os.path.join(os.path.split(__file__)[0], 45 | "three.js/examples/js/loaders/VTKLoader.js"), 46 | os.path.join(self.home_dir,"VTKLoader.js")) 47 | 48 | if not os.path.exists(os.path.join(self.home_dir,"TrackballControls.js")): 49 | shutil.copyfile(os.path.join(os.path.split(__file__)[0], 50 | "three.js/examples/js/controls/TrackballControls.js"), 51 | os.path.join(self.home_dir,"TrackballControls.js")) 52 | 53 | 54 | def _symlink_files(self, fnames): 55 | tmp_files = {} 56 | mapper = {} 57 | for i, f in enumerate(fnames): 58 | path, name, ext = split_filename(f) 59 | link = mktemp(prefix='tmp%02d' % i, suffix=ext, dir="papaya_data") 60 | os.symlink(f, link) 61 | _, name, _ = split_filename(link) 62 | #self.file_names[name + ext] = link 63 | tmp_files[name+ext] = link 64 | mapper[f] = link 65 | #print tmp_files 66 | return tmp_files, mapper 67 | 68 | def __init__(self, fnames, num=None, options=None, image_options=None, 69 | width=600, height=450): 70 | self._html_file = None 71 | self.file_names = {} 72 | if not isinstance(fnames, list): 73 | fnames = [fnames] 74 | if isinstance(image_options, dict): 75 | image_options = [image_options] * len(fnames) 76 | elif image_options is not None: 77 | if len(image_options) != len(fnames): 78 | raise ValueError("If you specify image_options as a list, you " 79 | "specify image_options for each image.") 80 | self.width = width 81 | self.height = height 82 | self.home_dir = os.path.join(os.path.expanduser("~"),".jupyter/custom/") 83 | if not os.path.exists(self.home_dir): 84 | os.makedirs(self.home_dir) 85 | # Check that the papaya_data exists and [temp].html, papaya.js and 86 | # papaya.css templates are in the right spot. papaya_data is used by 87 | # _symlink_files 88 | self._do_checks() 89 | 90 | # symlink our files to a place where the viewer can access it 91 | # Sets self.file_names for use by _edit_html 92 | self.file_names, self._mapper = self._symlink_files(fnames) 93 | 94 | def __del__(self): 95 | for name, link in self.file_names.items(): 96 | try: 97 | os.remove(link) 98 | except OSError: 99 | warn("Could not delete %s @ %s" % (name, link)) 100 | if self._html_file is not None: 101 | self._html_file.close() 102 | self._html_file = None 103 | 104 | class Brain(ViewerBase): 105 | def __init__(self, fnames, num=None, options=None, image_options=None, 106 | width=600, height=450): 107 | super(Brain,self).__init__(fnames, num, options, image_options, 108 | width=600, height=450) 109 | 110 | #edit viewer.html to point to our files 111 | 112 | if isinstance(image_options, dict): 113 | image_options = [image_options] * len(fnames) 114 | elif image_options is not None: 115 | if len(image_options) != len(fnames): 116 | raise ValueError("If you specify image_options as a list, you " 117 | "specify image_options for each image.") 118 | 119 | self._edit_html(options, image_options) 120 | open_brains[self.objid] = self 121 | 122 | 123 | def _edit_html(self, options, image_options): 124 | opt_json, imgopt_json = _parse_options(self.file_names, options, image_options) 125 | 126 | html = """ 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | Papaya Viewer 142 | 143 | 149 | 150 | 151 | 152 |
153 |
154 |
155 |
156 |
157 | 158 | 159 | """ 160 | html = html.format(images=json(sorted(list(self.file_names.keys()))), 161 | options=opt_json, 162 | image_options=imgopt_json) 163 | 164 | file = NamedTemporaryFile(suffix=".html", dir="papaya_data") 165 | file.write(html.encode("utf-8")) 166 | # Do not close because it'll delete the file 167 | file.flush() 168 | self._html_file = file 169 | path, name, ext = split_filename(file.name) 170 | self.objid = name 171 | 172 | return html 173 | 174 | class Surface(ViewerBase): 175 | def __init__(self, fnames, num=None, options=None, image_options=None, 176 | width=600, height=450): 177 | super(Surface, self).__init__(fnames, num, options, image_options, 178 | width, height) 179 | 180 | self._edit_html(options, image_options) 181 | open_brains[self.objid] = self 182 | 183 | def _edit_html(self,options,image_options): 184 | 185 | html = """ 186 | 187 | 188 | 189 | 190 | 191 | three.js webgl - loaders - vtk loader 192 | 193 | 194 | 195 | 196 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | """ 341 | 342 | html = html.format(images=json(list(self.file_names.keys()))) 343 | file = NamedTemporaryFile(suffix=".html", dir="papaya_data") 344 | file.write(html.encode("utf-8")) 345 | # Do not close because it'll delete the file 346 | file.flush() 347 | self._html_file = file 348 | path, name, ext = split_filename(file.name) 349 | self.objid = name 350 | 351 | return html 352 | 353 | class Overlay(ViewerBase): 354 | def __init__(self, image_options, num=None, options=None, 355 | width=600, height=450, host="localhost"): 356 | 357 | fnames = list(image_options.keys()) 358 | port = None 359 | #super(Overlay, self).__init__(fnames,port, num, options, image_options, 360 | # width, height, host) 361 | super(Overlay, self).__init__(fnames, num, options, image_options, 362 | width, height) 363 | new_mapper = {} 364 | print(self._mapper) 365 | 366 | for key, value in self._mapper.items(): 367 | newkey = "/files/{}".format(value) 368 | print(newkey) 369 | new_mapper[newkey] = image_options[key] 370 | new_mapper[newkey]["filename"] = "/files/{}".format(list(self._symlink_files([image_options[key]["filename"]])[1].values())[0]) 371 | 372 | self._javascript_object = new_mapper 373 | 374 | print(image_options, new_mapper) 375 | 376 | 377 | self._edit_html(options, image_options) 378 | open_brains[self.objid] = self 379 | 380 | def _edit_html(self,options,image_options): 381 | 382 | html = """ 383 | 384 | 385 | 386 | 387 | 388 | three.js webgl - loaders - vtk loader 389 | 390 | 391 | 392 | 393 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | """ 707 | 708 | html = html.replace("IPYTHON_NOTEBOOK_DICTIONARY", json(self._javascript_object)) 709 | file = NamedTemporaryFile(suffix=".html", dir="papaya_data") 710 | file.write(html.encode("utf-8")) 711 | # Do not close because it'll delete the file 712 | file.flush() 713 | self._html_file = file 714 | path, name, ext = split_filename(file.name) 715 | self.objid = name 716 | 717 | return html 718 | --------------------------------------------------------------------------------