{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from functools import partial\n", "\n", "import holoviews as hv\n", "import numpy as np\n", "import panel as pn\n", "import param\n", "import pyvista as pv\n", "\n", "from holoviews.operation.datashader import rasterize\n", "from bokeh.util.serialization import make_globally_unique_id\n", "from pyvista import examples\n", "from scipy.ndimage import zoom\n", "\n", "css = '''\n", ".custom-wbox > div.bk {\n", " padding-right: 10px;\n", "}\n", ".scrollable {\n", " overflow: auto !important;\n", "}\n", "'''\n", "js_files = {'jquery': 'https://code.jquery.com/jquery-1.11.1.min.js',\n", " 'goldenlayout': 'https://golden-layout.com/files/latest/js/goldenlayout.min.js'}\n", "css_files = ['https://golden-layout.com/files/latest/css/goldenlayout-base.css',\n", " 'https://golden-layout.com/files/latest/css/goldenlayout-dark-theme.css']\n", "\n", "pn.extension('vtk', js_files=js_files, raw_css=[css], css_files=css_files)\n", "\n", "hv.renderer('bokeh').theme = 'dark_minimal'\n", "hv.opts.defaults(hv.opts.Image(responsive=True, tools=['hover']))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Declare callbacks" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class ImageSmoother(param.Parameterized):\n", " \n", " smooth_fun = param.Parameter(default=None)\n", " smooth_level = param.Integer(default=5, bounds=(1,10))\n", " order = param.Selector(default=1, objects=[1,2,3])\n", " \n", " def __init__(self, **params):\n", " super(ImageSmoother, self).__init__(**params)\n", " self._update_fun()\n", "\n", " @param.depends('order', 'smooth_level', watch=True)\n", " def _update_fun(self):\n", " self.smooth_fun = lambda x: zoom(x, zoom=self.smooth_level, order=self.order)\n", "\n", "def update_camera_projection(*evts):\n", " volume.camera['parallelProjection'] = evts[0].new\n", " volume.param.trigger('camera')\n", "\n", "def hook_reset_range(plot, elem, lbrt):\n", " bkplot = plot.handles['plot']\n", " x_range = lbrt[0], lbrt[2]\n", " y_range = lbrt[1], lbrt[3]\n", " old_x_range_reset = bkplot.x_range.reset_start, bkplot.x_range.reset_end\n", " old_y_range_reset = bkplot.y_range.reset_start, bkplot.y_range.reset_end \n", " if x_range != old_x_range_reset or y_range != old_y_range_reset:\n", " bkplot.x_range.reset_start, bkplot.x_range.reset_end = x_range\n", " bkplot.x_range.start, bkplot.x_range.end = x_range\n", " bkplot.y_range.reset_start, bkplot.y_range.reset_end = y_range\n", " bkplot.y_range.start, bkplot.y_range.end = y_range\n", " \n", "def image_slice(dims, array, lbrt, mapper, smooth_fun):\n", " array = np.asarray(array)\n", " low = mapper['low'] if mapper else array.min()\n", " high = mapper['high'] if mapper else array.max()\n", " cmap = mapper['palette'] if mapper else 'fire'\n", " img = hv.Image(smooth_fun(array), bounds=lbrt, kdims=dims, vdims='Intensity')\n", " reset_fun = partial(hook_reset_range, lbrt=lbrt)\n", " return img.opts(clim=(low, high), cmap=cmap, hooks=[reset_fun])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Declare Panel" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Download datasets\n", "head = examples.download_head()\n", "brain = examples.download_brain()\n", "\n", "dataset_selection = pn.widgets.Select(name='Dataset', value=head, options={'Head': head, 'Brain': brain})\n", "\n", "volume = pn.pane.VTKVolume(\n", " dataset_selection.value, sizing_mode='stretch_both', height=400, \n", " display_slices=True, orientation_widget=True, render_background=\"#222222\",\n", " colormap='blue2cyan'\n", ")\n", "\n", "dataset_selection.link(target=volume, value='object')\n", "\n", "volume_controls = volume.controls(jslink=False, parameters=[\n", " 'render_background', 'display_volume', 'display_slices',\n", " 'slice_i', 'slice_j', 'slice_k', 'rescale'\n", "])\n", "\n", "toggle_parallel_proj = pn.widgets.Toggle(name='Parallel Projection', value=False)\n", "\n", "toggle_parallel_proj.param.watch(update_camera_projection, ['value'], onlychanged=True)\n", "\n", "smoother = ImageSmoother()\n", "\n", "@pn.depends(si=volume.param.slice_i, mapper=volume.param.mapper,\n", " smooth_fun=smoother.param.smooth_fun, vol=volume.param.object)\n", "def image_slice_i(si, mapper, smooth_fun, vol):\n", " arr = vol.active_scalars.reshape(vol.dimensions, order='F')\n", " lbrt = vol.bounds[2], vol.bounds[4], vol.bounds[3], vol.bounds[5]\n", " return image_slice(['y','z'], arr[si,:,::-1].T, lbrt, mapper, smooth_fun)\n", "\n", "@pn.depends(sj=volume.param.slice_j, mapper=volume.param.mapper,\n", " smooth_fun=smoother.param.smooth_fun, vol=volume.param.object)\n", "def image_slice_j(sj, mapper, smooth_fun, vol):\n", " arr = vol.active_scalars.reshape(vol.dimensions, order='F')\n", " lbrt = vol.bounds[0], vol.bounds[4], vol.bounds[1], vol.bounds[5]\n", " return image_slice(['x','z'], arr[:,sj,::-1].T, lbrt, mapper, smooth_fun)\n", "\n", "@pn.depends(sk=volume.param.slice_k, mapper=volume.param.mapper,\n", " smooth_fun=smoother.param.smooth_fun, vol=volume.param.object)\n", "def image_slice_k(sk, mapper, smooth_fun, vol):\n", " arr = vol.active_scalars.reshape(vol.dimensions, order='F')\n", " lbrt = vol.bounds[0], vol.bounds[2], vol.bounds[1], vol.bounds[3]\n", " return image_slice(['x', 'y'], arr[:,::-1,sk].T, lbrt, mapper, smooth_fun)\n", "\n", "dmap_i = rasterize(hv.DynamicMap(image_slice_i))\n", "dmap_j = rasterize(hv.DynamicMap(image_slice_j))\n", "dmap_k = rasterize(hv.DynamicMap(image_slice_k))\n", "\n", "controller = pn.WidgetBox(\n", " pn.Column(dataset_selection, toggle_parallel_proj, *volume_controls[1:], sizing_mode='fixed'),\n", " pn.Param(smoother, parameters=['smooth_level', 'order']),\n", " pn.layout.VSpacer(),\n", " css_classes=['panel-widget-box', 'custom-wbox'], sizing_mode='stretch_height'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Set up template" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "template = \"\"\"\n", "{%% extends base %%}\n", "\n", "{%% block contents %%}\n", "{%% set context = '%s' %%}\n", "{%% if context == 'notebook' %%}\n", " {%% set slicer_id = get_id() %%}\n", "
\n", "{%% endif %%}\n", "\n", "\n", "{%% endblock %%}\n", "\"\"\"\n", "\n", "\n", "tmpl = pn.Template(template=(template % 'server'), nb_template=(template % 'notebook'))\n", "tmpl.nb_template.globals['get_id'] = make_globally_unique_id\n", "\n", "tmpl.add_panel('controller', controller)\n", "tmpl.add_panel('scene3d', volume)\n", "tmpl.add_panel('slice_i', pn.panel(dmap_i, sizing_mode='stretch_both'))\n", "tmpl.add_panel('slice_j', pn.panel(dmap_j, sizing_mode='stretch_both'))\n", "tmpl.add_panel('slice_k', pn.panel(dmap_k, sizing_mode='stretch_both'))\n", "\n", "tmpl.servable(title='VTKSlicer')" ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 4 }