# Mayavi-更方便的可視化
雖然VTK 3D可視化軟件包功能強大,Python的TVTK包裝方便簡潔,但是要用這些工具快速編寫實用的三維可視化程序仍然需要花費不少的精力。因此基于VTK開發了許多可視化軟件,例如:ParaView、 VTKDesigner2、Mayavi2等等。
Mayavi2完全用Python編寫,因此它不但是一個方便實用的可視化軟件,而且可以方便地用Python編寫擴展,嵌入到用戶編寫的Python程序中,或者直接使用其面向腳本的API:mlab快速繪制三維圖。
## 用mlab快速繪圖
和Chaco的shell或者matplotlib的pylab一樣,mayavi的mlab模塊提供了方便快捷的繪制三維圖函數。只要把數據準備好,通常只需要調用一次mlab的函數就可以看到數據的三維顯示效果。非常適合在IPython中交互式地使用。下面讓我們來看一個例子:
```
import numpy as np
from enthought.mayavi import mlab
x, y = np.ogrid[-2:2:20j, -2:2:20j]
z = x * np.exp( - x**2 - y**2)
pl = mlab.surf(x, y, z, warp_scale="auto")
mlab.axes(xlabel='x', ylabel='y', zlabel='z')
mlab.outline(pl)
```

使用Mayavi將二維數組繪制成3D曲面
我們先用下面的語句載入mlab庫:
```
from enthought.mayavi import mlab
```
然后通過調用mlab.surf繪制一個三維空間中的曲面。曲面上的每個點的坐標由surf函數的三個二維數組參數x,y,z給出。由于數組x,y是由ogrid對象算出,它們分別是shape為n*1和1*n的數組,而z是一個n*n的數組。
通過調用mlab.axes和mlab.outline函數,分別在三維空間中添加坐標軸,和曲面區域的外框。
surf繪制的曲面在X-Y平面上的投影是一個等距離的網格,如果需要繪制更復雜的三維曲面的話,可以使用mesh函數。下面是mesh函數的一個例子:
```
# -*- coding: utf-8 -*-
from numpy import *
from enthought.mayavi import mlab
# Create the data.
dphi, dtheta = pi/20.0, pi/20.0
[phi,theta] = mgrid[0:pi+dphi*1.5:dphi,0:2*pi+dtheta*1.5:dtheta]
m0 = 4; m1 = 3; m2 = 2; m3 = 3; m4 = 6; m5 = 2; m6 = 6; m7 = 4;
r = sin(m0*phi)**m1 + cos(m2*phi)**m3 + sin(m4*theta)**m5 + cos(m6*theta)**m7
x = r*sin(phi)*cos(theta)
y = r*cos(phi)
z = r*sin(phi)*sin(theta)
# View it.
s = mlab.mesh(x, y, z, representation="wireframe", line_width=1.0 )
mlab.show()
```

使用mesh函數繪制的3D旋轉體
mesh和surf類似,其三個數組參數x, y, z也是二維數組,他們相同下標的三個元素組成曲面上某點的三維坐標。點之間的連接關系(邊和面)由其在x,y,z數組中間的位置關系決定。
由于這個程序所計算的曲面是一個旋轉體,曲面上的各個點的坐標是在球面坐標系中計算的,然后按照坐標轉換公式將球面坐標轉換為X-Y-Z坐標。
通過傳遞一個關鍵字參數representation給mesh函數,可以指定繪制的表現形式:
* **surface** : 缺省值,繪制曲面
* **wireframe** : 繪制邊線,將dphi, dtheta的改為較大值,例如pi/20之后,調用 :
```
s = mlab.mesh(x, y, z, representation="wireframe", line_width=1.0 )
```
得到如下結果:

使用mesh函數繪制的線框模型
為了方便理解mesh函數是如何繪制出曲面的,我們通過手工輸入坐標的方式,繪制如下圖所示的立方體表面的一部分:

