Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VTK volume viewer export color mapper informations to python side #1073

Merged
merged 22 commits into from Feb 15, 2020

Conversation

@xavArtley
Copy link
Collaborator

xavArtley commented Feb 5, 2020

With this PR we begin to have a real 3d slicer

import panel as pn
pn.extension('vtk')
import holoviews as hv
hv.extension('bokeh')
import pyvista as pv
from pyvista import examples
# Download a volumetric dataset
head = examples.download_head()
arr = head.active_scalar.reshape(head.dimensions, order='F')
interpolation_level = pn.widgets.IntSlider(value=5, start=1, end=20, name='Interpolation Level')
vol = pn.panel(head, sizing_mode='stretch_both', height=400, display_slices=True, rescale=True, display_volume=False,
              orientation_widget=True)
import scipy.ndimage as sn

@pn.depends(vol.param.slice_i,vol.param.slice_j,vol.param.slice_k,vol.param.mapper, interpolation_level.param.value)
def print_mapper(si, sj, sk, mapper, interp_level):
    im_interp = lambda x: sn.zoom(x, zoom=interp_level, order=1)
    low = mapper['low'] if mapper else arr.min()
    high = mapper['high'] if mapper else arr.max()
    cmap = mapper['palette'] if mapper else 'fire'
    
    im_i = hv.Image(im_interp(arr[si,::-1,:]), bounds=(head.extent[4],head.extent[2],head.extent[5],head.extent[3]), kdims=['z','y'], vdims='Intensity')
    im_j = hv.Image(im_interp(arr[::-1,sj,:]), bounds=(head.extent[4],head.extent[0],head.extent[5],head.extent[1]), kdims=['z','x'], vdims='Intensity')
    im_k = hv.Image(im_interp(arr[::-1,:,sk]), bounds=(head.extent[2],head.extent[0],head.extent[3],head.extent[1]), kdims=['y','x'], vdims='Intensity')
    return (im_i + im_j + im_k).opts(hv.opts.Image(clim=(low, high), cmap=cmap, invert_axes=True, data_aspect=1))
    
    
pn.Column(
    pn.Row(
        pn.Column(vol.controls(jslink=False,
                               parameters=['render_background', 'rescale', 'slice_i', 'slice_j', 
                                           'slice_k', 'display_volume', 'display_slices']),
                  interpolation_level),
        vol
    ),
    print_mapper
)

ezgif com-video-to-gif (1)

@xavArtley xavArtley force-pushed the xav/export_colormap branch from 49edbd2 to 1172866 Feb 5, 2020
@philippjfr

This comment has been minimized.

Copy link
Member

philippjfr commented Feb 5, 2020

Ready to merge?

@xavArtley

This comment has been minimized.

Copy link
Collaborator Author

xavArtley commented Feb 5, 2020

No I'd like to modify documentation before

@xavArtley

This comment has been minimized.

Copy link
Collaborator Author

xavArtley commented Feb 6, 2020

What do you think to had something like that to the gallery?

import param
import panel as pn
import holoviews as hv
import pyvista as pv
from pyvista import examples
from scipy.ndimage import zoom

pn.extension('vtk')
hv.extension('bokeh')


class ImageSmoother(param.Parameterized):
    
    smooth_fun = param.Parameter(default=None)
    order = param.Selector(default=1, objects=[1,2,3])
    zoom = param.Integer(default=2, bounds=(1,10))
    
    def __init__(self, **params):
        super().__init__(**params)
        self._update_fun()
    
    @param.depends('order', 'zoom', watch=True)
    def _update_fun(self):
        self.smooth_fun = lambda x: zoom(x, zoom=self.zoom, order=self.order)
    
    @property
    def stream(self):
        return hv.streams.Params(self, parameters=['smooth_fun'])



# Download a volumetric dataset
vol = examples.download_head()
volume = pn.panel(vol, sizing_mode='stretch_both', height=400, display_slices=True, orientation_widget=True)
volume_controls = volume.controls(jslink=False, parameters=['render_background', 'display_volume', 'display_slices',
                                                            'slice_i', 'slice_j', 'slice_k', 'rescale'])
toggle_parallel_proj = pn.widgets.Toggle(name='Parallel Projection', value=False)
def update_camera_projection(*evts):
    volume.camera['parallelProjection'] = evts[0].new
    volume.param.trigger('camera')
