使用VTK和Python进行体绘制

news2024/9/29 3:25:02

使用VTK和Python进行体绘制

    • Introduction
    • Volume Rendering
      • 1. Imports
      • 2. Helper-functions
      • 3.Options
      • 4. Image-Data Input
      • 5. Prep-work
      • 6. Volume Rendering

Introduction

科学可视化技术是运用计算机图形学、图像处理、计算机视觉等方法,将科学、工程学、医学等计算、测量过程中的符号、数字信息转换为直观的图形图像,并在屏幕上显示的理论、技术和方法。

体绘制是科学可视化领域中的一个技术方向。如前所述,体绘制的目标是在一副图片上展示空间体细节。举例而言,你面前有一间房子,房子中有家具、家电,站在房子外面只能看到外部形状,无法观察到房子的布局或者房子中的物体;假设房子和房子中的物体都是半透明的,这样你就可以同时查看到所有的细节。这就是体绘制所要达到的效果。

体绘制基本流程如下图
在这里插入图片描述
完成本文的体绘制工作,首先要下载大脑图像数据集,大脑图像数据集下载。使用jupyter notebook进行python代码编写和运行。

Volume Rendering

下面将从在数据集中加载和转换标签字段开始,以使其与将使用的 volume 映射类兼容。然后定义颜色和不透明度传递函数,这是整个 volume 渲染过程中最重要的部分,因为这些函数定义了最终渲染的外观。定义了这些之后,将演示使用 VTK 提供的一些不同 volume 映射类的体绘制,并展示它们的结果。

1. Imports

import os
import numpy
import vtk

其中 vtk 在 Jupyter Notebook 中使用 pip 安装即可

2. Helper-functions

下面定义几个帮助函数:

(1)vtk_show()
这个函数允许传递一个 vtkRenderer 对象并获得该渲染的 PNG 图像输出,与 IPython Notebook 单元格输出兼容。

from IPython.display import Image
def vtk_show(renderer, width=400, height=300):
    """
    Takes vtkRenderer instance and returns an IPython Image with the rendering.
    """
    renderWindow = vtk.vtkRenderWindow()
    renderWindow.SetOffScreenRendering(1)
    renderWindow.AddRenderer(renderer)
    renderWindow.SetSize(width, height)
    renderWindow.Render()
     
    windowToImageFilter = vtk.vtkWindowToImageFilter()
    windowToImageFilter.SetInput(renderWindow)
    windowToImageFilter.Update()
     
    writer = vtk.vtkPNGWriter()
    writer.SetWriteToMemory(1)
    writer.SetInputConnection(windowToImageFilter.GetOutputPort())
    writer.Write()
    data = str(buffer(writer.GetResult()))
    
    return Image(data)

(2)createDummyRenderer()
这个函数只是创建一个vtkRenderer对象,设置一些基本属性,并为本文的渲染目的配置相机。由于我们将渲染几个不同的场景,所以为每个案例创建一个新的渲染器和场景比一直移除或添加 actor 更简单,从而使每个渲染独立于其之前的渲染。

def createDummyRenderer():
    renderer = vtk.vtkRenderer()
    renderer.SetBackground(1.0, 1.0, 1.0)

    camera = renderer.MakeCamera()
    camera.SetPosition(-256, -256, 512)
    camera.SetFocalPoint(0.0, 0.0, 255.0)
    camera.SetViewAngle(30.0)
    camera.SetViewUp(0.46, -0.80, -0.38)
    renderer.SetActiveCamera(camera)
    
    return renderer

(3)两个lambda表达式
用于快速将 一个 list 或者一个 tuple 转换为 一个 numpy 的 n 维数组,反之亦然。

l2n = lambda l: numpy.array(l)
n2l = lambda n: list(n)

3.Options

在 Jupyter Notebook 开头定义一些变量:

# Path to the .mha file
filenameSegmentation = "./nac_brain_atlas/brain_segmentation.mha"

