VTK 的可视化方法:颜色映射

颜色映射的操作对象是数据集中的标量属性。它是一种常用的标量算法。它会根据数据集中各个部分不同的标量值,对各个部分上不同的颜色。与此相关的另一种上色方法是控制演员的颜色属性,但这样整个图形只有单一的颜色,这显然没有颜色映射方法灵活。

颜色映射的本质就是将标量数据附加到单元或者点上,然后再绘制的时候通过点集或者单元集的数据在颜色表中查找标量值对应的颜色,然后进行绘制。

颜色映射过程

假设有一个共256种颜色的查询表,且颜色的索引号范围为0~255,那么颜色映射就是将数据集的标量值映射到这些索引号的过程。在渲染时,与索引号对应的颜色将作为数据中相应部分的颜色显示出来。映射的方法如下图所示:

在这里插入图片描述

上图中,scalar表示数据集中的标量值,index表示映射后的索引号。smin和smax表示的是标量值的映射范围(注意不是数据集中标量值的取值范围)。若一个标量值小于smin,则其映射的索引号为0;若一个标量值大于smax,其映射的索引号为255;若一个标量值在这个范围之间,则映射过程就是一个求一元一次函数值的过程,其对应的曲线是一条直线,不过需要对函数值取整。实际上,在范围外的标量值也可以被看作smin和smax,然后对其求函数值。

在VTK中,颜色映射的过程是由映射器mapper完成的。可以通过调用映射器的方法SetScalarRange()来设置标量值的范围[smin, smax]。映射过程只是为每个标量值确定了一个索引号,最终该标量值映射为何种颜色,还需要看颜色查询表中颜色的分配情况。如果不手动创建一个查询表,则映射器会使用一个默认的映射表。

vtkDataSet

vtkDataSet是一个抽象类,不能直接实例化,它是VTK所有数据集类型的父类,主要为数据集类型声明了一些通用接口以及成员。

在这里插入图片描述

从这个继承树中可以看到vtkDataSet所有的子类。

实现颜色标量映射主要关注的只有两个接口:

  1. vtkCellData* GetCellData();
  2. vtkPointData* GetPointData();

这两个数据分别对应的是单元数据集点数据集。

单元数据集中的单元指得是计算机图形的图元集合,如果是多边形数据集那么它就是一个三角网格中所有三角形的集合,如果使用结构或者非结构化网格数据,那么它就是网格单元数据的集合。

点数据集合是单元集合中所用到所有的点的集合,根据数据集的类型不同,包含的数据也略微有点不同。

vtkDataSetAttributes

在这里插入图片描述

上述的vtkCellData和vtkPointData都继承于vtkDataSetAttributes,它可以用于包含标量、向量、法线、纹理坐标、张量、全局 id、谱系 id 和字段数据等数据。

// Description:
// Set/Get the scalar data.
int SetScalars(vtkDataArray* da);
int SetActiveScalars(const char* name);
vtkDataArray* GetScalars();// Description:
// Set/Get the vector data.
int SetVectors(vtkDataArray* da);
int SetActiveVectors(const char* name);
vtkDataArray* GetVectors();// Description:
// Set/get the normal data.
int SetNormals(vtkDataArray* da);
int SetActiveNormals(const char* name);
vtkDataArray* GetNormals();// Description:
// Set/Get the texture coordinate data.
int SetTCoords(vtkDataArray* da);
int SetActiveTCoords(const char* name);
vtkDataArray* GetTCoords();// Description:
// Set/Get the tensor data.
int SetTensors(vtkDataArray* da);
int SetActiveTensors(const char* name);
vtkDataArray* GetTensors();// Description:
// Set/Get the global id data.
int SetGlobalIds(vtkDataArray* da);
int SetActiveGlobalIds(const char* name);
vtkDataArray* GetGlobalIds();// Description:
// Set/Get the pedigree id data.
int SetPedigreeIds(vtkAbstractArray* da);
int SetActivePedigreeIds(const char* name);
vtkAbstractArray* GetPedigreeIds();

以上接口对应了每种数据的获取与设置,每种数据似乎可以添加多个序列,然后可以使用序列名称调整当前使用的数据,序列名称通过vtkDataArray类setName函数进行设置。序列数据中的顺序需要对应实际数据的顺序。这里的顺序指的是ID,如Point数据,每个点都会有一个ID,那么为这些点数据附加标量数据的时候,标量数据的传入顺序就需要与id一一对应,如果是cellData那么也同理。

vtkLookUpTable

图像彩色映射的原理是首先生成一个颜色查找表,然后根据图像的一个标量值向颜色查找表中查找对应的颜色,并用新颜色值替代原来的像素值。

VTK中vtkImageMapToColors负责图像彩色映射,vtkLookUpTable提供一个从标量到颜色的映射表。