組成立方體的各個面和頂點坐標
x,y,z數組的定義如下:
```
x = [[-1,1,1,-1,-1],
[-1,1,1,-1,-1]]
y = [[-1,-1,-1,-1,-1],
[1,1,1,1, 1]]
z = [[1,1,-1,-1,1],
[1,1,-1,-1,1]]
```
x, y, z數組對應坐標的元素組成三維坐標點,因此這三個數組實際描述的坐標點為:
```
[
[(-1, -1, 1), (1, -1, 1), (1, -1, -1), (-1, -1, -1), (-1, -1, 1)]
[(-1, 1, 1), (1, 1, 1), (1, 1, -1), (-1, 1, -1), (-1, 1, 1)]
]
```
點之間的關系有其在數組中的下標決定,因此由:
```
(-1,-1,1),(1,-1,1),(-1,1,1),(1,1,1)
```
構成一個mesh中的一個面。依次類推,第二個面由:
```
(1,-1,1),(1,-1,-1),(1,1,1),(1,1,-1)
```
構成,一共定義有4個面。
下面詳細介紹mlab中提供的繪圖函數。
* **points3d**, **plot3d** : 給它們傳遞的3個坐標數組x,y,z都是一維的,因此這兩個函數繪制出來的是三維空間中的一系列點(points3d),或者是一條曲線(plot3d)。下圖是采用plot3d繪制的洛侖茲吸引子的軌跡:

plot3d函數繪制的洛侖茲吸引子,曲線使用很細的圓管繪制
繪圖語句的程序如下:
```
mlab.plot3d(track1[:,0], track1[:,1], track1[:,2],color=(1,0,0), tube_radius=0.1)
```
其中track1為軌跡坐標數組,將其拆分為X,Y,Z軸的三個分量之后,傳遞給plot3d函數進行繪圖。tube_radius指定曲線的粗細,曲線實際上是采用極細的圓管繪制的。
洛侖茲吸引子的軌跡算法請參照: [_SciPy-數值計算庫_](scipy_intro.html)
* **imshow**, **surf**, **contour_surf** : 這三個函數都可以接收一個二維數組s,以其第一軸的下標為X軸坐標,第二軸的下標為Y軸坐標。imshow函數將此二維數組當作一個圖片顯示,每點的顏色為數組s的每個元素的值。surf函數則將此二維數組繪制成三維空間中的曲面,數組中每個元素的值為點的Z軸坐標。contour_surf則繪制二維數組的等高線。下面是imshow函數的繪制結果(所使用的數組和前面surf函數的例子相同):

imshow函數將二維數組繪制成圖像
同樣的數據采用contour_surf函數繪制等高線的結果如下圖所示:

