1. 개요
VTK를 이용한 데이터 처리를 할 때 가장 많이 사용하는 vtkImageData와 vtkPolyData의 구조를 분석하고, 특히 Scalar 값의 의미와 이를 활용하여 수행할 수 있는 vtkLookupTable을 활용한 색상 표시등의 활용도를 정리한다.
2. vtkDataSet 관계도
vtkDataSet은 추상화 클래스이며 이를 파생한 클래스 중에 vtkImageData와 vtkPolyData가 존재한다.
vtkDataSet에서 중요한 데이터는 Cell, CellData, Point, PointData이다.
- Point : DataSet에서 기본이 되는 데이터이다. Point는 0-Dimension을 갖는 기본 구성 데이터이다. 하지만 VTK 에서는 3차원 데이터를 기본으로 하고 있어서 double[3] 값을 갖는다.
- Cell : volume, surface, lines 와 같은 형상에 대한 정보를 갖는 vtkCell 객체에 대한 데이터이다. Cell 은 n개의 Point로 이루어진 형상 데이터이다.
- PointData : Point 데이터가 3차원 위치 정보를 갖는 데이터라면 PointData는 각 Point가 갖는 상수 값을 관리하는 데이터이다. 이 값은 GetScalar 로 얻을 수 있다.
- CellData : CellData 는 Cell 을 위해 정의된 값을 Scalar 값을 관리하는 데이터이다. Cell 은 n개의 Point로 이루어진 형형상 데이터이므로 Cell을 이루는 Point 의 Scalar 값을 기준으로 Interpolation 된 상수 값을 갖게 된다.
vtkImageData 분석
vtkImageData에서의 Point 는 Pixel을 의미하며 픽셀의 좌표 값을 의미한다.
PointData의 Scalar는 Pixel의 색상 값이 저장하고 있으며, 각 픽셀의 index 에 매치되는 순서로 값을 지닌다.
Pixel의 index는 이미지의 X 축(width) 방향으로 index가 1씩 증가하며 X축으로 Stride, 즉 width 의 크기가 Y 축(height) index의 곱으로 값이 증가한다.
ImageData의 PointData Scalar 값은 Component 의 갯수에 따라 1채널 또는 3채널 이미지를 갖게 된다.
예로 Color 이미지일 경우 Component 가 3개이므로 GetTuple3 를 통해 값을 얻을 수 있다.
Cell 은 2D 의 경우 인접한 4포인트를 연결한 형상을 구성한다. Cell의 정보를 얻어보면 Pixel로 나오는데 이 Pixel 데이터가 우리가 일반적으로 생각하는 이미지의 Pixel 과 다르다.
예로, 우리가 10x10 pixel의 이미지를 만들면, vtkImageData 는 100(10x10)개의 Point를 갖고 81(9x9)개의 Pixel(Cell)데이터를 갖기 때문이다.
이 Cell 정보에는 인접한 Point정보를 갖고 있으나, 특히 사용할 일은 드문것 같다.
또한 ImageData에서는 CellData 를 기본적으로 생성하지 않는다.
다만 vtkPointDataToCellData Filter를 이용하여 데이터를 생성할 수 있으며, CellData의 Scalar 값에 저장된다. CellData의 값은 인접한 Point 의 Scalar 값을 평균하여 상수 값으로 저장하게 된다.
[Test]
public void TestCellDataInterpolation()
{
ShowRenderWindow = false;
var width = 10;
var height = 10;
var whiteCanvas = VtkCanvasUtils.CreateCanvas(width, height, "white");
whiteCanvas.DrawPoint(new[]{ 1, 0}, "red" ); // (255, 0, 0)
whiteCanvas.DrawPoint(new[]{ 2, 0}, "lime"); // (0, 255, 0)
whiteCanvas.DrawPoint(new[]{ 1, 1}, "blue"); // (0, 0, 255)
whiteCanvas.Update();
var imgData = whiteCanvas.GetOutput();
var numPoints = imgData.GetNumberOfPoints();
var numCell = imgData.GetNumberOfCells();
Console.WriteLine($"Points:{numPoints}, Cells:{numCell}");
Console.WriteLine($"Scalar Size:{imgData.GetScalarSize()}, Comp:{imgData.GetNumberOfScalarComponents()}");
Console.WriteLine($"Imagedata #Tuple Point:{imgData.GetPointData().GetNumberOfTuples()}, {imgData.GetCellData().GetNumberOfTuples()}");
Console.WriteLine("Access Scalar thru PointData");
for (int y = 0; y < 4; y++)
{
for (int x = 0; x < 4; x++)
{
Console.Write($"[{imgData.GetPointData().GetScalars().GetTuple3(y * width + x).ToStringJoin()}] ");
}
Console.WriteLine();
}
vtkPointDataToCellData cvtFilter = vtkPointDataToCellData.New();
cvtFilter.SetInputData(imgData);
cvtFilter.PassPointDataOn(); // PointData를 동시에 갖도록 한다.
cvtFilter.Update();
var imgCellData = vtkImageData.SafeDownCast(cvtFilter.GetOutput());
Console.WriteLine($"Converted Imagedata #Tuple Point:{imgCellData.GetPointData().GetNumberOfTuples()}, {imgCellData.GetCellData().GetNumberOfTuples()}");
VtkImageUtils.WriteImageData(imgCellData, "cvImageCelldata.png");
// CellData 는 Cell을 구성하는 4개 Point의 값을 평균낸 값을 상수로 갖는다.
Console.WriteLine("Access CellData Scalar thru CellData");
for (int y = 0; y < 3; y++)
{
for (int x = 0; x < 3; x++)
{
// Cell width는 Pixel의 width-1 크기를 갖기 때문이다.
Console.Write($"[{imgCellData.GetCellData().GetScalars().GetTuple3(y * (width-1) + x).ToStringJoin()}] ");
}
Console.WriteLine();
}
Console.WriteLine();
vtkDoubleArray ptArr = vtkDoubleArray.New();
// Tuple index min,max 값에서 원하는 component 범위를 지정하고 이를 vtkDataArray 에 저장하여 return 한다.
imgData.GetPointData().GetScalars().GetData(0,3,0,2, ptArr);
Console.WriteLine(ptArr.ToListJoin().ToStringJoin());
}
vtkPolyData 분석
PolyData라고해서 ImageData 와 기본 구조는 동일하다. 다만 Point 정보는 Cell을 구성하는데 필요한 좌표 값이며 각 Cell들은 Point 의 index정보만을 갖고 Cell 타입에 따른 topology를 구성하게 된다.
기본적인 데이터의 정보는 ImageData와 같으나, PolyData에서 Scalar 가 사용되는 예로는 vtkContourFilter를 이용하여 Image 의 외곽선을 추출하면 output으로 PolyData가 만들어진다.
이 데이터는 경계선을 지나는 iso-value가 Scalar 값으로 입력된다.
3. vtkLookupTable
vtkLookupTable은 입력된 범위의 Scalar 값에 매칭되는 색상을 테이블 데이터로 생성한다.
Table 데이터는 Mapper 에 연결되며, InputData의 Scalar 값과 Table 의 색상값을 비교하여 Renderer 에 맞는 색상을 표시하게 된다.
참고로, Mapper에서 색상을 모두 표시하기 위해서는 몇가지 속성을 설정해야한다.
[Test]
public void TestMappingLookupTable()
{
AxesLength = 4;
BkgColor = "DarkGray";
// Scenario 1: SetRange 의 범위에 따라 HSV space의 색상을 Build 하여 만든다.
vtkLookupTable hsvLut = vtkLookupTable.New();
hsvLut.SetHueRange(0.0, 0.667);
hsvLut.SetSaturationRange(1, 1);
hsvLut.SetValueRange(1, 1);
hsvLut.SetNumberOfTableValues(256);
hsvLut.SetRange(-2.5, 2.5);
hsvLut.Build();
// Create a test dataset (a sphere)
vtkSphereSource sphere = vtkSphereSource.New();
sphere.SetRadius(2.5);
sphere.SetPhiResolution(21);
sphere.SetThetaResolution(21);
sphere.Update();
// Create a mapper
vtkPolyDataMapper mapper = vtkPolyDataMapper.New();
mapper.SetInputData(CreateRandomScalarValue(sphere));
mapper.SetLookupTable(hsvLut); // Scenario 1
mapper.SetScalarRange(hsvLut.GetRange().ToIntPtr()); // -2.5, 2.5
mapper.ScalarVisibilityOn();
// Create an actor
vtkActor actor = vtkActor.New();
actor.SetMapper(mapper);
// Scenario 2: Index 순서대로 지정된 색상을 Mapper의 ScalarRange의 크기에 맞춰서 색상을 표현하게 된다.
vtkLookupTable specificColorLut = vtkLookupTable.New();
specificColorLut.SetNumberOfTableValues(4);
specificColorLut.SetTableValue(0, 1, 0, 0, 1);
specificColorLut.SetTableValue(1, 0, 1, 0, 1);
specificColorLut.SetTableValue(2, 0, 0, 1, 1);
specificColorLut.SetTableValue(3, 0, 1, 1, 1);
specificColorLut.Build();
vtkSphereSource sphereColorLut = vtkSphereSource.New();
sphereColorLut.SetRadius(2.5);
sphereColorLut.SetPhiResolution(21);
sphereColorLut.SetThetaResolution(21);
sphereColorLut.Update();
// Create a mapper
vtkPolyDataMapper mapperColorLut = vtkPolyDataMapper.New();
mapperColorLut.SetInputData(CreateRandomScalarValue(sphereColorLut));
mapperColorLut.SetLookupTable(specificColorLut); // Scenario 2
mapperColorLut.SetScalarRange(hsvLut.GetRange().ToIntPtr()); // -2.5, 2.5
mapperColorLut.ScalarVisibilityOn();
// Create an actor
vtkActor actorColorLut = vtkActor.New();
actorColorLut.SetMapper(mapperColorLut);
renderer.AddActor(actor);
renderer.AddActor(actorColorLut);
}
좌측은 HSV Space로 만든 Actor 이고, 오른쪽은 SetTableValue를 통해서 만든 Actor이다.
4. 마무리
vtkDataSet의 Scalar는 Point 또는 Cell 데이터에 매칭되는 상수 값을 갖는다. 그리고 Scalar 값은 vtkLookupTable와 Mapper의 설정에 따라서 Renderer 에서 그리는데 참조되는 데이터로 사용할 수 있다.