1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
|
# This code simulates something the user would like to do. In this
# case the code allows a user to create a 3D cube of data (a numpy
# array), specify an equation for the scalars and view it using the
# mayavi plugin. The only "envisage bits" are the code that let one
# grab the running mayavi instance and script it. The application
# trait is set by Envisage and we use the application to get hold of
# the mayavi engine. Then we show the data once the mayavi engine has
# started.
# Standard library imports.
import numpy as np
# Enthought library imports
from traits.api import HasTraits, Button, Instance, \
Any, Str, Array
from traitsui.api import Item, View, TextEditor
######################################################################
# `Explorer3D` class.
######################################################################
class Explorer3D(HasTraits):
"""This class basically allows you to create a 3D cube of data (a
numpy array), specify an equation for the scalars and view it
using the mayavi plugin.
"""
########################################
# Traits.
# Set by envisage when this is offered as a service offer.
window = Instance('pyface.workbench.api.WorkbenchWindow')
# The equation that generates the scalar field.
equation = Str('sin(x*y*z)/(x*y*z)',
desc='equation to evaluate (enter to set)',
auto_set=False,
enter_set=True)
# Dimensions of the cube of data.
dimensions = Array(value=(128, 128, 128),
dtype=int,
shape=(3,),
cols=1,
labels=['nx', 'ny', 'nz'],
desc='the array dimensions')
# The volume of interest (VOI).
volume = Array(dtype=float,
value=(-5,5,-5,5,-5,5),
shape=(6,),
cols=2,
labels=['xmin','xmax','ymin','ymax','zmin','zmax'],
desc='the volume of interest')
# Clicking this button resets the data with the new dimensions and
# VOI.
update_data = Button('Update data')
########################################
# Private traits.
# Our data source.
_x = Array
_y = Array
_z = Array
data = Array
source = Any
_ipw1 = Any
_ipw2 = Any
_ipw3 = Any
########################################
# Our UI view.
view = View(Item('equation', editor=TextEditor(auto_set=False,
enter_set=True)),
Item('dimensions'),
Item('volume'),
Item('update_data', show_label=False),
resizable=True,
scrollable=True,
)
######################################################################
# `object` interface.
######################################################################
def __init__(self, **traits):
super(Explorer3D, self).__init__(**traits)
# Make some default data.
if len(self.data) == 0:
self._make_data()
# Note: to show the visualization by default we must wait till
# the mayavi engine has started. To do this we hook into the
# mayavi engine's started event and setup our visualization.
# Now, when this object is constructed (i.e. when this method
# is invoked), the services are not running yet and our own
# application instance has not been set. So we can't even
# get hold of the mayavi instance. So, we do the hooking up
# when our application instance is set by listening for
# changes to our application trait.
def get_mayavi(self):
from mayavi.plugins.script import Script
return self.window.get_service(Script)
######################################################################
# Non-public methods.
######################################################################
def _make_data(self):
dims = self.dimensions.tolist()
xmin, xmax, ymin, ymax, zmin, zmax = self.volume
x, y, z = np.ogrid[xmin:xmax:dims[0]*1j,
ymin:ymax:dims[1]*1j,
zmin:zmax:dims[2]*1j]
self._x = x.astype('f')
self._y = y.astype('f')
self._z = z.astype('f')
self._equation_changed('', self.equation)
def _show_data(self):
if self.source is not None:
return
mayavi = self.get_mayavi()
if mayavi.engine.current_scene is None:
mayavi.new_scene()
from mayavi.sources.array_source import ArraySource
vol = self.volume
origin = vol[::2]
spacing = (vol[1::2] - origin)/(self.dimensions -1)
src = ArraySource(transpose_input_array=False,
scalar_data=self.data,
origin=origin,
spacing=spacing)
self.source = src
mayavi.add_source(src)
from mayavi.modules.outline import Outline
from mayavi.modules.image_plane_widget import ImagePlaneWidget
from mayavi.modules.axes import Axes
# Visualize the data.
o = Outline()
mayavi.add_module(o)
a = Axes()
mayavi.add_module(a)
self._ipw1 = ipw = ImagePlaneWidget()
mayavi.add_module(ipw)
ipw.module_manager.scalar_lut_manager.show_scalar_bar = True
self._ipw2 = ipw_y = ImagePlaneWidget()
mayavi.add_module(ipw_y)
ipw_y.ipw.plane_orientation = 'y_axes'
self._ipw3 = ipw_z = ImagePlaneWidget()
mayavi.add_module(ipw_z)
ipw_z.ipw.plane_orientation = 'z_axes'
######################################################################
# Traits static event handlers.
######################################################################
def _equation_changed(self, old, new):
try:
g = np.__dict__
s = eval(new, g, {'x':self._x,
'y':self._y,
'z':self._z})
# The copy makes the data contiguous and the transpose
# makes it suitable for display via tvtk.
s = s.transpose().copy()
# Reshaping the array is needed since the transpose
# messes up the dimensions of the data. The scalars
# themselves are ravel'd and used internally by VTK so the
# dimension does not matter for the scalars.
s.shape = s.shape[::-1]
self.data = s
except:
pass
def _dimensions_changed(self):
"""This does nothing and only changes to update_data do
anything.
"""
return
def _volume_changed(self):
return
def _update_data_fired(self):
self._make_data()
src = self.source
if src is not None:
vol = self.volume
origin = vol[::2]
spacing = (vol[1::2] - origin)/(self.dimensions -1)
# Set the source spacing and origin.
src.trait_set(spacing=spacing, origin=origin)
# Update the sources data.
src.update_image_data = True
self._reset_ipw()
def _reset_ipw(self):
ipw1, ipw2, ipw3 = self._ipw1, self._ipw2, self._ipw3
if ipw1.running:
ipw1.ipw.place_widget()
if ipw2.running:
ipw2.ipw.place_widget()
ipw2.ipw.plane_orientation = 'y_axes'
if ipw3.running:
ipw3.ipw.place_widget()
ipw3.ipw.plane_orientation = 'z_axes'
self.source.render()
def _data_changed(self, value):
if self.source is None:
return
self.source.scalar_data = value
def _window_changed(self):
m = self.get_mayavi()
if m.engine.running:
if len(self.data) == 0:
# Happens since the window may be set on __init__ at
# which time the data is not created.
self._make_data()
self._show_data()
else:
# Show the data once the mayavi engine has started.
m.engine.on_trait_change(self._show_data, 'started')
|