contour_surf函數繪制二維圖像的等高線
## Mayavi應用程序
## 將Mayavi嵌入到界面中
Mayavi除了能夠單獨作為應用程序使用之外,也可以通過traits屬性嵌入到TraitsUI制作的用戶應用程序的界面中去,下面的程序演示了這一過程:
```
# -*- coding: utf-8 -*-
from enthought.traits.api import *
from enthought.traits.ui.api import *
from enthought.tvtk.pyface.scene_editor import SceneEditor
from enthought.mayavi.tools.mlab_scene_model import MlabSceneModel
from enthought.mayavi.core.ui.mayavi_scene import MayaviScene
class DemoApp(HasTraits):
plotbutton = Button(u"繪圖")
scene = Instance(MlabSceneModel, ()) # mayavi場景
view = View(
VGroup(
Item(name='scene',
editor=SceneEditor(scene_class=MayaviScene), # 設置mayavi的編輯器
resizable=True,
height=250,
width=400
),
'plotbutton',
show_labels=False
),
title=u"在TraitsUI中嵌入Mayavi"
)
def _plotbutton_fired(self):
self.plot()
def plot(self):
g = self.scene.mlab.test_mesh()
app = DemoApp()
app.configure_traits()
```
程序一開始除了從traits和traits.ui庫導入之外,還分別從不同的地方導入了SceneEditor、MlabSceneModel和MayaviScene等三個類。
MlabSceneModel類是包裝整個mlab的場景的模型,它是屬于模型(Model)方面的東西,因此程序中通過:
```
scene = Instance(MlabSceneModel, ())
```
創建一個traits屬性scene,使它是MlabSceneModel類的對象。接下來要在視圖(View)中創建一個編輯器,讓它正確顯示scene所代表的模型:
```
Item(name='scene',
editor=SceneEditor(scene_class=MayaviScene), # 設置mayavi的編輯器
resizable=True,
height=250,
width=400
)
```
SceneEditor是用來創建場景編輯器的工廠類,通過關鍵字scene_class指定真正創建場景對象類MayaviScene。
程序中我們還創建了一個plotbutton按鈕,當此按鈕被按下時,調用_plotbutton_fired函數,從而調用最后的繪制場景的函數plot,plot函數只有一句話:
```
g = self.scene.mlab.test_mesh()
```
scene.mlab和前面所介紹的mlab庫一樣使用,我們調用其test_mesh測試函數,快速在scene中創建一個如[下圖](#fig-mayavi2embed01)所示的很酷的曲面體。

將Mayavi嵌入到TraitsUI制作的界面中
下面讓我們來看一個有些實用價值的程序,用戶輸入一個使用x,y,z等變量的函數f(x,y,z),例如x*x+y*y+z*z,程序將使用此函數計算一個指定坐標范圍之內的三維標量場。并且添加等值面和切面兩個工具觀察此標量場。等值面可以是自動計算,或者通過滾動條手工配置;而切面的位置和方向則可以直接在場景中用鼠標進行操作。完整的程序請參考[_三維標量場觀察器_](example_mayavi_embed_fieldviewer.html),下面對程序中的重點部分進行說明。
用戶點擊描繪按鈕之后,調用plot函數繪圖,plot函數中首先計算三維標量場,注意我們使用mgrid快速產生三維網格,x0, x1, y0, y1, z0, z1, points, function等都是traits屬性,可以通過界面直接修改其值:
```
# 產生三維網格
x, y, z = mgrid[
self.x0:self.x1:1j*self.points,
self.y0:self.y1:1j*self.points,
self.z0:self.z1:1j*self.points]
scalars = eval(self.function) # 根據函數計算標量場的值
```
然后清空當前的場景:
```
self.scene.mlab.clf() # 清空當前場景
```
接下來調用scene.mlab中的axes, contour3d, pipeline.scalar_cut_plane等函數在場景中添加等值面、坐標軸和切面:
```
# 繪制等值面
g = self.scene.mlab.contour3d(x, y, z, scalars, contours=8, transparent=True)
g.contour.auto_contours = self.autocontour
self.scene.mlab.axes() # 添加坐標軸
# 添加一個X-Y的切面
s = self.scene.mlab.pipeline.scalar_cut_plane(g)
cutpoint = (self.x0+self.x1)/2, (self.y0+self.y1)/2, (self.z0+self.z1)/2
s.implicit_plane.normal = (0,0,1) # x cut
s.implicit_plane.origin = cutpoint
```
最后更新幾個屬性,其中v0和v1是標量場的最小和最大值,用來設置等值面滾動條的取值范圍:
```
self.g = g
self.scalars = scalars
# 計算標量場的值的范圍
self.v0 = np.min(scalars)
self.v1 = np.max(scalars)
```
當界面中的“自動等值”選項值(autocontour屬性)改變時,通過調用如下程序改變場景中的等值面的自動選項:
```
self.g.contour.auto_contours = self.autocontour
```
當界面中的滾動條值(contour屬性)發生變化時,通過下面的程序修改場景中等值面的值:
```
if not self.g.contour.auto_contours:
self.g.contour.contours = [self.contour]
```
剩下的部分都是使用標準的traits和TraitsUI庫編寫的,請參考[_TraitsUI-輕松制作用戶界面_](traitsUI_intro.html),這里就不再多解釋了。
此程序的界面截圖如[下圖](#fig-mayavi2embed02)所示:

三維標量場觀察器:`x*y*0.5+2*y*sin(2*x)+y*z*2.0`
- 用Python做科學計算
- 軟件包的安裝和介紹
- NumPy-快速處理數據
- SciPy-數值計算庫
- matplotlib-繪制精美的圖表
- Traits-為Python添加類型定義
- TraitsUI-輕松制作用戶界面
- Chaco-交互式圖表
- TVTK-三維可視化數據
- Mayavi-更方便的可視化
- Visual-制作3D演示動畫
- OpenCV-圖像處理和計算機視覺
- Traits使用手冊
- 定義Traits
- Trait事件處理
- 設計自己的Trait編輯器
- Visual使用手冊
- 場景窗口
- 聲音的輸入輸出
- 數字信號系統
- FFT演示程序
- 頻域信號處理
- Ctypes和NumPy
- 自適應濾波器和NLMS模擬
- 單擺和雙擺模擬
- 分形與混沌
- 關于本書的編寫
- 最近更新
- 源程序集
- 三角波的FFT演示
- 在traitsUI中使用的matplotlib控件
- CSV文件數據圖形化工具
- NLMS算法的模擬測試
- 三維標量場觀察器
- 頻譜泄漏和hann窗
- FFT卷積的速度比較
- 二次均衡器設計
- 單擺擺動周期的計算
- 雙擺系統的動畫模擬
- 繪制Mandelbrot集合
- 迭代函數系統的分形
- 繪制L-System的分形圖