5.3.5.2-3 三维图像切面提取+扩展

1、三维图像切面提取

切片是指三维图像中的一个切面对应的图像。切面可以是过图像内部一点且平行于XY、YZ、XZ平面的平面,也可以是任意的过三维图像内部一点任意方向的平面。通过提取切片可以方便的浏览和分析图像内部组织结构,是医学图像浏览软件中的一个重要的功能。在VTK中vtkImageReslice类实现图像切片提取功能。

//实现结果显示空白,后续有待研究解决
#include   
#include   
#include   
#include  
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   

int main()
{
	vtkSmartPointer reader =
		vtkSmartPointer::New();
	reader->SetFileName("head");
	reader->Update();

	int extent[6];
	double spacing[3];
	double origin[3];

	reader->GetOutput()->GetExtent(extent);
	reader->GetOutput()->GetSpacing(spacing);
	reader->GetOutput()->GetOrigin(origin);

	double center[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]);
	//*****************************************************************//  
	static double axialElements[16] = {
		1, 0, 0, 0,
		0, 1, 0, 0,
		0, 0, 1, 0,
		0, 0, 0, 1
	};

	vtkSmartPointer resliceAxes =
		vtkSmartPointer::New();
	resliceAxes->DeepCopy(axialElements);
	resliceAxes->SetElement(0, 3, center[0]);
	resliceAxes->SetElement(1, 3, center[1]);
	resliceAxes->SetElement(2, 3, center[2]);

	vtkSmartPointer reslice =
		vtkSmartPointer::New();
	reslice->SetInputConnection(reader->GetOutputPort());
	reslice->SetOutputDimensionality(1);
	reslice->SetResliceAxes(resliceAxes);
	reslice->SetInterpolationModeToLinear();
	//*****************************************************************//  
	vtkSmartPointer colorTable =
		vtkSmartPointer::New();
	colorTable->SetRange(0, 1000);
	colorTable->SetValueRange(0.0, 1.0);
	colorTable->SetSaturationRange(0.0, 0.0);
	colorTable->SetRampToLinear();
	colorTable->Build();
	vtkSmartPointer colorMap =
		vtkSmartPointer::New();
	colorMap->SetLookupTable(colorTable);
	colorMap->SetInputConnection(reslice->GetOutputPort());
	//*****************************************************************//  
	vtkSmartPointer imgActor =
		vtkSmartPointer::New();
	imgActor->SetInputData(colorMap->GetOutput());

	vtkSmartPointer renderer =
		vtkSmartPointer::New();
	renderer->AddActor(imgActor);
	renderer->SetBackground(1.0, 1.0, 1.0);

	vtkSmartPointer renderWindow =
		vtkSmartPointer::New();
	renderWindow->AddRenderer(renderer);
	renderWindow->Render();
	renderWindow->SetSize(640, 480);
	renderWindow->SetWindowName("Extract3Dslice");

	vtkSmartPointer rwi =
		vtkSmartPointer::New();
	vtkSmartPointer imagestyle =
		vtkSmartPointer::New();
	rwi->SetInteractorStyle(imagestyle);
	rwi->SetRenderWindow(renderWindow);
	rwi->Initialize();
	rwi->Start();

	return 0;
}

首先通过vtkMetaImageReader读取一张医学三维图像,并获取得到图像范围(extent),原点和像素间隔;由这三个参数可以计算图像的中心位置center;接下来定义了切面的变换矩阵axialElements,该矩阵的前三列分别表示x、y和z方向向量,第四列为中心点坐标;

代码中的axialElements表示切面变换矩阵与当前坐标系一致,且切面为过中心点center,并平行于XY平面的平面???当前,定义该切面时,也可以是其他平面,甚至是任意平面,但是必须要过图像内部点。

下面给出了一个常用的变换矩阵。

提取平行于XZ平面的切片:

[cpp]  view plain  copy
  1. static double coronalElements[16] = {  
  2.  1, 0, 0, 0,  
  3.  0, 0, 1, 0,  
  4.  0,-1, 0, 0,  
  5.  0, 0, 0, 1 };   
提取平行于YZ平面的切片:
[cpp]  view plain  copy
  1. static double sagittalElements[16] = {  
  2. 0, 0,-1, 0,  
  3. 1, 0, 0, 0,  
  4. 0,-1, 0, 0,  
  5. 0, 0, 0, 1 };   
提取斜切切片:
[cpp]  view plain  copy
  1. static double obliqueElements[16] = {  
  2. 1, 0, 0, 0,  
  3. 0, 0.866025, -0.5, 0,  
  4. 0, 0.5, 0.866025, 0,  
  5. 0, 0, 0, 1 };   