toggle_parallel_proj.param.watch(update_camera_projection, ['value'], onlychanged=True)



stream_i = hv.streams.Params(volume, parameters=['slice_i'], rename={'slice_i':'si'})
stream_j = hv.streams.Params(volume, parameters=['slice_j'], rename={'slice_j':'sj'})
stream_k = hv.streams.Params(volume, parameters=['slice_k'], rename={'slice_k':'sk'})
stream_mapper = hv.streams.Params(volume, parameters=['mapper'])
smoother = ImageSmoother()
stream_smooth = smoother.stream

arr = vol.active_scalar.reshape(vol.dimensions, order='F')

def image_slice_i(si, mapper, smooth_fun):
    low = mapper['low'] if mapper else arr.min()
    high = mapper['high'] if mapper else arr.max()
    cmap = mapper['palette'] if mapper else 'fire'
    lbrt = vol.extent[4], vol.extent[2], vol.extent[5], vol.extent[3]
    im_i = hv.Image(smooth_fun(arr[si,::-1,:]), bounds=lbrt, kdims=['z','y'], vdims='Intensity')
    frame_width = 500
    frame_height = int(frame_width * (lbrt[2]-lbrt[0])/(lbrt[3]-lbrt[1]))
    return im_i.opts(hv.opts.Image(clim=(low, high), cmap=cmap, frame_width=frame_width, frame_height=frame_height))
    
def image_slice_j(sj, mapper, smooth_fun):
    low = mapper['low'] if mapper else arr.min()
    high = mapper['high'] if mapper else arr.max()
    cmap = mapper['palette'] if mapper else 'fire'
    lbrt = vol.extent[4], vol.extent[0], vol.extent[5], vol.extent[1]
    im_j = hv.Image(smooth_fun(arr[::-1,sj,:]), bounds=lbrt, kdims=['z','x'], vdims='Intensity')
    frame_width = 500
    frame_height = int(frame_width * (lbrt[2]-lbrt[0])/(lbrt[3]-lbrt[1]))
    return im_j.opts(hv.opts.Image(clim=(low, high), cmap=cmap, frame_width=frame_width, frame_height=frame_height))
                     
def image_slice_k(sk, mapper, smooth_fun):
    low = mapper['low'] if mapper else arr.min()
    high = mapper['high'] if mapper else arr.max()
    cmap = mapper['palette'] if mapper else 'fire'
    lbrt = vol.extent[2], vol.extent[0], vol.extent[3], vol.extent[1]
    im_k = hv.Image(smooth_fun(arr[::-1,:,sk]), bounds=lbrt, kdims=['y','x'], vdims='Intensity')
    frame_width = 500
    frame_height = int(frame_width * (lbrt[2]-lbrt[0])/(lbrt[3]-lbrt[1]))
    return im_k.opts(hv.opts.Image(clim=(low, high), cmap=cmap, frame_width=frame_width, frame_height=frame_height))

hv.opts.defaults(hv.opts.Image(invert_axes=True, data_aspect=1))
dmap_i = hv.DynamicMap(image_slice_i, streams=[stream_i, stream_mapper, stream_smooth])
dmap_j = hv.DynamicMap(image_slice_j, streams=[stream_j, stream_mapper, stream_smooth])
dmap_k = hv.DynamicMap(image_slice_k, streams=[stream_k, stream_mapper, stream_smooth])


controller = pn.WidgetBox(toggle_parallel_proj, *volume_controls[1:], pn.Param(smoother, parameters=['zoom', 'order']))
pn.Row(pn.Column(pn.Row(controller, volume), pn.Row(dmap_i, dmap_j)), dmap_k, sizing_mode='stretch_height').show()
@codecov

This comment has been minimized.

Copy link

codecov bot commented Feb 6, 2020

Codecov Report

Merging #1073 into master will decrease coverage by 0.32%.
The diff coverage is 84.37%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1073      +/-   ##
==========================================
- Coverage    88.1%   87.77%   -0.33%     
==========================================
  Files         105      105              
  Lines       12380    12590     +210     
