文章

3D Slicer 术语表精读:坐标系、体数据与分割的那些概念

3D Slicer 术语表精读:坐标系、体数据与分割的那些概念

3D Slicer 术语表精读:坐标系、体数据与分割的那些概念

为什么值得专门整理这一套术语

3D Slicer 是医学影像领域用得最广的开源平台之一,它的价值不只在于软件本身好用。它背后那套概念模型——数据怎么组织、坐标系怎么定义、显示和数据怎么分离——被大量商业和自研影像软件直接或间接借鉴过。

换句话说,看懂 Slicer 的术语,相当于拿到了理解同类软件设计的一把钥匙。你自己做的三维重建模块、别人家的规划软件、开源库里的某个数据结构,多半都能在这套词汇里找到对应。

下面按四组整理。解释都用我自己的话重写,方便对照理解;权威定义请以官方文档为准,链接在文末。

一、坐标系与空间

这一组是最容易出错、后果也最严重的一组。

术语中文说明
Coordinate system坐标系/参考系由原点位置、各轴方向和长度单位共同确定。Slicer 里的坐标系一律是右手系
LPS左-后-上坐标系Left-Posterior-Superior,医学影像计算中最通用的解剖坐标系。Slicer 存盘时统一用它
RAS右-前-上坐标系Right-Anterior-Superior,Slicer 内部运算使用。与 LPS 相差前两个轴取反
IJK体素坐标数组索引坐标,整数值对应体素中心。按 VTK 约定,I 索引列、J 索引行、K 索引层
Bounds包围盒对象在三个轴上的范围,用六个浮点数表示:各轴的最小值与最大值
Extent整数范围三轴上的整数索引范围,同样六个值,首尾都包含在内。因此某轴长度是 max − min + 1
Spacing体素间距体素的物理尺寸,通常以毫米计
Resolution分辨率同样指体素尺寸,但这个词语义容易误导,后文单独说
Geometry几何对象在三维空间中的位置与形状
Transform变换把对象从一个坐标系换算到另一个。刚性变换只改变位置和方位;线性变换还能缩放、镜像、错切;非线性变换可以任意扭曲空间
Registration配准让两个对象在空间上对齐的过程。结果是一个变换,把”浮动”对象换算到”固定”对象所在的空间
Reference参考没有固定含义,通常指某个操作的次要输入,可能是数据、坐标系或几何

关于 Transform,有一个设计上的细节值得留意:要在世界坐标系里显示一幅体数据,需要把体数据重采样,因此需要的是从世界坐标系到体数据的变换;而要把其他类型的对象换算到世界坐标系,则需要正方向的变换。Slicer 的变换节点两个方向都能提供,存一个、另一个实时算出来。

二、数据对象

术语中文说明
Scene场景承载当前载入的全部数据,以及它们该如何显示、如何使用的信息。这个词源自计算机图形学
Node节点场景中的一个数据对象。可以是数据本身,也可以是描述显示方式、存储方式、空间变换的对象
MRML医学现实标记语言Slicer 用来存储、可视化和处理医学信息对象的底层库,节点体系就建立在它之上
Volume体数据存放三维体素数组的节点。几何由原点、间距和轴方向三者确定。二维图像被当作只有一层的体数据
Voxel体素体数据的一个元素,形状是长方体,坐标指的是它的中心点。值可以是标量,也可以是向量
Labelmap标签图体素值为离散整数的体数据,每个整数代表一个结构或区域。优点是能用一个数组紧凑地表示互不重叠的多个区域
Segmentation分割在图像中勾画三维结构的过程,也指这个过程产出的节点
Segment分段分割结果中的单个结构
Model模型存放表面网格(三角形、多边形)或体网格(四面体、楔形)的节点
Cell单元网格的基本拓扑元素,线、多边形、四面体都属于此类
Markups标注用户在视图中放置的简单几何对象和测量,包括点列表、线、曲线、平面、ROI 等类型
Fiducial基准点三维空间中的一个点。这个词来自影像引导手术领域,那里用”基准标记物”标定点的位置
ROI感兴趣区域三维中的一个盒状区域,可用于裁剪体数据、剪切模型

