13. VTK采集点法向量标记、平面切割

今天依旧是在摸索医学图像可视化的一天呢。这个笔记主要介绍了VTK上做法向量标记以及做切割平面的方法。

1. 将多边形数据的采集点法向量标记成锥形符号

在读取和使用.stl文件过程中,我们经常要用到法向量。这个例子展示了我们应该如何计算多边形数据的法向量并用vtkGlyph3D绘制圆锥型状将其标记出来。

  1. 读取stl文件构建多边形数据集
  2. 计算多边形数据的点法向量
  3. 使用vtkMaskPoints类采样部分数据,保留输入数据中的点数据及其属性
  4. 构建vtkGlyph3D对象,绘制形状设置为圆锥,数据源为采样点数据
  5. 绘制法向量数据和符号数据
cpp 复制代码
#include "vtkSmartPointer.h"
#include "vtkPolyDataReader.h"
#include "vtkPolyDataNormals.h"
#include "vtkPolyDataMapper.h"
#include "vtkMaskPoints.h"
#include "vtkConeSource.h"
#include "vtkGlyph3D.h"
#include "vtkActor.h"
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkSTLReader.h"
#include "vtkProperty.h"

#include "vtkAutoInit.h"
VTK_MODULE_INIT(vtkRenderingOpenGL2);
VTK_MODULE_INIT(vtkInteractionStyle);

int main()
{
	//读取模型文件
	vtkSmartPointer<vtkSTLReader> obj = vtkSmartPointer<vtkSTLReader>::New();
	obj->SetFileName("D:\\ct\\20201102113826651_3d\\pelvis.stl");

	// 计算模型每个面的法矢量,创建一个法向量对象
	vtkSmartPointer<vtkPolyDataNormals> normals = vtkSmartPointer<vtkPolyDataNormals>::New();
	//计算单元法向量时,要保持单元法向量一致才能得到合理的法向量。SetConsistency()可以自动调整单元点的顺序;SetAutoOrientNormals()可以自动调整法向量方向。
	//类vtkPolyDataNormals自动开启对锐边缘处理,如果检测到锐边缘,会将其分裂,使图形更加平滑,可通过SetSplitting()函数关闭该功能。
	//三维平面的法向量是指垂直该平面的向量。某点的法向量为垂直该点切平面的法向量。

	// 将多边形数据集作为法向量对象的数据输入
	normals->SetInputConnection(obj->GetOutputPort());
	// 指定锐边角度为30度
	normals->SetFeatureAngle(30);
	// 关闭单元法向量计算(单元法向量可以通过组成每个单元的任意两条边的叉乘向量归并化来表示)
	normals->SetComputeCellNormals(0);
	// 开启点法向量计算(点的法向量则是由使用该点的单元单元法向量的平均值表示)
	normals->SetComputePointNormals(1);
	// 开启正常方向的全局翻转
	normals->SetFlipNormals(1);
	// 如果检测到锐边缘,会将其分裂,使图形更加平滑
	normals->SetSplitting(1);
	vtkSmartPointer<vtkPolyDataMapper> objMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
	objMapper->SetInputConnection(normals->GetOutputPort());
	//由于读入的模型数据比较大,点比较多,因此使用vtkMaskPoints类采样部分数据,该类保留输入数据中的点数据及其属性,并支持点数据的采样
	vtkSmartPointer<vtkMaskPoints> ptMask = vtkSmartPointer<vtkMaskPoints>::New();
	ptMask->SetInputConnection(normals->GetOutputPort());

	//创建符号------圆锥形
	vtkSmartPointer<vtkConeSource> cone = vtkSmartPointer<vtkConeSource>::New();
	cone->SetAngle(26.5651);
	cone->SetHeight(1);
	cone->SetRadius(0.5);
	cone->SetResolution(6);
	cone->SetCapping(1);
	vtkSmartPointer<vtkGlyph3D> glyph = vtkSmartPointer<vtkGlyph3D>::New();
	//设置被标识的点
	glyph->SetInputConnection(normals->GetOutputPort());
	//设置符号
	glyph->SetSourceConnection(cone->GetOutputPort());

	vtkSmartPointer<vtkPolyDataMapper> glyphMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
	glyphMapper->SetInputConnection(glyph->GetOutputPort());

	vtkSmartPointer<vtkActor> glyphActor = vtkSmartPointer<vtkActor>::New();
	glyphActor->SetMapper(glyphMapper);
	glyphActor->GetProperty()->SetColor(1, 0, 0);

	vtkSmartPointer<vtkActor> normalActor = vtkSmartPointer<vtkActor>::New();
	normalActor->SetMapper(objMapper);

	// Setup render window, renderer, and interactor
	vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
	renderer->AddActor(normalActor);
	renderer->AddActor(glyphActor);

	vtkSmartPointer<vtkRenderWindow> renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
	renderWindow->AddRenderer(renderer);
	vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor = vtkSmartPointer<vtkRenderWindowInteractor>::New();
	renderWindowInteractor->SetRenderWindow(renderWindow);

	renderWindow->Render();
	renderWindowInteractor->Start();


	return 0;
}
运行结果:

备注:

这里要特别注意.stl文件的读取问题。我一开始的写法是:

cpp 复制代码
	obj->SetFileName("D:\ct\20201102113826651_3d\femur.stl");

就会出现以下问题:

需要将代码里的地址改为双斜杠,即可正常读取文件。

cpp 复制代码
obj->SetFileName("D:\\ct\\20201102113826651_3d\\pelvis.stl");

2. VTK构建切割面

在VTK中,如果我们需要构建一个切割面,需要做的步骤有:

  1. 指定切割面中心SetOrigin() 及切割面的法向量SetNormal()。

  2. 设定切割面的个数及位置SetValue()GenerateValue()。

  3. 设置切割过滤器对象vtkCutter , 指定被切割的数据集,及切割面隐函数vtkPlane。

  4. 计算数据集在指定点的位置的属性vtkProbeFilter,指定切割对象的数据集及被切割的数据集。

  5. 绘制vtkProbeFilter对象计算后的数据集(绘制的是切割对象的轮廓)。

cpp 复制代码
#include "vtkPlane.h"
#include "vtkSTLReader.h"
#include "vtkPlane.h"
#include "vtkPolyData.h"
#include "vtkProbeFilter.h"
#include "vtkDataSetMapper.h"
#include "vtkPointData.h"
#include "vtkSmartPointer.h"
#include "vtkCutter.h"
#include "vtkActor.h"
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"

#include "vtkAutoInit.h"
VTK_MODULE_INIT(vtkRenderingOpenGL2);
VTK_MODULE_INIT(vtkInteractionStyle);

int main()
{
	//读取模型文件,被切割的数据
	vtkSmartPointer<vtkSTLReader> obj = vtkSmartPointer<vtkSTLReader>::New();
	obj->SetFileName("D:\\ct\\20201102113826651_3d\\femur.stl");
	obj->Update();

	//创建切割平面
	vtkSmartPointer<vtkPlane> plane = vtkSmartPointer<vtkPlane>::New();
	//设定剪切平面在被剪切的数据中心位置
	vtkSmartPointer<vtkPolyData> polyData = obj->GetOutput();
	plane->SetOrigin(polyData->GetCenter());

	plane->SetNormal(-0.287, 0, 0.9579);

	// Print the plane parameters
	std::cout << "Plane Origin: " << plane->GetOrigin() << std::endl;
	std::cout << "Plane Normal: " << plane->GetNormal() << std::endl;

	//过滤器类,利用隐函数对数据进行剪切
	vtkSmartPointer<vtkCutter> planeCut = vtkSmartPointer<vtkCutter>::New();
	// 设置被裁剪的数据集
	planeCut->SetInputConnection(obj->GetOutputPort());
	//设定隐函数
	planeCut->SetCutFunction(plane);
	planeCut->SetValue(0, 50);
	planeCut->GenerateValues(10, 0, 500);
	//计算数据集在指定点的位置的属性
	vtkSmartPointer<vtkProbeFilter> probe = vtkSmartPointer<vtkProbeFilter>::New();
	//计算平面在数据集通过点的属性
	probe->SetInputConnection(planeCut->GetOutputPort());
	//被计算的数据集
	probe->SetSourceConnection(obj->GetOutputPort());
	vtkSmartPointer<vtkDataSetMapper> cutMapper = vtkSmartPointer<vtkDataSetMapper>::New();
	cutMapper->SetInputConnection(probe->GetOutputPort());
	//得到数据点集属性值的范围
	vtkSmartPointer<vtkPointData> pData = vtkSmartPointer<vtkPointData>::New();
	pData = polyData->GetPointData();

	//设定属性值范围
	//cutMapper->SetScalarRange(pData->GetScalars()->GetRange());
	cutMapper->ScalarVisibilityOn();

	vtkSmartPointer<vtkActor> cutActor = vtkSmartPointer<vtkActor>::New();
	cutActor->SetMapper(cutMapper);

	// 可视化
	vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
	renderer->SetBackground(.0, .0, .0);
	vtkSmartPointer<vtkRenderWindow> renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
	renderWindow->AddRenderer(renderer);
	vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor = vtkSmartPointer<vtkRenderWindowInteractor>::New();
	renderWindowInteractor->SetRenderWindow(renderWindow);

	renderer->AddActor(cutActor);

	renderWindow->Render();
	renderWindowInteractor->Start();

	return 0;
}
运行结果:

如果想修改切面数量或者其他属性,修改这几行的参数即可:

cpp 复制代码
	planeCut->SetCutFunction(plane);
	planeCut->SetValue(0, 50);
	planeCut->GenerateValues(50, 0, 500);

运行后,

备注:

一开始我运行代码的时候,出来的总是一片黑乎乎的,就很头疼。改了很久,发现是管道没有及时更新。这里要特别注意的是: 在VTK的某些版本中,可能需要手动更新管道。在渲染之前,我们可能需要调用**Update方法**来确保所有的过滤器和读取器都处理了数据。

果然我就加上了这一行,就可以正常出来结果了:

cpp 复制代码
	vtkSmartPointer<vtkSTLReader> obj = vtkSmartPointer<vtkSTLReader>::New();
	obj->SetFileName("D:\\ct\\20201102113826651_3d\\femur.stl");
	obj->Update();// 更新!!!确保STL文件被读取
相关推荐
IT技术分享社区1 分钟前
C#实战:使用腾讯云识别服务轻松提取火车票信息
开发语言·c#·云计算·腾讯云·共识算法
极客代码4 分钟前
【Python TensorFlow】入门到精通
开发语言·人工智能·python·深度学习·tensorflow
疯一样的码农10 分钟前
Python 正则表达式(RegEx)
开发语言·python·正则表达式
As977_14 分钟前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
ajsbxi17 分钟前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet
Rattenking18 分钟前
React 源码学习01 ---- React.Children.map 的实现与应用
javascript·学习·react.js
&岁月不待人&32 分钟前
Kotlin by lazy和lateinit的使用及区别
android·开发语言·kotlin
StayInLove36 分钟前
G1垃圾回收器日志详解
java·开发语言
dsywws40 分钟前
Linux学习笔记之时间日期和查找和解压缩指令
linux·笔记·学习
道法自然040241 分钟前
Ethernet 系列(8)-- 基础学习::ARP
网络·学习·智能路由器