注意使用这些变换矩阵的时候,需要将第四列替换为切片经过图像的一个点坐标,上例中将图像的中心添加到axialElements矩阵,并通过函数SetResliceAxes设置变换矩阵,SetOutputDimensionality(2)指定输出的图像为一个二维图像;而函数SetInterpolationModeToLinear()则指定了切面提取中的差值方式为线性差值,另外该类中还提供了其他的插值方式:
SetInterpolationModeToNearestNeighbor():最近邻方式
SetInterpolationModeToCubic():三次线性差值
设置完毕后,执行Update()即可完成切面计算。

东灵提供的预想结果应该是:

5.3.5.2-3 三维图像切面提取+扩展_第1张图片

问题:在实际运行过程中,并未提示错误,但运行出来没有结果(空白显示),后续有待研究解决!

2、三维图像切片交互提取(回调函数、观察者-命令模式)

学习三维图像切面的提取后,我们可以实现一个稍微复杂的程序——通过滑动鼠标来切换三维图像切片,这也是医学图像处理软件中一个很基本的功能。实现该功能难点是怎样在VTK中控制鼠标来实时提取图像切片。我们采用观察者/命令(Observer/Command)模式机制来实现。
VTK中鼠标消息是在交互类型对象(interactorstyle)中响应,因此通过为交互类型对象(interactorstyle)添加观察者(observer)来监听相应的消息,当消息触发时,由命令模式执行相应的回调函数。
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
  