三、显示与强度映射

术语中文说明
Image intensity图像强度通常就是体素值。最终显示出来的亮度和颜色,是它经过窗宽窗位和颜色查找表算出来的
Window/level窗宽/窗位体素值到显示亮度的线性映射。窗宽是被映射到完整显示范围的那段强度区间的大小,窗位是被映射到显示范围中心的那个体素值
Brightness/contrast亮度/对比度描述同一个线性映射的另一套参数:亮度是偏移量,对比度是乘数。医学影像里更常用窗宽窗位
Color legend颜色图例/色标叠加在切片或三维视图上的控件,说明各种颜色代表什么
Orientation marker方位标记箭头、方块或人形标记,用来指示视图里的轴方向
Slice切片三维对象与一个平面的交面
Slice view annotations切片视图标注切片视图角落显示的文字,含体数据名称和选定的 DICOM 标签
Ruler标尺有两个意思:视图中作为尺寸参照的叠加线;或标注模块里的距离测量线
VR体绘制/虚拟现实这个缩写有歧义,建议写全称,或在上下文里明确定义

窗宽窗位和亮度对比度是同一件事的两种参数化方式,这一点值得注意。如果你的软件同时提供两套调节方式,内部要保证它们指向同一个映射,否则用户会看到两个滑块互相”打架”。

四、软件架构

术语中文说明
Module模块由界面、逻辑算法组成的软件组件。可以提供新的节点类型、显示管理器、读写插件等。模块之间通常相互独立,靠修改节点来通信
Extension扩展未随主程序打包、可另行下载安装的模块集合
Extensions manager扩展管理器在应用内浏览、安装、卸载扩展的组件
Extensions index扩展索引存放各扩展描述信息的仓库,扩展目录由它构建
VTK可视化工具包Slicer 用于数据表示与可视化的底层库。Slicer 大量类派生自 VTK,因此也沿用了它的编码风格和接口约定
ITK洞察工具包Slicer 用于大部分图像处理运算的底层库
Source volume源体数据分割时那些依赖底层图像强度的效果所取用的体数据
Scalar value标量值一个简单数值,通常是浮点数
Scalar component标量分量向量中的一个元素。分量数就是向量长度
Frame帧时间序列中的一个时间点。为避免歧义,这个词不用来指体数据的某一层

模块之间只通过节点通信这个设计,值得自研软件借鉴。它意味着任何一个模块都可以被替换或单独测试,只要它对节点的读写约定不变。这对做单元测试和接口测试都是好事。

易错点一:RAS 与 LPS 相差前两轴取反

这是整份术语表里最该记牢的一条。

DICOM 标准用的是 LPS,Slicer 内部运算用的是 RAS,两者只差前两个轴的方向取反。转换本身只是几个符号的事,但出错的后果很不讲道理:

图像看起来完全正常,只是左右镜像了。

人体在解剖上是近似左右对称的,一幅镜像过的 CT 或 X 光片,从画面上几乎看不出异常。骨骼形态、组织灰度、图像质量全都正常。你只有在对照解剖标志、或者和另一套没出错的数据比对时才会发现。

对手术导航这类应用,这个 bug 的严重程度不需要多解释。

实际做测试时,建议专门设计一组验证方位的用例:

  • 用带明确左右不对称标志的数据(比如已知病灶在某一侧)
  • 或者在体模上做一个非对称的标记特征,专门用来验证方位
  • 覆盖全部的数据导入路径——不同来源、不同格式的数据,转换代码可能不是同一份
  • 任何涉及坐标系转换的代码改动,这组用例都要回归

不要指望靠”看图”发现这类问题。

易错点二:IJK 与 numpy 的索引顺序相反

VTK 的约定是 I 索引列、J 索引行、K 索引层,按 [I][J][K] 的顺序理解。而 numpy 数组的排布是 a[K][J][I],正好反过来。另一种说法是:I 是变化最快的索引,K 是变化最慢的。