==========================================
+ Hits        10907    11051     +144     
- Misses       1473     1539      +66
Impacted Files Coverage Δ
panel/pane/vtk/__init__.py 100% <ø> (ø) ⬆️
panel/models/vtk.py 100% <100%> (ø) ⬆️
panel/pane/vtk/vtk.py 86.45% <80%> (-3.77%) ⬇️
panel/util.py 86.16% <0%> (-3.15%) ⬇️
panel/interact.py 73.57% <0%> (-2.5%) ⬇️
panel/tests/test_reactive.py 98.76% <0%> (-1.24%) ⬇️
panel/io/notebook.py 61.13% <0%> (-1.22%) ⬇️
panel/pane/vtk/vtkjs_serializer.py 82.05% <0%> (-0.65%) ⬇️
panel/viewable.py 83.42% <0%> (+0.94%) ⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update ef31fb4...5577a2b. Read the comment docs.

@xavArtley

This comment has been minimized.

Copy link
Collaborator Author

xavArtley commented Feb 6, 2020

ezgif com-video-to-gif (2)

@philippjfr

This comment has been minimized.

Copy link
Member

philippjfr commented Feb 6, 2020

Wow, how did you do the draggable layout?

@xavArtley

This comment has been minimized.

Copy link
Collaborator Author

xavArtley commented Feb 6, 2020

I used http://golden-layout.com/
In the ipynb you can view the template.
It was not to difficult to learn it (just for the purpose of this example)

@philippjfr

This comment has been minimized.

Copy link
Member

philippjfr commented Feb 6, 2020

Very cool, you used it as a custom template?

@xavArtley

This comment has been minimized.

Copy link
Collaborator Author

xavArtley commented Feb 6, 2020

@philippjfr

This comment has been minimized.

Copy link
Member

philippjfr commented Feb 7, 2020

Going to merge and get this in 0.8.1 pretending it's a simple enhancement 😄

@xavArtley xavArtley force-pushed the xav/export_colormap branch from 6636f8a to 033ce54 Feb 7, 2020
philippjfr added 2 commits Feb 7, 2020
@philippjfr

This comment has been minimized.

Copy link
Member

philippjfr commented Feb 7, 2020

@xavArtley The VTKVolume.mapper isn't syncing for me in the notebook, any idea what's going on there?

@xavArtley

This comment has been minimized.

Copy link
Collaborator Author

xavArtley commented Feb 8, 2020

@xavArtley

This comment has been minimized.

Copy link
Collaborator Author

xavArtley commented Feb 8, 2020

I haven't read all the template documentation to handle template in notebook. I will make some modifications instead of testing the document title

@xavArtley

This comment has been minimized.

Copy link
Collaborator Author

xavArtley commented Feb 8, 2020

@xavArtley The VTKVolume.mapper isn't syncing for me in the notebook, any idea what's going on there?

I found something strange when looking at volume.model after displaying the template there is 2 reférences
Capture d’écran de 2020-02-08 13-16-08

@philippjfr

This comment has been minimized.

Copy link
Member

philippjfr commented Feb 10, 2020

I found something strange when looking at volume.model after displaying the template there is 2 reférences

This is in fact expected, templates have to do some funny stuff to linking set up.

@xavArtley

This comment has been minimized.

Copy link
Collaborator Author

xavArtley commented Feb 13, 2020

@philippjfr Do you think it should be possible to set data_aspect=1 to images while keeping layout responsivness?

xavArtley added 2 commits Feb 13, 2020
@philippjfr

This comment has been minimized.

Copy link
Member

philippjfr commented Feb 15, 2020

@philippjfr Do you think it should be possible to set data_aspect=1 to images while keeping layout responsivness?

Would definitely need some special handling I think only bokeh figures currently support aspect handling out of the box.

@philippjfr philippjfr merged commit 25a734c into master Feb 15, 2020
0 of 2 checks passed
0 of 2 checks passed
continuous-integration/travis-ci/pr The Travis CI build could not complete due to an error
Details
continuous-integration/travis-ci/push The Travis CI build could not complete due to an error
Details
@xavArtley

This comment has been minimized.

Copy link
Collaborator Author

xavArtley commented Feb 16, 2020

@philippjfr Python 2 failed on master because the property .active_scalars was introduced on pyvista 0.23.1 and for the python 2 build the version 0.22.4 seems to be installed.
2 solutions to correct it:

  • use the old property (trigger deprection warnings) .active_scalar
  • update pyvista version to >= 0.23for python 2 build however I don't know why it's not the same version on python 3 and python 2.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

2 participants
You can’t perform that action at this time.