问题
VTK图像处理交流群(962611958)里有朋友提了一个问题:使用交互器操作轴面图像的中心点时,出现图像随着鼠标移动;大家都认为平移时出现图像的平移是不可能的,因为在使用vtkImageReslice取图时,如果不指定OutputOrigin坐标位置,是默认以体数据的中心点在切面上的投影为中心点的图像,在同一个断层上移动,中心点是不会发生变化的,图像也就是同一张图。这个问题很有意思;
我这段时间也在做和切面重建相关的内容,跟着群友的代码来查这个问题;
参考群友的代码写的复现代码,每次平移只考虑了鼠标的XY的改变量,没有结合XY方向的spacing的值,这个地方暂时忽略,主要关注在鼠标移动事件MouseMoveEvent,代码如下:
#pragma once
#include "vtk_include.h"
#include <vtkMetaImageReader.h>
#include <vtkImageActor.h>
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;
}
void SetImageMapToColors(vtkImageMapToColors* mapToColors) {
this->mapToColors = mapToColors;
}
void SetImageActor(vtkImageActor* actor) {
myActor = actor;
}
void SetInteractor(vtkRenderWindowInteractor* interactor) {
this->Interactor = interactor;
}
vtkRenderWindowInteractor* GetInteractor() {
return this->Interactor;
}
virtual void Execute(vtkObject*, unsigned long event, void*) {
vtkRenderWindowInteractor* interactor = this->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)
{
int deltaX = lastPos[0] - currPos[0];
int deltaY = lastPos[1] - currPos[1];
double translateC[16] = {
1,0,0,deltaX,
0,1,0,deltaY,
0,0,1,0,
0,0,0,1 };
vtkImageReslice* reslice = this->ImageReslice;
reslice->Update();
vtkMatrix4x4* matrix = reslice->GetResliceAxes();
vtkMatrix4x4* translateM = vtkMatrix4x4::New();
vtkMatrix4x4* resultM = vtkMatrix4x4::New();
translateM->DeepCopy(translateC);
vtkMatrix4x4::Multiply4x4(matrix, translateM, resultM);
matrix->DeepCopy(resultM);
reslice->SetResliceAxes(matrix);
reslice->Update();
translateM->Delete();
resultM->Delete();
mapToColors->Update();
interactor->Render();
}
else {
vtkInteractorStyle* style = vtkInteractorStyle::SafeDownCast(interactor->GetInteractorStyle());
if (style) {
style->OnMouseMove();
}
}
}
}
private:
int Slicing;
vtkImageReslice* ImageReslice;
vtkRenderWindowInteractor* Interactor;
vtkImageMapToColors* mapToColors;
vtkImageActor* myActor;
};
class Test_Reslice
{
public:
Test_Reslice() {
reader = vtkSmartPointer<vtkMetaImageReader>::New();
reader->SetFileName("G:\\Data\\brain.mhd");
reader->Update();
reader->GetOutput()->GetExtent(extent);
reader->GetOutput()->GetSpacing(spacing);
reader->GetOutput()->GetOrigin(origin);
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]);
}
void Render()
{
this->renwin = vtkRenderWindow::New();
this->iren = vtkRenderWindowInteractor::New();
int extent[6];
double spacing[3];
double origin[3];
double center[3];
this->Getimagedata()->GetExtent(extent);
this->Getimagedata()->GetSpacing(spacing);
this->Getimagedata()->GetOrigin(origin);
center[0] = origin[0] + 0.5 * spacing[0] * (extent[0] + extent[1]);
center[1] = origin[1] + 0.5 * spacing[1] * (extent[2] + extent[3]);
center[2] = origin[2] + 0.5 * spacing[2] * (extent[4] + extent[5]);
double Axial[16] = {
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
};
vtkMatrix4x4 *AxialResliceMatrix = vtkMatrix4x4::New();
AxialResliceMatrix->DeepCopy(Axial);
AxialResliceMatrix->SetElement(0, 3, center[0]);
AxialResliceMatrix->SetElement(1, 3, center[1]);
AxialResliceMatrix->SetElement(2, 3, center[2]);
vtkImageReslice* myImageReslice = this->ImageReslice = vtkImageReslice::New();
//设置体数据来源
myImageReslice->SetInputData(this->Getimagedata());
myImageReslice->SetResliceAxes(AxialResliceMatrix);
myImageReslice->SetOutputDimensionality(2);
myImageReslice->SetInterpolationModeToLinear();
vtkLookupTable* myLookupTable = vtkLookupTable::New();
myLookupTable->SetRange(0, 2000);
myLookupTable->SetValueRange(0.0, 1.0);
myLookupTable->SetSaturationRange(0.0, 0.0);
myLookupTable->SetRampToLinear();
myLookupTable->Build();
vtkImageMapToColors* myMapToColors = this->ColorMap = vtkImageMapToColors::New();
myMapToColors->SetLookupTable(myLookupTable);
myMapToColors->SetInputConnection(myImageReslice->GetOutputPort());
myMapToColors->Update();
vtkImageActor* myImageActor = vtkImageActor::New();
myImageActor->SetInputData(myMapToColors->GetOutput());
this->ren = vtkRenderer::New();
this->ren->AddActor(myImageActor);
this->renwin->AddRenderer(this->ren);
vtkSmartPointer<vtkInteractorStyleImage> imagestyle = vtkSmartPointer<vtkInteractorStyleImage>::New();
this->iren->SetInteractorStyle(imagestyle);
iren->SetRenderWindow(this->renwin);
iren->Initialize();
vtkSmartPointer<vtkImageInteractionCallback> callback = vtkSmartPointer<vtkImageInteractionCallback>::New();
callback->SetImageReslice(ImageReslice);
callback->SetInteractor(this->iren);
callback->SetImageMapToColors(ColorMap);
callback->SetImageActor(myImageActor);
imagestyle->AddObserver(vtkCommand::MouseMoveEvent, callback);
imagestyle->AddObserver(vtkCommand::LeftButtonPressEvent, callback);
imagestyle->AddObserver(vtkCommand::LeftButtonReleaseEvent, callback);
this->iren->Start();
}
vtkImageData* Getimagedata() { return reader->GetOutput(); }
vtkSmartPointer<vtkMetaImageReader> reader;
int extent[6];
double spacing[3];
double origin[3];
double center[3];
vtkRenderer* ren;
vtkRenderWindow* renwin;
vtkRenderWindowInteractor* iren;
vtkImageReslice* ImageReslice;
vtkImageMapToColors* ColorMap;
};
复现结果是,左图为原始图像,右图为鼠标移动改变取图中心点后的图像。
可以发现图像向左下方向移动了,和鼠标的移动方向是一致的;
问题定位过程
1.拿到问题代码后,确实可以复现,其实问题百分百是可以解决的,最怕的就是不能复现和偶发的现象;
2.之前也说过我一直认为图像是不变的,那么要么是我的认知是错误的,要么是图像是对的渲染后在窗口中的展示结果是错误的;
3.验证从vtkImageReslice中获取的图像是否正确。这里犯了一个错误,我用vtkImageViewer2来展示我从vtkImageReslice中获取的图像,发现图像确实有偏移;难道是我理解的真有问题…… ,记录了当前右图对应的坐标,在代码中我初始时对vtkImageReslice设置这个坐标,发现图像居中,没有偏移,这个和刚才那个vtkImageViewer2显示的结果相悖;这个时候大概感觉到是vtkImageViewer2和vtkImageActor的坑了,因为图像是从vtkImageData放到vtkImageActor中然后在窗口渲染显示;
4.步骤3验证了图像没有问题,那么只能是渲染的问题,从vtkImageActor提供的接口来看,vtkImageActor内确实有自己的坐标,使用vtkImageActor的GetCenter方法获取坐标位置;
cout << "++++++++++++++++++x:" << myActor->GetCenter()[0] << endl;
cout << "++++++++++++++++++y:" << myActor->GetCenter()[1] << endl;
随着鼠标的移动,Center坐标确实发生了改变;
我使用SetOrigin和SetPosition妄想将vtkImageActor的坐标设置为(0,0),发现没有效果,图像照旧向鼠标移动方向平移;
double zero_center[3] = { 0 };
myActor->SetOrigin(zero_center);
myActor->SetPosition(zero_center);
从VTK的源码中,可以得知vtkImageActor的Center是由Bounds计算的:
double *vtkProp3D::GetCenter()
{
this->GetBounds();
this->Center[0] = (this->Bounds[1] + this->Bounds[0])/2.0;
this->Center[1] = (this->Bounds[3] + this->Bounds[2])/2.0;
this->Center[2] = (this->Bounds[5] + this->Bounds[4])/2.0;
return this->Center;
}
打印vtkImageActor的Bounds,从上图上看Bounds是在改变的;
double bounds[6] = { 0 };
myActor->GetBounds(bounds);
结论
可以定位是vtkImageActor和vtkImageViewer2的问题,群友给出的解决办法是使用vtkRenderer的ResetCamera方法,根据图像的坐标,重新计算相机的坐标。不过实现项目中很少会遇到这种问题,因为项目中一般不会使用交互器,也很少使用vtkImageActor的(这里仅限于图像切面重建,不包括体渲染)。
注:多看VTK的源码,还是极其必要的。