原文鏈接:http://www.aosabook.org/en/vtk.html
作者:Berk Geveci 與 Will Schroeder
可視化工具箱(Visualization Toolkit, VTK)是一種廣泛使用的數據處理與可視化軟件系統(tǒng)。它應用于科學計算、醫(yī)學影像分析、計算幾何、渲染、圖像處理以及信息學等領域。本章,我們展示一個VTK的簡要概覽,包括一些使之成為一個成功系統(tǒng)的基本設計模式。
要真正理解一個軟件系統(tǒng),關鍵之處不僅要理解它能夠解決什么問題,而且還要了解它出現(xiàn)時的特定文化環(huán)境。在VTK的案例中,系統(tǒng)表面看起來要設計成用于科學數據的三維可視化系統(tǒng)。但VTK出現(xiàn)時的文化語境為奮斗者們添上了一個意義深遠的背后故事,這有助于解釋軟件為什么是這樣設計和部署的。
在VTK創(chuàng)生和開始編寫之時,它的初始作者(Will Schroeder、Ken Martin、Bill Lorensen)還是GE研發(fā)部門的科研人員。我們向一個名為LYMB的先驅性系統(tǒng)投入了大量精力,該系統(tǒng)是一種以C實現(xiàn)的、類似Smalltalk的開發(fā)環(huán)境。在那個時代,它是一個偉大的系統(tǒng),我們作為科研人員一再地被阻止在兩大障礙上:1)IP問題(此處意指知識產權(Intellectual Property, IP)——譯注。)和2)非標準的、有所有權的軟件。IP問題之所以是個問題,是因為一旦GE公司的律師介入,那么嘗試將軟件向公司外部公布軟件就幾乎不可能了。第二,即使我們在GE公司內部部署軟件,許多我們的用戶也會受制于學習一個有所有權的、非標準系統(tǒng),因為為了掌握它而作出的努力不能在他離開公司后轉移到新的雇主那里;并且,這種軟件沒有標準工具集所提供的廣泛支持。于是,VTK的原始動機就是開發(fā)一個開放標準,或曰“協(xié)作平臺”,通過它,我們能夠很容易地將技術傳授給我們的用戶。因此,為VTK選擇一個開源許可證或許是我們所做出的最重要的設計決策。
最終選擇了非互利的、自由的許可證(比如:選BSD(即BSD許可證(Berkeley Software Distribution License,BSD)。——譯注。)而不選GPL(即GPL許可證(GNU General Public License)?!g注。))在事后證明是一個值得效仿的決策,因為它最終使基于商業(yè)的服務和咨詢成為可能,而這正成就了Kitware。在我們做出這個決定的時候,我們最感興趣的是降低與學術界、研究機構以及商務實體之間合作的壁壘。我們從那時也發(fā)現(xiàn),許多組織都避免使用互利性許可證,由于它們可能造成的嚴重問題。事實上,我們可能會爭論互利性許可證在延緩開源軟件的接收上有很大作用,但這另當別論。這里的要點是:與任何軟件系統(tǒng)相關的重要設計決策之一就是著作權許可證的選擇。重新審視項目的目標,然后再恰當地解決IP問題是很重要的。
VTK最初是以一個科學數據可視化系統(tǒng)出現(xiàn)的??梢暬I域之外的許多人都天真地把它當成一種特殊的幾何渲染:查看虛擬物體并與之交互。盡管這些確實是可視化的一部分,但是通常的數據可視化還包括把數據轉換成感知性輸入的整個過程,典型的數據是圖像,此外還包括觸覺、聽覺等其他形式。數據形式不僅由幾何拓撲結構組成——比如像網格或者復雜空間分解等抽象形式,還有核心結構的屬性,諸如標量(如:溫度或壓強),矢量(如:速度),張量(如:應力與張力),以及渲染屬性,諸如表面法線和紋理坐標等。
注意,通常情況下,表示時空信息的數據被看做是科學可視化的一部分。然而,還有更抽象數據形式,比如市場統(tǒng)計資料、網頁、文檔以及其它信息,它們只能通過諸如非結構文檔、表格、圖和樹等抽象(即:非時空)關系來表示。這些抽象數據一般通過信息可視化的方法來處理。在社區(qū)的幫助下,VTK現(xiàn)在能夠完成科學可視化和信息可視化方面的工作。
作為一種可視化系統(tǒng),VTK的角色是以這些形式獲取數據,并最終將它們轉換成利于人類感官理解的形式。因此,VTK的核心需求之一就是創(chuàng)建數據流管線的能力,這種管線能夠讀入、處理、表示并最終渲染數據。這樣,工具箱就必須構建成一個靈活的系統(tǒng),它的設計在許多層面上反映了這一點。例如,我們有目的地將VTK設計成這樣一種工具箱,它具有許多可互換的組件,這些組件可以組合起來用于處理多種數據。
在深入介紹VTK特殊的架構特性之前,先介紹頂層的概念,它們系統(tǒng)的開發(fā)和使用都產生了深遠的影響。其中之一就是VTK的混合包裝設施。該設施從VTK的C++實現(xiàn)自動生成Python,Java,和Tcl等的語言綁定(還可綁定更多的語言,并且有些已經實現(xiàn)了——譯注)。最具實力的開發(fā)者將使用C++進行工作。使用者和應用程序開發(fā)者也可以使用C++,但是通常情況下,上文提到的解釋性語言更加適合這兩個群體。混合的編譯性/解釋性環(huán)境將這兩個領域的優(yōu)勢結合在了一起:計算密集型算法的高性能和樣機或開發(fā)的靈活性。事實上,這種多語言計算的方法在許多科學計算社區(qū)中得到廣泛應用,并且許多團隊將VTK作為他們自己軟件的一個范本。
就軟件過程而言,VTK采用CMake來控制構建過程;CDash/CTest用于測試;然后CPack用于跨平臺部署。VTK確實可以在幾乎任何計算機上進行編譯,包括因其簡陋的開發(fā)環(huán)境而聲名狼藉的超級計算機。此外,開發(fā)工具外圍還包括網頁、wiki、郵件列表(用戶區(qū)和開發(fā)者區(qū)),文檔生成設施(即:Doxygen)和bug追蹤系統(tǒng)(Mantis)。
由于VTK是面向對象系統(tǒng),在其內部,對類的訪問和數據成員的實例化都被小心地管理起來。通常情況下,所有的數據成員的訪問權限均為protected或private。通過Set
和Get
方法來訪問這些數據成員,這兩種方法具有各種類型的形參,例如:布爾型數據、模態(tài)數據、字符串、以及向量。這些方法中的多數的創(chuàng)建是通過向類的頭文件中插入宏來實現(xiàn)的。例如:
vtkSetMacro(Tolerance, double);
vtkGetMacro(Tolerance, double);
可以展開為如下形式:
virtual void SetTolerance(double);
virtual double GetTolerance();
使用這些宏的原因已經超出了僅僅使代碼清晰。VTK中有重要的數據成員控制調試、更新對象的修改時間(MTime)、并恰當地管理引用計數。這些宏正確地操作這些數據,因而強烈推薦使用它們。例如,當一個對象的修改時間沒有得到恰當的管理時,VTK中就會出現(xiàn)一個尤其嚴重的bug。在這種情況下,代碼就不會按其應該運行的方式運行,或者還會執(zhí)行多次。
VTK的優(yōu)勢之一就是其相對簡單的用于表示和管理數據的方法。典型的情況下,各種特殊數據(例如:vtkFloatArray
)的數組用于表示信息的連續(xù)片段。例如:一個裝載有三個三維坐標點的表可以用具有9個元素的vtkFloatArray
來表示。這些數組有一種元組的記法,故有一個三維坐標點即一個3元組,而一個對稱的3×3張量矩陣可以由一個6元組表示()。專門采用這種設計是因為在科學計算中,與操作數組的系統(tǒng)(例如:Fortran)接口是很常見的,并且這樣還能使對大塊連續(xù)數據的內存分配與回收變得更加高效。再者,連續(xù)數據的通信、串行、以及IO操作通常更有效率。這些(可以加載各種類型數據的)核心數據數組表示了VTK中的大部分數據,且具有多種方便的方法,以進行信息的插入和訪問,包括用于快速訪問的方法、以及在添加更多數據時所需要的自動分配內存的方法。數據數組是抽象類vtkDataArray
的子類,該抽象類的意義在于:通用的虛方法可用于簡化編碼。但是,為了實現(xiàn)更高的性能,靜態(tài)的、模版化的函數被引入,這樣就可以根據不同的參數類型進行切換,并實現(xiàn)隨后對連續(xù)數據數組的直接訪問。
即使由于性能方面的原因,模板被廣泛地使用,C++模板通常在公有類的API中也是不可見的。這點在STL中也是如此:我們采用了PIMPL設計模式來隱藏模版實現(xiàn)的復雜細節(jié)。這種模式為我們提供了很大幫助,尤其是在以前文所述將代碼包裝為解釋性代碼的時候。避免公有API中模板的復雜性意思是:在應用程序開發(fā)者看來,VTK實現(xiàn)大部分是無需考慮數據類型的選擇的。當然,在其外殼之下,代碼的執(zhí)行是由數據類型來驅動的,而該數據類型則一般是運行時訪問數據時確定的。
一些用戶很想知道為什么VTK使用引用計數來管理內存而不是垃圾回收這一對用戶來說更為友好的方式?;镜拇鸢甘钱敂祿粍h除的時候,VTK需要對其完全控制,因為要處理的數據量可能十分巨大。例如,一組1000×1000×1000字節(jié)的體數據的數據量是1G字節(jié)。把這么大的數據留在內存中等待垃圾回收器來決定是否應該釋放它們,確實不是一個好主意。在VTK中,大部分類(vtkObject
的子類)具有內建的引用計數能力。每個對象都包含有一個引用計數,它在該對象實例化時被初始化為1。每次使用該對象都會進行注冊,然后引用計數就加1。類似地,當使用該對象進行了反注冊(或者等效地認為該對象被刪除),那么引用計數就會減1。最終的對象引用計數減至0,此時該對象自毀。下面列舉一個典型的例子:
vtkCamera *camera = vtkCamera::New(); // reference count is 1
camera->Register(this); // reference count is 2
camera->Unregister(this); // reference count is 1
renderer->SetActiveCamera(camera); // reference count is 2
renderer->Delete(); // ref count is 1 when renderer is deleted
camera->Delete(); // camera self destructs
這里還有另外一個關于為什么引用計數對于VTK很重要的原因——它提供了有效復制數據的能力。例如:想象有一個數據對象D1,它由許多數據數組組成:點、多邊形、顏色、標量、以及紋理坐標等。現(xiàn)在假設處理該數據來生成一個新的數據對象D2,此對象與第一個對象相同,還外加了向量數據(用于定位點)。一種浪費資源的方式是完全復制(深拷貝)D1來創(chuàng)建D2,然后向其中加入新的向量數據數組。另有一種方法,我們創(chuàng)建一個空的D2,然后將D1中的數組傳給D2(淺拷貝),使用引用計數來追蹤數據所有權,最終向添加新的向量數組。后者方法避免了復制數據,這正如前文所述,對一個優(yōu)秀可視化系統(tǒng)是必不可少的。我們在本章的稍后內容中可以看到,數據處理的管線例行公事式地實現(xiàn)了這種運行機制,即:將數據從算法的數據復制至輸出,此時引用計數對于VTK是必不可少的。
當然,引用計數也有一些臭名昭著的問題。偶爾會存在引用周期,這時循環(huán)中的對象以一種相互支持的配置來引用彼此。這種情況下,就需要明智的介入,或者在VTK中,一種在vtkGarbageCollector
中實現(xiàn)的特殊設施就可以用來管理牽涉與上述循環(huán)中的對象。當這樣的類被鑒別到的時候(這被期望發(fā)生在開發(fā)過程中),該類就會將其自身注冊至垃圾回收器,并管理其自己的Register
和Unregister
方法的開銷。然后緊接著的對象銷毀(或者反注冊)方法對局部的引用計數網絡進行拓撲分析,搜索已經分離了的相互引用的對象群。這些都將被垃圾回收器予以刪除。
VTK中的多數實例化過程是通過一種以靜態(tài)類成員實現(xiàn)的對象工廠運行。典型的語義表達如下:
vtkLight *a = vtkLight::New();
這里要認識到的重要之處是:這里實際被實例化的可能不是vtkLight
,可能是vtkLight
的子類(例如:vtkOpenGLLight
)。采用對象工廠的動機多種多樣,最為重要的是應用的可移植性和設備不相關性。例如,前文中我們在一個渲染場景中創(chuàng)建了一個光源。在一個運行于特定平臺上的特定的應用程序中,vtkLight::New
可能會生成一個OpenGL光源,然而在不同的平臺上,存在著圖形系統(tǒng)中其他渲染庫或方法來創(chuàng)建光源的可能性。到底實例化什么樣的派生類是一種運行時系統(tǒng)信息的功能。在早期的VTK中,可以有包括gl、PHIGS、Starbase、XGL、以及OpenGL等多種選擇。然而這些圖形庫中的多數現(xiàn)在已經消失了,出現(xiàn)了包括DirectX和基于GPU方法在內的新方法。隨著時間的推移,一個利用VTK寫成的應用程序沒必要進行修改,因為開發(fā)者已經派生出了特定的對應于新設備的vtkLight
的子類和其他渲染類來支持不斷發(fā)展的技術。另外一個對象工廠的重要用處是使性能增強變動的運行時替換成為可能。例如,一個vtkImageFFT
可能取代一個訪問特種用途硬件或數值計算庫的類。
VTK的一個優(yōu)點就是其表示數據復雜形式的能力。這些數據形式包括從簡單表格到有限元網格之類的復雜結構。所有這些數據形式都是vtkDataObject
的子類,如圖24.1所示(注意這是數據對象類的繼承圖的一部分)。
圖24.2:數據集類
VTK由若干主干子系統(tǒng)組成。與可視化包關聯(lián)最緊密的子系統(tǒng)或許應該是數據流/管線架構了。從概念上講,管線架構由三類基本對象組成:表示數據的對象(上文中的vtkDataObject
),將數據從一種形式處理、變換、濾波或者映射成另外一種形式的對象(vtkAlgorithm
),以及執(zhí)行管線的對象(vtkExecutive
)——此管線控制著一個由交錯數據(?)和過程對象(即:管線)組成的連通圖。圖24.3展示了一個典型的管線。
盡管這是交互的一種原始形式,它也是許多使用VTK的應用程序的基本要素。例如:上述的簡短代碼可以很容易地轉換、用于顯示并管理圖形界面中的進度條。這一命令/觀察者子系統(tǒng)也是VTK中三維掛件的核心,這些掛件是用于數據的請求、操縱以及編輯的復雜的交互性對象,下文將予以描述。
提到上面的例子,很重要的一點是,VTK中的事件都是預定義的,但是這里也為自定義事件開了后門。vtkCommand
類定義了一組枚舉型事件(例如:上面例子中的vtkCommand::ProgressEvent
)以及一個用戶事件。UserEvent
只是一個整形數值,一般用作一組應用程序中自定義事件的起始抵消值。于是,vtkCommand::UserEvent+100
可能是指一個VTK預定義的事件之外的某個事件。
從用戶的角度來看,一個VTK掛件可以看作是場景中的一個演員,只是用戶可以通過操縱句柄或者其它幾何特性(句柄操縱與幾何特性操縱均是基于前文所述之抓取功能——原文:picking functionality,即24.2.4一節(jié)中最后一段所述——的)來與之交互。與掛件的交互是很直觀的:用戶抓住球面句柄并將其移動,或者抓住一條直線并將其移動。然而,在場景的背后,事件被發(fā)送出去(例如:InteractionEvent
),而一個編寫合理的應用程序就能夠觀察到這些事件,并采取恰當的行動。例如,它們通常由下面所給出的vtkCommand::InteractorEvent
所觸發(fā):
vtkLW2Callback *myCallback = vtkLW2Callback::New();
myCallback->PolyData = seeds; // streamlines seed points, updated on interaction
myCallback->Actor = streamline; // streamline actor, made visible on interaction
vtkLineWidget2 *lineWidget = vtkLineWidget::New();
lineWidget->SetInteractor(iren);
lineWidget->SetRepresentation(rep);
lineWidget->AddObserver(vtkCommand::InteractionEvent, myCallback);
實際上,VTK掛件由兩個對象構建而成:一個是vtkInteractorObserver
的子類,另一個是vtkProp
的子類。vtkInteractorObserver
只是觀察渲染窗中的用戶交互(例如:鼠標事件和鍵盤事件)并處理之。這些操縱通常由突出顯示句柄,改變鼠標指針的外觀,以及變換數據等所組成,它們都會修改vtkProp
的幾何特征。當然,這些掛件的特殊細節(jié)要求編寫子類來控制其行為的細微差別,目前系統(tǒng)中擁有50多個不同的掛件。
VTK是一個大型軟件工具箱。目前,系統(tǒng)由大約1500萬行代碼(包括注釋,但是不包括自動生成的包裹層軟件),約1000個C++類組成。為了管理系統(tǒng)的復雜度并減少構建和鏈接的時間,系統(tǒng)被分割放置在十幾個子路徑中。表24.1列出了這些子路徑,并簡要總結了這些庫所提供的功能。
| Common
| VTK核心類 |
| Filtering
| 用于管理管線數據流的類 |
| Rendering
| 渲染,抓取,查看圖像,以及交互 |
| VolumeRendering
| 體繪制技術 |
| Graphics
| 三維幾何處理 |
| GenericFiltering
| 非線性三維幾何處理 |
| Imaging
| 圖像處理管線 |
| Hybrid
| 同時要求使用圖形學和圖像處理功能的類 |
| Widgets
| 復雜的交互 |
| IO
| VTK的輸入和輸出 |
| Infovis
| 信息可視化 |
| Parallel
| 并行處理(控制器和通信器) |
| Wrapping
| 對Tcl,Python以及Java的包裹的支持 |
| Examples
| 內容廣泛、文檔良好的示例 |
表24.1:VTK的子路徑
VTK一直是一個非常成功的系統(tǒng)。雖然第一行代碼于1993年寫出,但是目前,VTK仍然在不斷成長壯大、其開發(fā)速度也在不斷加快2。本節(jié),我們將談談一些經驗和將來的挑戰(zhàn)。
VTK發(fā)展歷程中,最令人驚嘆的方面之一就是項目的壽命。開發(fā)的速度歸因于若干主要原因:
雖然成長是令人興奮的,確證軟件系統(tǒng)的建立,預測VTK的未來,但妥善的管理卻是極其困難的。因此,近期VTK將更多地專注于管理社區(qū)以及軟件的成長。為此,已經采取了若干措施。
首先,創(chuàng)立了正式的管理架構。創(chuàng)建了架構審查委員會(Architectural Review Board),來指導社區(qū)和技術的發(fā)展,專注于高層次的、戰(zhàn)略性的議題。VTK社區(qū)也正在組建一個由意見領袖組成的公認的團隊,來指導某些VTK子系統(tǒng)的技術開發(fā)。
其次,制定了關于更進一步使工具箱模塊化的計劃,尤其是應對由git引入的工作流功能,還認識到使用者和開發(fā)者一般都想在工作中使用工具箱中小的子系統(tǒng),并且不想構建并鏈接整個包。此外,為了支持不斷成長的社區(qū),對新的功能和子系統(tǒng)的支持是很重要的,即使它們并不一定是工具箱的核心部分。通過創(chuàng)建松散的、模塊化的一群模塊,在維持核心的穩(wěn)定性的同時,適應外圍的大量代碼貢獻是可能的。
除了軟件過程之外,在開發(fā)管線當中還有許多技術創(chuàng)新。
最后,Kitware和更加廣泛的VTK社區(qū)決定加入Open Science。從務實的角度講,它一個這樣的方式,我們將傳播公開的數據、公開的發(fā)表、以及公開的源代碼——這是確保我們正在創(chuàng)建可重現(xiàn)的科學系統(tǒng)所必需的特征。雖然VTK一直以來都以開源和公開數據的系統(tǒng)的形式傳播,但是文檔過程卻一直缺乏。在擁有正式書籍[Kit10,SML06]的同時,還一直有各種非正式的方法來收集包括新的源碼在內的技術發(fā)表物。我們正在通過開發(fā)像是VTK Journal3的新的發(fā)表機制來改善這種狀況,該期刊可以發(fā)表由文檔、源代碼、數據、以及有效的測試圖像組成的文章。它還實現(xiàn)了自動化的代碼審查(利用VTK的高質量的軟件測試過程)以及人對遞交文章的審查。
雖然VTK很成功,但是還有許多事情我們沒有處理好:
vtkImageClass
,它內部處理像素數據組成的數組。然而,在某些情況下,我們不得不將之重構為小的片段,并繼續(xù)這一過程。一個基本的例子就是數據處理管線。最初,數據管線是通過數據和算法對象的交互而隱式實現(xiàn)的。我們最終認識到我們得創(chuàng)建一種顯式的管線執(zhí)行對象來協(xié)調數據與算法之間的交互,并且用于實現(xiàn)不同的數據處理策略。像VTK這樣的開源系統(tǒng)的好處之一是許多這些錯誤能夠并且將會隨著時間而得以糾正。我們擁有一個積極的、有能力的開發(fā)社區(qū),他們每天都在改進著這個系統(tǒng),并且我們希望在可預見的將來,這一狀態(tài)能夠維持下去。
腳注
1.?http://en.wikipedia.org/wiki/Opaque_pointer.
2. See the latest VTK code analysis at?http://www.ohloh.net/p/vtk/analyses/latest.
3.?http://www.midasjournal.org/?journal=35.
更多建議: