天天看點

VTK經驗分享 4. VTK資料集執行個體

建立資料集的方式多種多樣,我們可以用程式設計的方式從無到地建立起一個資料集,也可以利用vtk的io功能直接從檔案讀取資料集。

我們首先看程式設計的方式,再說讀檔案的方式。

4.1 程式設計方式建立資料集

在開始建立資料集之前,我們一定要清楚:資料集有六種(請見前篇介紹),我們即将建立的這個資料集最符合哪一種?之後,我們就使用對應的vtk資料集類,運用相關api,開始建立資料集。

無論我們使用何種資料集類型,如何程式設計,最終的目的都是一樣的:直接或者間接地指明這個資料集的幾何結構、拓撲結構,和資料屬性(Data Attribute)。具體來說,就是:

①. 在正交坐标系空間中,用一系列3維坐标點表示出資料集的幾何結構。

②. 将這些點以某方式聯系起來,進而在這個空間中劃分出若幹基元,表示出資料集的拓撲結構。

③. 在①步驟中指定的3維坐标點,或者②步驟中劃分出的基元上,綁定有實體意義的資料(Data Attributes),這些資料可以是标量、矢量、或是張量

4.1.1 Demo1:麻雀雖小五髒俱全 -- 四個點,一個基元,也是資料集:)

對于demo來說,當然是越簡單越好,越能說清楚事情越好。于是我們将建立一個僅由四個點的組成的資料集,它唯一的基元便是這四個點圍起來形成的。那它屬于那種資料集呢?進一步分析一下:這個資料集實際上就是一個四邊形,也就是說,它是一個最基本的圖元(參考前篇3.1.2),再回顧一下PolyData,它的定義是“a collection of graphics primitives”,即它是圖元的集合,那我們這個資料集就是polyData了。

ps:請多注意下polyData這類資料集,雖然它的幾何、拓撲結構都是不規則的,但是它本身就是由圖元組成,而圖元是可以直接渲染的。是以這種資料集實際上是最簡潔、高效的資料集。

全部代碼如下所示,關鍵地方請看注釋。

package linke;

import vtk.vtkActor;
import vtk.vtkCellArray;
import vtk.vtkDoubleArray;
import vtk.vtkFloatArray;
import vtk.vtkIntArray;
import vtk.vtkInteractorStyle;
import vtk.vtkInteractorStyleTrackballCamera;
import vtk.vtkPoints;
import vtk.vtkPolyData;
import vtk.vtkPolyDataMapper;
import vtk.vtkRenderWindow;
import vtk.vtkRenderWindowInteractor;
import vtk.vtkRenderer;

public class Demo1 {
	
	static {
		System.loadLibrary("vtkCommonJava");
		System.loadLibrary("vtkFilteringJava");
		System.loadLibrary("vtkRenderingJava");
	}
	
	public static void main(String[] args) {
		//vtk[Int/Float/Double]Array類代表了一類有規律、有意義的資料組,一般有兩個作用:
		//①.裝載表示資料集的幾何結構的點集的坐标資料  —— 3Component per Tuple(點)
		//②.裝載描述資料集的幾何結構或拓撲結構的資料屬性(Data Attributes)的資料 —— 标量(1Component/Tuple)、矢量(3Comps/Tuple)、張量(9)
		vtkFloatArray pcoords = new vtkFloatArray(); // 作用①:表示點集 —— 幾何結構
		pcoords.SetNumberOfComponents(3); // 因為是坐标資料,是以Tuple就代表了“點”,一個Tuple顯然得有3個基礎資料(Component),來對應點的x、y、z了
		pcoords.SetNumberOfTuples(4); // 這句話是說一共有4個元組(點)
		float pts[][] = { // 我們的資料集中的四個點
				{0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f},
				{1.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 0.0f}
		};
		for (int i = 0; i < pts.length; i++) { // 将這四個點按順序放入vtkFloatArray 
			// pcoords.SetTuple(id0, id1, id2)
			pcoords.SetTuple3(i, pts[i][0], pts[i][1], pts[i][2]);
		}
		// This can work, too:
		// float pts1[] = new float[]{0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f};
		// pcoords.SetJavaArray(pts1);
		vtkPoints points = new vtkPoints(); // 這個類就是vtk表示“幾何結構”的封裝類
		points.SetData(pcoords);

		vtkCellArray strips = new vtkCellArray(); // 代表拓撲結構
		strips.InsertNextCell(4);//表示下一個Cell由4個點組成,然後【按順序】指明是哪四個點
		strips.InsertCellPoint(0);
		strips.InsertCellPoint(1);
		strips.InsertCellPoint(2);
		strips.InsertCellPoint(3);//InsertCellPoint(x)的x對應的是上面“pcoords ”所表示的點的index,不防把順序由0,1,2,3改為0,3,2,1看看是什麼效果,再想想為何?
		
		vtkIntArray temperature = new vtkIntArray(); // 作用②,代表點(幾何結構)上的标量資料了
		temperature.SetName("Temperature"); // 嗯,“溫度”資料
		temperature.InsertNextValue(10);
		temperature.InsertNextValue(20);
		temperature.InsertNextValue(30);
		temperature.InsertNextValue(40);
		
                // 經過一系列準備,終于可以讓vtkPolyData閃亮登場了
		vtkPolyData polydata = new vtkPolyData();
		// 設定點和基元
		polydata.SetPoints(points);
		polydata.SetStrips(strips);
		// 設定幾何結構上的标量資料
		polydata.GetPointData().SetScalars(temperature);
		
                // 将生成的資料集加入pipeline
		vtkPolyDataMapper mapper = new vtkPolyDataMapper();
		mapper.SetInput(polydata);
		mapper.SetScalarRange(0, 40);
		
		vtkActor actor = new vtkActor();
	        actor.SetMapper(mapper);
	    
	        vtkRenderer ren = new vtkRenderer();
	        ren.AddActor( actor );
	        ren.SetBackground( 0, 0, 0 );

	        //Add renderer to renderwindow and render
	        vtkRenderWindow renWin = new vtkRenderWindow();
	        renWin.AddRenderer(ren);
	        renWin.SetSize(600, 600);
	 
	        vtkRenderWindowInteractor iren = new vtkRenderWindowInteractor();
	        vtkInteractorStyle style = new vtkInteractorStyleTrackballCamera();
		iren.SetInteractorStyle(style);
	        iren.SetRenderWindow(renWin);
	        // renWin.Render();
		iren.Initialize();
	        iren.Start();
	}

}
           

這個例子是Java代碼,是在C++的例子的基礎上改寫的。運作成功後,出來的是這樣的效果:

VTK經驗分享 4. VTK資料集執行個體

我們可以看到,這個四邊形被染上了顔色。這些顔色是根據我們設定的标量,以及顔色對應表(vtkLookupTable)決定的。vtkLookupTable這裡不多講,可以認為它是标量數值 -- 顔色的對應表。有興趣的同學去看看api就好了,比較容易的。

我們接下來可以看着這個結果,想想這個結果和我們設定的資料的對應關系。試着改變一下資料,然後再對比結果。應該會有所收獲的。

4.1.2 Demo2 :找出資料規律,借助相關API,“隐式”地快速建立資料集

Demo1幾乎是一個最簡單的資料集,我們就已經寫了不少代碼,真實的資料往往是幾何級别,像Demo1那樣建立資料集是很麻煩的,尤其是要建立完整的cell來表示拓撲結構,幾乎不可能。

不過好在真實的資料集大多都是有規律的,我們可以像4.1節開篇說的,找出真實資料能夠對應的資料集類型,再利用vtk相應的API建立之。下面是一個建立線性網格資料集(RectilinearGrid,關于這種資料集的特點,請見前篇的介紹)的Demo,來自于一個C++例子,翻譯為Java的了:

package linke.dataset;

import vtk.vtkActor;
import vtk.vtkFloatArray;
import vtk.vtkInteractorStyle;
import vtk.vtkInteractorStyleTrackballCamera;
import vtk.vtkPolyDataMapper;
import vtk.vtkRectilinearGrid;
import vtk.vtkRectilinearGridGeometryFilter;
import vtk.vtkRectilinearGridWriter;
import vtk.vtkRenderWindow;
import vtk.vtkRenderWindowInteractor;
import vtk.vtkRenderer;

public class RectilinearGridDemo {

	static {
		System.loadLibrary("vtkCommonJava");
		System.loadLibrary("vtkFilteringJava");
		System.loadLibrary("vtkRenderingJava");
		System.loadLibrary("vtkGraphicsJava"); // vtkRectilinearGridGeometryFilter
		System.loadLibrary("vtkIOJava");
	}

	public static void main(String[] args) {
		// 假設我們已經從符合RectilinearGrid結構特點的真實資料中提取出了所需要的各個坐标,如下:
		double x[]={ // 47個
				-1.22396, -1.17188, -1.11979, -1.06771, -1.01562, -0.963542, 
				-0.911458, -0.859375, -0.807292, -0.755208, -0.703125, -0.651042, 
				-0.598958, -0.546875, -0.494792, -0.442708, -0.390625, -0.338542, 
				-0.286458, -0.234375, -0.182292, -0.130209, -0.078125, -0.026042, 
				0.0260415, 0.078125, 0.130208, 0.182291, 0.234375, 0.286458, 
				0.338542, 0.390625, 0.442708, 0.494792, 0.546875, 0.598958, 
				0.651042, 0.703125, 0.755208, 0.807292, 0.859375, 0.911458, 
				0.963542, 1.01562, 1.06771, 1.11979, 1.17188};
		double y[]={ // 33個
				-1.25, -1.17188, -1.09375, -1.01562, -0.9375, -0.859375, 
				-0.78125, -0.703125, -0.625, -0.546875, -0.46875, -0.390625, 
				-0.3125, -0.234375, -0.15625, -0.078125, 0, 0.078125, 
				0.15625, 0.234375, 0.3125, 0.390625, 0.46875, 0.546875, 
				0.625, 0.703125, 0.78125, 0.859375, 0.9375, 1.01562, 
				1.09375, 1.17188, 1.25};
		double z[]={ // 44個
				0, 0.1, 0.2, 0.3, 0.4, 0.5, 
				0.6, 0.7, 0.75, 0.8, 0.9, 1, 
				1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 
				1.7, 1.75, 1.8, 1.9, 2, 2.1, 
				2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 
				2.75, 2.8, 2.9, 3, 3.1, 3.2, 
				3.3, 3.4, 3.5, 3.6, 3.7, 3.75, 
				3.8, 3.9};
		
		// Create a rectilinear grid by defining three arrays specifying the
		// coordinates in the x-y-z directions.
		int i;
		vtkFloatArray xCoords = new vtkFloatArray();
		for (i=0; i<47; i++) xCoords.InsertNextValue(x[i]);

		vtkFloatArray yCoords = new vtkFloatArray();
		for (i=0; i<33; i++) yCoords.InsertNextValue(y[i]);

		vtkFloatArray zCoords = new vtkFloatArray();
		for (i=0; i<44; i++) zCoords.InsertNextValue(z[i]);

		// The coordinates are assigned to the rectilinear grid. Make sure that
		// the number of values in each of the XCoordinates, YCoordinates, 
		// and ZCoordinates is equal to what is defined in SetDimensions().
		
		vtkRectilinearGrid rgrid = new vtkRectilinearGrid();
		rgrid.SetDimensions(47,33,44);
		rgrid.SetXCoordinates(xCoords);
		rgrid.SetYCoordinates(yCoords);
		rgrid.SetZCoordinates(zCoords);
		
		vtkRectilinearGridWriter writer = new vtkRectilinearGridWriter();
		writer.SetFileName("vtkRectilinearGrid.vtk");
		writer.SetInput(rgrid);
		writer.Write();
		
		// 這個filter顧名思義,就是展現出RectilinearGrid的幾何結構
		vtkRectilinearGridGeometryFilter plane = new vtkRectilinearGridGeometryFilter();
		plane.SetInput(rgrid);
		plane.SetExtent(0,46, 0,32, 0,43);
                // 接下來是按部就班的pipeline
		vtkPolyDataMapper rgridMapper = new vtkPolyDataMapper();
		rgridMapper.SetInputConnection(plane.GetOutputPort());

		vtkActor actor = new vtkActor();
		actor.SetMapper(rgridMapper);

		vtkRenderer ren = new vtkRenderer();
		ren.AddActor(actor);
		ren.SetBackground(0, 0, 0);

		vtkRenderWindow renWin = new vtkRenderWindow();
		renWin.AddRenderer(ren);
		renWin.SetSize(600, 600);

		vtkRenderWindowInteractor iren = new vtkRenderWindowInteractor();
		vtkInteractorStyle style = new vtkInteractorStyleTrackballCamera();
		iren.SetInteractorStyle(style);
		iren.SetRenderWindow(renWin);
		// renWin.Render();
		iren.Initialize();
		iren.Start();
	}
}
           

4.2 從檔案讀取資料集

上面的例子中,有關于vtkRectilinearGridWriter的片段,它屬于vtk的IO API,用來把記憶體中的vtk資料集持久化為磁盤檔案,或者從磁盤檔案将資料集讀入記憶體。在運作了上面的例子以後,Java工程的目錄下應該會多個檔案vtkRectilinearGrid.vtk,打開之後是這個樣子:

# vtk DataFile Version 3.0
vtk output
ASCII
DATASET RECTILINEAR_GRID 【資料類型】
DIMENSIONS 47 33 44 【各個方向次元】
X_COORDINATES 47 float 【x軸坐标】
-1.22396 -1.17188 -1.11979 -1.06771 -1.01562 -0.963542 -0.911458 -0.859375 -0.807292 
-0.755208 -0.703125 -0.651042 -0.598958 -0.546875 -0.494792 -0.442708 -0.390625 -0.338542 
-0.286458 -0.234375 -0.182292 -0.130209 -0.078125 -0.026042 0.0260415 0.078125 0.130208 
0.182291 0.234375 0.286458 0.338542 0.390625 0.442708 0.494792 0.546875 0.598958 
0.651042 0.703125 0.755208 0.807292 0.859375 0.911458 0.963542 1.01562 1.06771 
1.11979 1.17188 
Y_COORDINATES 33 float 【y軸坐标】
-1.25 -1.17188 -1.09375 -1.01562 -0.9375 -0.859375 -0.78125 -0.703125 -0.625 
-0.546875 -0.46875 -0.390625 -0.3125 -0.234375 -0.15625 -0.078125 0 0.078125 
0.15625 0.234375 0.3125 0.390625 0.46875 0.546875 0.625 0.703125 0.78125 
0.859375 0.9375 1.01562 1.09375 1.17188 1.25 
Z_COORDINATES 44 float 【z軸坐标】
0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.75 
0.8 0.9 1 1.1 1.2 1.3 1.4 1.5 1.6 
1.7 1.75 1.8 1.9 2 2.1 2.2 2.3 2.4 
2.5 2.6 2.7 2.75 2.8 2.9 3 3.1 3.2 
3.3 3.4 3.5 3.6 3.7 3.75 3.8 3.9            

關于使用vtkIO類的例子這裡不再舉例,因為這些API的接口都非常簡單,一看就會了。隻要我們記住這些就夠了:

1. VTK中的每種資料類型,都有對應的IO類(vtkXXXReader,vtkXXXWriter),以及對應的檔案格式來記錄其内容。

2. Reader類是代表pipeline起始的算法(Algorithm)類,Writer是代表pipeline結束的算法類。

隻要明白前面這些,清楚Reader類和Writer類在pipeline中的位置,就可以了。

我們不妨自己把工作中接觸到的資料集都用對應的IO類寫成檔案,然後打開來看看它結構,就可以更快地了解vtk以檔案儲存資料集的方式了。

繼續閱讀