VTK教程系列:VTK基礎及應用開發教程
5.7 區域提取
5.7.1 提取感興趣區域
感興趣區域(Volum of Interest)是指影象內部的一個子區域。在VTK中vtkExtractVOI類實現由使用者指定的區域範圍提取影象的子影象。該Filter的輸入和輸出都是一個vtkImageData,因此其結果可以直接作為影象儲存。
1: vtkSmartPointer<vtkBMPReader> reader =
2: vtkSmartPointer<vtkBMPReader>::New();
3: reader->SetFileName ( "lena.bmp" );
4: reader->Update();
5:
6: int dims[3];
7: reader->GetOutput()->GetDimensions(dims);
8:
9: vtkSmartPointer<vtkExtractVOI> extractVOI =
10: vtkSmartPointer<vtkExtractVOI>::New();
11: extractVOI->SetInputConnection(reader->GetOutputPort());
12: extractVOI->SetVOI(dims[0]/4.,3.*dims[0]/4.,dims[1]/4.,3.*dims[1]/4., 0, 0);
13: extractVOI->Update();
上例程式碼實現了提取一副影象的子區域。首先讀取一個影象,並獲取影象的維數。然後定義vtkExtractVOI物件,該物件接收兩個輸入一個是影象資料,第二個是區域大小。設定區域大小的函式原型:
void SetVOI(int _arg1, int _arg2, int _arg3, int _arg4, int _arg5, int _arg6)
void SetVOI(int _arg[])
其引數是提取的區域各個方向的大小,共6個引數,依次表示x方向最小值,x方向最大值,y方向最小值,y方向最大值,z方向最小值和z方向最大值。上例中由於讀取的是二維影象,因此z方向的區域為[0,0],而在x方向範圍為[ dims[0]/4 , 3*dims[0]/4 ],y方向範圍為[ dims[1]/4 , 3*dims[1]/4 ],即提取影象原圖中間1/4影象。執行結果如下:
圖5.18 提取感興趣區域
5.7.2 三維影象切片提取
切片是指三維影象中的一個切面對應的影象。切面可以是過影象內部一點且平行於XY、YZ、XZ平面的平面,也可以是任意的過三維影象內部一點任意方向的平面。通過提取切片可以方便的瀏覽和分析影象內部組織結構,是醫學影象瀏覽軟體中的一個重要的功能。在VTK中vtkImageReslice類實現影象切片提取功能。下面首先看一段切片提取的程式碼。
1: vtkSmartPointer<vtkMetaImageReader> reader =
2: vtkSmartPointer<vtkMetaImageReader>::New();
3: reader->SetFileName ( " brain.mhd" );
4: reader->Update();
5:
6: int extent[6];
7: double spacing[3];
8: double origin[3];
9:
10: reader->GetOutput()->GetExtent(extent);
11: reader->GetOutput()->GetSpacing(spacing);
12: reader->GetOutput()->GetOrigin(origin);
13:
14: double center[3];
15: center[0] = origin[0] + spacing[0] * 0.5 * (extent[0] + extent[1]);
16: center[1] = origin[1] + spacing[1] * 0.5 * (extent[2] + extent[3]);
17: center[2] = origin[2] + spacing[2] * 0.5 * (extent[4] + extent[5]);
18:
19: static double axialElements[16] = {
20: 1, 0, 0, 0,
21: 0, 1, 0, 0,
22: 0, 0, 1, 0,
23: 0, 0, 0, 1 };
24:
25: vtkSmartPointer<vtkMatrix4x4> resliceAxes =
26: vtkSmartPointer<vtkMatrix4x4>::New();
27: resliceAxes->DeepCopy(axialElements);
28:
29: resliceAxes->SetElement(0, 3, center[0]);
30: resliceAxes->SetElement(1, 3, center[1]);
31: resliceAxes->SetElement(2, 3, center[2]);
32:
33:
34: vtkSmartPointer<vtkImageReslice> reslice =
35: vtkSmartPointer<vtkImageReslice>::New();
36: reslice->SetInputConnection(reader->GetOutputPort());
37: reslice->SetOutputDimensionality(2);
38: reslice->SetResliceAxes(resliceAxes);
39: reslice->SetInterpolationModeToLinear();
40:
41: vtkSmartPointer<vtkLookupTable> colorTable =
42: vtkSmartPointer<vtkLookupTable>::New();
43: colorTable->SetRange(0, 1000);
44: colorTable->SetValueRange(0.0, 1.0);
45: colorTable->SetSaturationRange(0.0, 0.0);
46: colorTable->SetRampToLinear();
47: colorTable->Build();
48:
49: vtkSmartPointer<vtkImageMapToColors> colorMap =
50: vtkSmartPointer<vtkImageMapToColors>::New();
51: colorMap->SetLookupTable(colorTable);
52: colorMap->SetInputConnection(reslice->GetOutputPort());
53:
54: vtkSmartPointer<vtkImageActor> imgActor =
55: vtkSmartPointer<vtkImageActor>::New();
56: imgActor->SetInput(colorMap->GetOutput());
57:
58: vtkSmartPointer<vtkRenderer> renderer =
59: vtkSmartPointer<vtkRenderer>::New();
60: renderer->AddActor(imgActor);
61: renderer->SetBackground(.4, .5, .6);
62:
63: vtkSmartPointer<vtkRenderWindow> renderWindow =
64: vtkSmartPointer<vtkRenderWindow>::New();
65: renderWindow->SetSize(500, 500);
66: renderWindow->AddRenderer(renderer);
67:
68: vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor =
69: vtkSmartPointer<vtkRenderWindowInteractor>::New();
70: vtkSmartPointer<vtkInteractorStyleImage> imagestyle =
71: vtkSmartPointer<vtkInteractorStyleImage>::New();
72:
73: renderWindowInteractor->SetInteractorStyle(imagestyle);
74: renderWindowInteractor->SetRenderWindow(renderWindow);
75: renderWindowInteractor->Initialize();
76:
77: renderWindowInteractor->Start();
首先通過vtkMetaImageReader讀取一副醫學三維影象,並獲取得到影象範圍(extent),原點和畫素間隔;由這三個引數可以計算影象的中心位置center;接下來定義了切面的變換矩陣axialElements,該矩陣的前三列分別表示x、y和z方向向量,第四列為中心點座標;程式碼中的axialElements表示切面變換矩陣與當前座標系一致,且切面為過中心點center,並平行於XY平面的平面。當前,定義該切面時,也可以是其他平面,甚至是任意平面,但是必須要過影象內部點。下面給出了一個常用的變換矩陣:
static double coronalElements[16] = {
1, 0, 0, 0,
0, 0, 1, 0,
0,-1, 0, 0,
0, 0, 0, 1 }; 提取平行於XZ平面的切片
static double sagittalElements[16] = {
0, 0,-1, 0,
1, 0, 0, 0,
0,-1, 0, 0,
0, 0, 0, 1 }; 提取平行於YZ平面的切片
static double obliqueElements[16] = {
1, 0, 0, 0,
0, 0.866025, -0.5, 0,
0, 0.5, 0.866025, 0,
0, 0, 0, 1 }; 提取斜切切片
注意使用這些變換矩陣的時候,需要將第四列替換為切片經過影象的一個點座標,上例中將影象的中心新增到axialElements矩陣,並通過函式SetResliceAxes設定變換矩陣,SetOutputDimensionality(2)指定輸出的影象為一個二維影象;而函式SetInterpolationModeToLinear()則指定了切面提取中的差值方式為線性差值,另外該類中還提供了其他的差值方式:
SetInterpolationModeToNearestNeighbor():最近鄰方式
SetInterpolationModeToCubic():三次線性差值
設定完畢後,執行Update()即可完成切面計算。執行結果如下圖:
圖5.19 切片提取
5.7.3 擴充套件
學習三維影象切面的提取後,我們在上節的程式上做一個擴充套件,實現一個稍微複雜的程式——通過滑動滑鼠來切換三維影象切片,這也是醫學影象處理軟體中一個很基本的功能。實現該功能難點是怎樣在VTK中控制滑鼠來實時提取影象切片。在前面的章節中已經介紹觀察者/命令(Observer/Command)模式,我們也採用這種機制來實現。VTK中滑鼠訊息是在互動型別物件(interactorstyle)中響應,因此通過為互動型別物件(interactorstyle)新增觀察者(observer)來監聽相應的訊息,當訊息觸發時,由命令模式執行相應的回撥函式。閒話少說,放程式碼。
1: class vtkImageInteractionCallback : public vtkCommand
2: {
3: public:
4:
5: static vtkImageInteractionCallback *New()
6: {
7: return new vtkImageInteractionCallback;
8: }
9:
10: vtkImageInteractionCallback()
11: {
12: this->Slicing = 0;
13: this->ImageReslice = 0;
14: this->Interactor = 0;
15: }
16:
17: void SetImageReslice(vtkImageReslice *reslice)
18: {
19: this->ImageReslice = reslice;
20: }
21:
22: vtkImageReslice *GetImageReslice()
23: {
24: return this->ImageReslice;
25: }
26:
27: void SetInteractor(vtkRenderWindowInteractor *interactor)
28: {
29: this->Interactor = interactor;
30: }
31:
32: vtkRenderWindowInteractor *GetInteractor()
33: {
34: return this->Interactor;
35: }
36:
37: virtual void Execute(vtkObject *, unsigned long event, void *)
38: {
39: vtkRenderWindowInteractor *interactor = this->GetInteractor();
40:
41: int lastPos[2];
42: interactor->GetLastEventPosition(lastPos);
43: int currPos[2];
44: interactor->GetEventPosition(currPos);
45:
46: if (event == vtkCommand::LeftButtonPressEvent)
47: {
48: this->Slicing = 1;
49: }
50: else if (event == vtkCommand::LeftButtonReleaseEvent)
51: {
52: this->Slicing = 0;
53: }
54: else if (event == vtkCommand::MouseMoveEvent)
55: {
56: if (this->Slicing)
57: {
58: vtkImageReslice *reslice = this->ImageReslice;
59:
60: // Increment slice position by deltaY of mouse
61: int deltaY = lastPos[1] - currPos[1];
62:
63: reslice->Update();
64: double sliceSpacing = reslice->GetOutput()->GetSpacing()[2];
65: vtkMatrix4x4 *matrix = reslice->GetResliceAxes();
66: // move the center point that we are slicing through
67: double point[4];
68: double center[4];
69: point[0] = 0.0;
70: point[1] = 0.0;
71: point[2] = sliceSpacing * deltaY;
72: point[3] = 1.0;
73: matrix->MultiplyPoint(point, center);
74: matrix->SetElement(0, 3, center[0]);
75: matrix->SetElement(1, 3, center[1]);
76: matrix->SetElement(2, 3, center[2]);
77: interactor->Render();
78: }
79: else
80: {
81: vtkInteractorStyle *style = vtkInteractorStyle::SafeDownCast(
82: interactor->GetInteractorStyle());
83: if (style)
84: {
85: style->OnMouseMove();
86: }
87: }
88: }
89: }
90:
91: private:
92: int Slicing;
93: vtkImageReslice *ImageReslice;
94: vtkRenderWindowInteractor *Interactor;
95: };
vtkImageInteractionCallback繼承自vtkCommand類,並覆蓋父類函式Execute()。該類提供了兩個介面:SetImageReslice和SetInteractor。SetImageReslice用以設定vtkImageSlice物件,vtkImageSlice根據設定的變換矩陣提取三維影象切片。SetInteractor用以設定vtkRenderWindowInteractor,vtkRenderWindowInteractor類物件負責每次提取切片後重新整理檢視。
下面我們重點來看一下Execute函式,該函式提供了具體的切片提取功能。在該函式裡面,主要監聽了三個訊息:
vtkCommand::LeftButtonPressEvent,
vtkCommand::LeftButtonReleaseEvent,
vtkCommand::MouseMoveEvent,
前兩個訊息分別是滑鼠左鍵的按下和彈起訊息。當滑鼠左鍵按下時,就設定切片提取標誌為1,而當彈起時,將標誌置為0。這樣在滑鼠移動時,只有在確定切片提取標誌為1時,執行切片提取功能。
vtkCommand::MouseMoveEvent即為滑鼠移動訊息。當檢測到該訊息時,首先檢查切片提取標誌,當為1時提取切片。提取切片時,需要為vtkImageSlice物件設定變換矩陣。這裡在函式開始時,首先獲取了滑鼠滑動的前後兩次點的位置lastPos和currPos。然後根據兩點的Y座標差deltaY,計算新的中心點center並變換至vtkImageSlice當前變換矩陣中,得到變換中心點,將其設定到原來的變換矩陣matrix中,並設定到vtkImageSlice中,最後執行interactor->Render()即可不斷的根據滑鼠移動重新整理影象。
Command物件定義完畢後,即可為互動物件InteractorStyle新增觀察者,響應滑鼠訊息。這裡可以在上節的程式上進行修改,前面程式碼一致,只需要在最後新增如下程式碼:
1: vtkSmartPointer<vtkImageInteractionCallback> callback =
2: vtkSmartPointer<vtkImageInteractionCallback>::New();
3: callback->SetImageReslice(reslice);
4: callback->SetInteractor(renderWindowInteractor);
5:
6: imagestyle->AddObserver(vtkCommand::MouseMoveEvent, callback);
7: imagestyle->AddObserver(vtkCommand::LeftButtonPressEvent, callback);
8: imagestyle->AddObserver(vtkCommand::LeftButtonReleaseEvent, callback);
9:
10: renderWindowInteractor->Start();
這裡主要是定義了vtkImageInteractionCallback物件,並設定vtkImageSlice物件和vtkRenderWindowInteractor物件。然後為互動物件vtkInteractorStyle新增觀察者來監控相應的訊息,這裡主要是三個訊息:
vtkCommand::LeftButtonPressEvent,
vtkCommand::LeftButtonReleaseEvent,
vtkCommand::MouseMoveEvent,
當響應到這三個訊息時,立即執行vtkImageInteractionCallback的Execute函式,以便實現切片的實時提取和更新。完成以後,執行程式,當滑鼠在影象上移動時,會發現影象會跟著滑鼠的移動而變化,神奇吧?有興趣的話,還可以實現YZ平面、XZ平面切片提取,甚至是任意方向的切面提取。