# Path to colorfile.txt 
filenameColorfile = "./nac_brain_atlas/colorfile.txt"

# Opacity of the different volumes (between 0.0 and 1.0)
volOpacityDef = 0.25
  • filenameSegmentation 用于保存 .mha 文件的位置;该文件在同一文件中同时包含标题和二进制图像数据
  • filenameColorfile 用于保存 .txt 文件的位置;该文件是附带的颜色文件,它本质上是一个CSV 文件,列出了标签字段中的每个索引以及代表的大脑结构的名称和推荐的 RGB 颜色。
  • volOpacityDef 稍后在我们为volume映射器定义不透明度传递函数时发挥作用,它是所有渲染大脑结构的基线不透明度。

4. Image-Data Input

根据提供的 .mha 文件加载标签字段
VTK 支持读取 .mha 格式的未压缩 MetaImage 文件,通过 vtkMetaImageReader 类进行读取

reader = vtk.vtkMetaImageReader()
reader.SetFileName(filenameSegmentation)

castFilter = vtk.vtkImageCast()
castFilter.SetInputConnection(reader.GetOutputPort())
castFilter.SetOutputScalarTypeToUnsignedShort()
castFilter.Update()

imdataBrainSeg = castFilter.GetOutput()

读取之后,vtkImageCast 在下方创建一个新对象 castFilter 并将其输入连接到 reader 输出,从而为其提供图像。然后,关键是调用适当的方法来设置输出图像的所需数据类型。在我们的例子中,我们希望输出图像是unsigned short类型的,因此我们调用该SetOutputScalarTypeToUnsignedShort方法。castFilter 随后调用 Update,reader 获取“原始”图像数据,并将其转换为 的类型 unsigned short。最后通过检索该数据 GetOutput 并将其存储在 imdataBrainSeg 变量中.

5. Prep-work

(1)Transfer functions: Read colorfile into a dictionary
VTK任何体绘制的外观都归结为定义传递函数。这些传递函数告诉volume映射器要为输出中的每个像素赋予什么颜色和不透明度。

import csv
fid = open(filenameColorfile, "r")
reader = csv.reader(fid)

dictRGB = {}
for line in reader:
    dictRGB[int(line[0])] = [float(line[2])/255.0,
                             float(line[3])/255.0,
                             float(line[4])/255.0]
fid.close()

循环遍历从颜色文件中读取的每个条目,并创建一个字典dictRGB,其中索引标签充当key,value是一个列表,其中包含分配给该组织的 RGB 颜色。
在这里插入图片描述

(2)Transfer functions : Define color transfer function
定义一个颜色传递函数。这是 vtkColorTransferFunction 类的一个实例,并充当标量像素值到给定 RBG 颜色的映射。

funcColor = vtk.vtkColorTransferFunction()

for idx in dictRGB.keys():
    funcColor.AddRGBPoint(idx, 
                          dictRGB[idx][0],
                          dictRGB[idx][1],
                          dictRGB[idx][2])

首先创建一个新 vtkColorTransferFunction 的 funcColor,它允许我们创建我们之前讨论过的标签索引颜色映射。然后我们简单地遍历dictRGB之前创建的字典中的所有键,即所有标签索引,并使用 AddRGBPoint 方法添加具有该标签索引和匹配 RGB 颜色的点。

(3)Transfer functions : Define scalar opacity transfer function
下面需要定义一个标量不透明度函数。我们将使用它来简单地将每个标签与不透明度值匹配。由于我们没有针对不同组织预定义不透明度值,将所有不透明度设置为变量 volOpacityDef 中定义的单个值:

funcOpacityScalar = vtk.vtkPiecewiseFunction()

for idx in dictRGB.keys():
    funcOpacityScalar.AddPoint(idx, volOpacityDef if idx<>0 else 0.0)

请注意,不透明度函数是通过 vtkPiecewiseFunction 定义的,因为它只是定义了一个 1-1 映射。还要注意 AddPoint 添加新点的方法的用法。注意到 0 标签的透明度设置为了 0.0。这意味着我们正在制作background,即分割周围的黑色空白空间,完全不可见(否则我们只会在渲染周围看到一个巨大的黑色块)。

(4)Transfer functions : Define gradient opacity transfer function
标量不透明度函数简单地为每个像素强度分配一个不透明度值。然而,这通常会导致外观相当均匀的渲染,其中外部组织在图像中占主导地位。这就是渐变不透明度函数发挥作用的地方。通过这样的函数,我们将标量空间梯度(即标量在空间中变化的程度)映射到不透明度乘数。这些梯度在“行进”通过同质区域时趋向于变小,而在穿过不同组织时变大。因此,通过这样的功能,我们可以使组织的“内部”变得相当透明,同时使组织之间的边界更加突出,从而使整个volume的图像更加清晰。

funcOpacityGradient = vtk.vtkPiecewiseFunction()

funcOpacityGradient.AddPoint(1,   0.0)
funcOpacityGradient.AddPoint(5,   0.1)
funcOpacityGradient.AddPoint(100,   1.0)

这个函数也是通过一个vtkPiecewiseFunction对象来定义的。通过这个函数,高于1.0的低梯度的像素,将其不透明度乘以0.0,梯度在1和5之间的像素,其不透明度乘以0.0~0.1,梯度高于5的像素,其透明度将乘以一个大于1的数。

(5)Volume Properties
下面定义 volume 的基本属性,创建和配置一个 vtkVolumeProperty 对象,该对象“代表渲染 volume 的通用属性”:

propVolume = vtk.vtkVolumeProperty()
propVolume.ShadeOff()
propVolume.SetColor(funcColor)
propVolume.SetScalarOpacity(funcOpacityScalar)
propVolume.SetGradientOpacity(funcOpacityGradient)
propVolume.SetInterpolationTypeToLinear()

通过 SetColor ,SetScalarOpacity 和 SetGradientOpacity 方法,把我们之前定义的 funcColor、funcOpacityScalar 和funcOpacityGradient三个传递函数分配给体积属性。最后,对 volume 进行插值。这里选择最近邻插值,通过SetInterpolationTypeToNearest 方法设置,线性插值通过SetInterpolationTypeToLinear方法设置。通常,在处理离散数据时,就像案例中的标签字段一样,我们会选择最近邻插值,因为这样我们就不会引入与任何组织都不匹配的“新”值。

6. Volume Rendering

(1)vtkVolumeRayCastMapper
vtkVolumeRayCastMapper 是 VTK 中的“经典”体绘制类,其将自己宣传为“用于体绘制的缓慢但准确的映射器”。顾名思义,vtkVolumeRayCastMapper该类通过类型为 vtkVolumeRayCastFunction 的合适的光线投射函数进行光线投射的方式来执行体绘制。

vtkVolumeRayCastMapper 包含下面三种:

  • vtkVolumeRayCastCompositeFunction:根据存储在 volume 的 vtkVolumeProperty 中的属性沿着光线进行合成。
  • vtkVolumeRayCastMIPFunction:计算沿射线遇到的最大值。
  • vtkVolumeRayCastIsosurfaceFunction:将射线与标量场中的解析等值面相交。

在本例中,选择 vtkVolumeRayCastCompositeFunction :

首先通过 vtkVolumeRayCastCompositeFunction 类创建一个名为funcRayCasttype的新对象。然后我们通过SetCompositeMethodToClassifyFirst 方法将函数设置为在插值之前进行的首次像素分类。这使标签保持相对均匀,但如果我们决定采用该方法的另一种SetCompositeMethodToInterpolateFirst 方法,不同的标签会被破坏并且会得到混乱的渲染。

funcRayCast = vtk.vtkVolumeRayCastCompositeFunction()
funcRayCast.SetCompositeMethodToClassifyFirst()