#include  //建立“观察者/命令”模式监听鼠标消息 完成交互  
#include   
#include   
class vtkImageInteractionCallback : public vtkCommand  
{  
public:  
    static vtkImageInteractionCallback *New() //回调函数初始化函数  
    {  
        return new vtkImageInteractionCallback;  
    }  
    vtkImageInteractionCallback()  
    {  
        this->Slicing = 0;  
        this->ImageReslice = 0;  
        this->Interactor = 0;  
    }  
    void SetImageReslice(vtkImageReslice *reslice)  
    {  
        this->ImageReslice = reslice;  
    }  
    vtkImageReslice *GetImageReslice()  
    {  
        return this->ImageReslice;  
    }  
    void SetInteractor(vtkRenderWindowInteractor *interactor)  
    {  
        this->Interactor = interactor;  
    }  
    vtkRenderWindowInteractor *GetInteractor()  
    {  
        return  this->Interactor;  
    }  
    virtual void Execute(vtkObject * ,unsigned long event,void *)  
    {  
        vtkRenderWindowInteractor *interactor = GetInteractor();  
        int lastPos[2];  
        interactor->GetLastEventPosition(lastPos);  
        int currPos[2];  
        interactor->GetEventPosition(currPos);  
  
        if (event == vtkCommand::LeftButtonPressEvent)  
        {  
            this->Slicing = 1; //标志位   
        }  
        else if (event == vtkCommand::LeftButtonReleaseEvent)  
        {  
            this->Slicing = 0; //标志位   
        }  
        else if (event == vtkCommand::MouseMoveEvent)  
        {  
            if (this->Slicing)//检验鼠标左键已经按下 正在执行操作  
            {  
                vtkImageReslice *reslice = this->ImageReslice;  
                //记下鼠标Y向变化的幅值大小  
                int deltaY = lastPos[1] - currPos[1];  
  
                reslice->Update();  
                double sliceSpacing = reslice->GetOutput()->GetSpacing()[2];  
                vtkMatrix4x4 *matrix = reslice->GetResliceAxes();  
                //重新定位切片需要经过的中心点  
                double point[4];  
                double center[4];  
                point[0] = 0;  
                point[1] = 0;  
                point[2] = sliceSpacing*deltaY;  
                point[3] = 1.0;  
                matrix->MultiplyPoint(point, center);  
                matrix->SetElement(0, 3, center[0]);  
                matrix->SetElement(1, 3, center[1]);  
                matrix->SetElement(2, 3, center[2]);  
  
                interactor->Render();  
            }  
            else  
            {  
                vtkInteractorStyle *style = vtkInteractorStyle::SafeDownCast(  
                    interactor->GetInteractorStyle());  
                if (style)  
                {  
                    style->OnMouseMove();  
                }  
            }  
        }  
    }  
private:  
    int Slicing;  
    vtkImageReslice *ImageReslice;  
    vtkRenderWindowInteractor *Interactor;  
};  
//**********************************************************************************//  
int main()  
{  
    vtkSmartPointer reader =  
        vtkSmartPointer::New();  
    reader->SetFileName("brain.mhd");  
    reader->Update();  
  
    int extent[6];  
    double spacing[3];  
    double origin[3];  
  
    reader->GetOutput()->GetExtent(extent);  
    reader->GetOutput()->GetSpacing(spacing);  
    reader->GetOutput()->GetOrigin(origin);  
  
    double center[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]);  
  
    static double axialElements[16] = {  
        1, 0, 0, 0,  
        0, 1, 0, 0,  
        0, 0, 1, 0,  
        0, 0, 0, 1  
    };  
  
    vtkSmartPointer resliceAxes =  
        vtkSmartPointer::New();  
    resliceAxes->DeepCopy(axialElements);  
  
    resliceAxes->SetElement(0, 3, center[0]);  
    resliceAxes->SetElement(1, 3, center[1]);  
    resliceAxes->SetElement(2, 3, center[2]);  
  
    vtkSmartPointer reslice =  
        vtkSmartPointer::New();  
    reslice->SetInputConnection(reader->GetOutputPort());  
    reslice->SetOutputDimensionality(2);  
    reslice->SetResliceAxes(resliceAxes);  
    reslice->SetInterpolationModeToLinear();  
  
    vtkSmartPointer colorTable =  
        vtkSmartPointer::New();  
    colorTable->SetRange(0, 1000);  
    colorTable->SetValueRange(0.0, 1.0);  
    colorTable->SetSaturationRange(0.0, 0.0);  
    colorTable->SetRampToLinear();  
    colorTable->Build();  
  
    vtkSmartPointer colorMap =  
        vtkSmartPointer::New();  
    colorMap->SetLookupTable(colorTable);  
    colorMap->SetInputConnection(reslice->GetOutputPort());  
  
    vtkSmartPointer imgActor =  
        vtkSmartPointer::New();  
    imgActor->SetInputData(colorMap->GetOutput());  
  
    vtkSmartPointer renderer =  
        vtkSmartPointer::New();  
    renderer->AddActor(imgActor);  
    renderer->SetBackground(.4, .5, .6);  
  
    vtkSmartPointer renderWindow =  
        vtkSmartPointer::New();  
    renderWindow->SetSize(500, 500);  
    renderWindow->AddRenderer(renderer);  
  
    vtkSmartPointer renderWindowInteractor =  
        vtkSmartPointer::New();  
    vtkSmartPointer imagestyle =  
        vtkSmartPointer::New();  
  
    renderWindowInteractor->SetInteractorStyle(imagestyle);  
    renderWindowInteractor->SetRenderWindow(renderWindow);  
    renderWindowInteractor->Initialize();  
    //****************建立 观察者-命令 模式****************//  
    vtkSmartPointer callback =  
        vtkSmartPointer::New();  
    callback->SetImageReslice(reslice);  
    callback->SetInteractor(renderWindowInteractor);  
  
    imagestyle->AddObserver(vtkCommand::MouseMoveEvent, callback);  
    imagestyle->AddObserver(vtkCommand::LeftButtonPressEvent, callback);  
    imagestyle->AddObserver(vtkCommand::LeftButtonReleaseEvent, callback);  
  
    renderWindowInteractor->Start();  
  
    return 0;  
}
tkImageInteractionCallback继承自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添加观察者,响应鼠标消息。
这里主要是定义了vtkImageInteractionCallback对象,并设置vtkImageSlice对象和vtkRenderWindowInteractor对象。然后为交互对象vtkInteractorStyle添加观察者来监控相应的消息,这里主要是三个消息:
vtkCommand::LeftButtonPressEvent,
vtkCommand::LeftButtonReleaseEvent,
vtkCommand::MouseMoveEvent,

当响应到这三个消息时,立即执行vtkImageInteractionCallback的Execute函数,以便实现切片的实时提取和更新。完成以后,运行程序,当鼠标在图像上移动时,会发现图像会跟着鼠标的移动而变化。

注:此文知识学习笔记,仅记录完整程序和实现结果,具体原理参见:

https://blog.csdn.net/www_doling_net/article/details/8541534

https://blog.csdn.net/shenziheng1/article/category/6114053/4

参考资料:

1.《The Visualization Toolkit – AnObject-Oriented Approach To 3D Graphics (4th Edition)》
2. 张晓东, 罗火灵. VTK图形图像开发进阶[M]. 机械工业出版社, 2015.

所用软件:vtk7.0+visual studio 2013

你可能感兴趣的:(VTK)