//设置颜色的过渡模式
void SetRamp(int);
void SetRampToLinear();//线性
void SetRampToSCurve();//s曲线
void SetRampToSQRT();//y=sqrt(x)
int GetRamp();
//设置颜色的与标量的映射模式
void SetScale(int);
void SetScaleToLinear();//线性
void SetScaleToLog10();//对数
int GetScale();

void SetTableRang(double r[2]);//设置标量的范围(最小,最大值)
void SetHueRang(double,double);//设置色调范围,取[0,1]之间
void SetSaturationRange(double,double);//设置饱和度,取[0,1]之间
void SetValueRange(double,double);//设置颜色值的取值范围,取[0,1]之间
void SetAlphaRange();//设置透明的取值范围,取[0,1]之间
void SetNanColor(double,double,double,double);//设置遇到Nan(非数)时使用的颜色
void SetBelowRangeColor(double,double,double,double);//设置当低于范围使用的颜色
void SetAboveRangeColor(double,double,double,double);//设置当超出范围时使用的颜色
void SetNumberOfTableValues(vtkIdType number);//设置颜色数量(色块)
void SetTableValue(vtkIdType indx,double r, double g, double b, double a=1.0);//直接设置颜色值
//根据各种参数生成颜色表
void Build();

这是它的一些关键接口,通过这些结构可以构造一个完整的颜色表,然后再调用映射器对象的SetLookupTable (vtkScalarsToColors *lut)函数设置即可。

vtkScalarBarActor

这个类不是颜色映射时必须要的类,但是它可以直观的将颜色映射表显示到窗口上,非常的方便。

使用起来也很简单,它继承于vtkActor,只需要调用SetLookupTable (vtkScalarsToColors *)函数设置好对应的颜色表,然后将Actor添加到渲染器中即可。

实例:

//为三角形准备三个顶点
vtkNew<vtkPoints> points;
points->InsertNextPoint(1, 0, 0);
points->InsertNextPoint(0, 0, 0);
points->InsertNextPoint(0, 1, 0);

//生成点的标量数据
vtkNew<vtkIntArray> scalar;
scalar->InsertNextTuple1(30);
scalar->InsertNextTuple1(50);
scalar->InsertNextTuple1(100);

//使用顶点构建一个三角形 点使用ID传入
vtkNew<vtkTriangle> triangle;
triangle->GetPointIds()->SetId(0, 0);
triangle->GetPointIds()->SetId(1, 1);
triangle->GetPointIds()->SetId(2, 2);

//将三角形传入单元集
vtkNew<vtkCellArray> cellArray;
cellArray->InsertNextCell(triangle);

//为多边形数据添加单元集和点集
vtkNew<vtkPolyData> polydata ;
polydata->SetPolys(cellArray);
polydata->SetPoints(points);
//为点数据添加标量
polydata->GetPointData()->SetScalars(scalar);

vtkNew<vtkPolyDataMapper> mapper;
mapper->SetInputData(polydata);
mapper->SetScalarRange(scalar->GetRange());
mapper->Update();
//为映射器添加一个颜色映射表
vtkNew<vtkLookupTable> lookupTable;
lookupTable->SetTableRange(scalar->GetRange());
lookupTable->Build();
mapper->SetLookupTable(lookupTable);

//为窗口添加一个图例
vtkNew<vtkScalarBarActor> scalarBarActor;
scalarBarActor->SetLookupTable(lookupTable);

vtkNew<vtkActor> actor;
actor->SetMapper(mapper);
actor->GetProperty()->SetColor(255, 0, 0);

vtkNew<vtkRenderer> renderer;
vtkNew<vtkRenderWindow> window;
window->AddRenderer(renderer);

vtkNew<vtkRenderWindowInteractor> interactor;
interactor->SetRenderWindow(window);

renderer->AddActor(actor);
renderer->AddActor(scalarBarActor);
window->Render();
interactor->Start();

运行结果:

在这里插入图片描述

代码实现了一个三角形的构建,并为三角形的三个顶点添加标量,然后在显示时使用标量进行颜色映射,可以说这是一个最简单的标量映射的一个例子。

需要注意的是因为代码中是为point添加的标量,除了顶点以外的其他位置的标量值都是经过线性插值得到的。

实例

#include "VTKColorMapping.h"

#include <vtkConeSource.h>
#include <vtkSTLReader.h>
#include <vtkMultiBlockPLOT3DReader.h>
#include <vtkDataSet.h>
#include <vtkMultiBlockDataSet.h>
#include <vtkShrinkPolyData.h>
#include <vtkStructuredGridGeometryFilter.h>
#include <vtkStructuredGridOutlineFilter.h>
#include <vtkLookupTable.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>