接下来,我们需要创建一个 volume 映射器。先通过 vtkVolumeRayCastMapper 类创建了一个名为 mapperVolume 的新对象,并通过 SetVolumeRayCastFunction 方法将新创建的光线投射函数 funcRayCast 提供给 mapperVolume 。最后,我们将正在渲染的实际图像数据提供给 mapperVolume ,这些数据存储在对象 imdataBrainSeg 中。

mapperVolume = vtk.vtkVolumeRayCastMapper()
mapperVolume.SetVolumeRayCastFunction(funcRayCast)
mapperVolume.SetInput(imdataBrainSeg)

第三步,我们创建一个 vtkVolume 对象,命名为 actorVolume ,这相当于一个 vtkActor 但是用于 volume 数据。我们把映射器映射到 mapperVolume ,把属性设置为我们在准备过程中创建的 vtkVolumeProperty 对象 propVolume。

actorVolume = vtk.vtkVolume()
actorVolume.SetMapper(mapperVolume)
actorVolume.SetProperty(propVolume)

最后,我们只是完成预渲染动作。我们通过在开始时定义的辅助函数 createDummyRenderer 创建一个新的渲染器,并加入actorVolume。

renderer = createDummyRenderer()
renderer.AddActor(actorVolume)

执行

vtk_show(renderer, 800, 800)

输出效果如下图:
在这里插入图片描述
(2)Clipping Plane
由于数据集的组织太多,脑回彼此混合,上图展示出来的不是特别清晰。下面通过裁剪的方式进行优化。
vtkVolumeRayCastMapper 类以及在 VTK 中的volume映射器都支持裁剪,为了使用裁剪,首先需要创建一个具有适当位置和方向的平面:

_origin = l2n(imdataBrainSeg.GetOrigin())
_spacing = l2n(imdataBrainSeg.GetSpacing())
_dims = l2n(imdataBrainSeg.GetDimensions())
_center = n2l(_origin+_spacing*(_dims/2.0))

使用 vtkImageData 的适当方法来检索图像数据的原点坐标、间距和尺寸,并使用 l2n 辅助函数快速将这些列表转换为numpy.ndarray对象,以允许我们和这些数做一些数学运算。然后,计算图像数据的中心坐标并将其存储在 _center 下。

然后我们需要做的就是创建一个新的 vtkPlane,将其原点设置为图像数据的中心,并将其法线设置为负 Z 轴。

planeClip = vtk.vtkPlane()
planeClip.SetOrigin(_center)
planeClip.SetNormal(0.0, 0.0, -1.0)

接下来重复vtkVolumeRayCastMapper过程的代码,唯一区别是将新创建的裁剪平面添加到 volume 映射器:

mapperVolume.AddClippingPlane(planeClip)

执行

vtk_show(renderer, 800, 800)

输出效果如下图:
在这里插入图片描述
(3)vtkVolumeTextureMapper2D
vtkVolumeTextureMapper2D 是另一个 volume 渲染器,它使用完全在 GPU 端发生的基于纹理的体绘制,并在很短的时间内生成更漂亮的渲染。
下面的代码和我们之前的唯一区别是,mapperVolume现在是 vtkVolumeTextureMapper2D,而不是vtkVolumeRayCastMapper。此外,我们没有定义任何光线投射函数,因为这不是此类的操作方式。除了这些差异之外,代码的其余部分与之前相同。

mapperVolume = vtk.vtkVolumeTextureMapper2D()
mapperVolume.SetInput(imdataBrainSeg)
mapperVolume.AddClippingPlane(planeClip)

actorVolume = vtk.vtkVolume()
actorVolume.SetMapper(mapperVolume)
actorVolume.SetProperty(propVolume)

renderer = createDummyRenderer()
renderer.AddActor(actorVolume)

执行

vtk_show(renderer, 800, 800)

输出效果如下图:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/129065.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

亚马逊跨境电商可靠吗?2023年还可以做吗?

