1、感兴趣区域(Volume ofInterest,VOI)
它是图像内部的一块子区域。在VTK中,vtkExtractVOI 类可根据用户指定的区域范围提取子图像。该Filter 的输入和输出都是一个vtkImageData,因此其结果可以直接作为图像保存。
代码:
cs
private void TestExtractVOI()
{
vtkBMPReader reader = vtkBMPReader.New();
reader.SetFileName("F:\\code\\VTK\\TestActiViz\\bin\\Debug\\data\\lena.bmp");
reader.Update();
int[] dims = reader.GetOutput().GetDimensions();
//提取红色对应灰度图像
vtkExtractVOI extractVOI = vtkExtractVOI.New();
extractVOI.SetInputData(reader.GetOutput());
// 6个参数 依次表示X方向坐标最小值、最大值 Y方向最小值、最大值 Z方向最小值、最大值。
extractVOI.SetVOI(dims[0] / 4, (int)3.0 * dims[0] / 4, dims[1] / 4, (int)3.0 * dims[1] / 4, 0, 0);
extractVOI.Update();
vtkImageActor orgActor = vtkImageActor.New();
orgActor.SetInputData(reader.GetOutput());
vtkImageActor voiActor = vtkImageActor.New();
voiActor.SetInputData(extractVOI.GetOutput());
vtkRenderer orgRenderer = vtkRenderer.New();
orgRenderer.AddActor(orgActor);
orgRenderer.SetViewport(0.0, 0.0, 0.5, 1.0);
orgRenderer.ResetCamera();
orgRenderer.SetBackground(1, 1, 1);
vtkRenderer renderer3 = vtkRenderer.New();
renderer3.SetViewport(0.5, 0.0, 1, 1.0);
renderer3.AddActor(voiActor);
renderer3.ResetCamera();
renderer3.SetBackground(1, 1, 1);
vtkRenderWindow renderWindow = renderWindowControl.RenderWindow;
renderWindow.AddRenderer(orgRenderer);
renderWindow.AddRenderer(renderer3);
renderWindow.Render();
}
效果:
先读取一幅 BMP 图像,并获取图像的维数;然后定义 vtkExtractVOI对象,该对象接收两个输入:第一个是图像数据,第二个是区域大小。设置区域大小的函数原型的代码如下:
cs
public virtual void SetVOI(int _arg1, int _arg2, int _arg3, int _arg4, int _arg5, int _arg6);
public virtual void SetVOI(IntPtr _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图像。
2、三维图像切面提取
除了冠状面、矢状面和横断面,即过图像内部一点且平行于 XY、YZ.XZ平面的平面,切面也可以是过三维图像内部一点且平行于任意方向的平面。通过提取切面可以方便地浏览和分析图像内部组织结构,这是医学图像浏览软件中的一个重要的功能。VTK中 vtkImageReslice 类可实现图像切面的提取。
代码:
cs
private void TestReslice()
{
vtkMetaImageReader reader = vtkMetaImageReader.New();
reader.SetFileName("F:\\code\\VTK\\TestActiViz\\bin\\Debug\\data\\brain.mhd");
reader.Update();
int[] extent = reader.GetOutput().GetExtent();
double[] spacing = reader.GetOutput().GetSpacing();
double[] origin = reader.GetOutput().GetOrigin();
double[] center = new double[3];
center[0] = origin[0] + spacing[0] * 0.5 * (extent[0] + extent[1]);
center[1] = origin[1] + spacing[1] * 0.5 * (extent[2] + extent[3]);
center[2] = origin[2] + spacing[2] * 0.5 * (extent[4] + extent[5]);
double[] axialElements =
// {
// 0, 0, -1, 0,
// 1, 0, 0, 0,
// 0, -1, 0, 0,
// 0, 0, 0, 1
//};//提取平行于YZ平面的切片
// {
// 1, 0, 0, 0,
// 0, 1, 0, 0,
// 0, 0, 1, 0,
// 0, 0, 0, 1
// };//提取平行于XZ平面的切片
{
1, 0, 0, 0,
0, 0.866025, -0.5, 0,
0, 0.5, 0.866025, 0,
0, 0, 0, 1
};//提取斜切切片
IntPtr ptrData = Marshal.AllocHGlobal(sizeof(double) * axialElements.Length);
Marshal.Copy(axialElements, 0, ptrData, axialElements.Length);
vtkMatrix4x4 resliceAxes = vtkMatrix4x4.New();
resliceAxes.DeepCopy(ptrData);
resliceAxes.SetElement(0, 3, center[0]);
resliceAxes.SetElement(1, 3, center[1]);
resliceAxes.SetElement(2, 3, center[2]);
vtkImageReslice reslice = vtkImageReslice.New();
reslice.SetInputData(reader.GetOutput());
reslice.SetOutputDimensionality(2);
reslice.SetResliceAxes(resliceAxes);
// reslice.SetInterpolationModeToLinear(); //指定切面提取中的插值方式为线性插值
reslice.SetInterpolationModeToNearestNeighbor(); //最近邻插值
// reslice.SetInterpolationModeToCubic(); //三次线性插值
reslice.Update();
vtkLookupTable colorTable = vtkLookupTable.New();
colorTable.SetRange(0, 1000);
colorTable.SetValueRange(0, 1);
colorTable.SetSaturationRange(0, 0);
colorTable.SetRampToLinear();
colorTable.Build();
vtkImageMapToColors colorMap = vtkImageMapToColors.New();
colorMap.SetLookupTable(colorTable);
colorMap.SetInputConnection(reslice.GetOutputPort());
colorMap.Update();
vtkImageActor orgActor = vtkImageActor.New();
orgActor.SetInputData(colorMap.GetOutput());
vtkRenderer renderer3 = vtkRenderer.New();
renderer3.AddActor(orgActor);
renderer3.ResetCamera();
renderer3.SetBackground(1, 1, 1);
vtkRenderWindow renderWindow = renderWindowControl.RenderWindow;
renderWindow.AddRenderer(renderer3);
renderWindow.Render();
}
效果:
先通过 vtkMetaImageReader 读取一幅三维图像,获取图像范围、原点和像素间隔,由这三个参数可以计算图像的中心位置;接下来定义了切面的变换矩阵axialElements,该矩阵的前三列分别表示X、Y和乙方向矢量,第四列为切面坐标系原点。通过修改切面坐标系原点,可以得到不同位置的切面图像。代码中的 axialElements 表示切面坐标系与图像坐标系一致,且经过图像中心点 center。定义该切面时,也可以使用其他平面,甚至是任意平面,但是必须过图像内部点。下面给出了一个常用的变换矩阵:
cs
double[] axialElements =
// {
// 0, 0, -1, 0,
// 1, 0, 0, 0,
// 0, -1, 0, 0,
// 0, 0, 0, 1
//};//提取平行于YZ平面的切片
// {
// 1, 0, 0, 0,
// 0, 1, 0, 0,
// 0, 0, 1, 0,
// 0, 0, 0, 1
// };//提取平行于XZ平面的切片
{
1, 0, 0, 0,
0, 0.866025, -0.5, 0,
0, 0.5, 0.866025, 0,
0, 0, 0, 1
};//提取斜切切片
注意:使用这些变换矩阵时,需要将第四列替换为切片经过图像的一个点坐标,上例中将图像的中心添加到变换矩阵 axialElements,并通过函数 SetResliceAxes()设置变换矩阵。
SetOutputDimensionality(2)指定输出的图像为一个二维图像;
SetlnterpolationModeToLinear()则指定了切面提取中的插值方式为线性插值.
另外该类中还提供了其他插值方式:
SetInterpolationModeToNearestNeighbor():最近邻插值。
SetInterpolationModeToCubic():三次线性插值。
3、 扩展
在三维图像切面提取程序的基础上进行扩展,实现一个稍微复杂的程序,即通过滑动鼠标来切换三维图像切面,这也是医学图像处理软件中一个基本的功能。实现该功能的难点是怎样在 VTK 中控制鼠标来实时提取图像切面。可以通过观察者/命令模式来实现这种功能。VTK中鼠标消息是在交互器样式(InteractorStyle)中响应,因此通过为交互器样式添加观察者(Observer)来监听相应的消息,当消息触发时,由命令模式执行相应的回调函数即可。
在c++中,继承后进行更改Execute函数。并进行注册。
cpp
class vtkMyCallback : public vtkCommand
{
public:
static vtkMyCallback* New()
{
return new vtkMyCallback;
}
void Execute(vtkObject* caller, unsigned long, void*)
{
vtkTransform* t = vtkTransform::New();
vtkBoxWidget* widget = reinterpret_cast<vtkBoxWidget*>(caller);
widget->GetTransform(t);
widget->GetProp3D()->SetUserTransform(t);
t->Delete();
}
};
c#版本使用包装控件会报错,暂时没有查找到问题。
cs
private void TestInteractorStyle()
{
vtkMetaImageReader reader = vtkMetaImageReader.New();
reader.SetFileName("F:\\code\\VTK\\TestActiViz\\bin\\Debug\\data\\brain.mhd");
reader.Update();
int[] extent = reader.GetOutput().GetExtent();
double[] spacing = reader.GetOutput().GetSpacing();
double[] origin = reader.GetOutput().GetOrigin();
double[] center = new double[3];
center[0] = origin[0] + spacing[0] * 0.5 * (extent[0] + extent[1]);
center[1] = origin[1] + spacing[1] * 0.5 * (extent[2] + extent[3]);
center[2] = origin[2] + spacing[2] * 0.5 * (extent[4] + extent[5]);
double[] axialElements ={
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
};//提取平行于YZ平面的切片
IntPtr ptrData = Marshal.AllocHGlobal(sizeof(double) * axialElements.Length);
Marshal.Copy(axialElements, 0, ptrData, axialElements.Length);
vtkMatrix4x4 resliceAxes = vtkMatrix4x4.New();
resliceAxes.DeepCopy(ptrData);
resliceAxes.SetElement(0, 3, center[0]);
resliceAxes.SetElement(1, 3, center[1]);
resliceAxes.SetElement(2, 3, center[2]);
vtkImageReslice reslice = vtkImageReslice.New();
reslice.SetInputData(reader.GetOutput());
reslice.SetOutputDimensionality(2);
reslice.SetResliceAxes(resliceAxes);
reslice.SetInterpolationModeToLinear(); //指定切面提取中的插值方式为线性插值
reslice.Update();
vtkLookupTable colorTable = vtkLookupTable.New();
colorTable.SetRange(0, 1000);
colorTable.SetValueRange(0, 1);
colorTable.SetSaturationRange(0, 0);
colorTable.SetRampToLinear();
colorTable.Build();
vtkImageMapToColors colorMap = vtkImageMapToColors.New();
colorMap.SetLookupTable(colorTable);
colorMap.SetInputConnection(reslice.GetOutputPort());
colorMap.Update();
vtkImageActor orgActor = vtkImageActor.New();
orgActor.SetInputData(colorMap.GetOutput());
vtkRenderer renderer3 = vtkRenderer.New();
renderer3.AddActor(orgActor);
renderer3.ResetCamera();
renderer3.SetBackground(0.4, 0.5, 0.6);
vtkRenderWindow renderWindow = renderWindowControl.RenderWindow;
renderWindow.AddRenderer(renderer3);
vktImageInteractionCallback callback = new vktImageInteractionCallback();
callback.SetImageReslice(reslice);
callback.SetInteractor(renderWindow);
renderWindow.AddObserver((uint)vtkCommand.EventIds.LeftButtonPressEvent, callback, 0);
renderWindow.AddObserver((uint)vtkCommand.EventIds.MouseMoveEvent, callback, 0);
renderWindow.AddObserver((uint)vtkCommand.EventIds.LeftButtonReleaseEvent, callback, 0);
renderWindow.Render();
}