李健壯,鮑蘇蘇,高旋輝,邱文超
(華南師范大學(xué)計算機學(xué)院,廣東 廣州 510631)
Android是一個以Linux為基礎(chǔ)的半開源操作系統(tǒng),主要用于移動設(shè)備,由Google成立的Open Handset Alliance(OHA,開放手持設(shè)備聯(lián)盟)持續(xù)領(lǐng)導(dǎo)與開發(fā)。Android最初只支持手機,后來逐漸拓展到平板電腦及其他領(lǐng)域上[1]。
醫(yī)學(xué)圖像可視化在臨床上已成為輔助診斷和輔助治療的重要手段[2]。二維醫(yī)學(xué)切片是醫(yī)生獲取病人信息的傳統(tǒng)途徑,而三維醫(yī)學(xué)圖像由于其豐富的信息和直觀逼真的視覺效果,在臨床診斷、治療和醫(yī)學(xué)研究中扮演著越來越重要的角色。
為了讓醫(yī)生可以隨時隨地獲得醫(yī)學(xué)圖像,本文介紹一種基于Android平臺的醫(yī)學(xué)圖像可視化系統(tǒng),該系統(tǒng)能夠顯示二維的DICOM數(shù)據(jù)和三維的STL模型數(shù)據(jù),還可以對其中顯示的內(nèi)容進行簡單的交互,如對二維醫(yī)學(xué)圖像進行窗寬和窗位的調(diào)節(jié),在冠狀面、矢狀面和橫斷面之間進行切換,對三維醫(yī)學(xué)圖像進行旋轉(zhuǎn)、移動、縮放、調(diào)節(jié)顏色和透明度等。
Android操作系統(tǒng)自頂向下分為4個層次:應(yīng)用層、應(yīng)用框架層、Android運行時庫與其他庫層、Linux內(nèi)核層。
應(yīng)用層。一系列核心應(yīng)用程序包會隨著Android操作系統(tǒng)一同發(fā)布,其中包括電子郵件、日歷、短信、地圖、網(wǎng)頁瀏覽器、通訊錄等。這些程序除了可以為用戶提供必要的功能外,還能為開發(fā)者開發(fā)Android應(yīng)用程序提供良好的范本。Android平臺上的大部分應(yīng)用程序由Java語言編寫,但由于Android的虛擬機Dalvik支持JNI編程方式,也就是第三方應(yīng)用程序可以通過JNI調(diào)用自己的C動態(tài)庫,即在Android平臺上,“Java+C”的編程方式是可以實現(xiàn)的。
應(yīng)用框架層。Android框架提供了許多有用的API,開發(fā)者可以方便地使用這些API來構(gòu)建屬于自己的應(yīng)用程序。
Android運行時庫與其他庫層。Android包含一套C/C++函數(shù)庫,主要包括 libc、Media Framework、WebKit、SGL、OpenGL ES 等,開發(fā)者可以通過 Android應(yīng)用框架提供的API使用這些庫提供的功能。Android運行時庫實現(xiàn)了Java編程語言核心庫的大多數(shù)功能。
Linux內(nèi)核層。Android是一個基于Linux內(nèi)核開發(fā)的操作系統(tǒng)。Linux內(nèi)核為系統(tǒng)提供底層服務(wù),包括內(nèi)存管理、安全機制、網(wǎng)絡(luò)堆棧、進程管理及一系列的驅(qū)動模塊。該層是位于硬件層與其他軟件層之間的一個虛擬中間層。
VTK(Visualization Toolkit)是一套基于OpenGL的三維圖形、圖像處理及可視化的開發(fā)工具包。該包最初是針對醫(yī)療領(lǐng)域的應(yīng)用而設(shè)計的,所以對于醫(yī)療的可視化方面,如CT的掃描數(shù)據(jù)、MRI數(shù)據(jù)等,具有強大的圖像處理功能。VTK可視化流程采用管道化的機制來實現(xiàn),即在可視化過程中,上一流程的處理結(jié)果作為下一流程的輸入。VTK由I/O、數(shù)據(jù)處理、數(shù)據(jù)分析、網(wǎng)絡(luò)和繪制等多個模塊構(gòu)成,這些模塊對移動平臺上應(yīng)用程序的開發(fā)非常有用。
JNI(Java Native Interface)是Java本地調(diào)用接口,它使得運行于Android平臺的Java程序可以使用C和C++甚至匯編語言編寫動態(tài)鏈接庫。使用JNI有兩個優(yōu)點:(1)在需要頻繁訪問內(nèi)存或復(fù)雜計算的情況下(如圖像處理),使用C和C++動態(tài)鏈接庫比在Android平臺上使用Java語言來實現(xiàn)相同功能更具有效率;(2)可以直接使用已經(jīng)編寫好的C和C++庫,提高開發(fā)的效率。
在本文中,JNI主要用于連接Android頂層用于編寫界面的Java語言和底層用于圖像處理的C/C++語言,其架構(gòu)如圖1所示。
圖1 JNI架構(gòu)
總體設(shè)計目的是在Android平臺上構(gòu)建一個醫(yī)學(xué)圖像可視化系統(tǒng)。根據(jù)用戶(醫(yī)生等)的需求和軟件的特點,本系統(tǒng)的功能可劃分成3個部分:數(shù)據(jù)輸入、數(shù)據(jù)顯示與交互和數(shù)據(jù)節(jié)點管理。系統(tǒng)總體架構(gòu)如圖2所示。
圖2 系統(tǒng)架構(gòu)
圖2中:DM是數(shù)據(jù)管理器,用于管理數(shù)據(jù)節(jié)點;FX是文件瀏器,是讓用戶選擇讀取的數(shù)據(jù);DICOMVIew,用于顯示DICOM數(shù)據(jù)的3個截面;GLView,用于顯示OpenGL繪制圖像和獲取用戶觸摸手勢輸入;ViewerActivity,負(fù)責(zé)界面布局和管理上層模塊,并通過JNI與下層模塊進行通信;JNI,是Java代碼與C/C++代碼溝通的橋梁;ViewerApp,接收來自上層的消息,調(diào)用下層相應(yīng)的模塊,并向上層返回結(jié)果;Data-Loader,調(diào)用VTK中相應(yīng)的方法把數(shù)據(jù)讀入內(nèi)存中;DICOMRep,對DICOM數(shù)據(jù)進行處理;STLRep,對STL數(shù)據(jù)進行處理;VTK,底層支撐,提供必要的算法。
在圖2中,數(shù)據(jù)輸入包括FX和DataLoader等模塊。
數(shù)據(jù)輸入包括 DICOM[6]和 STL[7]兩種數(shù)據(jù)類型。一套DICOM數(shù)據(jù)一般由單個文件夾下多個DICOM文件組成,所以需要讀取文件夾并進行二維顯示。一個STL文件代表一個三維模型數(shù)據(jù),而一套DICOM數(shù)據(jù)經(jīng)過三維重建和圖像處理后會產(chǎn)生多個STL文件,所以需要對STL文件進行逐個讀入。
首先,在用戶通過FX選擇數(shù)據(jù)文件或文件夾后,F(xiàn)X通過ViewerActivity和ViewerApp把文件或文件夾的路徑傳給 DataLoader;然后,DataLoader調(diào)用VTK中相應(yīng)的方法把數(shù)據(jù)讀入到內(nèi)存中;其次,ViewerApp根據(jù)數(shù)據(jù)類型調(diào)用DICOMRep或STLRep進行繪制,并把結(jié)果傳回給ViewerActivity;最后,Viewer-Activity把結(jié)果傳給GLView進行顯示。
在圖2中,數(shù)據(jù)顯示與交互包括 DICOMView、GLView、DICOMRep和STLRep等模塊。
數(shù)據(jù)顯示與交互需要對DICOM數(shù)據(jù)和STL數(shù)據(jù)分別進行顯示與交互。DICOM數(shù)據(jù)的顯示分2種情況:(1)在DICOMView中對DICOM數(shù)據(jù)中的冠狀面、矢狀面和橫斷面分別進行二維顯示;(2)在GLView中進行顯示,并根據(jù)用戶需求進行二維顯示與三維顯示之間的切換。由于DICOM數(shù)據(jù)需要在DICOMView中進行二維顯示,在GLView中進行二維顯示和在GLView中進行三維顯示,所以DICOM數(shù)據(jù)的交互需要分別對這3種顯示情況進行交互。
當(dāng)DICOM數(shù)據(jù)在DICOMView中進行二維顯示時,用戶可以單點某一截面(冠狀面、矢狀面和橫斷面之一),這里假設(shè)為冠狀面,則ViewerActivity會對GLView當(dāng)前畫面進行截圖,再把結(jié)果傳到冠狀面所屬的 DICOMView中顯示;然后 ViewerActivity通過ViewerApp讓DICOMRep傳回冠狀面的數(shù)據(jù)并在GLView中顯示冠狀面,GLView進入DICOM數(shù)據(jù)的二維顯示和交互模式,此時,用戶在GLView中的交互包括:單點左右拖動切換前后切片、兩點觸摸縮放圖片、單點上下拖動調(diào)節(jié)窗寬窗位等,這些交互都是GLView獲取用戶輸入傳給DICOMRep,DICOMRep進行處理后返回給GLView,最后再由GLView完成顯示。以上說明了DICOM數(shù)據(jù)在DICOMView中進行二維顯示和在GLView中進行二維顯示2種情況下交互的過程。第3種情況是DICOM在GLView中進行三維顯示,即GLView同時對DICOM數(shù)據(jù)的3個截面進行三維顯示,此時用戶的交互包括單點拖動旋轉(zhuǎn)和兩點觸摸縮放,其完成過程與前2種情況大同小異。
STL數(shù)據(jù)的顯示只有一種情況,即在GLView中顯示。用戶的交互包括單點拖動旋轉(zhuǎn)、兩點觸摸縮放、顏色調(diào)節(jié)、透明度調(diào)節(jié)和可見性調(diào)節(jié)等。其中單點拖動旋轉(zhuǎn)和兩點觸摸縮放由GLView獲取用戶輸入,STLRep處理,再由GLView顯示結(jié)果完成;其余3項交互由DM獲取輸入,STLRep處理,再由GLView顯示結(jié)果完成。
數(shù)據(jù)節(jié)點管理主要指圖2中的DM,功能包括顯示當(dāng)前已讀取數(shù)據(jù)節(jié)點的名稱和屬性,調(diào)節(jié)其中某一節(jié)點的屬性等。DICOM數(shù)據(jù)節(jié)點屬性由在GLView中整體可見性和某一截面可見性組成;STL數(shù)據(jù)節(jié)點屬性包括在GLView中顏色、透明度和可見性等。用戶交互時,DM獲取用戶對某一節(jié)點某一屬性的輸入,然后把輸入交由DICOMRep或STlRep進行處理,最后由GLView顯示結(jié)果。
DM是一個數(shù)據(jù)節(jié)點管理器,需要顯示當(dāng)前已讀取數(shù)據(jù)節(jié)點的名稱和屬性,調(diào)節(jié)其中某一節(jié)點的屬性等。DM繼承自 Android提供的 ExpandableList-View[8]??梢娦韵嚓P(guān)屬性采用 Android提供的Switch[9],透明度屬性和顏色屬性采用Android提供的SeekBar[10],其中顏色屬性通過分別調(diào)節(jié) RGB分量的數(shù)值來調(diào)節(jié)。DM提供一個setOnPropertyListener函數(shù),讓ViewerActivity注冊DM中數(shù)據(jù)節(jié)點屬性改變時的回調(diào)函數(shù):
其中dm和mView分別是DM實例和GLView實例的一個引用。每次屬性發(fā)生變化都要通知GLView進行重繪,即調(diào)用其中的 requestRender()[11]函數(shù)。
FX是一個文件瀏覽器,需要實現(xiàn)文件/文件夾列表顯示和文件/文件夾選擇功能,由ViewerActivity控制FX進入文件/文件夾模式,最后FX會向Viewer-Activity返回一個文件或一個文件夾的絕對路徑。
DICOMView繼承自Android提供的 Image-View[12],記錄當(dāng)前屬于哪一截面,重寫其中的onDraw函數(shù),對ViewerActivity傳入的Bitmap進行繪制。
GLView需要對DICOM數(shù)據(jù)進行二維和三維顯示,對STL數(shù)據(jù)進行三維顯示,獲取用戶觸摸輸入。GLView 繼承自 Android提供的 GLSurfaceGLView[11],通過實現(xiàn)自己的 GLSurfaceView.Renderer[13]來進行三維顯示。二維顯示是三維顯示的一個特例。當(dāng)需要進行二維顯示時,就把視角移動到對應(yīng)的位置并把投影方式改為平行投影。獲取用戶觸摸輸入需要用到多點觸摸技術(shù),GLView通過Luke Hutchison的多點觸摸庫[14]的 MultiTouchObjectCanvas接口來實現(xiàn)多點觸摸。用戶輸入會經(jīng)由ViewerActivity分發(fā)給底層的DICOMRep和STLRep進行處理。
ViewerActivity需要完成的功能主要包括主界面布局和分發(fā)事件。主界面包括一個DM、一個GLView和三個DICOMView。首先用XML語法寫出符合Anroid規(guī)定的布局[15]文件,然后在 ViewerActivity中讀取布局文件,通知Android系統(tǒng)繪制界面。ViewerActivity繼承自Android提供的Activity[16],重寫其中的onActivityResult函數(shù),用于接收FX返回的文件/文件夾絕對路徑;需要重寫onTouch函數(shù)接收觸摸事件,并根據(jù)需要通知DM、GLView和DICOMView進行重繪。
這幾個模塊都是由C/C++語言編寫的。上層模塊通過JNI來調(diào)用這幾個模塊。
DataLoader主要功能為讀取數(shù)據(jù)。當(dāng)DataLoader收到ViewerApp分發(fā)的文件或文件夾路徑時,通過VTK 提供的 vtkStlReader[17]或 vtkDICOMImageReader[18]來 讀 取,并 向 ViewerApp 返 回 vtkDataSet[19];ViewerApp主要用于接收和分發(fā)事件;DICOMRep和STLRep分別用于表示DICOM數(shù)據(jù)和STL數(shù)據(jù),兩者均繼承自同一父類Rep。Rep定義了一組響應(yīng)觸摸事件的虛函數(shù)。ViewerApp中有一個保存已讀取數(shù)據(jù)指針的Vector<Rep>reps,當(dāng)讀取數(shù)據(jù)時,reps用Vector[20]提供的 push_back函數(shù)來保存數(shù)據(jù)指針。這樣,當(dāng)調(diào)用相應(yīng)的觸摸事件時,可用以下方式來實現(xiàn)(此處用處理單點拖動手勢函數(shù)來說明):
ViewerApp的父類實現(xiàn)了三維顯示和交互情況下的handleSingleTouchPanGesture,因此ViewerApp只需判斷是否為特殊情況(DICOM數(shù)據(jù)的二維顯示和交互)即可。
經(jīng)過在Motorola Xoom上進行反復(fù)測試,本系統(tǒng)可以實現(xiàn)對DICOM數(shù)據(jù)和STL數(shù)據(jù)進行可視化和簡單交互的功能。該系統(tǒng)在設(shè)計與實現(xiàn)中,加入了對網(wǎng)絡(luò)數(shù)據(jù)進行處理的接口,下一步將實現(xiàn)對網(wǎng)絡(luò)文件進行可視化和交互。由于一套DICOM數(shù)據(jù)通常會達到數(shù)百兆字節(jié),在進行顯示與交互的過程中,整套數(shù)據(jù)都會載入到內(nèi)存中,對于內(nèi)存比較小的移動設(shè)備,如果需要讀取多套數(shù)據(jù),無疑是一個巨大的挑戰(zhàn),這也是項目組正在深入研究和探討的問題。
[1]維基百科.Android[EB/OL].http://zh.wikipedia.org/wiki/Android,2013-03-04.
[2]朱玲利,徐紅升,鮑蘇蘇.基于VTK的肝臟組織三維可視化體繪制[J].計算機與數(shù)字工程,2010,38(6):119-121.
[3]耿東久,索岳,陳渝,等.基于Android手機的遠程訪問和控制系統(tǒng)[J].計算機應(yīng)用,2011,31(2):559-561,571.
[4]Kitware Inc.The VTK User’s Guide[M].Kitware Inc.,2011.
[5]Oracle Corporation.Java Native Interface[EB/OL].http://docs.oracle.com/javase/1.4.2/docs/guide/jni/,2013-03-04.
[6]DICOM Homepage[EB/OL].http://medical.nema.org/,2013-03-04.
[7]3D Quickparts.What is an STL File?[EB/OL].http://www.quickparts.com/LearningCenter/WhatIsAnSTLFile.aspx,2013-03-04.
[8]Developer.ExpandableListView[EB/OL].http://developer.android.com/reference/android/widget/Expandable-ListView.html,2013-03-04.
[9]Developer.Switch[EB/OL].https://developer.android.com/reference/android/widget/Switch.html,2013-03-04.
[10]Developer.Seekbar[EB/OL].https://developer.android.com/reference/android/widget/SeekBar.html,2013-03-04.
[11]Developer.GLSurfaceView[EB/OL].https://developer.android. com/reference/android/opengl/GLSurfaceView.html,2013-03-04.
[12]Developer.ImageView[EB/OL].https://developer.android.com/reference/android/widget/ImageView.html,2013-03-04.
[13]Developer.Renderer[EB/OL].https://developer.android.com/reference/android/opengl/GLSurfaceView.Renderer.html,2013-03-04.
[14]HutchisonL.Simple Multitouch Pinch-Zoom Library for Android[EB/OL].https://code.google.com/p/androidmultitouch-controller/,2013-03-04.
[15]Developer.Layouts[EB/OL].https://developer.android.com/guide/topics/ui/declaring-layout.html,2013-03-04.
[16]Developer.Activity[EB/OL].https://developer.android.com/reference/android/app/Activity.html,2013-03-04.
[17]Kitware Inc.vtkSTLReader Class Reference[EB/OL].http://www.vtk.org/doc/nightly/html/classvtkSTLReader.html,2013-03-04.
[18]Kitware Inc.vtkDICOMImageReader Class Reference[EB/OL].http://www.vtk.org/doc/nightly/html/classvtkDICOMImageReader.html,2013-03-04.
[19]Kitware Inc.vtkDataSet Class Reference[EB/OL].http://www.vtk.org/doc/nightly/html/classvtkDataSet.html,2013-03-04.
[20]Cppreference.com.std::Vector[EB/OL].http://en.cppreference.com/w/cpp/container/vector,2013-03-04.
[21]Motorola.Xoom[EB/OL].http://www.motorola.com/staticfiles/Consumers/xoom-android-tablet/us-en/techspecs.html,2013-03-04.