# TVTK-三維可視化數據
VTK ([http://www.vtk.org/](http://www.vtk.org/)) 是一套三維的數據可視化工具,它由C++編寫,包涵了近千個類幫助我們處理和顯示數據。它在Python下有標準的綁定,不過其API和C++相同,不能體現出Python作為動態語言的優勢。因此enthought.com開發了一套TVTK庫對標準的VTK庫進行包裝,提供了Python風格的API、支持Trait屬性和numpy的多維數組。本文將以TVTK為標準對VTK的一些功能進行介紹,如果讀者已經對VTK很了解,想知道TVTK和VTK的區別的話,可以直接跳到第二節。
## TVTK使用簡介
a
### 顯示圓錐
作為第一例子,讓我們來看一個顯示圓錐的小程序:
```
# -*- coding: utf-8 -*-
from enthought.tvtk.api import tvtk
# 創建一個圓錐數據源,并且同時設置其高度,底面半徑和底面圓的分辨率(用36邊形近似)
cs = tvtk.ConeSource(height=3.0, radius=1.0, resolution=36)
# 使用PolyDataMapper將數據轉換為圖形數據
m = tvtk.PolyDataMapper(input = cs.output)
# 創建一個Actor
a = tvtk.Actor(mapper=m)
# 創建一個Renderer,將Actor添加進去
ren = tvtk.Renderer(background=(0.1, 0.2, 0.4))
ren.add_actor(a)
# 創建一個RenderWindow(窗口),將Renderer添加進去
rw = tvtk.RenderWindow(size=(300,300))
rw.add_renderer(ren)
# 創建一個RenderWindowInteractor(窗口的交互工具)
rwi = tvtk.RenderWindowInteractor(render_window=rw)
# 開啟交互
rwi.initialize()
rwi.start()
```
此程序的運行畫面如下:

使用TVTK繪制簡單的圓錐
首先從tvtk.api中載入tvtk,tvtk像是一個工廠,能夠幫助我們創建vtk中的各種對象:
```
>>> from enthought.tvtk.api import tvtk
```
下面創建了一個ConeSource(圓錐數據源)對象,并用變量cs保存它。原始的VTK對象的屬性,在tvtk中都以trait屬性的形式進行包裝,因此我們可以在創建對象的同時,傳遞關鍵字參數直接配置各個trait屬性的值,在這個例子中,同時設置了圓錐的高度,底面半徑和底面圓的分辨率(用36邊形近似)等屬性,最后調用print_traits顯示所創建的圓錐數據的所有trait屬性,為了節省篇幅,這里只挑選了其中的幾個屬性:
```
>>> cs = tvtk.ConeSource(height=3.0, radius=1.0, resolution=36)
>>> cs.print_traits()
...
angle: 18.43494882292201
...
center: array([ 0., 0., 0.])
class_name: 'vtkConeSource'
...
direction: array([ 1., 0., 0.])
...
height: 3.0
...
radius: 1.0
...
resolution: 36
...
```
在VTK中將原始數據轉換為我們看到的屏幕上的一幅圖像,要經過許多步驟的處理,這些步驟由眾多的VTK的對象共同協調完成,就好象生產線上加工零件一樣,每位工人都負責一部分的工作,整條生產線就能將原材料制作成產品。因此在VTK中,這種對象之間協調完成工作的過程被稱作流水線(Pipeline)。
原始數據被轉換為圖像要經過兩條流水線:
* 可視化流水線(Visualization Pipeline):它的工作是將原始數據加工成圖形數據。通常我們需要可視化的數據本身并不是圖形數據,例如某個零件內部各個部分的溫度,或者是流體在各個坐標點上的速度等等。
* 圖形流水線(Graphics Pipeline):它的工作是將將圖形數據加工為我們所看到的圖像。可視化流水線所產生的圖形數據通常是三維空間的數據,如何在二維的屏幕上顯示出來就需要圖形流水線的加工了。
映射器(Mapper)則是可視化流水線的終點,圖形流水線的起點,它將各種派生類能將眾多的數據映射為圖形數據以供圖形流水線加工。
讓我們對照一下前面的的圓錐的例子:ConeSource的對象通過程序內部計算輸出一組描述圓錐的數據(PolyData):然后,PolyData通過PolyDataMapper映射器將數據映射為圖形數據。在這個例子中,可視化流水線由ConSource和PolyDataMapper組成。
圖形數據依次通過Actor、Renderer最終在RenderWindow中顯示出來,這一部分就是圖形流水線。
* **Actor** : 表示潤色場景中的一個實體。它包括一個圖形數據(mapper),并且具有描述實體的位置、方向、大小的屬性。
* **Renderer** : 表示潤色的場景。它包括多個需要潤色的Actor。在圓錐的例子中,它只包括一個表示圓錐的Actor。
* **RenderWindow** : 表示潤色用的圖形窗口,它包括一個或者多個Render。在圓錐的例子中,它只包括一個Renderer。
* **RenderWindowInteractor** : 給圖形窗口提供一些用戶交互功能,例如平移、旋轉、放大縮小。這些交互式操作并不改變Actor或者圖形數據的屬性,只是調整場景中的照相機(Camera)的一些設置而已。
什么是PolyData
PolyData是一個描述一組三維空間中的點、線、面的數據結構。點、線、面通過以下幾個屬性描述:
> * **points** : 類型為Points,保存三維空間中的點的坐標的數組,這些數據不是用來顯示的。
> * **verts** : 類型為CellArray,它描述需要顯示的頂點,其值為 **points** 某個坐標點的下標,即通過 **verts** 屬性描述 **points** 中的哪些點是最終需要顯示的。
> * **line** : 類型為CellArray,它描述需要顯示的邊線,其值為邊線的兩個端點在 **points** 中的下標。
> * **polys** : 類型為CellArray,它描述需要顯示的面,其值為構成面的各個點在 **points** 中的下標。
### 用ivtk觀察流水線
為了方便我們操作和觀察流水線,交互式地修改各個tvtk對象的屬性,TVTK庫為我們提供了一個叫做ivtk的對象。下面是使用ivtk顯示圓錐的程序:
```
# -*- coding: utf-8 -*-
from enthought.tvtk.api import tvtk
# 載入ivtk所需要的對象
from enthought.tvtk.tools import ivtk
from enthought.pyface.api import GUI
cs = tvtk.ConeSource(height=3.0, radius=1.0, resolution=36)
m = tvtk.PolyDataMapper(input = cs.output)
a = tvtk.Actor(mapper=m)
# 創建一個GUI對象,和一個帶Crust(Python shell)的ivtk窗口
gui = GUI()
window = ivtk.IVTKWithCrustAndBrowser(size=(800,600))
window.open()
window.scene.add_actor( a ) # 將圓錐的actor添加進窗口的場景中
gui.start_event_loop()
#window.scene.reset_zoom()
```
此程序的運行畫面如下:

帶流水線瀏覽器和Python Shell的界面
除了顯示圓錐的場景之外,ivtk創建的窗口還為我們提供了如下幾個元素:
* **場景工具條** : 位于潤色場景的上方。主要提供了各種視角、全屏顯示、保存圖像等幾個功能。
* **流水線瀏覽器** : 場景的左邊是一個用樹狀結構表示的流水線。從葉子節點(ConeSource)開始逐步向上層直到根節點RenderWindow,是完整的顯示圓錐的流水線。
* **Python Shell** : 下方提供了一個Python Shell,便于我們直接輸入命令操作各個對象。例如圖中我們打印出ConeSource的輸出PolyData的points屬性的值。即構成圓錐圖形的各個點的三維坐標。
流水線瀏覽器中顯示的各個對象的類型都繼承于HasTraits類,因此它們都可以提供一個用戶界面交互式地修改它們的trait屬性。下圖是雙擊ConeSource之后出現的修改ConeSource屬性的界面。
Note
在我的電腦上,雙擊ConeSource之后出現一個很小的窗口,需要手工調整大小。

編輯ConeSource對象的屬性的對話框
我們看到可以通過此界面直接修改height、Radius、resolution等屬性,并且修改之后場景中的圓錐按照最新的屬性值立即更新顯示。
### 從文件讀取數據
大多數情況下我們不會只用VTK顯示圓錐這樣的簡單物體,VTK的建模功能并不強大,因此它支持許多種格式的文件,能將其它軟件產生的數據通過各種Reader類讀入VTK,放到流水線上處理。下面的例子從文件42400-IDGH.stl中載入模型數據,并且潤色顯示。
```
# -*- coding: utf-8 -*-
from enthought.tvtk.api import tvtk
from enthought.tvtk.tools import ivtk
from enthought.pyface.api import GUI
part =tvtk.STLReader(file_name = "42400-IDGH.stl")
part_mapper = tvtk.PolyDataMapper( input = part.output )
part_actor = tvtk.Actor( mapper = part_mapper )
gui = GUI()
window = ivtk.IVTKWithBrowser(size=(800,600))
window.open()
window.scene.add_actor( part_actor )
gui.start_event_loop()
```
此程序的運行畫面如下:

顯示文件中的3D模型
對比顯示圓錐的程序,除了PolyDataMapper的輸入從ConeSource改為STLReader之外,其他的部分沒有任何區別。STLReader對象知道如何讀取STL文件中的數據,并且轉換為PolyData,以供PolyDataMapper使用。另外請注意我們這次用ivtk.IVTKWithBrowser產生一個不帶Python Shell的ivtk窗口。
STL是什么文件
STL的全稱為stereo-lithography,由3D Systems公司開發,它使用三角形面片來表示三維實體模型,現已成為CAD/CAM系統接口文件格式的工業標準之一,絕大多數造型系統能支持并生成此種格式文件。例子中的42400-IDGH.stl文件來自于VTK的示例數據。筆者對模具設計沒有研究,只是照葫蘆畫瓢,把VTK的例子轉換為TVTK而已。
### 過濾數據
前面的例子中包括一個數據源和mapper對象,但是流水線中沒有過濾器對數據進行過濾,下面我們看看如何對數據進行過濾以減少多邊形面的數量。對上節的程序進行修改,在STLReader和PolyDataMapper之間插入一個ShrinkPolyData對象:
```
part =tvtk.STLReader(file_name = "42400-IDGH.stl")
shrink = tvtk.ShrinkPolyData(input = part.output, shrink_factor = 0.5 )
part_mapper = tvtk.PolyDataMapper( input = shrink.output )
```
ShrinkPolyData過濾器的輸入和輸出都是PolyData,它可以減少輸入PolyData對象中單元(點線面)的數目,但是會造成不單元之間不連續。

使用ShrinkPolyData過濾器過濾后的模型
### 控制照相機
如果你使用ivtk顯示3D數據的話,在左邊的流水線瀏覽器中可以找到OpenGLCamera,雙擊它彈出如下窗口:

編輯照相機屬性的對話框
這個窗口顯示的是3D場景的照相機的所有配置。如果你需要用程序控制照相機的話,可以用:
```
>>> camera = window.scene.renderer.active_camera
```
獲得場景中的當前照相機對象,然后就可以獲得或者修改照相機的各項配置:
```
>>> camera.clipping_rage
array([ 20.46912341, 51.21854284])
>>> camera.view_up = 0,1,0
```
下面介紹一些照相機的一些常用屬性:
* **clipping_plane** : 它有兩個元素,分別表示照相機到近、遠兩個裁剪平面的距離。在這兩個平面之外的對象將不會被顯示出來。
* **position** : 照相機在三維空間中的坐標
* **focal_point** : 照相機所聚焦的焦點坐標
* **view_up** : 照相機的上方向矢量
* **parallel_projection** : 如果為True的話表示采用平行透視,即在3D場景中平行的線投影到2D平面上將仍然是平行的
這些屬性雖然可以完全控制照相機的位置和方向,但是實際操作起來并不方便。當將照相機的焦點已經固定好在某個位置上的話,可以通過調用: ..TODO * **azimuth** : 以焦點為圓心,沿著緯度線旋轉指定角度,即水平旋轉,改變其經度
* **elevation** : 沿著經度線方向旋轉指定角度,即垂直旋轉,改變其緯度
在以焦點為原點的球體坐標系中對照相機進行操作。這兩個函數保持view_up屬性不變。
### 控制照明
照明比照相機容易配置得多,假設你運行了ivtk的圓錐的例子的話,直接在窗口下方的命令行中輸入:
```
>>> camera = window.scene.renderer.active_camera
>>> light = tvtk.Light(color=(1,0,0))
>>> light.position=camera.position
>>> light.focal_point=camera.focal_point
>>> window.scene.renderer.add_light(light)
```
..TODO 即可在照相機所在處添加一個紅色的光源,它的照射方向為朝向focal_point點。如果你設置light的positional屬性為True的話,那么它就變成一個探照燈光源,這時照射方向有效。并且可以通過cone_angle屬性設置探照燈的光錐角度。光錐為180度的話,就是無方向光源。
### 控制3D Props
在3D場景中顯示的物體通常被稱作prop,有幾種prop類型,其中包括:Prop3D和Actor。3D場景中所有prop的都從Prop3D繼承。
## TVTK的改進
下面是使用Python的標準VTK庫顯示一個圓錐的例子:
```
import vtk
# Source object .
cone = vtk.vtkConeSource( )
cone.SetHeight( 3.0 )
cone.SetRadius( 1.0 )
cone.SetResolution(10)
# The mapper .
coneMapper = vtk.vtkPolyDataMapper( )
coneMapper.SetInput( cone.GetOutput( ) )
# The actor.
coneActor = vtk.vtkActor( )
coneActor.SetMapper ( coneMapper )
# Set it to render in wireframe
coneActor.GetProperty( ).SetRepresentationToWireframe( )
# Renderer and render window .
ren1 = vtk.vtkRenderer( )
ren1.AddActor( coneActor )
ren1.SetBackground( 0.1 , 0.2 , 0.4 )
renWin = vtk.vtkRenderWindow( )
renWin.AddRenderer( ren1 )
renWin.SetSize(300 , 300)
# On screen interaction .
iren = vtk.vtkRenderWindowInteractor( )
iren.SetRenderWindow( renWin )
iren.Initialize( )
iren.Start( )
```
我們可以出這個例子和C++的程序的區別僅僅是沒有聲明變量的類型,其它的用法完全是按照C++的VTK API調用的。官方所提供的VTK-Python包和C++語言的接口相似,許多地方沒有能夠體現出Python作為動態語言的優勢,可以說標準的VTK-Python庫不夠Python風格。為了彌補標準庫的這些不足之處,Enthought.com開發了TVTK庫進一步對VTK進行包裝,它具有如下的一些優點:
* 支持Trait屬性
* 支持元素的Pickle操作
* API更接近Python風格
* 能自動處理numpy的數組或者Python的列表
* 高級的腳本式的mlab API,和流水線瀏覽器ivtk
* tvtk的場景和流水線瀏覽器支持Envisage插件
### TVTK的基本用法
下面是用TVTK實現上面的顯示圓錐的例子:
```
from enthought.tvtk.api import tvtk
cone = tvtk.ConeSource( height=3.0, radius=1.0, resolution=10 )
cone_mapper = tvtk.PolyDataMapper( input = cone.output )
cone_actor = tvtk.Actor( mapper=cone_mapper )
cone_actor.property.representation = "w"
ren1 = tvtk.Renderer()
ren1.add_actor( cone_actor )
ren1.background = 0.1, 0.2, 0.4
ren_win = tvtk.RenderWindow()
ren_win.add_renderer( ren1 )
ren_win.size = 300, 300
iren = tvtk.RenderWindowInteractor( render_window = ren_win )
iren.initialize()
iren.start()
```
可以看到這個程序比標準VTK版本要簡短得多,從中我們可以看到TVTK的一些重要的更改:
* tvtk用 from enthought.tvtk.api import tvtk 語句載入
* tvtk的類名為VTK的類名除去前綴"vtk"。有些類名在"vtk"之后是數字:vtk3DSImporter,由于Python的標示符首字符不能為數字,因此tvtk對此進行特殊處理:如果首字符為數字,則用其英文單詞代替: ThreeDSImporter。
* tvtk對象的方法名按照Enthought的一貫用法,采用下劃線連接單詞:例如如果VTK中的AddItem,將對應tvtk中的add_item。
* 許多VTK的方法在tvtk中用trait屬性替代,例如前面的例子中我們用m.input = cs.output表示VTK中的m.SetInput(cs.GetOutput()),p.representation = 'w'表示VTK中的p.SetRepresentationToWireframe()。由于在VTK中許多需要用Set*, Get*的屬性在TVTK中都是以Trait屬性表現的,Trait屬性的初始化可以在產生對象的同時進行配置。
* trait屬性可以在創建類的對象的同時通過關鍵字參數進行設置
在內部實現中,所有的tvtk對象都內部包裝有一個VTK對象,對tvtk對象的方法的調用將轉給相應的VTK對象的方法執行,如果返回值是VTK對象的話,將被包裝成tvtk對象返回。如果方法的參數是tvtk對象的話,其中的VTK對象將傳遞給VTK的方法。
通過調用tvtk.to_tvtk(p),可以得到p中所包裝的VTK對象
### Trait屬性
所有的tvtk類都繼承于traits.HasStrictTraits,HasStrictTraits規定了它的子類的對象在創建之后不能對不存在的屬性進行賦值。
VTK中所有和基本狀態有關的的方法在tvtk中都以trait屬性表示。trait屬性為我們帶來如下的便利:
* 通過調用set方法可以一次設置多個trait屬性:
```
>>> p = tvtk.Property()
>>> p.set(opacity=0.5, color=(1,0,0), representation="w")
```
* 通過調用edit_traits或者configure_traits方法直接出界面編輯屬性。對trait屬性的更改將自動作用到內部的VTK對象之上,反過來,內部的VTK對象的狀態改變也將自動更新trait屬性。下面是一個例子:
```
>>> p.edit_traits()
```

每個TVTK對象都可以有自己的編輯屬性的對話框界面
* 我們可以通過tvtk.to_tvtk(p)函數得到任何tvtk對象所包裝的TVK對象:
```
>>> print p.representation
wireframe
>>> p_vtk = tvtk.to_vtk(p)
>>> p_vtk.SetRepresentationToSurface()
>>> print p.representation
surface
```
### 序列化(Pickling)
tvtk對象支持簡單的序列化處理。單個tvtk對象的狀態可以被序列化:
```
>>> import cPickle
>>> p = tvtk.Property()
>>> p.representation="w"
>>> s = cPickle.dumps(p)
>>> del p
>>> q = cPickle.loads(s)
>>> q.representation
'wireframe'
```
但是序列化僅僅能保存對象的狀態,對象之間的引用無法被保存。因此VTK的整個流水線無法用序列化保存。
通常pickle.load將創建新的對象,如果我們希望更新某個已經存在的對象的狀態的話,可以如下調用:
```
>>> p = tvtk.Property()
>>> p.interpolation = "flat"
>>> d = p.__getstate__()
>>> del p
>>> q = tvtk.Property()
>>> q.interpolation
'gouraud'
>>> q.__setstate__(d)
>>> q.interpolation
'flat'
```
### 集合迭代
從tvtk.Collection繼承的對象可以像標準的Python序列對象一樣使用:
```
>>> ac = tvtk.ActorCollection()
>>> len(ac)
0
>>> ac.append(tvtk.Actor())
>>> ac.append(tvtk.Actor())
>>> len(ac)
2
>>> for a in ac:
... print a
...
vtkOpenGLActor (06A99EB8)
......
vtkOpenGLActor (069C4270)
......
>>> del ac[0]
>>> len(ac)
1
```
我們看到ActorCollection可以像Python的列表對象一樣支持len, append和for循環。對比一下VTK的相應的版本,就能體會出tvtk的好處了:
```
>>> ac = vtk.vtkActorCollection()
>>> ac.GetNumberOfItems()
0
>>> ac.AddItem(vtk.vtkActor())
>>> ac.AddItem(vtk.vtkActor())
>>> ac.GetNumberOfItems()
2
>>> ac.InitTraversal()
>>> for i in range(ac.GetNumberOfItems()):
... print ac.GetNextItem()
...
vtkOpenGLActor (05E0A750)
......
vtkOpenGLActor (05E0A8C0)
......
>>> ac.RemoveItem(0)
>>> ac.GetNumberOfItems()
1
```
### 數組操作
所有繼承于DataArray類的對象和Python的序列一樣,支持迭代接口,以及 \_\_getitem\_\_, \_\_setitem\_\_, \_\_repr\_\_, append, extend等等。此外,它還可以直接用numpy的數組或者python的列表直接進行賦值(使用from_array方法),或者將DataArray中保存的數據轉換為numpy的數組。Points和IdList等類也同樣支持這些特性:
```
>>> pts = tvtk.Points()
>>> p_array = np.eye(3)
>>> p_array
array([[ 1., 0., 0.],
[ 0., 1., 0.],
[ 0., 0., 1.]])
>>> pts.from_array(p_array)
>>> pts.print_traits()
_in_set: 0
_vtk_obj: <vtkCommonPython.vtkPoints vtkobject at 069A0FB0>
actual_memory_size: 1L
bounds: (0.0, 1.0, 0.0, 1.0, 0.0, 1.0)
class_name: 'vtkPoints'
data: [(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)]
data_type: 'double'
...
number_of_points: 3
reference_count: 1
>>> pts.to_array()
array([[ 1., 0., 0.],
[ 0., 1., 0.],
[ 0., 0., 1.]])
```
此外tvtk的方法或者屬性如果接受DataArray, Points, IdList或者CellArray的對象的話,那么它也同時支持數組和列表:
```
>>> points = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1]], 'f')
>>> triangles = np.array([[0,1,3],[0,3,2],[1,2,3],[0,2,1]])
>>> values = np.array([1.1, 1.2, 2.1, 2.2])
>>> mesh = tvtk.PolyData(points=points, polys=triangles)
>>> mesh.point_data.scalars = values
>>> mesh.points
[(0.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)]
>>> mesh.polys
<tvtk_classes.cell_array.CellArray object at 0x142D4F60>
>>> mesh.polys.to_array()
array([3, 0, 1, 3, 3, 0, 3, 2, 3, 1, 2, 3, 3, 0, 2, 1])
>>> mesh.point_data.scalars
[1.1000000000000001, 1.2, 2.1000000000000001, 2.2000000000000002]
```
注意CellArray類(mesh的polys屬性)的處理有所不同,我們給它傳入的是一個二維數組,在內存中保存的卻是一維的: array([3, 0, 1, 3, 3, 0, 3, 2, 3, 1, 2, 3, 3, 0, 2, 1])。它的格式是[Cell的數據個數, Cell數據..., Cell的數據個數, Cell數據...],如下圖所示。
CellArray用來描述多邊形(Cell)和頂點之間的關系,由于每個Cell可以由不同數量的定點組成,因此內部采用上面所述的形式保存。
### TVTK是什么
我們通過from enthought.tvtk.api import tvtk載入tvtk,然后像使用一個模塊一樣使用它。事實上,我們載入的tvtk并不是一個模塊,而是某個類的一個實例。之所以會如此設計,是因為VTK庫有近千個類,而TVTK對所有這些類都進行了包裝,如果一次性載入這么多類,會極大地影響庫的載入速度。
我們載入的tvtk雖然是某個類的實例,但是用起來就和模塊一樣:
* 通過tvtk對象可以使用所有的TVTK類
* 它不需要載入近千個TVTK類就能支持類名的自動完成
* 只有在真正使用某個TVTK類時,它才會被載入
所有tvtk相關的代碼全部都保存在tvtk_classes.zip文件中。而tvtk對象的類在此壓縮文件里的tvtk_helper.py中定義。對于TVTK中的每個類,tvtk對象都有一個同名的屬性和類相對應。
- 用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的分形圖