新的一年新的打算&#xff0c;不少小伙伴在问&#xff0c;亚马逊跨境电商可靠吗&#xff1f;2023年还可以做亚马逊跨境电商吗&#xff1f;为此我们小编就来简单说说自己的想法吧&#xff01; 亚马逊跨境电商可靠吗&#xff1f; 【回答】&#xff1a;首先我们需要肯定一点的是&…

【Unity3D日常开发】Unity3D中屏蔽不想显示的黄色警告消息

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客QQ群&#xff1a;1040082875 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 在开发中&#xff0c;会有一些脚本…

在vue项目中使用rem的完整步骤

首先要知道几个概念&#xff1a; 设计稿是物理像素&#xff0c;在移动端上是css像素&#xff0c;1css像素2物理像素/3物理像素&#xff1b; 要想实现一张设计稿的尺寸能在各个移动端上适配&#xff0c;因为不同的移动端的css像素和物理像素比不一样&#xff0c;所以固定的物理…

【前端】Vue项目:旅游App-(1)搭建项目、重置css、配置router和store(pinia)

文章目录创建项目搭建和配置项目&#xff1a;项目目录结构划分重置CSSnormalize.cssreset.css目录结构配置router对应页面组件index.js配置store创建项目 npm init vuelatest本项目相关选择&#xff1a; 安装相关依赖&#xff1a; npm install试着跑一下&#xff1a; npm ru…

提面录取占比:浙大MBA MPA MEM复试中不可忽视的关键因素之一。

对于复试考生来说&#xff0c;单纯的探讨某个专业有多少人报考没有太大意义&#xff0c;单纯的关注这个专业招多少人也没有太多意义&#xff0c;我们要更加关注在复试阶段还能剩余多少录取指标&#xff0c;因为这个才是复试考生直接相关的数据。不同项目和专业间目前对提前批面…

剖析免密登录,集群之间的免密登录

免密登录1.免密登录的原理2.实现2.1首先配置每个节点的hosts文件2.2 在server1生成秘钥2.3了解文件2.4 实验是否可行3.补充1.免密登录的原理 每台主机authorized_keys文件&#xff0c;该文件就是身份验证的钥匙&#xff0c;该文件里如果有另一台主机的公钥&#xff08;id_rsa.…

Pytest自动化测试框架之Allure报告

目录 简介 部署使用 1、安装&#xff1a; 2、基本使用 测试报告 简介 Allure Framework是一种灵活的、轻量级、多语言测试报告工具。 不仅可以以简洁的网络报告形式非常简洁地显示已测试的内容&#xff0c; 而且还允许参与开发过程的每个人从日常执行中提取最大程度的有…

Android设计模式详解之享元模式

前言 享元模式是对象池的一种实现&#xff0c;用来尽可能减少内存使用量&#xff0c;适合用于可能存在大量重复对象的场景&#xff0c;来缓存可共享的对象&#xff1b; 定义&#xff1a;使用共享对象可有效地支持大量的细粒度的对象&#xff1b; 使用场景&#xff1a; 系统…

STM32/51单片机实训day7——电机驱动|ULN2003A步进电机|Proteus电路设计|旋转角度控制函数|驱动函数|Keil5程序设计

目录 1 ULN2003A步进电机简介 2 步进电机电路设计 3 旋转角度控制函数 4 程序设计 motor.c motor.h 前期LCD参考文章&#xff1a;​​​​​​​ 内 容&#xff1a;编程实现控制步进电机旋转不同角度 学 时&#xff1a;3学时 知识点&#xff1a; GPIO配置、步进电机…

【pygame学习_5】窗口设计

1、引言 窗体是游戏的交互界面&#xff0c;一般我们会遇到窗口大小可调&#xff0c;窗口无边框&#xff0c;全屏显示&#xff0c;最小化设计&#xff0c;改名字&#xff0c;换图标等设计需求。 屏幕绘制有如下重要函数&#xff1a; 2、屏幕模式函数 pygame.display.set.mode …

