除了MPR之外,在CT重建后处理中还有很多别的常用方法,包括
- 多层面重建(MPR)
- 最大密度投影(MIP)
- 最小密度投影(MinIP)
- 表面阴影遮盖(SSD)
- 容积漫游技术(VRT)
- 曲面重建(CPR)
- 虚拟内镜技术(VE)
后面有时间我会慢慢补;
MPR,全称是multi-planar reformation / reconstruction,是常用的医学图像后处理技术
1、多平面重建(MPR)是将扫描范围内所有的轴位图像叠加起来再对某些标线标定的重组线所指定的组织进行冠状、矢状位、任意角度斜位图像重组。
优点:
(1)能任意产生新的断层图像,而无需重复扫描。
(2)原图像的密度值被忠实保持到了结果图像上。
(3)曲面重组能在一幅图像里展开显示弯曲物体的全长。
缺点:
(1)难以表达复杂的空间结构
(2)曲面重组易造成假阳性。
vtkImageReslice
vtkImageReslice类可以从体数据内的一点沿着不同的方向切出一个平面图像;
vtkImageReslice是图像几何过滤器的瑞士军刀:它可以以合理高效的任意组合排列、旋转、翻转、缩放、重采样、变形和填充图像数据。置换、重采样和填充等简单操作的效率与专用vtkImagePermute、vtkImageResample和vtkImagePad过滤器类似。 vtkimanageslice非常适合执行以下任务:
1) 对图像进行简单的旋转、缩放和平移。通常,最好先使用vtkImageChangeInformation使图像居中,这样缩放和旋转就发生在图像的中心而不是左下角。
2) 通过SetInformationInput()方法对一个数据集进行重采样,以匹配第二个数据集的体素采样,例如为了比较两个图像或合并两个图像。如果两幅图像不在同一坐标空间中,可以通过SetResliceTransform()方法同时应用线性或非线性变换。
3) 从图像体中提取切片。使用vtkImageReslice可以从体数据中获取到指定的正交、斜切方向的上的切面图像;最方便的方法是使用SetResliceAxesDirectionCosines()指定切片的方向。方向余弦表示输出体积的x、y和z轴。SetOutputDimensionality(2)方法用于指定要输出切片而不是Volume的对象。SetResliceAxesOrigin()方法用于提供切片将通过的(x,y,z)点。可以同时使用ResliceAxes和ResliceTransform,以便从已应用转换的Volume中提取切片。
3)内容是比较常用的内容,可以提取平行于XY平面、YZ平面、XZ平面的切片,还可以提取斜切切片;
使用 vtkImageReslice 重建 callback 用来处理鼠标,切换图层;
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;
}
//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);
int x = currPos[0];
int y = currPos[1];
printf("x:%d===y:%d==\n", x, y);
if (event == vtkCommand::LeftButtonPressEvent)
{
this->Slicing = 1; //标志位
if (x < 300 && y < 300)
{
mode = 3; //#"CORONAL"
}
else if(x < 600 && y < 300 )
{
mode = 1; //#"AXIAL"
}
else if (x > 300 && y > 300)
{
mode = 0; //#"SAGITTAL"
}
else
mode = 4;
}
else if (event == vtkCommand::LeftButtonReleaseEvent)
{
this->Slicing = 0; //标志位
}
else if (event == vtkCommand::MouseMoveEvent)
{
if (this->Slicing)//检验鼠标左键已经按下 正在执行操作
{
vtkImageReslice *reslice = ImageReslicex;
if (0 == mode)
{
reslice = ImageReslicey;
}
else if(3 == mode)
{
reslice = ImageReslicez;
}
//else
// return;
//记下鼠标Y向变化的幅值大小
int deltaY = lastPos[1] - currPos[1];
reslice->Update();
double sliceSpacing = reslice->GetOutput()->GetSpacing()[2];
vtkMatrix4x4 *matrix = reslice->GetResliceAxes();
// move the center point that we are slicing through
double point[4];
double center[4];
point[0] = 0.0;
point[1] = 0.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]);
//mapToColors->Update();
interactor->Render();
}
else
{
vtkInteractorStyle *style = vtkInteractorStyle::SafeDownCast(
interactor->GetInteractorStyle());
if (style)
{
style->OnMouseMove();
}
}
}
}
private:
int Slicing;
int mode;
vtkRenderWindowInteractor *Interactor;
vtkImageMapToColors *mapToColors;
public:
vtkImageReslice *ImageReslicex;
vtkImageReslice *ImageReslicey;
vtkImageReslice *ImageReslicez;
};
初始化三个 Actor
void initImageActor(double* Matrix,double * center, vtkSmartPointer<vtkImageCast>pImageCast,
vtkSmartPointer<vtkImageReslice> imageReslice,vtkSmartPointer<vtkImageActor> actor)
{
vtkMatrix4x4 *AxialResliceMatrix = vtkMatrix4x4::New();
AxialResliceMatrix->DeepCopy(Matrix);
AxialResliceMatrix->SetElement(0, 3, center[0]);
AxialResliceMatrix->SetElement(1, 3, center[1]);
AxialResliceMatrix->SetElement(2, 3, center[2]);
//设置体数据来源
imageReslice->SetInputConnection(pImageCast->GetOutputPort());
//设置输出是一个切片,而不是一个卷
imageReslice->SetOutputDimensionality(2);
//pImageResliceX->SetResliceAxesDirectionCosines(sagittalX, sagittalY, sagittalZ);
//设置vtkImageReslice的切面坐标系矩阵
imageReslice->SetResliceAxes(AxialResliceMatrix);
//pImageResliceX->SetResliceAxesOrigin(center);
//设置切面算法的插值方式为线性插值
imageReslice->SetInterpolationModeToLinear();
imageReslice->Update();
//pImageMapToColorsX->SetLookupTable(pWindowLevelLookupTable);
//pImageMapToColorsX->SetInputConnection(pImageResliceX->GetOutputPort());
actor->SetInputData(imageReslice->GetOutput());
actor->SetPosition(0, 0, 0);
actor->Update();
}
主函数:
先通过vtkMetaImageReader读取一副三维图像,获取图像范围、原点和像素间隔,由这三个参数可以计算图像的中心位置。
接下来定义了切面的变换矩阵axialElements,该矩阵的前三列分别表示X、Y和Z方向矢量,第四列为切面坐标系原点。通过修改切面坐标系原点,可以得到不同位置的切面图像。
然后将读取的图像作为vtkImageReslice的输入,通过函数SetResliceAxes()设置变换矩阵resliceAxis
int main()
{
vtkSmartPointer<vtkImageReslice> pImageResliceX = vtkSmartPointer<vtkImageReslice>::New();
vtkSmartPointer<vtkImageReslice> pImageResliceY = vtkSmartPointer<vtkImageReslice>::New();
vtkSmartPointer<vtkImageReslice> pImageResliceZ = vtkSmartPointer<vtkImageReslice>::New();
vtkSmartPointer<vtkXMLImageDataReader> pXMLImageDataReader = vtkSmartPointer<vtkXMLImageDataReader>::New();
vtkSmartPointer<vtkImageCast> pImageCast = vtkSmartPointer<vtkImageCast>::New();
vtkSmartPointer<vtkImageActor> pImageActorX = vtkSmartPointer<vtkImageActor>::New();
vtkSmartPointer<vtkImageActor> pImageActorY = vtkSmartPointer<vtkImageActor>::New();
vtkSmartPointer<vtkImageActor> pImageActorZ = vtkSmartPointer<vtkImageActor>::New();
vtkSmartPointer<vtkRenderer> pRendererX = vtkSmartPointer<vtkRenderer>::New();
vtkSmartPointer<vtkRenderer> pRendererY = vtkSmartPointer<vtkRenderer>::New();
vtkSmartPointer<vtkRenderer> pRendererZ = vtkSmartPointer<vtkRenderer>::New();
vtkSmartPointer<vtkRenderer> pRenderer = vtkSmartPointer<vtkRenderer>::New();
vtkSmartPointer<vtkRenderWindow> pRenderWindow = vtkSmartPointer<vtkRenderWindow>::New();
//vtkSmartPointer<vtkMetaImageReader> reader =
// vtkSmartPointer<vtkMetaImageReader>::New();
//reader->SetFileName("D:/datasource/brain.mhd");
//reader->Update();
vtkSmartPointer<vtkDICOMImageReader> reader = vtkSmartPointer<vtkDICOMImageReader>::New();
reader->SetDirectoryName("D:/datasource/fei/ScalarVolume_13");
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]);
//轴状面
double Axial[16] = {
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1 };
//冠状面
double Coronal[16] = {
1, 0, 0, 0,
0, 0, -1, 0,
0, 1, 0, 0,
0, 0, 0, 1 };
//矢状面
double Sagittal[16] = {
0, 0, 1, 0,
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 0, 1 };
pImageCast->SetInputConnection(reader->GetOutputPort());
pImageCast->SetOutputScalarTypeToChar();
pImageCast->ClampOverflowOn();
pImageCast->Update();
//pImageCast->SetUpdateExtentToWholeExtent();
//x
initImageActor(Axial, center, pImageCast, pImageResliceX, pImageActorX);
//y
initImageActor(Coronal, center, pImageCast, pImageResliceY, pImageActorY);
//z
initImageActor(Sagittal, center, pImageCast, pImageResliceZ, pImageActorZ);
float fOpac = 0.5;
pRendererX->AddActor(pImageActorX);
pRendererY->AddActor(pImageActorY);
pRendererZ->AddActor(pImageActorZ);
//pRenderer->SetBackground(1, 1, 1);
pRendererX->SetBackground(0, 0, 0);
pRendererY->SetBackground(0, 0, 0);
pRendererZ->SetBackground(0, 0, 0);
/*
# renderer 0: BOTTOM LEFT
# renderer 1: BOTTOM RIGHT
# renderer 2: TOP LEFT
# renderer 3: TOP RIGHT
*/
double ltView[4] = { 0, 0, 0.5, 0.5 };
double rtView[4] = { 0.5, 0, 1, 0.5 };
double lbView[4] = { 0, 0.5, 0.5, 1 };
double rbView[4] = { 0.5, 0.5, 1, 1 };
//pRenderer->SetViewport(0, 0, 0.6, 1);
//pRendererX->SetViewport(0.6, 0.66, 1, 1);
//pRendererY->SetViewport(0.6, 0.33, 1, 0.66);
//pRendererZ->SetViewport(0.6, 0, 1, 0.33);
pRenderer->SetViewport(rtView);
pRendererX->SetViewport(lbView);
pRendererY->SetViewport(rbView);
pRendererZ->SetViewport(ltView);
pRenderWindow->AddRenderer(pRendererX);
pRenderWindow->AddRenderer(pRendererY);
pRenderWindow->AddRenderer(pRendererZ);
pRenderWindow->AddRenderer(pRenderer);
vtkSmartPointer<vtkRenderWindowInteractor> pRenderWindowInteractor =
vtkSmartPointer<vtkRenderWindowInteractor>::New();
pRenderWindow->SetSize(600, 600);
// add observer;
vtkSmartPointer<vtkInteractorStyleImage> imagestyle =
vtkSmartPointer<vtkInteractorStyleImage>::New();
pRenderWindowInteractor->SetInteractorStyle(imagestyle);
pRenderWindowInteractor->SetRenderWindow(pRenderWindow);
pRenderWindowInteractor->Initialize();
vtkSmartPointer<vtkImageInteractionCallback> callback =
vtkSmartPointer<vtkImageInteractionCallback>::New();
//callback->SetImageReslice(reslice);
callback->ImageReslicex = pImageResliceX;
callback->ImageReslicey = pImageResliceY;
callback->ImageReslicez = pImageResliceZ;
callback->SetInteractor(pRenderWindowInteractor);
//callback->SetImageMapToColors(colorMap);
imagestyle->AddObserver(vtkCommand::MouseMoveEvent, callback);
imagestyle->AddObserver(vtkCommand::LeftButtonPressEvent, callback);
imagestyle->AddObserver(vtkCommand::LeftButtonReleaseEvent, callback);
pRenderWindow->Render();
pRenderWindowInteractor->Initialize();
pRenderWindowInteractor->Start();
return 0;
}
参考资料
1.vtkImageReslice Class Reference