VTKColorMapping::VTKColorMapping(QWidget* parent)
	: QMainWindow(parent)
{
	ui.setupUi(this);

	_pVTKWidget = new QVTKOpenGLNativeWidget();
	this->setCentralWidget(_pVTKWidget);
	// this->showMaximized();

	// 1. generate data
	// vtkSmartPointer<vtkConeSource> cone = vtkSmartPointer<vtkConeSource>::New();
	// or, read data
	// vtkMultiBlockPLOT3DReader 是一个读取器对象,用于读取 PLOT3D 格式的文件并在输出时生成结构化网格
	vtkSmartPointer<vtkMultiBlockPLOT3DReader> plot3dReader = vtkSmartPointer<vtkMultiBlockPLOT3DReader>::New();
	plot3dReader->SetXYZFileName("combxyz.bin");
	plot3dReader->SetQFileName("combq.bin");
	plot3dReader->SetScalarFunctionNumber(100);
	plot3dReader->SetVectorFunctionNumber(202);
	qDebug() << plot3dReader->GetOutput()->GetNumberOfBlocks();
	// 反向更新管线
	plot3dReader->Update();
	qDebug() << plot3dReader->GetOutput()->GetNumberOfBlocks();
	vtkDataSet* plot3dOutput = (vtkDataSet*)(plot3dReader->GetOutput()->GetBlock(0));


	// 2. filter
	// 提取作为多边形几何(点,线,表面)的栅格的一部分
	vtkSmartPointer<vtkStructuredGridGeometryFilter> plane = vtkSmartPointer<vtkStructuredGridGeometryFilter>::New();
	plane->SetInputData(plot3dOutput);
	plane->SetExtent(1, 100, 1, 100, 7, 7);
	// 产生结构化栅格边界的一个线轮廓
	vtkSmartPointer<vtkStructuredGridOutlineFilter> outline = vtkSmartPointer<vtkStructuredGridOutlineFilter>::New();
	outline->SetInputData(plot3dOutput);
	// 颜色映射表
	vtkSmartPointer<vtkLookupTable> lut = vtkSmartPointer<vtkLookupTable>::New();
	lut->SetNumberOfColors(256); // 指定颜色映射表中有多少种颜色
	lut->Build();

	// 3. mapper
	vtkSmartPointer<vtkPolyDataMapper> planeMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
	vtkSmartPointer<vtkPolyDataMapper> outlineMapper = vtkSmartPointer<vtkPolyDataMapper>::New();

	// 4. actor
	vtkSmartPointer<vtkActor> planeActor = vtkSmartPointer<vtkActor>::New();
	vtkSmartPointer<vtkActor> outlineActor = vtkSmartPointer<vtkActor>::New();

	// 5. renderer
	vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
	renderer->SetBackground(0.3, 0.6, 0.3); // Background Color: Green

	// 6. connect
	planeMapper->SetLookupTable(lut);
	planeMapper->SetInputConnection(plane->GetOutputPort());
	planeMapper->SetScalarRange(plot3dOutput->GetScalarRange()); // 设置标量值的范围
	outlineMapper->SetInputConnection(outline->GetOutputPort());
	planeActor->SetMapper(planeMapper);
	outlineActor->SetMapper(outlineMapper);
	renderer->AddActor(planeActor);
	renderer->AddActor(outlineActor);

	this->_pVTKWidget->renderWindow()->AddRenderer(renderer);
	this->_pVTKWidget->renderWindow()->Render();
}

VTKColorMapping::~VTKColorMapping()
{}

本程序采用 vtkMultiBlockPLOT3DReader 读取数据,它是一个读取器对象,用于读取 PLOT3D 格式的文件并在输出时生成结构化网格。本程序读取了 combxyz.bin 和 combq.bin 两个文件。

读取完之后,需要 plot3dReader->Update() 来更新数据源对象,我们在更新前后分别使用 plot3dReader->GetOutput()->GetNumberOfBlocks() 打印了数据源的块数,更新前是 0,更新后变为 1。

之后,从 plot3dReader 的输出中取出下标为 0 的块作为 plot3dOutput。创建一个 vtkStructuredGridGeometryFilter 类型的过滤器 plane,它可以提取作为多边形几何(点,线,表面)的栅格的一部分,使用 plane->SetExtent(1, 100, 1, 100, 7, 7) 提取指定3D范围的数据;还创建了一个 vtkStructuredGridOutlineFilter 类型的过滤器 outline,它可以产生结构化栅格边界的一个线轮廓。这两个过滤器的输入都是 plot3dOutput。新建一个颜色映射表 lut,指定颜色映射表中有256种颜色,使用 lut->Build() 把这个颜色映射表构建好。

对 plane 和 outline 分别创建 mapper 和 actor,planeMapper 还需要设置 lut 和标量值的范围,都绑定到渲染器上,就可以在屏幕上显示了。

运行结果:

在这里插入图片描述

资源下载

https://github.com/UestcXiye/VTKColorMapping

参考

  1. http://cppdebug.com/archives/242
  2. https://blog.csdn.net/Littlehero_121/article/details/131204958
  3. https://shenchunxu.blog.csdn.net/article/details/54696460
  4. https://blog.csdn.net/webzhuce/article/details/78077561
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