MPR(Multi-planar Reformation),多平面重建;多平面重建是将扫描范围内所有的轴位图像叠加起来,再对某些标线标定的重组线所指定的组织进行冠状位、矢状位、任意角度斜位图像重组。
1.能任意产生新的断层图像,而无需重复扫描;
2.原图像的密度值被忠实保持到了结果图像上。
3.曲面重组能在一副图像里展开显示弯曲物体的全长。
1.难以表达复杂的空间结构;
2.曲面重组易造成假阳性;
多平面重建(MPR)是CT三维数据呈现的重要内容,其在三维数据的任一点空间位置,采用XY、XZ和YZ三个平面切空间数据得到三个切面分别为轴状面、冠状面和矢状面,而且X/Y/Z三个轴可以绕三维坐标原点任意旋转。
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平面的切片,还可以提取斜切切片;
设置切片的步骤是先计算出一个切片的中心位置,计算出切面的变换矩阵(4*4矩阵),前三列分别表示X、Y和Z方向矢量,第四列为切面坐标系原点。通过修改切面坐标系原点,可以得到不同位置的切面图像;切面的原点必须过图像内部一点;
变换矩阵是4*4的四阶矩阵,其中左上角的3*3矩阵是3个方向上的向量余弦值;一般情况下,Z轴方向上的方向向量是XY的叉积,也就是Z轴方向会垂直于XY平面;
三个方向上的方向余弦值:
virtual void SetResliceAxes(vtkMatrix4x4*);
vtkGetObjectMacro(ResliceAxes, vtkMatrix4x4);
void SetResliceAxesDirectionCosines(double x0, double x1, double x2, double y0, double y1,
double y2, double z0, double z1, double z2);
void SetResliceAxesDirectionCosines(const double x[3], const double y[3], const double z[3]){
this->SetResliceAxesDirectionCosines(x[0], x[1], x[2], y[0], y[1], y[2], z[0], z[1], z[2]);
}
void SetResliceAxesDirectionCosines(const double xyz[9]){
this->SetResliceAxesDirectionCosines(xyz[0], xyz[1], xyz[2], xyz[3], xyz[4], xyz[5], xyz[6], xyz[7], xyz[8]);
}
void GetResliceAxesDirectionCosines(double x[3], double y[3], double z[3]);
void GetResliceAxesDirectionCosines(double xyz[9]){
this->GetResliceAxesDirectionCosines(&xyz[0], &xyz[3], &xyz[6]);
}
double* GetResliceAxesDirectionCosines() VTK_SIZEHINT(9){
this->GetResliceAxesDirectionCosines(this->ResliceAxesDirectionCosines);
return this->ResliceAxesDirectionCosines;
}
当OutputDimensionality
设置为3时,可以通过SetResliceAxes
或者SetResliceTransform
生成转换矩阵,将体数据进行缩放,扭曲,平移等变换;
SetResliceAxesOrigin可以指定重新切片轴的原点,是变换矩阵最后一列的前三个元素。如果vtkImageReslice有变换矩阵就修改当前的变换矩阵内的值,如果没有,就创建新的变换矩阵;
void SetResliceAxesOrigin(double x, double y, double z);
void SetResliceAxesOrigin(const double xyz[3]){
this->SetResliceAxesOrigin(xyz[0], xyz[1], xyz[2]);
}
void GetResliceAxesOrigin(double xyz[3]);
double* GetResliceAxesOrigin() VTK_SIZEHINT(3){
this->GetResliceAxesOrigin(this->ResliceAxesOrigin);
return this->ResliceAxesOrigin;
}
设定由vtkTransform表示变换矩阵信息:
virtual void SetResliceTransform(vtkAbstractTransform*);
vtkGetObjectMacro(ResliceTransform, vtkAbstractTransform);
OutputDimensionality表示输出的维度有四种:1、2、3或0,默认值是3。不过一般切片的话,就是二维图像,设置为2;
如果维度是2D,则输出的Z范围被强制为(0,0),输出的Z原点被强制为0.0(即输出范围被限制在xy平面)。如果维度是1D,则输出范围限制在x轴上。对于0D,输出范围由位于(0,0,0)的单个体素组成。
vtkSetMacro(OutputDimensionality, int);
vtkGetMacro(OutputDimensionality, int);
这里vtkImageReslice类输入的一般就是vtkImageData,vtkImageData为三维体数据,毕竟是从体数据中获取切片图像的嘛。
使用SetInputConnection和SetInputData方法,SetInformationInput用来设置渲染管线中的信息,之前有讲过;
virtual void SetInformationInput(vtkImageData*);
vtkGetObjectMacro(InformationInput, vtkImageData);
TransformInputSampling用来指定是否根据重切片轴的方向余弦和原点变换输入的间距、原点和范围,然后再将其应用为默认输出间距、原点和范围(默认值:true);
vtkSetMacro(TransformInputSampling, vtkTypeBool);
vtkBooleanMacro(TransformInputSampling, vtkTypeBool);
vtkGetMacro(TransformInputSampling, vtkTypeBool);
AutoCropOutput项可以确保输出的范围足够大时,不会裁剪任何数据;默认值是禁用的。
vtkSetMacro(AutoCropOutput, vtkTypeBool);
vtkBooleanMacro(AutoCropOutput, vtkTypeBool);
vtkGetMacro(AutoCropOutput, vtkTypeBool);
Wrap包裹标识;
vtkSetMacro(Wrap, vtkTypeBool);
vtkGetMacro(Wrap, vtkTypeBool);
vtkBooleanMacro(Wrap, vtkTypeBool);
Mirror镜像标识;
vtkSetMacro(Mirror, vtkTypeBool);
vtkGetMacro(Mirror, vtkTypeBool);
vtkBooleanMacro(Mirror, vtkTypeBool);
用Border设置是否将外观输入边界延伸半个体素(默认值:On)。
这将更改在输入图像的边界处处理插值的方式:如果输出体素的中心超出输入图像的边缘,但在边缘的半体素宽度范围内(使用输入体素宽度),则计算输出体素的值,如同输入的边缘体素被复制到输入图像的边缘。如果“Mirror”或“Wrap”处于启用状态,则此操作无效。
vtkSetMacro(Border, vtkTypeBool);
vtkGetMacro(Border, vtkTypeBool);
vtkBooleanMacro(Border, vtkTypeBool);
当Border为On开启状态时,用BorderThickness设置图像的边框厚度(默认值:0.5);
vtkSetMacro(BorderThickness, double);
vtkGetMacro(BorderThickness, double);
InterpolationMode是vtkImageReslice类使用的插值模式,默认为nearest neighbor插值;
SetInterpolator设置要使用的插值器vtkAbstractImageInterpolator。默认插值器支持最近(Nearest)、线性(Linear)和三次插值模式(Cubic);
vtkSetClampMacro(InterpolationMode, int, VTK_RESLICE_NEAREST, VTK_RESLICE_CUBIC);
vtkGetMacro(InterpolationMode, int);
void SetInterpolationModeToNearestNeighbor() { this->SetInterpolationMode(VTK_RESLICE_NEAREST); }
void SetInterpolationModeToLinear() { this->SetInterpolationMode(VTK_RESLICE_LINEAR); }
void SetInterpolationModeToCubic() { this->SetInterpolationMode(VTK_RESLICE_CUBIC); }
virtual const char* GetInterpolationModeAsString();
virtual void SetInterpolator(vtkAbstractImageInterpolator* sampler);
virtual vtkAbstractImageInterpolator* GetInterpolator();
void SetInterpolate(int t) {
if (t && !this->GetInterpolate()) {
this->SetInterpolationModeToLinear();
}
else if (!t && this->GetInterpolate()){
this->SetInterpolationModeToNearestNeighbor();
}
}
void InterpolateOn() { this->SetInterpolate(1); }
void InterpolateOff() { this->SetInterpolate(0); }
int GetInterpolate() { return (this->GetInterpolationMode() != VTK_RESLICE_NEAREST); }
SlabMode是板模式,用来生成厚切片(类似MIP功能),默认值是Mean。如果调用SetSlabNumberOfSlices(N)时N大于1,则每个输出片实际上是N个片的组合。使用此方法需要指定要使用的合成模式。合成模式有四种:Min(最小)、Max(最大)、Mean(平均值)和Sum(累加和);
vtkSetClampMacro(SlabMode, int, VTK_IMAGE_SLAB_MIN, VTK_IMAGE_SLAB_SUM);
vtkGetMacro(SlabMode, int);
void SetSlabModeToMin() { this->SetSlabMode(VTK_IMAGE_SLAB_MIN); }
void SetSlabModeToMax() { this->SetSlabMode(VTK_IMAGE_SLAB_MAX); }
void SetSlabModeToMean() { this->SetSlabMode(VTK_IMAGE_SLAB_MEAN); }
void SetSlabModeToSum() { this->SetSlabMode(VTK_IMAGE_SLAB_SUM); }
virtual const char* GetSlabModeAsString();
SlabNumberOfSlices用来设置Slab模式下,合并时用到的图层个数;
vtkSetMacro(SlabNumberOfSlices, int);
vtkGetMacro(SlabNumberOfSlices, int);
SlabTrapezoidIntegration设定使用梯形积分进行板计算。Slab模式合并时,做加法和平均数的时候,把第一片和最后一片的权重减半。默认情况下,它处于禁用状态。
vtkSetMacro(SlabTrapezoidIntegration, vtkTypeBool);
vtkBooleanMacro(SlabTrapezoidIntegration, vtkTypeBool);
vtkGetMacro(SlabTrapezoidIntegration, vtkTypeBool);
SlabSliceSpacingFraction是板间距,是输出片间距的一部分。
当选择了各种slab模式中的一种时,通过生成几个“临时”输出片,然后根据slab模式组合它们来生成每个输出片。默认情况下,这些临时切片之间的间距是OutputSpacing的Z分量。此方法将这些临时切片之间的间距设置为输出间距的一小部分。
vtkSetMacro(SlabSliceSpacingFraction, double);
vtkGetMacro(SlabSliceSpacingFraction, double);
优化标识Optimization,打开和关闭优化(默认为打开,仅应出于测试目的关闭它们);
vtkSetMacro(Optimization, vtkTypeBool);
vtkGetMacro(Optimization, vtkTypeBool);
vtkBooleanMacro(Optimization, vtkTypeBool);
设置输出标量数据类型,有 VTK_CHAR, VTK_SIGNED_CHAR, VTK_UNSIGNED_CHAR, VTK_SHORT, VTK_UNSIGNED_SHORT, VTK_INT, VTK_UNSIGNED_INT, VTK_FLOAT, or VTK_DOUBLE,除此之外的类型均不支持,如果设定了输出类型,那么标量数值就被限定在类型的有效范围内。
vtkSetMacro(OutputScalarType, int);
vtkGetMacro(OutputScalarType, int);
SetBackgroundColor对多分量图像设置背景颜色,这里背景颜色是不是数值的意思,暂时还没弄明白;
SetBackgroundLevel对单通道的灰度图像设置设置背景灰度值;
SetStencilData使用模具将计算限制到输出的特定区域。输出模具的“outside”的部分将被清除为背景色(莫非是刚才的背景色)。
SetGenerateStencilOutput设置是否生成一个输出模具,该模具定义哪些像素被插值,哪些像素超出输入范围。
vtkSetVector4Macro(BackgroundColor, double);
vtkGetVector4Macro(BackgroundColor, double);
void SetBackgroundLevel(double v) { this->SetBackgroundColor(v, v, v, v); }
double GetBackgroundLevel() { return this->GetBackgroundColor()[0]; }
void SetStencilData(vtkImageStencilData* stencil);
vtkImageStencilData* GetStencil();
vtkSetMacro(GenerateStencilOutput, vtkTypeBool);
vtkGetMacro(GenerateStencilOutput, vtkTypeBool);
vtkBooleanMacro(GenerateStencilOutput, vtkTypeBool);
vtkAlgorithmOutput* GetStencilOutputPort() { return this->GetOutputPort(1); }
vtkImageStencilData* GetStencilOutput();
void SetStencilOutput(vtkImageStencilData* stencil);
1.使用vtkMetaImageReader类读取一个.mha文件;
mha文件一定要和.raw文件放在同一个文件夹,要不然会报错;
ERROR: In I:\v-vtk\VTK-8.2.0\IO\Image\vtkMetaImageReader.cxx, line 237
vtkMetaImageReader (013DEC00): MetaImage cannot read data from file.
2.计算获取切面的变换矩阵vtkMatrix4x4信息;
3.使用vtkImageReslice类获取切面数据;
4.进行渲染显示;
#include "vtkSmartPointer.h"
#include "vtkMetaImageReader.h"
#include "vtkImageData.h"
#include "vtkMatrix4x4.h"
#include "vtkImageReslice.h"
#include "vtkLookupTable.h"
#include "vtkImageMapToColors.h"
#include "vtkImageActor.h"
#include "vtkImageMapper3D.h"
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkInteractorStyleImage.h"
#include "vtkAutoInit.h"
VTK_MODULE_INIT(vtkRenderingOpenGL2);
VTK_MODULE_INIT(vtkInteractionStyle);
using namespace std;
int main(){
vtkNew<vtkMetaImageReader> reader;
reader->SetFileName("D:\\data\\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
};
vtkNew<vtkMatrix4x4> resliceAxes;
resliceAxes->DeepCopy(axialElements);
resliceAxes->SetElement(0, 3, center[0]);
resliceAxes->SetElement(1, 3, center[1]);
resliceAxes->SetElement(2, 3, center[2]);
vtkNew<vtkImageReslice> reslice;
reslice->SetInputConnection(reader->GetOutputPort());
reslice->SetOutputDimensionality(2);
reslice->SetResliceAxes(resliceAxes);
reslice->SetInterpolationModeToLinear();
vtkNew<vtkLookupTable> colorTable;
colorTable->SetRange(0, 1000);
colorTable->SetValueRange(0.0, 1.0);
colorTable->SetSaturationRange(0.0, 0.0);
colorTable->SetRampToLinear();
colorTable->Build();
vtkNew<vtkImageMapToColors> colorMap;
colorMap->SetLookupTable(colorTable);
colorMap->SetInputConnection(reslice->GetOutputPort());
vtkNew<vtkImageActor> imgActor;
imgActor->GetMapper()->SetInputConnection(colorMap->GetOutputPort());
vtkNew<vtkRenderer> renderer;
renderer->AddActor(imgActor);
renderer->SetBackground(1.0, 1.0, 1.0);
vtkSmartPointer<vtkRenderWindow> renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
renderWindow->AddRenderer(renderer);
renderWindow->Render();
renderWindow->SetSize(640, 480);
renderWindow->SetWindowName("ImageResliceExample");
vtkNew<vtkRenderWindowInteractor> renderWindowInteractor;
vtkNew<vtkInteractorStyleImage> imagestyle;
renderWindowInteractor->SetInteractorStyle(imagestyle);
renderWindowInteractor->SetRenderWindow(renderWindow);
renderWindowInteractor->Initialize();
renderWindowInteractor->Start();
return 0;
}
#include "pch.h"
#include "TestDicomImage.h"
#include "vtk_include.h"
#include "dcmtk\config\osconfig.h"
#include "dcmtk\dcmdata\dctk.h"
#include /* for dcmjpeg decoders */
#include
#include //for JPEG-LS decode
#include //for JPEG-LS encode
#include
#include
using namespace std;
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 = 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){
vtkImageReslice *reslice = this->ImageReslice;
// Increment slice position by deltaY of mouse
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] = {0.0};
double center[4];
point[2] = sliceSpacing * deltaY;
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;
vtkImageReslice *ImageReslice;
vtkRenderWindowInteractor *Interactor;
vtkImageMapToColors *mapToColors;
};
void TestDicomImage::Test(){
DJDecoderRegistration::registerCodecs();
DJLSEncoderRegistration::registerCodecs(); //JPEG-LS encoder registerCodecs
DJLSDecoderRegistration::registerCodecs(); //JPEG-LS decoder registerCodecs
int count = 273;
int single_img = 512 * 512;
int size_all = count * single_img;
short* pBuf = new short[size_all] {0};
for (int i = 1; i < 274; i++) {
string fileName = "G:\\data\\" + to_string(i) + ".DCM";
DcmFileFormat *myFileFormat = new DcmFileFormat;
OFCondition cond = myFileFormat->loadFile(fileName.c_str());
DcmElement* pElement = nullptr;
myFileFormat->getDataset()->chooseRepresentation(EXS_DeflatedLittleEndianExplicit, NULL);
myFileFormat->getDataset()->findAndGetElement(DCM_PixelData, pElement);
if (pElement != NULL) {
int size = pElement->getLength() / 2;
OFCondition cond = pElement->loadAllDataIntoMemory();
if (cond.good()) {
Uint16 * ptr;
cond = pElement->getUint16Array(ptr);
if (cond.good()) {
memcpy(pBuf + (i - 1)*single_img, ptr, size * sizeof(short));
}
}
}
delete myFileFormat;
}
int extent[6] = { 0,511,0,511,0,272 };
double spacing[3] = { 0.3, 0.3, 0.75 };
double origin[3] = { 0 };
double center[3] = { 0 };
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]);
vtkNew<vtkShortArray> dataArray;
dataArray->SetArray(pBuf, size_all, 1);
vtkNew<vtkImageData> pALLImageData;
pALLImageData->AllocateScalars(VTK_SHORT, 1);
pALLImageData->SetDimensions(512, 512, 273);
pALLImageData->GetPointData()->SetScalars(dataArray);
pALLImageData->SetSpacing(spacing);
pALLImageData->SetOrigin(origin);
static double axialElements[16] = {
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
};
vtkNew<vtkMatrix4x4> resliceAxes;
resliceAxes->DeepCopy(axialElements);
resliceAxes->SetElement(0, 3, center[0]);
resliceAxes->SetElement(1, 3, center[1]);
resliceAxes->SetElement(2, 3, center[2]);
vtkNew<vtkImageReslice> reslice;
reslice->SetInputData(pALLImageData);
reslice->SetOutputDimensionality(2);
reslice->SetResliceAxes(resliceAxes);
reslice->SetInterpolationModeToLinear();
vtkNew<vtkLookupTable> colorTable;
colorTable->SetRange(0, 1000);
colorTable->SetValueRange(0.0, 1.0);
colorTable->SetSaturationRange(0.0, 0.0);
colorTable->SetRampToLinear();
colorTable->Build();
vtkNew<vtkImageMapToColors> colorMap;
colorMap->SetLookupTable(colorTable);
colorMap->SetInputConnection(reslice->GetOutputPort());
colorMap->Update();
vtkNew<vtkImageActor> imgActor;
imgActor->SetInputData(colorMap->GetOutput());
vtkNew<vtkRenderer> renderer;
renderer->AddActor(imgActor);
renderer->SetBackground(0.3, 0.4, 0.3);
vtkNew<vtkRenderWindow> renderWindow;
renderWindow->SetSize(500, 500);
renderWindow->AddRenderer(renderer);
vtkNew<vtkRenderWindowInteractor> renderWindowInteractor;
vtkNew<vtkInteractorStyleImage> imagestyle;
renderWindowInteractor->SetInteractorStyle(imagestyle);
renderWindowInteractor->SetRenderWindow(renderWindow);
renderWindowInteractor->Initialize();
vtkNew<vtkImageInteractionCallback> callback;
callback->SetImageReslice(reslice);
callback->SetInteractor(renderWindowInteractor);
callback->SetImageMapToColors(colorMap);
imagestyle->AddObserver(vtkCommand::MouseMoveEvent, callback);
imagestyle->AddObserver(vtkCommand::LeftButtonPressEvent, callback);
imagestyle->AddObserver(vtkCommand::LeftButtonReleaseEvent, callback);
renderWindowInteractor->Start();
}
1.vtkImageReslice Class Reference