Event Loop

javascript是单线程语言 那么&#xff0c;你可能要问&#xff0c;javascript为什么是单线程&#xff0c;难道不能实现多线程吗&#xff1f; 这跟历史有关系。javascript从诞生的时候就是单线程&#xff0c;原因大概是不想让浏览器变得太复杂&#xff0c;因为多线程需要共享资源…

dark room - 2020 年苹果设计奖得主,一个足够强大的照片视频编辑器

dark room - 2020 年苹果设计奖得主&#xff0c;一个足够强大的照片视频编辑器 2020年苹果设计奖得主 2015年App Store最佳应用 Darkroom 是一个高级照片和视频编辑器。它对业余摄影师来说很容易操作&#xff0c;但对专业摄影师来说足够强大。 下载 ➤ Darkroom 下载安装 ⇲…

七十二——八十八

七十二、JavaScript——面向对象简介 面向对象编程&#xff08;OOP) 1. 程序是干嘛的 - 程序是现实世界的抽象&#xff08;照片就是对人的抽象&#xff09; 2. 对象是干嘛的&#xff1f; - 一个事物抽象到程序后就变成了对象 - 在程序的试接中&#xff0c;一切皆对象 - 一个事物…

来到CSDN一周年(hacker的2022年终总结)

✅作者简介&#xff1a;CSDN内容合伙人、阿里云专家博主、51CTO专家博主、新星计划第三季python赛道Top1&#x1f3c6; &#x1f4c3;个人主页&#xff1a;hacker707的csdn博客 &#x1f4ac;个人格言&#xff1a;不断的翻越一座又一座的高山&#xff0c;那样的人生才是我想要的…

【数据结构】排序算法大总结

文章目录1. 排序的概念及运用2. 常见排序算法的实现2.1 插入排序2.1.1 直接插入排序2.1.2 希尔排序2.2 选择排序2.2.1 直接选择排序2.2.2 堆排序2.3 交换排序2.3.1 冒泡排序2.3.1 快速排序小区间优化hoare版本挖坑法前后指针法2.3.2 快排非递归2.4 归并排序2.4.1 归并排序递归2…

本地缓存天花板-Caffeine

前言 caffeine是一款高性能的本地缓存组件&#xff0c;关于它的定义&#xff0c;官方描述如下&#xff1a; Caffeine is a high performance, near optimal caching library. 翻译过来就是Caffeine是一款高性能、最优缓存库。 同时文档中也说明了caffeine是受Google guava启发…

【Git】一文带你入门Git分布式版本控制系统(分支管理策略、Bug分支)

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;也会涉及到服务端 &#x1f4c3;个人状态&#xff1a; 在校大学生一枚&#xff0c;已拿多个前端 offer&#xff08;秋招&#xff09; &#x1f680;未…

Eth04 - Eth分层模块架构和索引方案

文章目录 1 Eth分层模块架构2 索引方案3 发送成功和接收成功回调函数传送门 ==>> AutoSAR入门和实战系列总目录 1 Eth分层模块架构 下面的图片表明了以太网控制器驱动程序和硬件的关系;从EthIf看来,要通过以太网控制器层去访问以太网控制器硬件,以太网控制器层有多…

AcWing1204.错误票据——学习笔记

题目&#xff1a;1204. 错误票据 - AcWing题库https://www.acwing.com/problem/content/description/1206/ import java.util.Scanner;public class Main {public static void main(String args[]){Scanner input new Scanner(System.in);int line input.nextInt();int loseI…

Python开发环境

1. Python开发环境 开发环境&#xff0c;英文是IDE&#xff08;Integrated Development Environment 集成开发环境&#xff09;。 不要纠结于使用哪个开发环境。开发环境本质上就是对Python解释器python.exe的封装&#xff0c;核心都一样。可以说:“开发环境IDE&#xff0c;只…