VTK知识学习(28)-区域提取

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();

           
        }
相关推荐
西岸行者6 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意6 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码6 天前
嵌入式学习路线
学习
毛小茛6 天前
计算机系统概论——校验码
学习
babe小鑫6 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms6 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下6 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。6 天前
2026.2.25监控学习
学习
im_AMBER6 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J6 天前
从“Hello World“ 开始 C++
c语言·c++·学习