• No results found

Volume slicer advanced example

EXAMPLE GALLERY 10.1 Mlab functions gallery

10.3 Interactive examples

10.3.15 Volume slicer advanced example

An efficient implementation of the triple-plane view showing 3 cut planes on volumetric data, and side views showing each cut, with a cursor to move the other cuts.

This is an example of complex callback interaction. It builds on the Volume slicer examplebut has more complex logic. You should try to understand theVolume slicer examplefirst.

In this example, the VolumeSlicer object displays a position attribute giving the position of the cut in data coordinates. Traits callbacks are used to move the cut planes when this position attribute is modifed.

In the 3D window, the 3D cuts are displayed using ImagePlaneWidgets cutting the 3D volumetric data. The data extracted by the ImagePlaneWidgets for plotting is captured using the TVTK ImagePlaneWidget’s _get_reslice_output method. The resulting dataset is plotted in each side view using another ImagePlaneWidget. As a result the data is not copied (at the VTK level, there is only one pipeline), and modifications of the data plotted on the planes in the 3D view (for instance when these planes are moved) are propagated to the 2D side views by the VTK pipeline.

A cursor is displayed in each side view using a glyph. The cursor indicates the position of the cut.

In the side view, when the mouse button is pressed on the planes, it creates a VTK InteractionEvent. When this happens, VTK calls an callback (observer, it VTK terms), that we use to move the position of the cut. The Traits callbacks do the rest for the updating.

Source code: volume_slicer_advanced.py import numpy as np

from enthought.traits.api import HasTraits, Instance, Array, \ Bool, Dict, on_trait_change

from enthought.traits.ui.api import View, Item, HGroup, Group

from enthought.tvtk.api import tvtk

from enthought.tvtk.pyface.scene import Scene

from enthought.mayavi import mlab

Mayavi User Guide, Release 3.3.1

from enthought.mayavi.core.api import PipelineBase, Source from enthought.mayavi.core.ui.api import SceneEditor

from enthought.mayavi.tools.mlab_scene_model import MlabSceneModel

################################################################################ # The object implementing the dialog

class VolumeSlicer(HasTraits): # The data to plot

data = Array

# The position of the view position = Array(shape=(3,))

# The 4 views displayed

scene3d = Instance(MlabSceneModel, ()) scene_x = Instance(MlabSceneModel, ()) scene_y = Instance(MlabSceneModel, ()) scene_z = Instance(MlabSceneModel, ())

# The data source

data_src = Instance(Source)

# The image plane widgets of the 3D scene ipw_3d_x = Instance(PipelineBase)

ipw_3d_y = Instance(PipelineBase) ipw_3d_z = Instance(PipelineBase)

# The cursors on each view: cursors = Dict() disable_render = Bool _axis_names = dict(x=0, y=1, z=2) #--- # Object interface #---

def __init__(self, **traits):

super(VolumeSlicer, self).__init__(**traits) # Force the creation of the image_plane_widgets:

self.ipw_3d_x self.ipw_3d_y self.ipw_3d_z #--- # Default values #---

def _position_default(self):

return 0.5*np.array(self.data.shape)

def _data_src_default(self):

return mlab.pipeline.scalar_field(self.data,

figure=self.scene3d.mayavi_scene, name=’Data’,)

def make_ipw_3d(self, axis_name):

Mayavi User Guide, Release 3.3.1

ipw = mlab.pipeline.image_plane_widget(self.data_src, figure=self.scene3d.mayavi_scene,

plane_orientation=’%s_axes’ % axis_name, name=’Cut %s’ % axis_name)

return ipw

def _ipw_3d_x_default(self):

return self.make_ipw_3d(’x’)

def _ipw_3d_y_default(self):

return self.make_ipw_3d(’y’)

def _ipw_3d_z_default(self):

return self.make_ipw_3d(’z’)

#--- # Scene activation callbacks

#--- @on_trait_change(’scene3d.activated’)

def display_scene3d(self):

outline = mlab.pipeline.outline(self.data_src, figure=self.scene3d.mayavi_scene, )

self.scene3d.mlab.view(40, 50)

# Interaction properties can only be changed after the scene # has been created, and thus the interactor exists

for ipw in (self.ipw_3d_x, self.ipw_3d_y, self.ipw_3d_z): ipw.ipw.interaction = 0

self.scene3d.scene.background = (0, 0, 0) # Keep the view always pointing up

self.scene3d.scene.interactor.interactor_style = \ tvtk.InteractorStyleTerrain()

self.update_position()

def make_side_view(self, axis_name):

scene = getattr(self, ’scene_%s’ % axis_name) scene.scene.parallel_projection = True

