李健壮,鲍苏苏,高旋辉,邱文超
(华南师范大学计算机学院,广东 广州 510631)
Android是一个以Linux为基础的半开源操作系统,主要用于移动设备,由Google成立的Open Handset Alliance(OHA,开放手持设备联盟)持续领导与开发。Android最初只支持手机,后来逐渐拓展到平板电脑及其他领域上[1]。
医学图像可视化在临床上已成为辅助诊断和辅助治疗的重要手段[2]。二维医学切片是医生获取病人信息的传统途径,而三维医学图像由于其丰富的信息和直观逼真的视觉效果,在临床诊断、治疗和医学研究中扮演着越来越重要的角色。
为了让医生可以随时随地获得医学图像,本文介绍一种基于Android平台的医学图像可视化系统,该系统能够显示二维的DICOM数据和三维的STL模型数据,还可以对其中显示的内容进行简单的交互,如对二维医学图像进行窗宽和窗位的调节,在冠状面、矢状面和横断面之间进行切换,对三维医学图像进行旋转、移动、缩放、调节颜色和透明度等。
Android操作系统自顶向下分为4个层次:应用层、应用框架层、Android运行时库与其他库层、Linux内核层。
应用层。一系列核心应用程序包会随着Android操作系统一同发布,其中包括电子邮件、日历、短信、地图、网页浏览器、通讯录等。这些程序除了可以为用户提供必要的功能外,还能为开发者开发Android应用程序提供良好的范本。Android平台上的大部分应用程序由Java语言编写,但由于Android的虚拟机Dalvik支持JNI编程方式,也就是第三方应用程序可以通过JNI调用自己的C动态库,即在Android平台上,“Java+C”的编程方式是可以实现的。
应用框架层。Android框架提供了许多有用的API,开发者可以方便地使用这些API来构建属于自己的应用程序。
Android运行时库与其他库层。Android包含一套C/C++函数库,主要包括 libc、Media Framework、WebKit、SGL、OpenGL ES 等,开发者可以通过 Android应用框架提供的API使用这些库提供的功能。Android运行时库实现了Java编程语言核心库的大多数功能。
Linux内核层。Android是一个基于Linux内核开发的操作系统。Linux内核为系统提供底层服务,包括内存管理、安全机制、网络堆栈、进程管理及一系列的驱动模块。该层是位于硬件层与其他软件层之间的一个虚拟中间层。
VTK(Visualization Toolkit)是一套基于OpenGL的三维图形、图像处理及可视化的开发工具包。该包最初是针对医疗领域的应用而设计的,所以对于医疗的可视化方面,如CT的扫描数据、MRI数据等,具有强大的图像处理功能。VTK可视化流程采用管道化的机制来实现,即在可视化过程中,上一流程的处理结果作为下一流程的输入。VTK由I/O、数据处理、数据分析、网络和绘制等多个模块构成,这些模块对移动平台上应用程序的开发非常有用。
JNI(Java Native Interface)是Java本地调用接口,它使得运行于Android平台的Java程序可以使用C和C++甚至汇编语言编写动态链接库。使用JNI有两个优点:(1)在需要频繁访问内存或复杂计算的情况下(如图像处理),使用C和C++动态链接库比在Android平台上使用Java语言来实现相同功能更具有效率;(2)可以直接使用已经编写好的C和C++库,提高开发的效率。
在本文中,JNI主要用于连接Android顶层用于编写界面的Java语言和底层用于图像处理的C/C++语言,其架构如图1所示。
图1 JNI架构
总体设计目的是在Android平台上构建一个医学图像可视化系统。根据用户(医生等)的需求和软件的特点,本系统的功能可划分成3个部分:数据输入、数据显示与交互和数据节点管理。系统总体架构如图2所示。
图2 系统架构
图2中:DM是数据管理器,用于管理数据节点;FX是文件浏器,是让用户选择读取的数据;DICOMVIew,用于显示DICOM数据的3个截面;GLView,用于显示OpenGL绘制图像和获取用户触摸手势输入;ViewerActivity,负责界面布局和管理上层模块,并通过JNI与下层模块进行通信;JNI,是Java代码与C/C++代码沟通的桥梁;ViewerApp,接收来自上层的消息,调用下层相应的模块,并向上层返回结果;Data-Loader,调用VTK中相应的方法把数据读入内存中;DICOMRep,对DICOM数据进行处理;STLRep,对STL数据进行处理;VTK,底层支撑,提供必要的算法。
在图2中,数据输入包括FX和DataLoader等模块。
数据输入包括 DICOM[6]和 STL[7]两种数据类型。一套DICOM数据一般由单个文件夹下多个DICOM文件组成,所以需要读取文件夹并进行二维显示。一个STL文件代表一个三维模型数据,而一套DICOM数据经过三维重建和图像处理后会产生多个STL文件,所以需要对STL文件进行逐个读入。
首先,在用户通过FX选择数据文件或文件夹后,FX通过ViewerActivity和ViewerApp把文件或文件夹的路径传给 DataLoader;然后,DataLoader调用VTK中相应的方法把数据读入到内存中;其次,ViewerApp根据数据类型调用DICOMRep或STLRep进行绘制,并把结果传回给ViewerActivity;最后,Viewer-Activity把结果传给GLView进行显示。
在图2中,数据显示与交互包括 DICOMView、GLView、DICOMRep和STLRep等模块。
数据显示与交互需要对DICOM数据和STL数据分别进行显示与交互。DICOM数据的显示分2种情况:(1)在DICOMView中对DICOM数据中的冠状面、矢状面和横断面分别进行二维显示;(2)在GLView中进行显示,并根据用户需求进行二维显示与三维显示之间的切换。由于DICOM数据需要在DICOMView中进行二维显示,在GLView中进行二维显示和在GLView中进行三维显示,所以DICOM数据的交互需要分别对这3种显示情况进行交互。
当DICOM数据在DICOMView中进行二维显示时,用户可以单点某一截面(冠状面、矢状面和横断面之一),这里假设为冠状面,则ViewerActivity会对GLView当前画面进行截图,再把结果传到冠状面所属的 DICOMView中显示;然后 ViewerActivity通过ViewerApp让DICOMRep传回冠状面的数据并在GLView中显示冠状面,GLView进入DICOM数据的二维显示和交互模式,此时,用户在GLView中的交互包括:单点左右拖动切换前后切片、两点触摸缩放图片、单点上下拖动调节窗宽窗位等,这些交互都是GLView获取用户输入传给DICOMRep,DICOMRep进行处理后返回给GLView,最后再由GLView完成显示。以上说明了DICOM数据在DICOMView中进行二维显示和在GLView中进行二维显示2种情况下交互的过程。第3种情况是DICOM在GLView中进行三维显示,即GLView同时对DICOM数据的3个截面进行三维显示,此时用户的交互包括单点拖动旋转和两点触摸缩放,其完成过程与前2种情况大同小异。
STL数据的显示只有一种情况,即在GLView中显示。用户的交互包括单点拖动旋转、两点触摸缩放、颜色调节、透明度调节和可见性调节等。其中单点拖动旋转和两点触摸缩放由GLView获取用户输入,STLRep处理,再由GLView显示结果完成;其余3项交互由DM获取输入,STLRep处理,再由GLView显示结果完成。
数据节点管理主要指图2中的DM,功能包括显示当前已读取数据节点的名称和属性,调节其中某一节点的属性等。DICOM数据节点属性由在GLView中整体可见性和某一截面可见性组成;STL数据节点属性包括在GLView中颜色、透明度和可见性等。用户交互时,DM获取用户对某一节点某一属性的输入,然后把输入交由DICOMRep或STlRep进行处理,最后由GLView显示结果。
DM是一个数据节点管理器,需要显示当前已读取数据节点的名称和属性,调节其中某一节点的属性等。DM继承自 Android提供的 ExpandableList-View[8]。可见性相关属性采用 Android提供的Switch[9],透明度属性和颜色属性采用Android提供的SeekBar[10],其中颜色属性通过分别调节 RGB分量的数值来调节。DM提供一个setOnPropertyListener函数,让ViewerActivity注册DM中数据节点属性改变时的回调函数:
其中dm和mView分别是DM实例和GLView实例的一个引用。每次属性发生变化都要通知GLView进行重绘,即调用其中的 requestRender()[11]函数。
FX是一个文件浏览器,需要实现文件/文件夹列表显示和文件/文件夹选择功能,由ViewerActivity控制FX进入文件/文件夹模式,最后FX会向Viewer-Activity返回一个文件或一个文件夹的绝对路径。
DICOMView继承自Android提供的 Image-View[12],记录当前属于哪一截面,重写其中的onDraw函数,对ViewerActivity传入的Bitmap进行绘制。
GLView需要对DICOM数据进行二维和三维显示,对STL数据进行三维显示,获取用户触摸输入。GLView 继承自 Android提供的 GLSurfaceGLView[11],通过实现自己的 GLSurfaceView.Renderer[13]来进行三维显示。二维显示是三维显示的一个特例。当需要进行二维显示时,就把视角移动到对应的位置并把投影方式改为平行投影。获取用户触摸输入需要用到多点触摸技术,GLView通过Luke Hutchison的多点触摸库[14]的 MultiTouchObjectCanvas接口来实现多点触摸。用户输入会经由ViewerActivity分发给底层的DICOMRep和STLRep进行处理。
ViewerActivity需要完成的功能主要包括主界面布局和分发事件。主界面包括一个DM、一个GLView和三个DICOMView。首先用XML语法写出符合Anroid规定的布局[15]文件,然后在 ViewerActivity中读取布局文件,通知Android系统绘制界面。ViewerActivity继承自Android提供的Activity[16],重写其中的onActivityResult函数,用于接收FX返回的文件/文件夹绝对路径;需要重写onTouch函数接收触摸事件,并根据需要通知DM、GLView和DICOMView进行重绘。
这几个模块都是由C/C++语言编写的。上层模块通过JNI来调用这几个模块。
DataLoader主要功能为读取数据。当DataLoader收到ViewerApp分发的文件或文件夹路径时,通过VTK 提供的 vtkStlReader[17]或 vtkDICOMImageReader[18]来 读 取,并 向 ViewerApp 返 回 vtkDataSet[19];ViewerApp主要用于接收和分发事件;DICOMRep和STLRep分别用于表示DICOM数据和STL数据,两者均继承自同一父类Rep。Rep定义了一组响应触摸事件的虚函数。ViewerApp中有一个保存已读取数据指针的Vector<Rep>reps,当读取数据时,reps用Vector[20]提供的 push_back函数来保存数据指针。这样,当调用相应的触摸事件时,可用以下方式来实现(此处用处理单点拖动手势函数来说明):
ViewerApp的父类实现了三维显示和交互情况下的handleSingleTouchPanGesture,因此ViewerApp只需判断是否为特殊情况(DICOM数据的二维显示和交互)即可。
经过在Motorola Xoom上进行反复测试,本系统可以实现对DICOM数据和STL数据进行可视化和简单交互的功能。该系统在设计与实现中,加入了对网络数据进行处理的接口,下一步将实现对网络文件进行可视化和交互。由于一套DICOM数据通常会达到数百兆字节,在进行显示与交互的过程中,整套数据都会载入到内存中,对于内存比较小的移动设备,如果需要读取多套数据,无疑是一个巨大的挑战,这也是项目组正在深入研究和探讨的问题。
[1]维基百科.Android[EB/OL].http://zh.wikipedia.org/wiki/Android,2013-03-04.
[2]朱玲利,徐红升,鲍苏苏.基于VTK的肝脏组织三维可视化体绘制[J].计算机与数字工程,2010,38(6):119-121.
[3]耿东久,索岳,陈渝,等.基于Android手机的远程访问和控制系统[J].计算机应用,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.