在实际项目中,常遇到不同类型的数据在同一个渲染窗口显示;如网格多边形与灰度图像的显示、体渲染与多边形的显示、体渲染与灰度图像的显示,如下面几张图的效果;
如何实现这种混合显示的效果呢?在初接触VTK时,就认为是多个Actor在同一个Renderer下渲染到同一个Window下就可以了,至于使用哪一种Actor没有仔细验证;
前段时间从阿兵-AI医疗处看到一个例子VTK交互之vtkVolumePicker,是用vtkVolumePicker做体数据光线投射的拾取器;例子中展示的效果图里包含了体渲染、多边形和灰度图像,就是我想要实现的效果,写个笔记记录一下;
vtkImageActor的介绍VTK笔记-图像相关-vtkImageActor类;
使用vtkImageActor和vtkActor实现,图像和网格多边形的展示;
auto cyl = vtkSmartPointer<vtkCylinderSource>::New();
cyl->SetResolution(32);
cyl->SetHeight(1000);
cyl->SetRadius(10);
cyl->SetCenter(0, .5, 0);
cyl->Update();
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputData(cyl->GetOutput());
vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
actor->SetMapper(mapper);
actor->GetProperty()->SetAmbient(0.5);
actor->GetProperty()->SetDiffuse(0.5);
actor->GetProperty()->SetSpecular(0.1);
vtkSmartPointer<vtkBMPReader> reader = vtkSmartPointer<vtkBMPReader>::New();
reader->SetFileName("G:/Data/lena.bmp");
reader->Update();
vtkSmartPointer<vtkImageActor> imgActor = vtkSmartPointer<vtkImageActor>::New();
imgActor->GetMapper()->SetInputConnection(reader->GetOutputPort());
vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
renderer->AddActor(imgActor);
renderer->AddActor(actor);
renderer->SetBackground(0.4, 0.5, 0.6);
vtkSmartPointer<vtkRenderWindow> renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
renderWindow->SetSize(500, 500);
renderWindow->AddRenderer(renderer);
vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor =
vtkSmartPointer<vtkRenderWindowInteractor>::New();
vtkSmartPointer<vtkInteractorStyleImage> style = vtkSmartPointer<vtkInteractorStyleImage>::New();
//renderWindowInteractor->SetInteractorStyle(style);
renderWindowInteractor->SetRenderWindow(renderWindow);
renderWindowInteractor->Initialize();
renderWindowInteractor->Start();
展示效果如下,一个旗杆上飘着的蕾娜:
vtkVolumePicker的例子
具体步骤:
使用vtkImageReader2类读取一个图像序列,设置相关参数,可以获取vtkImageData对象;
使用vtkMarchingCubes从vtkImageData对象中提取面渲染,这里可参考VTK笔记-CT图像获取皮肤等值面-vtkContourFilter类的使用;
灰度图像则是直接由vtkImageActor设置图像范围,取得一个矢状位的图像;
创建了两个圆锥体,用于指示拾取点位置;
使用方法AddViewProp将前面创建的vtkImageActor、vtkActor、vtkVolume设置到render中进行渲染;
#include <vtkRenderWindow.h>
#include <vtkRenderer.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkImageReader2.h>
#include <vtkGPUVolumeRayCastMapper.h>
#include <vtkColorTransferFunction.h>
#include <vtkPiecewiseFunction.h>
#include <vtkVolumeProperty.h>
#include <vtkMarchingCubes.h>
#include <vtkPolyDataNormals.h>
#include <vtkStripper.h>
#include <vtkCellLocator.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkActor.h>
#include <vtkLookupTable.h>
#include <vtkImageMapToColors.h>
#include <vtkImageActor.h>
#include <vtkImageMapper3D.h>
#include <vtkTransform.h>
#include <vtkPlane.h>
#include <vtkCamera.h>
#include <vtkConeSource.h>
#include <vtkDataSetMapper.h>
#include <vtkVolumePicker.h>
#include <vtkCommand.h>
class vtkMyMouseCommand : public vtkCommand
{
public:
static vtkMyMouseCommand *New()
{
return new vtkMyMouseCommand;
}
//A function to point an actor along a vector
void PointCone(vtkActor *actor, double nx, double ny, double nz)
{
actor->SetOrientation(0.0, 0.0, 0.0);
double n = std::sqrt(nx*nx + ny * ny + nz * nz);
if (nx < 0.0)
{
actor->RotateWXYZ(180, 0, 1, 0);
n = -n;
}
actor->RotateWXYZ(180, (nx + n)*0.5, ny*0.5, nz*0.5);
}
virtual void Execute(vtkObject *caller, unsigned long eventId, void *callData)
{
//renWin->HideCursor();
int x, y;
iren->GetEventPosition(x, y);
picker->Pick(x, y, 0, ren);
double p[3];
picker->GetPickPosition(p);
std::cout << p[0] << " " << p[1] << " " << p[2] << std::endl;
double n[3];
picker->GetPickNormal(n);
redCone->SetPosition(p[0], p[1], p[2]);
PointCone(redCone, n[0], n[1], n[2]);
greenCone->SetPosition(p[0], p[1], p[2]);
PointCone(greenCone, -n[0], -n[1], -n[2]);
iren->Render();
}
vtkRenderWindow *renWin;
vtkRenderer *ren;
vtkRenderWindowInteractor *iren;
vtkVolumePicker *picker;
vtkActor *redCone;
vtkActor *greenCone;
};
int main(int, char*[])
{
vtkNew<vtkRenderer> ren;
vtkNew<vtkRenderWindow> renWin;
renWin->AddRenderer(ren);
vtkNew<vtkRenderWindowInteractor> iren;
iren->SetRenderWindow(renWin);
//read the volume
vtkNew<vtkImageReader2> reader;
reader->SetDataExtent(0, 63, 0, 63, 0, 92);
reader->SetFileNameSliceOffset(1);
reader->SetDataScalarTypeToUnsignedShort();
reader->SetDataByteOrderToLittleEndian();
reader->SetFilePrefix("G:/Data/headsq/quarter");
reader->SetDataSpacing(3.2, 3.2, 1.5);
//set up the volume rendering
vtkNew<vtkGPUVolumeRayCastMapper> volumeMapper;
volumeMapper->SetInputConnection(reader->GetOutputPort());
volumeMapper->CroppingOn();
double croppingRegionPlanes[6] = { 0.0, 141.6, 0.0, 201.6, 0.0, 138.0 };
volumeMapper->SetCroppingRegionPlanes(croppingRegionPlanes);
vtkNew<vtkColorTransferFunction> volumeColor;
volumeColor->AddRGBPoint(0, 0.0, 0.0, 0.0);
volumeColor->AddRGBPoint(180, 0.3, 0.1, 0.2);
volumeColor->AddRGBPoint(1000, 1.0, 0.7, 0.6);
volumeColor->AddRGBPoint(2000, 1.0, 1.0, 0.9);
vtkNew<vtkPiecewiseFunction> volumeScalarOpacity;
volumeScalarOpacity->AddPoint(0, 0.0);
volumeScalarOpacity->AddPoint(180, 0.0);
volumeScalarOpacity->AddPoint(1000, 0.2);
volumeScalarOpacity->AddPoint(2000, 0.8);
vtkNew<vtkPiecewiseFunction> volumeGradientOpacity;
volumeGradientOpacity->AddPoint(0, 0.0);
volumeGradientOpacity->AddPoint(90, 0.5);
volumeGradientOpacity->AddPoint(100, 1.0);
vtkNew<vtkVolumeProperty> volumeProperty;
volumeProperty->SetColor(volumeColor);
volumeProperty->SetScalarOpacity(volumeScalarOpacity);
volumeProperty->SetGradientOpacity(volumeGradientOpacity);
volumeProperty->SetInterpolationTypeToLinear();
volumeProperty->ShadeOff();
volumeProperty->SetAmbient(0.6);
volumeProperty->SetDiffuse(0.6);
volumeProperty->SetSpecular(0.1);
vtkNew<vtkVolume> volume;
volume->SetMapper(volumeMapper);
volume->SetProperty(volumeProperty);
// Do the surface rendering
vtkNew<vtkMarchingCubes> boneExtractor;
boneExtractor->SetInputConnection(reader->GetOutputPort());
boneExtractor->SetValue(0, 1150);
boneExtractor->Update();
vtkNew<vtkPolyDataNormals>boneNormals;
boneNormals->SetInputConnection(boneExtractor->GetOutputPort());
boneNormals->SetFeatureAngle(60.0);
vtkNew<vtkStripper> boneStripper;
boneStripper->SetInputConnection(boneNormals->GetOutputPort());
vtkNew<vtkCellLocator> boneLocator;
boneLocator->SetDataSet(boneExtractor->GetOutput());
boneLocator->LazyEvaluationOn();
vtkNew<vtkPolyDataMapper> boneMapper;
boneMapper->SetInputConnection(boneStripper->GetOutputPort());
boneMapper->ScalarVisibilityOff();
vtkNew<vtkProperty> boneProperty;
boneProperty->SetColor(1.0, 1.0, 0.9);
vtkNew<vtkActor> bone;
bone->SetMapper(boneMapper);
bone->SetProperty(boneProperty);
//Create an image actor
vtkNew<vtkLookupTable> table;
table->SetRange(0, 2000);
table->SetRampToLinear();
table->SetValueRange(0, 1);
table->SetHueRange(0, 0);
table->SetSaturationRange(0, 0);
vtkNew<vtkImageMapToColors> mapToColors;
mapToColors->SetInputConnection(reader->GetOutputPort());
mapToColors->SetLookupTable(table);
mapToColors->Update();
vtkNew<vtkImageActor> imageActor;
imageActor->GetMapper()->SetInputConnection(mapToColors->GetOutputPort());
imageActor->SetDisplayExtent(32, 32, 0, 63, 0, 92);
//make a transform and some clipping planes
vtkNew<vtkTransform> transform;
transform->RotateWXYZ(-20, 0.0, -0.7, 0.7);
volume->SetUserTransform(transform);
bone->SetUserTransform(transform);
imageActor->SetUserTransform(transform);
auto c = volume->GetCenter();
vtkNew<vtkPlane> volumeClip;
volumeClip->SetNormal(0, 1, 0);
volumeClip->SetOrigin(c[0], c[1], c[2]);
vtkNew<vtkPlane> boneClip;
boneClip->SetNormal(1, 0, 0);
boneClip->SetOrigin(c[0], c[1], c[2]);
volumeMapper->AddClippingPlane(volumeClip);
boneMapper->AddClippingPlane(boneClip);
ren->AddViewProp(volume);
ren->AddViewProp(bone);
ren->AddViewProp(imageActor);
auto camera = ren->GetActiveCamera();
camera->SetFocalPoint(c[0], c[1], c[2]);
camera->SetPosition(c[0] + 500, c[1] - 100, c[2] - 100);
camera->SetViewUp(0, 0, -1);
//the cone points along the - x axis
vtkNew<vtkConeSource> coneSource;
coneSource->CappingOn();
coneSource->SetHeight(12);
coneSource->SetRadius(5);
coneSource->SetResolution(31);
coneSource->SetCenter(6, 0, 0);
coneSource->SetDirection(-1, 0, 0);
vtkNew<vtkDataSetMapper> coneMapper;
coneMapper->SetInputConnection(coneSource->GetOutputPort());
vtkNew<vtkActor> redCone;
redCone->PickableOff();
redCone->SetMapper(coneMapper);
redCone->GetProperty()->SetColor(1, 0, 0);
vtkNew<vtkActor> greenCone;
greenCone->PickableOff();
greenCone->SetMapper(coneMapper);
greenCone->GetProperty()->SetColor(0, 1, 0);
//Add the two cones(or just one, if you want);
ren->AddViewProp(redCone);
ren->AddViewProp(greenCone);
//the picker
vtkNew<vtkVolumePicker> picker;
picker->SetTolerance(1e-6);
picker->SetVolumeOpacityIsovalue(0.1);
//locator is optional, but improves performance for large polydata
picker->AddLocator(boneLocator);
renWin->Render();
vtkNew<vtkMyMouseCommand> mouseCommand;
mouseCommand->renWin = renWin;
mouseCommand->ren = ren;
mouseCommand->iren = iren;
mouseCommand->picker = picker;
mouseCommand->redCone = redCone;
mouseCommand->greenCone = greenCone;
iren->AddObserver(vtkCommand::MouseMoveEvent, mouseCommand);
iren->Start();
return 0;
}
下面两张图是渲染效果,从两张图上也发现了问题,第一张图灰度图和体渲染和多边形渲染是有遮挡关系的;第二张图明显可以发现从灰度图可以透视到体渲染;可能是VTK中对image图像和体渲染的遮挡关系的bug,之前也发现了在一个平面上纹理映射一张图像,也存在这种透明关系的错误;