ipw_3d = getattr(self, ’ipw_3d_%s’ % axis_name)

# We create the image_plane_widgets in the side view using a # VTK dataset pointing to the data on the corresponding # image_plane_widget in the 3D view (it is returned by # ipw_3d._get_reslice_output())

ipw = mlab.pipeline.image_plane_widget(

ipw_3d.ipw._get_reslice_output(), plane_orientation=’z_axes’, vmin=self.data.min(), vmax=self.data.max(), figure=scene.mayavi_scene, name=’Cut view %s’ % axis_name, )

setattr(self, ’ipw_%s’ % axis_name, ipw) # Make left-clicking create a crosshair ipw.ipw.left_button_action = 0

Mayavi User Guide, Release 3.3.1

x, y, z = self.position

cursor = mlab.points3d(x, y, z, mode=’axes’, color=(0, 0, 0),

scale_factor=2*max(self.data.shape), figure=scene.mayavi_scene,

name=’Cursor view %s’ % axis_name, )

self.cursors[axis_name] = cursor

# Add a callback on the image plane widget interaction to # move the others

this_axis_number = self._axis_names[axis_name]

def move_view(obj, evt):

# Disable rendering on all scene

position = list(obj.GetCurrentCursorPosition())[:2]

position.insert(this_axis_number, self.position[this_axis_number]) # We need to special case y, as the view has been rotated.

if axis_name is ’y’:

position = position[::-1]

self.position = position

ipw.ipw.add_observer(’InteractionEvent’, move_view) ipw.ipw.add_observer(’StartInteractionEvent’, move_view)

# Center the image plane widget

ipw.ipw.slice_position = 0.5*self.data.shape[

self._axis_names[axis_name]] # 2D interaction: only pan and zoom

scene.scene.interactor.interactor_style = \

tvtk.InteractorStyleImage() scene.scene.background = (0, 0, 0)

# Some text:

mlab.text(0.01, 0.8, axis_name, width=0.08)

# Choose a view that makes sens

views = dict(x=(0, 0), y=(90, 180), z=(0, 0)) mlab.view(*views[axis_name],

focalpoint=0.5*np.array(self.data.shape), figure=scene.mayavi_scene)

scene.scene.camera.parallel_scale = 0.52*np.mean(self.data.shape) @on_trait_change(’scene_x.activated’)

def display_scene_x(self):

return self.make_side_view(’x’) @on_trait_change(’scene_y.activated’)

def display_scene_y(self):

return self.make_side_view(’y’) @on_trait_change(’scene_z.activated’)

def display_scene_z(self):

return self.make_side_view(’z’)

#---

Mayavi User Guide, Release 3.3.1

# Traits callback

#--- @on_trait_change(’position’)

def update_position(self):

""" Update the position of the cursors on each side view, as well as the image_plane_widgets in the 3D view.

"""

# First disable rendering in all scenes to avoid unecessary # renderings

self.disable_render = True

# For each axis, move image_plane_widget and the cursor in the # side view

for axis_name, axis_number in self._axis_names.iteritems(): ipw3d = getattr(self, ’ipw_3d_%s’ % axis_name)

ipw3d.ipw.slice_position = self.position[axis_number] # Go from the 3D position, to the 2D coordinates in the # side view

position2d = list(self.position) position2d.pop(axis_number) if axis_name is ’y’:

position2d = position2d[::-1] # Move the cursor

self.cursors[axis_name].mlab_source.set(

x=[position2d[0]], y=[position2d[1]], z=[0])

# Finally re-enable rendering

self.disable_render = False

@on_trait_change(’disable_render’)

def _render_enable(self):

for scene in (self.scene3d, self.scene_x, self.scene_y,

self.scene_z): scene.scene.disable_render = self.disable_render

#--- # The layout of the dialog created

#--- view = View(HGroup(

Group(

Item(’scene_y’,

editor=SceneEditor(scene_class=Scene), height=250, width=300),

Item(’scene_z’,

editor=SceneEditor(scene_class=Scene), height=250, width=300),

show_labels=False, ),

Group(

Item(’scene_x’,

editor=SceneEditor(scene_class=Scene), height=250, width=300),

Item(’scene3d’,

editor=SceneEditor(scene_class=Scene),

Mayavi User Guide, Release 3.3.1 height=250, width=300), show_labels=False, ), ), resizable=True,

title=’Volume Slicer’, )

################################################################################ if __name__ == ’__main__’:

# Create some data

x, y, z = np.ogrid[-5:5:64j, -5:5:64j, -5:5:64j] data = np.sin(3*x)/x + 0.05*z**2 + np.cos(3*y)

m = VolumeSlicer(data=data) m.configure_traits()