这个顺序搞反了不会抛异常,程序照常跑完,只是结果被转置了。如果转置发生在中间某一步,后续的处理会得到一堆看似合理实则错乱的数据。

做影像相关的 Python 脚本时,建议养成两个习惯:

取到数组后先打印 shape。 确认各维度的长度和你预期的层数、行数、列数对得上。如果各维度长度恰好相同(比如 512×512×512 的立方体数据),这个检查就失效了,这时更要小心。

在数据边界处写明约定。 函数的文档字符串里写清楚参数是 IJK 顺序还是 KJI 顺序,比靠记忆可靠。

易错点三:Resolution 这个词语义误导

Resolution 在 Slicer 里指的是体素尺寸,单位是毫米每像素。问题在于日常语感和它是反的:

数值大 = 间距大 = 图像粗糙。

说”这套数据分辨率是 1.0”,听起来像是挺高,实际上比 0.5 的那套粗一倍。正因为容易误解,Slicer 界面上很少直接用这个词,而是用 Spacing(间距)。

自研软件可以直接学这个做法:界面上写”体素间距”,不写”分辨率”。用户看到”间距 0.5mm”不会产生歧义,看到”分辨率 0.5”就会犹豫一下。

Segmentation 与 Labelmap:为什么要分成两个概念

大多数影像软件用一个 labelmap 来存分割结果——一个三维整数数组,值为 1 的体素属于结构 A,值为 2 的属于结构 B,以此类推。这个做法简单紧凑,但有两个硬限制。

第一,分段不能重叠。 一个体素只能有一个值,也就只能属于一个结构。但实际需求里重叠是常见的:肿瘤和它所在的器官、血管和它穿过的组织、规划中的安全边界和实际靶区。用单个 labelmap 表达不了。

第二,只有一种表示形式。 labelmap 是体素形式,适合做运算和统计,但不适合快速三维显示。要在三维视图里流畅地转动一个分割结果,需要的是表面网格。每次显示都现场做一次等值面提取,交互会很卡。

Slicer 的做法是引入一个专门的 segmentation 节点,它既不是 labelmap 节点也不是 model 节点,而是一个可以同时持有多种表示形式的容器:

  • 用多个二值 labelmap 分别存放各个分段,从而允许重叠
  • 同时保留封闭表面表示,供三维视图直接使用
  • 各种表示之间可以按需相互转换,且保持同步

这个设计的核心思想是:把”数据是什么”和”数据以什么形式存储”解耦开。一个分割结果在概念上是一组结构,至于它眼下以体素还是网格形式存在,是实现细节,调用方不必关心。

做自研软件时,如果你的分割模块现在是直接操作一个 labelmap 数组,不妨评估一下将来会不会遇到重叠需求或显示性能问题。这两个需求一旦出现,改起来的成本比一开始就把结构分开要高很多。

这套概念对自研影像软件的借鉴意义

把上面的内容收拢一下,有四点是可以直接拿来用的。

数据与显示分离。 Slicer 把”数据节点”和”显示节点”分成两类对象,同一份数据可以有多套显示配置,在不同视图里呈现成不同样子。这比把颜色、透明度直接塞进数据对象要灵活得多。

坐标系约定要显式化,而且只定一次。 Slicer 内部统一 RAS、存盘统一 LPS,转换集中在读写边界上完成。这种做法的好处是转换逻辑只有一处,出问题只需要查一个地方。最怕的是各个模块各自转换,转多了转少了都很难查。

多表示形式的容器设计。 前面讲的 segmentation 节点是一个典型例子。凡是同一份数据存在多种存储形式、而调用方只关心语义的场景,都可以套用这个模式。

模块间只通过数据通信。 模块彼此独立、只靠读写节点交互,这让单元测试和替换实现都变得容易。自研软件如果模块之间互相直接调用方法,耦合会越积越深。


术语内容参考自 3D Slicer 官方文档的 Glossary 章节,解释部分为本文作者重写。原文见 https://slicer.readthedocs.io/。