MITK
|
|
|
- 毓 廉
- 9 years ago
- Views:
Transcription
1 集 成 化 医 学 影 像 算 法 平 台 MITK 的 研 究 与 实 现 i
2 集 成 化 医 学 影 像 算 法 平 台 MITK 的 研 究 与 实 现 目 录 1 绪 论 医 学 影 像 算 法 平 台 研 究 的 背 景 及 意 义 医 学 影 像 算 法 平 台 研 究 的 内 容 整 体 框 架 的 研 究 医 学 影 像 算 法 的 研 究 医 学 影 像 算 法 平 台 的 国 内 外 研 究 现 状 VTK 简 介 ITK 简 介 VTK 和 ITK 的 局 限 性 本 书 的 主 要 内 容 MITK 的 总 体 设 计 MITK 的 设 计 目 标 统 一 的 风 格 有 限 目 标 可 移 植 性 代 码 优 化 MITK 的 整 体 计 算 框 架 基 于 数 据 流 模 型 的 整 体 框 架 数 据 模 型 算 法 模 型 MITK 的 基 础 设 施 搭 建 Object 提 供 的 服 务 内 存 管 理 跨 平 台 的 实 现 SSE 加 速 的 实 现 小 结...27 ii
3 集 成 化 医 学 影 像 算 法 平 台 MITK 的 研 究 与 实 现 3 面 绘 制 (SURFACE RENDERING) 的 框 架 与 实 现 表 面 重 建 算 法 及 其 在 MITK 中 的 实 现 传 统 的 Marching Cubes 算 法 基 于 分 割 的 Marching Cubes 方 法 [1] MITK 中 的 表 面 绘 制 框 架 表 面 绘 制 框 架 的 设 计 表 面 绘 制 框 架 的 实 现 小 结 体 绘 制 (VOLUME RENDERING) 的 框 架 与 实 现 体 绘 制 算 法 综 述 MITK 中 的 体 绘 制 算 法 框 架 体 绘 制 算 法 在 MITK 中 的 实 现 View 中 绘 制 操 作 的 实 现 VolumeModel 的 实 现 VolumeProperty 的 实 现 VolumeRenderer 的 实 现 Ray Casting 算 法 的 实 现 小 结 三 维 人 机 交 互 的 设 计 与 实 现 背 景 介 绍 以 3D WIDGETS 为 核 心 的 三 维 人 机 交 互 的 框 架 设 计 D Widgets 的 设 计 准 则 以 3D Widgets 为 核 心 的 三 维 交 互 框 架 总 体 结 构 以 3D Widgets 为 核 心 的 三 维 交 互 框 架 设 计 以 3D WIDGETS 为 核 心 的 三 维 人 机 交 互 的 实 现 Manipulator 的 实 现 实 现 具 体 的 WidgetModel 三 维 交 互 的 应 用 实 例 iii
4 集 成 化 医 学 影 像 算 法 平 台 MITK 的 研 究 与 实 现 mitklinewidgetmodel3d 的 应 用 实 例 mitkanglewidgetmodel3d 的 应 用 实 例 mitkclippingplanewidget 的 应 用 实 例 小 结 分 割 算 法 的 设 计 与 实 现 MITK 中 的 分 割 算 法 框 架 数 据 模 块 数 据 获 取 模 块 数 据 输 出 模 块 数 据 处 理 模 块 基 于 阈 值 的 分 割 算 法 在 MITK 中 的 实 现 原 理 概 述 阈 值 分 割 算 法 开 发 包 设 计 与 实 现 阈 值 分 割 结 果 示 意 图 区 域 增 长 算 法 在 MITK 中 的 实 现 原 理 概 述 区 域 生 长 算 法 开 发 包 的 设 计 与 实 现 区 域 生 长 分 割 结 果 交 互 式 分 割 在 MITK 中 的 实 现 原 理 概 述 交 互 式 分 割 算 法 开 发 包 的 设 计 与 实 现 交 互 式 分 割 算 法 的 分 割 结 果 LIVE WIRE 算 法 在 MITK 中 的 实 现 原 理 概 述 live Wire 算 法 包 的 设 计 与 实 现 live wire 分 割 结 果 FAST MARCHING 算 法 在 MITK 中 的 实 现 原 理 概 述 Fast Marching 算 法 开 发 包 的 设 计 与 实 现 iv
5 集 成 化 医 学 影 像 算 法 平 台 MITK 的 研 究 与 实 现 Fast Marching 分 割 结 果 LEVEL SET 算 法 在 MITK 中 的 实 现 原 理 概 述 level set 算 法 开 发 包 的 设 计 与 实 现 level set 分 割 结 果 配 准 算 法 的 设 计 与 实 现 配 准 算 法 简 介 MITK 中 的 配 准 算 法 框 架 几 何 变 换 刚 性 变 换 算 法 线 性 变 换 与 一 对 一 变 换 变 换 算 法 在 MITK 中 的 实 现 图 像 插 值 最 近 邻 插 值 线 性 插 值 PV 插 值 插 值 算 法 在 MITK 中 的 实 现 相 似 性 测 度 灰 度 平 均 差 测 度 归 一 化 相 关 系 数 Pattern Intensity 互 信 息 相 似 性 测 度 在 MITK 中 的 实 现 函 数 优 化 配 准 算 法 实 现 应 用 实 例 小 结 DICOM 标 准 的 实 现 v
6 集 成 化 医 学 影 像 算 法 平 台 MITK 的 研 究 与 实 现 8.1 DICOM 标 准 简 介 DICOM 标 准 的 产 生 和 演 化 DICOM 标 准 的 主 要 特 点 DICOM 标 准 的 总 体 结 构 和 主 要 内 容 MITK 中 DICOM 标 准 的 实 现 DICOM 数 据 编 码 方 式 和 文 件 结 构 [1] [1] DICOM 文 件 读 写 模 块 (DICOM Utility) 的 实 现 DICOM Utility 在 MITK 中 的 封 装 小 结 应 用 MITK 开 发 实 际 项 目 开 发 环 境 的 设 置 一 个 简 单 的 图 像 浏 览 器 用 MITK 进 行 表 面 重 建 一 个 比 较 完 善 的 例 子 扩 充 MITK 功 能 扩 充 MITK 功 能 的 预 备 知 识 实 例 之 一 : 扩 充 READER 功 能 扩 充 Reader 功 能 的 一 般 步 骤 实 例 程 序 的 功 能 实 例 程 序 的 制 作 实 例 之 二 : 扩 充 FILTER 功 能 扩 充 Filter 功 能 的 一 般 步 骤 实 例 程 序 的 功 能 实 例 程 序 的 制 作 小 结 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMED 的 设 计 与 实 现 背 景 介 绍 相 关 工 作 vi
7 集 成 化 医 学 影 像 算 法 平 台 MITK 的 研 究 与 实 现 DVIEWNIX 系 统 简 介 VolView 系 统 简 介 DMED 的 整 体 设 计 DMed 的 设 计 目 标 DMed 提 供 的 功 能 简 介 DMED 的 PLUGIN 整 体 框 架 的 实 现 Plugin SDK 的 实 现 Plugins 的 实 现 DMed Kernel 的 实 现 应 用 实 例 小 结 开 发 3DMED 的 PLUGIN 总 体 介 绍 PLUGIN 实 例 : 使 用 MITK 工 程 的 建 立 及 设 置 实 例 制 作 插 入 到 3DMed PLUGIN 实 例 : 不 使 用 MITK 工 程 的 建 立 及 设 置 实 例 制 作 插 入 到 3DMed 小 结 附 录 A 医 学 影 像 数 据 集 附 录 B MITK 网 站 介 绍 vii
8 1 绪 论 1 绪 论 1.1 医 学 影 像 算 法 平 台 研 究 的 背 景 及 意 义 自 从 德 国 科 学 家 伦 琴 在 1895 年 发 明 X 射 线 以 来,CT( 计 算 机 断 层 成 像 ) MRI( 核 磁 共 振 成 像 ) CR( 计 算 机 X 线 成 像 ) B 超 电 子 内 窥 镜 等 现 代 医 学 影 像 设 备 先 后 出 现, 使 得 传 统 的 医 学 诊 断 方 式 发 生 了 革 命 性 的 变 化 使 用 计 算 机 对 医 学 影 像 设 备 采 集 到 的 影 像 进 行 处 理 这 一 技 术 被 称 为 医 学 影 像 处 理 与 分 析, 它 可 以 辅 助 医 生 进 行 更 好 更 准 确 的 诊 断 随 着 现 代 计 算 机 科 学 技 术 的 发 展, 医 学 影 像 处 理 与 分 析 越 来 越 多 地 受 到 人 们 的 重 视, 现 在 已 经 成 为 一 门 新 兴 的 发 展 迅 速 的 交 叉 科 学 领 域 [1] 21 世 纪 是 以 人 为 中 心 的 世 纪, 如 何 更 全 面 地 掌 握 和 更 有 效 地 利 用 人 的 信 息 将 成 为 许 多 高 新 技 术 产 业 的 关 键 因 素, 而 跟 全 社 会 人 民 的 医 疗 保 健 和 健 康 事 业 息 息 相 关 的 医 学 影 像 处 理 与 分 析 学 科 也 将 在 21 世 纪 得 到 快 速 的 发 展 医 学 影 像 处 理 与 分 析 是 计 算 机 信 息 学 物 理 学 和 医 学 等 相 结 合 的 产 物, 在 20 世 纪 内 经 历 了 学 科 形 成 发 展 和 快 速 发 展 的 过 程, 如 果 说 20 世 纪 是 医 学 影 像 形 成 和 快 速 发 展 的 世 纪, 在 21 世 纪 就 将 是 医 学 影 像 广 泛 应 用 的 世 纪 目 前 随 着 改 革 开 放 和 国 力 不 断 提 高, 我 国 从 国 外 进 口 了 越 来 越 多 的 高 精 密 的 医 疗 设 备 并 在 医 疗 临 床 上 广 泛 使 用 然 而, 由 于 国 内 缺 乏 配 套 的 开 发 队 伍, 也 没 有 形 成 开 展 跨 学 科 开 发 研 究 的 机 制, 一 般 使 用 的 都 是 国 外 公 司 随 机 器 附 带 的 软 件, 使 得 我 国 对 这 些 高 精 密 医 疗 设 备 利 用 及 研 发 的 速 度 缓 慢 考 虑 到 目 前 以 硬 件 设 备 为 主 的 医 疗 器 械 都 必 须 配 套 相 应 的 计 算 机 软 件, 而 软 件 的 成 本 和 开 支 将 逐 步 超 过 硬 件, 目 前 我 国 已 经 逐 步 具 备 了 医 疗 器 械 的 生 产 能 力, 但 是 高 质 量 的 配 套 软 件 仍 然 相 当 缺 乏, 因 为 软 件 的 编 制 依 赖 于 对 科 学 问 题 的 数 学 描 述 和 计 算 方 法 21 世 纪 的 产 业, 医 学 影 像 的 计 算 模 型 和 计 算 方 法 将 成 为 制 约 医 学 软 件 业 的 首 要 因 素, 抓 住 这 一 契 机 将 成 为 我 国 医 疗 信 息 高 质 量 稳 定 发 展 和 参 与 国 际 竞 争 的 重 中 之 重 而 积 累 发 展 具 有 我 国 自 主 知 识 产 权 的 高 质 量 的 医 学 影 像 软 件 平 台, 尤 其 是 底 层 的 算 法 研 发 平 台, 对 促 进 我 国 医 疗 仪 器 设 备 的 应 用 尤 其 是 医 学 影 像 软 件 业 的 持 续 发 展, 并 直 接 造 福 于 人 民 的 医 疗 保 健 和 健 康 事 业 是 非 常 重 要 的 1
9 1 绪 论 由 于 医 学 影 像 领 域 的 研 究 涉 及 的 面 非 常 宽, 研 究 本 身 需 要 多 学 科 的 交 叉, 这 就 导 致 开 发 医 学 影 像 领 域 的 高 质 量 软 件, 尤 其 是 算 法 研 发 平 台 非 常 困 难 但 是 一 旦 研 究 成 功 之 后, 受 益 面 将 会 非 常 大, 今 后 的 发 展 前 途 非 常 广 阔 中 国 是 世 界 上 人 口 最 多 的 国 家, 国 际 间 的 竞 争, 国 家 的 安 全 本 质 上 是 国 民 平 均 素 质 之 间 的 竞 争 利 用 现 代 信 息 学 已 取 得 的 科 学 成 果, 对 人 体 医 学 影 像 信 息 进 行 挖 掘, 建 立 相 应 的 理 论 方 法 并 开 发 相 应 的 软 件 开 发 平 台, 必 将 为 人 类 对 自 身 的 认 知 疾 病 的 诊 断 和 治 疗 国 民 教 育 及 商 业 软 件 的 开 发 提 供 极 好 的 机 会, 使 得 医 学 影 像 信 息 学 科 在 中 国 的 发 展 奠 定 良 好 基 础 可 以 预 计 : 医 学 影 像 将 会 扩 展 到 医 院 的 每 个 科 室, 每 个 病 人, 以 至 全 社 会 每 个 家 庭 成 员, 成 为 每 个 人 健 康 状 况 记 录 的 基 本 信 息 源 之 一 在 发 达 国 家, 尤 其 是 美 国 对 开 发 高 质 量 的 医 学 影 像 软 件 和 算 法 研 发 平 台 非 常 重 视 美 国 国 家 卫 生 院 下 属 的 国 立 医 学 图 书 馆 近 年 投 入 巨 资 支 持 三 家 科 研 机 构 ( 包 括 University of North Carolina University of Utah University of Pennsylvania) 开 发 医 学 影 像 分 割 与 配 准 算 法 的 研 发 平 台 ITK ( Insight Segmentation and Registration Toolkit)[2], 现 在 已 经 开 发 出 初 步 的 版 本 在 医 学 影 像 领 域 的 主 流 国 际 会 议 SPIE Medical Imaging 在 2004 年 的 年 会 上 有 一 个 专 门 的 Session, 叫 做 Visualization Toolkits, 探 讨 医 学 影 像 领 域 内 算 法 研 发 平 台 的 研 究 ; 而 在 Medical Image Computing & Computer Assisted Intervention(MICCAI)2003 会 议 上 有 一 个 专 门 的 Workshop, 叫 做 Software Development Issues for Medical Imaging Computing & Computer Assisted Interventions, 也 是 探 讨 未 来 在 医 学 影 像 领 域 内 高 质 量 软 件, 尤 其 是 算 法 研 发 平 台 (Toolkits) 的 研 究 问 题 考 虑 到 医 学 影 像 技 术 在 未 来 世 纪 的 重 要 性, 以 及 国 内 外 相 关 研 究 人 员 对 一 个 集 成 化 的 医 学 影 像 算 法 研 发 平 台 的 需 求, 本 书 的 工 作 是 希 望 通 过 MITK 的 研 究 与 开 发 以 及 免 费 发 行, 能 够 推 广 医 学 影 像 软 件 在 国 内 的 广 泛 使 用, 为 我 国 民 族 软 件 事 业 和 医 疗 事 业 做 出 自 己 微 薄 的 贡 献 1.2 医 学 影 像 算 法 平 台 研 究 的 内 容 对 于 一 个 实 用 的 医 学 影 像 算 法 平 台 来 说, 有 两 个 大 的 方 面 需 要 研 究 : 第 一 个 方 面 是 整 体 框 架 的 研 究 ; 第 二 个 方 面 是 医 学 影 像 算 法 方 面 的 研 究, 又 包 括 医 学 影 像 分 割 医 学 影 像 配 准 三 维 可 视 化 这 三 大 研 究 领 域 2
10 1 绪 论 整 体 框 架 的 研 究 医 学 影 像 处 理 与 分 析 的 目 的 是 从 数 据 到 知 识, 其 中 在 算 法 层 面 包 含 医 学 影 像 分 割 医 学 影 像 配 准 ( 包 括 图 像 信 息 融 合 ) 三 维 可 视 化 ( 包 括 表 面 绘 制 体 绘 制 和 数 字 几 何 处 理 ) 等 ; 在 数 据 表 达 层 面, 医 学 影 像 数 据 具 有 多 源 (CT MRI PET 等 ) 多 维 ( 二 维 三 维 四 维 及 更 高 维 ) 多 模 态 ( 形 态 生 理 心 理 病 理 ) 异 构 信 息 ( 像 素 矩 阵 表 示 的 图 像 像 素 集 合 表 示 的 目 标 符 号 表 示 的 知 识 ) 等 特 点 如 何 对 这 么 多 的 算 法 以 及 这 么 复 杂 的 数 据 进 行 抽 象, 得 到 统 一 的 数 据 表 达 和 高 效 处 理 方 法, 以 便 能 够 在 一 个 统 一 的 软 件 框 架 中 高 效 精 确 地 来 处 理 是 一 个 比 较 困 难 的 问 题 另 外, 目 前 研 究 人 员 在 各 个 算 法 分 支 已 经 提 出 了 多 种 算 法, 并 且 现 在 对 于 各 种 算 法 性 能 的 评 价 的 研 究 也 越 来 越 受 到 重 视, 比 如 对 分 割 算 法 准 确 性 的 评 价 已 经 成 为 一 个 非 常 前 沿 的 问 题 如 果 将 这 些 算 法 置 入 一 个 统 一 的 计 算 框 架, 组 成 一 条 管 道 式 的 流 水 线, 在 统 一 的 数 据 表 达 统 一 的 算 法 框 架 以 及 统 一 的 参 数 设 置 下, 去 研 究 各 个 算 法 的 性 能 前 一 算 法 对 后 续 算 法 性 能 的 影 响 等 等, 不 仅 能 更 好 地 在 局 部 上 去 理 解 单 个 算 法 的 性 能 指 标 不 同 算 法 的 性 能 差 异, 还 有 助 于 在 整 体 上 去 理 解 和 把 握 不 同 算 法 之 间 的 影 响 整 体 的 瓶 颈 所 在 不 同 信 息 的 整 合 等 医 学 影 像 算 法 的 研 究 目 前 医 学 影 像 算 法 方 面 的 研 究 主 要 分 为 医 学 影 像 分 割 医 学 影 像 配 准 三 维 可 视 化 这 三 大 研 究 领 域, 其 中 每 一 类 下 面 又 可 根 据 一 定 的 原 则 分 为 不 同 的 子 类, 并 且 每 一 个 子 类 都 有 非 常 多 不 同 的 算 法 (1) 医 学 影 像 分 割 算 法 医 学 影 像 分 割 的 主 要 目 的 是 将 医 学 影 像 中 感 兴 趣 的 物 体 ( 一 般 是 病 灶 区 ) 提 取 出 来, 以 前 医 生 往 往 是 通 过 自 己 的 经 验 手 工 分 割 病 灶 区, 而 分 割 的 目 的 就 是 尽 量 自 动 准 确 地 将 这 些 病 灶 区 提 取 出 来 图 像 分 割 是 图 像 处 理 图 像 分 析 和 计 算 机 视 觉 等 领 域 最 经 典 的 研 究 课 题 之 一, 也 是 最 大 的 难 点 之 一, 其 理 论 和 方 法 至 今 尚 未 获 得 圆 满 的 解 决 在 医 学 领 域, 图 像 分 割 也 一 直 是 一 个 非 常 活 跃 的 研 究 课 题, 吸 引 着 很 多 的 研 究 人 员 去 探 索 这 个 问 题 医 学 影 像 分 割 可 以 大 致 分 为 以 下 几 种 类 型 : 基 于 区 域 的 分 割 方 法, 包 括 阈 值 分 割 [3][4] 区 域 生 长 等 [5]; 基 于 边 缘 的 分 割 方 法, 包 括 梯 度 算 子 Roberts 算 子 3
11 1 绪 论 Sobel 算 子 Prewitt 算 子 Laplacian 算 子 和 Kirsch 算 子 等 [1]; 基 于 形 变 模 型 的 方 法, 包 括 Active Contour[6] Level Set[7] Fast Marching[8] 等 ; 基 于 人 机 交 互 的 方 法, 包 括 手 工 交 互 式 分 割 Live Wire 分 割 [9] 等 (2) 医 学 影 像 配 准 算 法 医 学 影 像 配 准 的 主 要 目 的 是 将 两 幅 医 学 影 像 上 的 对 应 点 达 到 空 间 上 的 一 致, 也 就 是 找 出 两 幅 图 像 中 对 应 于 人 体 同 一 位 置 的 点 的 对 应 关 系, 从 而 可 以 让 医 生 把 多 幅 不 同 模 态 的 图 像 融 合 起 来 获 得 更 多 的 信 息 目 前 对 于 多 模 态 医 学 影 像 的 配 准, 以 及 对 具 有 弹 性 形 变 的 图 像 之 间 的 配 准 都 是 非 常 热 门 的 研 究 领 域 医 学 影 像 配 准 算 法 也 有 很 多 不 同 的 类 型, 根 据 空 间 维 数 的 数 目 和 时 间 是 否 为 附 加 维 这 两 点 可 以 分 为 2D/2D 2D/3D 3D/3D 图 像 配 准 ; 根 据 配 准 所 依 据 的 特 征 可 分 为 基 于 外 部 特 征 和 基 于 内 部 特 征 两 大 类 ; 根 据 变 换 的 性 质 可 分 为 刚 性 变 换 仿 射 变 换 投 影 变 换 和 曲 线 变 换 四 种 ; 根 据 用 户 交 互 性 的 多 少, 可 分 为 交 互 的, 半 自 动 的 和 自 动 的 三 种 ; 根 据 配 准 的 医 学 图 像 模 态 可 以 分 为 单 模 图 像 之 间 的 配 准 多 模 图 像 之 间 的 配 准 和 患 者 和 模 态 之 间 的 配 准 这 四 种 ; 根 据 配 准 过 程 中 变 换 参 数 确 定 的 方 式 可 以 分 为 两 种, 一 种 是 通 过 直 接 计 算 公 式 来 得 到, 另 一 种 是 通 过 在 参 数 空 间 中 寻 求 某 个 函 数 的 最 优 解 来 得 到 [10] (3) 三 维 可 视 化 算 法 三 维 可 视 化 的 主 要 目 的 是 将 医 学 影 像 设 备 得 到 的 一 系 列 的 二 维 的 切 片, 使 用 计 算 机 图 形 学 的 技 术, 构 造 出 一 个 器 官 的 三 维 模 型, 并 且 非 常 逼 真 地 显 示 给 医 生, 它 是 科 学 可 视 化 研 究 的 一 个 重 要 的 分 支 目 前 随 着 医 疗 设 备 的 进 展, 医 学 影 像 数 据 集 越 来 越 大, 海 量 的 数 据 给 传 统 的 三 维 重 建 与 绘 制 技 术 带 来 了 非 常 大 的 挑 战, 所 以 目 前 大 数 据 量 的 三 维 医 学 影 像 的 快 速 重 建 和 绘 制 技 术 也 是 一 个 非 常 有 挑 战 性 的 问 题 三 维 可 视 化 可 以 分 为 三 个 大 类 : 表 面 绘 制 (Surface Rendering) 体 绘 制 (Volume Rendering) 和 数 字 几 何 处 理 表 面 绘 制 包 括 经 典 的 Marching Cubes 算 法 [11][12],Cuberille[13] 算 法 等 ; 体 绘 制 包 括 经 典 的 Ray Casting[14] 算 法 Splatting[15] 算 法 Shear Warp[16] 算 法, 以 及 新 近 出 现 的 基 于 图 形 硬 件 GPU 的 加 速 算 法 [17][18] 等 ; 而 数 字 几 何 处 理 [19][20] 是 在 SigGraph 2001 上 才 提 出 的 一 个 新 概 念, 用 于 对 表 面 绘 制 产 生 的 网 格 进 行 处 理, 包 括 网 格 化 简 网 格 细 分 网 4
12 1 绪 论 格 平 滑 等 算 法 1.3 医 学 影 像 算 法 平 台 的 国 内 外 研 究 现 状 如 上 所 述, 医 学 影 像 处 理 算 法 的 三 个 主 要 研 究 领 域 目 前 都 已 经 有 非 常 多 的 成 熟 的 算 法, 并 且 新 的 算 法 还 在 不 断 出 现 除 了 在 算 法 研 究 方 面 的 努 力 外, 一 些 研 究 组 织 为 了 更 好 地 利 用 现 有 的 算 法, 避 免 重 复 的 劳 动, 开 发 了 许 多 算 法 平 台 (Algorithms Toolkit), 这 些 算 法 平 台 不 仅 封 装 了 他 们 自 己 的 算 法, 还 封 装 了 很 多 已 经 成 熟 的 相 关 算 法 这 些 算 法 平 台 极 大 地 便 利 了 医 学 影 像 领 域 的 研 究 者, 他 们 可 以 在 这 些 平 台 上 来 创 建 自 己 的 实 验 环 境, 验 证 自 己 的 算 法, 而 不 用 从 头 再 写 一 些 已 经 成 熟 的 算 法 可 以 作 个 比 喻, 这 些 平 台 的 作 用 就 相 当 于 Matlab 对 于 研 究 人 员 的 作 用 一 样, 不 过 它 们 是 专 门 针 对 医 学 影 像 领 域 的 目 前 在 医 学 影 像 研 究 人 员 中 使 用 最 广 泛 的 两 个 算 法 平 台 是 VTK(Visualization Toolkit) 和 ITK, 虽 然 还 有 其 它 一 些 平 台, 但 是 它 们 都 不 系 统, 只 是 针 对 某 一 个 特 定 的 领 域 的, 另 外,VTK 和 ITK 也 是 对 MITK 的 设 计 影 响 最 大, 最 相 关 的 两 个 算 法 平 台, 所 以 下 面 分 别 介 绍 一 下 它 们 的 发 展 历 程 和 发 展 现 状, 并 给 出 它 们 的 局 限 性 VTK 简 介 VTK(Visualization ToolKit)[21] 是 一 套 进 行 数 据 可 视 化 的 开 发 工 具 包, 最 早 在 1993 年 12 月 由 美 国 GE 公 司 研 发 部 门 的 Will Schroeder 和 Ken Martin 首 次 发 布, 当 时 是 作 为 The Visualization Toolkit: An Object-Oriented Approach to 3D Graphics 这 本 书 的 配 套 软 件 赠 送, 后 来 在 1998 年 时, 此 书 出 版 第 二 版, 并 且 在 此 五 年 期 间 使 用 VTK 的 人 数 不 断 增 加, 形 成 了 一 个 社 区,VTK 也 以 开 放 源 码 (Open Source) 的 形 式 开 发 现 在 这 本 书 已 经 出 到 第 三 版 [22], 同 时 VTK 也 由 美 国 Kitware 公 司 负 责 维 护, 全 世 界 的 开 发 人 员 都 可 以 贡 献 自 己 的 力 量 VTK 完 全 采 用 面 向 对 象 的 设 计 思 想 来 设 计 与 开 发 [23], 其 提 供 了 非 常 强 大 的 功 能, 提 供 了 超 过 300 个 C++ 类, 并 且 可 以 支 持 跨 平 台 开 发, 支 持 Windows Unix Linux 等 多 种 平 台 [24] 值 得 一 提 的 是,VTK 并 不 是 专 门 针 对 医 学 影 像 领 域 开 发 的 平 台, 它 的 主 要 目 标 是 通 用 可 视 化 领 域, 但 是 它 仍 然 在 医 学 影 像 领 域 得 到 了 比 较 广 泛 的 应 用, 这 一 方 面 与 其 几 个 主 力 研 发 人 员 在 GE 公 司 的 背 景 分 不 开 ( 他 们 是 表 面 绘 制 经 典 算 法 Marching Cubes 算 法 的 发 明 人 ), 另 外 一 个 方 面 是 因 为 VTK 里 面 提 供 了 表 面 绘 制 体 绘 制 一 部 分 数 字 几 何 处 理 算 法, 这 些 都 对 医 学 5
13 1 绪 论 影 像 领 域 内 的 研 究 有 所 帮 助 发 展 到 现 在,VTK 的 稳 定 版 本 已 经 发 行 到 4.2 版 本, 并 且 新 的 5.0 版 本 也 在 持 续 地 开 发 中, 已 经 成 为 通 用 可 视 化 领 域 内 最 负 盛 名 的 软 件 开 发 包, 也 在 医 学 影 像 领 域 内 赢 得 了 尊 敬 ITK 简 介 ITK(Insight Segmentation and Registration Toolkit)[2] 的 主 要 目 的 是 提 供 一 个 医 学 影 像 分 割 与 配 准 的 算 法 平 台, 它 的 起 源 还 是 基 于 美 国 的 可 视 人 体 项 目 [25], 当 可 视 人 体 的 数 据 采 集 完 成 以 后, 对 这 些 数 据 进 行 配 准 并 分 割 就 形 成 了 迫 切 的 需 求, 因 此 在 1999 年, 由 美 国 NIH( 国 家 卫 生 院 ) 下 属 的 NLM( 国 立 医 学 图 书 馆 ) 发 起 了 一 个 投 标 活 动, 要 出 资 资 助 开 发 一 个 分 割 与 配 准 的 算 法 研 发 平 台, 作 为 可 视 人 体 项 目 的 一 个 工 具, 对 可 视 人 体 项 目 得 到 的 数 据 进 行 处 理 与 分 析 最 终 选 中 六 家 单 位 合 作 开 发, 包 括 University of North Carolina,University of Utah,University of Pennsylvania 三 个 大 学, 以 及 Kitware GE Insightful 三 个 商 业 公 司 从 1999 年 10 月 开 始, 到 2002 年 10 月, 已 经 完 成 了 第 一 个 三 年 计 划, 并 成 功 发 行 了 ITK 1.0 与 VTK 相 同 的 是,ITK 的 框 架 设 计 也 是 由 Kitware 公 司 来 完 成 的 ; 但 是 与 VTK 严 重 不 同 的 是, 它 们 的 设 计 风 格 截 然 不 同 ITK 大 量 使 用 了 1998 年 以 后 ANSI C++ 标 准 里 面 的 新 特 性, 尤 其 是 Template( 模 板 ), 并 且 ITK 整 个 就 是 基 于 范 型 编 程 (Generic Programming)[26] 这 种 设 计 思 想 来 设 计 与 实 现 的 ITK 也 可 以 支 持 跨 平 台 开 发, 支 持 Windows Unix Linux 等 多 种 平 台, 目 前 也 是 采 用 Open Source 的 形 式 发 行, 最 大 限 度 地 推 广 它 经 过 四 年 多 的 开 发, 目 前 ITK 的 稳 定 版 本 已 经 发 行 到 1.6, 提 供 了 几 乎 所 有 主 流 的 医 学 影 像 分 割 与 配 准 算 法 [27], 并 且 现 在 还 一 直 在 持 续 地 进 化, 它 已 经 并 且 将 继 续 为 医 学 影 像 领 域 内 的 研 究 人 员 提 供 一 个 分 割 与 配 准 算 法 的 仓 库 和 基 础 VTK 和 ITK 的 局 限 性 VTK 和 ITK 目 前 已 经 成 为 国 际 上 非 常 知 名 的 可 视 化 与 医 学 影 像 分 割 与 配 准 的 算 法 研 发 平 台, 已 经 并 且 正 在 为 研 究 人 员 提 供 着 非 常 多 的 便 利 但 是 由 于 一 些 原 因, 导 致 VTK 或 者 ITK 或 者 VTK+ITK 有 一 些 自 己 的 缺 陷, 影 响 了 其 在 更 大 范 围 的 广 泛 使 用 首 先, 因 为 ITK 并 不 提 供 可 视 化 的 能 力, 所 以 一 般 要 与 VTK 联 合 起 来 使 用 6
14 1 绪 论 才 能 构 成 一 个 比 较 完 整 的 医 学 影 像 的 处 理 与 分 析 系 统 首 先 使 用 ITK 进 行 影 像 的 配 准 分 割, 然 后 再 使 用 VTK 进 行 三 维 可 视 化, 观 看 结 果, 这 样 就 极 大 地 增 加 了 复 杂 度 尤 其 是 VTK 和 ITK 所 使 用 的 编 程 风 格 完 全 不 同,VTK 开 发 的 比 较 早, 在 1998 年 ANSI C++ 标 准 制 定 之 前 就 已 经 比 较 成 型, 所 以 使 用 的 是 传 统 的 Object-Oriented( 面 向 对 象 ) 的 设 计 和 开 发 方 法 ; 而 ITK 是 在 1999 年 才 开 始 开 发 的, 所 以 运 用 了 许 多 新 的 C++ 语 言 的 特 性, 以 及 Generic Programming( 范 型 编 程 ) 的 设 计 和 开 发 方 法 这 种 编 程 风 格 上 的 不 一 致, 导 致 了 同 时 使 用 VTK 和 ITK 时, 必 须 学 习 两 套 规 模 都 相 当 庞 大 的 开 发 平 台, 对 使 用 者 造 成 了 一 定 的 学 习 难 度 其 次,VTK 是 一 个 面 向 通 用 可 视 化 领 域 的 一 个 开 发 平 台, 并 不 是 专 门 针 对 医 学 领 域 的 VTK 的 规 模 相 当 庞 大, 里 面 的 算 法 也 很 多, 这 样 一 方 面 使 得 它 的 适 用 面 非 常 宽, 有 很 多 领 域 的 研 究 人 员 都 可 以 使 用 它, 但 是 另 外 一 方 面 也 使 得 它 的 复 杂 度 大 大 增 加 同 时 因 为 要 照 顾 到 各 个 领 域, 在 设 计 VTK 的 时 候, 主 要 目 标 是 一 个 通 用 的 灵 活 的 框 架, 并 没 有 对 某 一 个 特 定 的 算 法 进 行 优 化, 因 此 VTK 的 速 度 并 不 是 太 令 人 满 意 最 后,ITK 是 专 门 针 对 医 学 影 像 的 分 割 和 配 准 而 实 现 的 一 个 开 发 平 台, 因 为 历 史 性 的 原 因,ITK 并 没 有 重 新 实 现 可 视 化 的 功 能, 而 是 利 用 VTK 的 可 视 化 的 功 能 对 ITK 来 说, 设 计 的 时 候 利 用 了 非 常 多 的 现 代 C++ 语 言 的 新 特 性, 并 且 大 量 使 用 了 模 板 这 本 来 是 一 件 非 常 好 的 事 情, 但 是 因 为 C++ 的 新 标 准 刚 颁 布 没 有 几 年, 导 致 现 在 有 很 多 编 译 器 不 能 完 全 编 译 ITK; 并 且 因 为 ITK 里 面 大 量 依 赖 于 STL 和 模 板, 导 致 最 终 只 能 编 译 成 一 个 静 态 的 函 数 库, 而 不 能 实 现 动 态 的 加 载 ; 同 时 最 重 要 的 一 点 是, 有 很 多 医 学 影 像 领 域 的 研 究 人 员 对 C++ 的 新 标 准 以 及 模 板 编 程 技 术 了 解 的 不 是 很 深 入, 导 致 他 们 在 使 用 ITK 时 感 觉 就 象 在 学 习 一 门 新 的 编 程 语 言 由 于 上 面 的 这 些 原 因, 现 在 ITK 还 只 是 在 一 个 比 较 小 的 范 围 内 被 使 用 1.4 本 书 的 主 要 内 容 考 虑 到 上 面 这 些 因 素, 本 书 介 绍 的 主 要 是 我 们 实 验 室 研 发 的 集 成 化 的 医 学 影 像 处 理 与 分 析 算 法 研 发 平 台 MITK(Medical Imaging ToolKit) 其 目 的 很 简 单, 所 谓 集 成 化, 就 是 指 在 一 个 统 一 的 框 架 里 面 来 实 现 医 学 影 像 分 割 配 准 7
15 1 绪 论 三 维 可 视 化 等 算 法, 弥 补 VTK+ITK 的 缺 憾 MITK 并 不 是 要 取 代 VTK 和 ITK, 而 只 是 给 医 学 影 像 领 域 内 的 研 究 人 员 和 开 发 人 员 提 供 另 外 的 一 个 可 用 选 择, 用 来 丰 富 国 际 上 的 医 学 影 像 算 法 平 台 另 外, 为 了 验 证 使 用 MITK 开 发 复 杂 医 学 影 像 实 用 系 统 的 能 力, 我 们 还 基 于 MITK 设 计 并 实 现 了 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed Kitware 公 司 也 曾 经 使 用 VTK 来 开 发 了 一 个 医 学 影 像 可 视 化 系 统 VolView, 但 可 惜 的 是 这 是 一 个 商 业 软 件, 即 使 用 于 科 研 目 的 也 是 需 要 购 买 License 的 MITK 和 3DMed 均 作 为 免 费 软 件 (Freeware) 在 Internet 上 发 布 ( 本 书 工 作 的 另 外 一 层 意 义 就 是 希 望 能 够 推 广 医 学 影 像 软 件 在 国 内 的 广 泛 使 用 具 体 来 讲, 本 书 介 绍 的 内 容 主 要 集 中 在 两 个 方 面 : 第 一 个 方 面 是 算 法 研 究 方 面, 在 表 面 绘 制 的 算 法 尤 其 是 大 规 模 医 学 数 据 的 可 视 化 算 法 的 研 究 方 面 作 了 一 些 探 讨, 这 个 部 分 主 要 在 第 三 章 中 介 绍 ; 第 二 个 方 面 是 平 台 的 框 架 设 计 以 及 分 割 配 准 可 视 化 算 法 在 MITK 中 的 实 现 方 面, 主 要 是 设 计 并 实 现 集 成 化 的 医 学 影 像 处 理 与 分 析 算 法 平 台 MITK, 这 个 部 分 的 内 容 分 散 在 本 书 的 各 个 章 节 本 书 的 主 要 内 容 包 括 两 个 方 面 : 一 是 研 究 并 设 计 实 现 了 一 个 集 成 化 的 医 学 影 像 处 理 与 分 析 算 法 平 台 MITK(Medical Imaging ToolKit) MITK 在 一 个 统 一 的 框 架 里 面 实 现 了 医 学 影 像 处 理 与 分 析 的 三 大 研 究 领 域 的 算 法, 包 括 医 学 影 像 分 割 医 学 影 像 配 准 以 及 三 维 可 视 化, 而 国 际 上 相 同 类 型 的 平 台 如 VTK 和 ITK 均 只 实 现 了 一 部 分 功 能, 并 且 它 们 还 具 有 完 全 不 同 的 框 架 和 风 格 另 外,MITK 是 专 门 针 对 医 学 影 像 这 一 特 定 领 域 的, 遵 循 小 而 精 的 原 则, 对 一 些 重 要 算 法 作 了 特 定 的 优 化, 支 持 新 的 CPU 指 令 集 SSE 的 优 化 以 及 新 的 显 卡 中 的 GPU 的 利 用 为 了 最 大 限 度 地 使 MITK 得 到 使 用, 其 在 Internet 上 面 向 国 际 免 费 发 行, 相 关 科 研 人 员 可 以 免 费 地 下 载 并 在 自 己 的 科 研 工 作 中 进 行 二 次 开 发 二 是 基 于 MITK 设 计 并 开 发 了 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed, 一 方 面 证 明 了 MITK 可 以 胜 任 现 实 世 界 当 中 医 学 影 像 领 域 复 杂 软 件 系 统 对 算 法 平 台 的 需 求, 另 一 方 面 的 目 的 是 为 相 关 科 研 人 员 和 普 通 用 户 提 供 一 个 易 于 使 用 的 软 件 工 具 目 前 3DMed 也 作 为 免 费 软 件 发 行, 如 果 MITK 和 3DMed 能 够 推 广 医 学 影 像 软 件 在 国 内 的 广 泛 使 用, 那 么 也 就 体 现 出 本 书 工 作 的 意 义 所 在 了 本 书 以 MITK 的 研 究 与 设 计 为 主 线 贯 穿 始 终, 在 软 件 设 计 框 图 的 绘 制 上, 始 8
16 1 绪 论 终 遵 循 UML( 统 一 建 模 语 言 )[28] 的 规 范 内 容 安 排 如 下 : 第 二 章 介 绍 MITK 的 总 体 框 架 的 设 计 ; 第 三 章 侧 重 介 绍 在 面 绘 制 算 法 研 究 方 面 的 工 作, 包 括 基 于 单 层 表 面 跟 踪 的 重 建 算 法 和 基 于 点 的 重 建 算 法, 并 且 给 出 了 面 绘 制 算 法 在 MITK 中 的 实 现 ; 第 四 章 介 绍 MITK 中 的 一 个 重 要 部 分, 即 体 绘 制 的 框 架 设 计 及 具 体 的 算 法 实 现 ; 第 五 章 给 出 了 在 三 维 人 机 交 互 方 面 的 一 些 探 索, 并 给 出 了 其 在 MITK 中 的 框 架 和 实 现 ; 第 六 七 章 分 别 给 出 了 不 同 的 分 割 算 法 和 配 准 算 法 在 MITK 中 的 实 现 ; 第 八 章 介 绍 了 DICOM 标 准 以 及 其 在 MITK 中 的 实 现, 第 九, 十 两 章 分 别 介 绍 应 用 MITK 开 发 项 目 和 MITK 的 扩 展 功 能, 以 便 读 者 可 以 方 便 的 使 用 MITK; 第 十 一 章 介 绍 基 于 MITK 算 法 平 台 的 三 维 医 学 影 像 处 理 与 分 析 实 用 软 件 系 统 3DMed 的 设 计 与 实 现 ; 第 十 二 章 介 绍 了 3DMed 中 的 Plugin, 这 样 读 者 也 可 以 将 自 己 的 算 法 用 plugin 开 发 出 来, 集 成 到 3dMed 中 附 录 A 介 绍 一 些 医 学 影 像 数 据 集 的 资 源, 方 便 读 者 用 来 测 试 自 己 算 法 附 录 B 介 绍 了 MITK 论 坛 的 情 况 参 考 文 献 1. 田 捷, 包 尚 联, 周 明 全. 医 学 影 像 处 理 与 分 析. 北 京 : 电 子 工 业 出 版 社, Insight Segmentation and Registration Toolkit, 3. Y. J. Zhang, J. J. Gerbrands. Transition region determination based thresholding. Pattern Recognition Letter, 1991, 12: P.Sahoo, C.Wilkins and J.Yeager, Threshold selection using Renyi's entropy. Pattern Recognition, 1997, 30(1): I.N. Manousakas, P.E. Undrill, G.G. Cameron, T.W. Redpath, Split-and-merge segmentation of magnetic resonance medical images: performance evaluation and extension to three dimensions. Computers and Biomedical Research, 1998, 31: M. Kass, A. Witkin, and D. Terzopoulos, Snakes - Active Contour Models, International Journal of Computer Vision, 1(4): , S. Osher and J. Sethian, Fronts propagating with curvature dependent speed, J. Comput. Phys., Vol.79, pp.12-49, J.A.Sethian, Fast marching methods, SIAM Rev., 41(1999), pp A. X. Falcao, J. K. Udupa, S. Samarasekera, Shoba Sharma, User-steered Image Segmentation Paradigms : Live Wire and Live Lane, Graphic models and Image Processing, 1998, 60: J. B. A. Maintz and M. A. Viergever, A survey of medical image registration, Medical Image Analysis, 2(1):1-36,
17 1 绪 论 11. W. Lorensen and H. Cline, Marching cubes: a high resolution 3D surface construction algorithm. ACM Computer Graphics, 21(4): pp , Gregory M. Nielson, On Marching Cubes, IEEE Transaction on Visualization and Computer Graphics, Vol. 9, No. 3, pp , G. T. Herman, H. K. Liu, Three-Dimensional Display of Human Organs form Computed Tomography, Computer Graphics & Image Processing, 1979, Vol. 9, pp M. Levoy, Display of surfaces from volume data, IEEE Transaction on Computer Graphics and Applications, 1988, 8(3): D. Laur and P. Hanrahan, Hierarchical Splatting: A Progressive Refinement Algorithm for Volume Rendering, ACM Computer Graphics, Proc. SIGGRAPH 93, 25(4): , July P. Lacroute and M. Levoy, Fast volume rendering using a shear-warp factorization of the viewing transformation, Proc. SIGGRAPH 94, pp , K. Engel, M. Kraus, and T. Ertl, High-quality pre-integrated volume rendering using hardware accelerated pixel shading, in Proc. Eurographics/SIGGRAPH Workshop on Graphics Hardware C. Resk-Salama, K. Engel, M. Bauer, G. Greiner, T. Ertl, Interactive Volume Rendering on Standard PC Graphics Hardware Using Multi-Textures and Multi-Stage-Rasterization,in Proc. Eurographics/SIGGRAPH Workshop on Graphics Hardware Wim Sweldens, Peter Schr oder, Digital Geometry Processing, Course Notes for SigGraph'2001, Los Angeles, California, Aug. 12, 何 晖 光, 数 字 几 何 的 研 究 及 其 在 医 学 可 视 化 中 的 应 用,[ 博 士 论 文 ], 北 京 : 中 科 院 自 动 化 所, Visualization Toolkit, Will Schroeder, Ken Martin, Bill Lorensen Schroeder, The Visualization Toolkit: An Object Oriented Approach to 3D Graphics 3rd Edition, Kitware, Inc. Publisher, William J. Schroeder, Kenneth M. Martin, William E. Lorensen, The Design and Implementation Of An Object-Oriented Toolkit For 3D Graphics And Visualization, Proc. Of IEEE Visualization ' William J. Schroeder, Lisa S. Avila, William Hoffman, Visualizing with VTK: A Tutorial, IEEE Transaction on Computer Graphics and Applications, Vol. 20, No. 5, pp , Ackerman, M. J., The Visible Human Project, Medicine Meets Virtual Reality II: Interactive Technology and Healthcare, pp Matthew H. Austern, Generic Programming and the Stl : Using and Extending the C++ 10
18 1 绪 论 Standard Template Library, Addison-Wesley Professional Computing Series, Luis Ibanez, Will Schroeder, Lydia Ng, Josh Cates. The ITK Software Guide: The Insight Segmentation and Registration Toolkit (version 1.4), Kitware, Inc. Publisher, Fowler M., Scott K. UML Distilled Second Edition: A Brief Guide to the Standard Object Modeling Language, Addison Wesley,
19 2 MITK 的 总 体 设 计 2 MITK 的 总 体 设 计 为 了 在 一 个 统 一 的 框 架 里 面 实 现 一 个 具 有 一 致 接 口 的 可 复 用 的 包 括 可 视 化 分 割 配 准 功 能 的 集 成 化 的 医 学 影 像 算 法 平 台,MITK 使 用 了 基 于 数 据 流 的 模 型 [1], 把 算 法 对 象 和 数 据 对 象 分 开 来 考 虑, 减 少 它 们 之 间 的 藕 合 性, 同 时 形 成 一 个 优 雅 的 算 法 的 计 算 框 架 另 外, 为 了 增 加 其 易 用 性,MITK 并 没 有 象 ITK 那 样 使 用 范 型 编 程 和 模 板, 而 是 采 用 的 传 统 的 面 向 对 象 的 设 计 方 法 [2] 另 外,MITK 的 设 计 还 吸 收 了 近 几 年 流 行 起 来 的 基 于 Design Pattern[3] 的 设 计 方 法 所 谓 Design Pattern, 就 是 软 件 工 业 界 在 长 期 的 编 写 大 型 复 杂 软 件 的 过 程 中 总 结 出 来 的 一 些 经 典 的 解 决 问 题 的 方 法, 其 中 每 个 都 是 行 之 有 效 的, 经 过 实 践 的 检 验 Design Pattern 结 合 了 面 向 对 象 设 计 与 分 析, 面 向 特 定 问 题 的 分 析 等 设 计 方 法, 可 以 很 大 程 度 地 提 高 软 件 开 发 的 质 量 和 效 率 虽 然 已 经 有 研 究 者 总 结 了 流 行 最 广 泛 的 Design Pattern, 但 是 这 些 都 是 面 向 通 用 领 域 的 比 较 成 功 的 设 计 软 件 的 方 法, 面 向 医 学 影 像 处 理 与 分 析 这 一 特 定 领 域 的 软 件 设 计 的 Design Pattern 还 没 有 得 到 充 分 的 重 视 本 书 通 过 对 MITK 的 研 究 与 设 计, 同 时 还 探 讨 了 Design Pattern 在 医 学 影 像 领 域 内 软 件 开 发 的 应 用 本 章 首 先 给 出 了 MITK 的 总 体 设 计 目 标, 然 后 介 绍 它 的 总 体 框 架, 从 数 据 模 型 和 算 法 模 型 两 个 方 面 给 出 说 明, 最 后 给 出 了 MITK 整 体 框 架 里 面 的 一 些 基 础 设 施, 便 于 后 续 章 节 的 描 述 2.1 MITK 的 设 计 目 标 对 于 软 件 设 计, 尤 其 是 特 定 领 域 内 的 复 杂 软 件 设 计, 必 须 事 先 有 一 个 非 常 明 确 的 设 计 目 标 MITK 从 一 开 始 设 计, 就 始 终 追 求 以 下 几 个 高 层 的 设 计 目 标 : 统 一 的 风 格 VTK 和 ITK 由 于 历 史 性 的 原 因, 使 用 了 不 同 的 编 程 风 格 VTK 在 1998 年 ANSI C++ 标 准 制 定 之 前 就 已 经 比 较 成 型, 所 以 使 用 的 是 传 统 的 Object-Oriented( 面 向 对 象 ) 的 设 计 和 开 发 方 法 ; 而 ITK 是 在 1999 年 才 开 始 开 发 的, 所 以 运 用 了 许 多 新 的 C++ 标 准 规 定 的 语 言 特 性, 以 及 Generic Programming( 范 型 编 程 ) 的 设 计 和 开 发 方 法 这 种 编 程 风 格 上 的 不 一 致, 给 VTK+ITK 的 使 用 者 带 来 了 很 大 的 不 方 12
20 2 MITK 的 总 体 设 计 便 而 MITK 使 用 统 一 的 面 向 对 象 的 设 计 方 法, 再 加 上 一 些 Design Patterns( 设 计 模 式 )[3] 的 使 用, 提 供 一 个 统 一 的 编 程 风 格 和 整 体 框 架 有 限 目 标 MITK 是 专 门 面 向 医 学 影 像 领 域 的, 只 关 注 于 这 一 特 定 领 域 内 的 算 法, 不 追 求 大 而 全, 只 追 求 少 而 精 例 如 MITK 中 可 视 化 算 法 只 包 括 规 则 数 据 场 ( 医 学 影 像 设 备 得 到 的 数 据 场 即 为 此 类 ) 的 支 持, 分 割 算 法 的 输 出 也 只 限 于 是 一 个 二 值 数 据 场 这 样 的 设 计 准 则 简 化 了 整 个 MITK, 使 得 其 保 持 在 一 个 中 等 的 规 模, 但 同 时 提 供 了 必 须 的 功 能, 包 括 主 流 的 可 视 化 分 割 和 配 准 算 法 的 实 现 可 移 植 性 为 了 使 MITK 能 够 得 到 最 广 泛 的 应 用, 可 移 植 性 是 非 常 重 要 的 一 个 环 节 整 个 MITK 的 代 码 全 部 使 用 ANSI C++ 编 写, 没 有 使 用 任 何 编 译 器 提 供 的 特 殊 关 键 字 或 者 特 殊 函 数, 并 且 尽 量 降 低 平 台 相 关 的 代 码 量 在 整 个 MITK 中, 与 平 台 相 关 的 部 分 就 是 与 窗 口 系 统 打 交 道 的 部 分, 此 处 针 对 不 同 的 操 作 系 统 写 了 不 同 的 代 码, 目 前 支 持 Windows 系 列 操 作 系 统 Unix Linux 等 而 MITK 目 前 可 以 在 多 数 主 流 的 C++ 编 译 器 下 编 译 通 过, 包 括 对 模 板 支 持 不 完 善 的 编 译 器 代 码 优 化 因 为 医 学 影 像 处 理 与 分 析 算 法 中 很 多 算 法 计 算 量 大, 尤 其 是 可 视 化 算 法, 对 实 时 性 要 求 很 高, 这 些 就 需 要 对 代 码 进 行 优 化 因 为 MITK 的 规 模 保 持 在 中 等, 这 就 使 得 对 一 些 关 键 算 法 进 行 优 化 成 为 可 能 MITK 支 持 对 CPU 的 扩 展 指 令 集 的 使 用, 比 如 Intel 的 MMX SSE 指 令 集, 为 了 不 至 于 违 背 可 移 植 性 目 标,MITK 中 在 使 用 SSE 等 指 令 集 时, 并 没 有 直 接 使 用 汇 编 语 言, 而 是 使 用 了 编 译 器 提 供 的 Intrinsics 指 令, 目 前 MITK 当 中 实 现 了 SSE 加 速 的 矩 阵 和 矢 量 运 算 双 线 性 和 三 线 性 插 值 计 算 等 ; 另 外 MITK 还 支 持 对 目 前 主 流 显 卡 中 GPU 的 编 程, 实 现 了 使 用 纹 理 映 射 进 行 Volume Rendering( 体 绘 制 ) 的 加 速 算 法 [4][5] 2.2 MITK 的 整 体 计 算 框 架 医 学 影 像 处 理 与 分 析 技 术 包 含 可 视 化 分 割 配 准 三 大 类 算 法, 每 一 类 下 面 又 都 有 很 多 不 同 的 方 法, 医 学 影 像 数 据 本 身 也 包 含 多 维 ( 一 维 二 维 三 维 ) 多 源 (CT MRI 等 ) 多 态 ( 以 像 素 矩 阵 形 式 表 达 的 图 像 信 息 以 像 素 集 合 形 式 13
21 2 MITK 的 总 体 设 计 表 达 的 目 标 信 息 以 及 以 几 何 形 状 形 式 表 达 的 知 识 信 息 ) 的 特 点, 这 些 都 决 定 了 将 其 整 合 进 一 个 统 一 的 框 架 之 内 是 一 个 比 较 复 杂 的 过 程, 必 须 经 过 抽 象 建 模, 才 能 得 到 一 个 比 较 灵 活 可 用 的 整 体 计 算 框 架 有 了 计 算 框 架 以 后, 还 可 以 在 相 同 的 条 件 之 下 比 较 同 一 种 类 不 同 算 法 的 性 能 效 果, 进 行 算 法 的 比 较 ; 另 外 还 可 以 进 行 算 法 的 评 价, 比 如 对 分 割 算 法 配 准 算 法 的 准 确 性 进 行 评 价 基 于 数 据 流 模 型 的 整 体 框 架 正 如 前 面 提 到 的 一 样,MITK 的 计 算 框 架 采 用 基 于 数 据 流 的 模 型, 以 数 据 处 理 为 中 心, 将 算 法 和 数 据 对 象 分 开 考 虑, 这 种 模 型 很 适 合 那 些 牵 涉 大 量 不 同 的 算 法 需 要 处 理 不 同 类 型 数 据 的 领 域 的 应 用 在 MITK 的 数 据 流 模 型 中, 数 据 和 算 法 均 使 用 对 象 表 示 ; 一 个 算 法 被 抽 象 成 一 个 滤 波 器 (Filter), 它 接 受 一 个 输 入, 生 成 一 个 输 出, 其 中 输 入 和 输 出 均 为 数 据 对 象 (Data), 一 个 算 法 的 输 出 可 以 作 为 另 外 一 个 算 法 的 输 入 通 过 这 样 的 模 型, 一 连 串 的 算 法 可 以 被 串 成 一 个 流 水 线, 组 成 统 一 的 计 算 框 架, 如 图 2-1 所 示 图 2-1 MITK 的 计 算 框 架 在 图 中,Data 表 示 抽 象 的 数 据 对 象,Source Filter 和 Target 分 别 表 示 三 种 不 同 的 抽 象 算 法 对 象, 它 们 的 意 义 分 别 如 下 所 示 : Data: Data 是 对 医 学 影 像 领 域 内 要 处 理 的 数 据 所 进 行 的 抽 象 对 于 医 学 影 像 处 理 与 分 析 系 统 来 说, 要 能 够 处 理 多 种 不 同 的 数 据, 在 MITK 中 通 过 Data 的 各 个 具 体 的 子 类 来 描 述 不 同 的 数 据 对 象, 代 表 了 数 据 流 模 型 中 最 重 要 的 数 据 对 象 在 下 一 小 节 将 会 具 体 讲 述 MITK 中 的 数 据 模 型 Source: Source 是 算 法 类 的 一 种, 但 是 它 只 有 输 出, 没 有 输 入, 代 表 一 个 流 水 线 的 起 源 Source 的 作 用 是 负 责 生 成 整 个 流 水 线 的 起 始 数 据, 比 如 从 磁 盘 上 14
22 2 MITK 的 总 体 设 计 读 文 件, 或 者 用 某 些 算 法 生 成 数 据 Filter: Filter 是 算 法 类 的 一 种, 它 负 责 对 数 据 对 象 进 行 处 理, 代 表 各 种 各 样 的 算 法 Filter 有 一 个 输 入, 一 个 输 出, 为 了 概 念 上 简 单,MITK 中 不 支 持 多 输 入 多 输 出 的 Filter, 而 是 通 过 Filter 的 具 体 子 类 提 供 的 辅 助 函 数 来 实 现 在 小 节 中 将 具 体 讲 述 MITK 中 的 算 法 模 型 Target:Target 是 算 法 类 的 一 种, 顾 名 思 义, 它 代 表 整 个 流 水 线 数 据 的 终 点 Target 只 有 输 入, 没 有 输 出, 它 的 作 用 是 将 最 后 的 数 据 放 在 一 个 合 适 的 位 置, 终 止 流 水 线 的 执 行 比 如 将 得 到 的 结 果 数 据 保 存 至 磁 盘, 或 者 将 得 到 的 结 果 在 屏 幕 上 显 示 出 来 有 了 这 些 高 层 的 概 念 以 后, 就 可 以 很 容 易 地 将 这 些 组 件 联 系 在 一 块, 组 成 一 个 实 用 的 系 统 如 图 2-1 所 示, 整 个 流 水 线 从 一 个 Source 开 始, 经 过 中 间 n 个 Filter 的 处 理, 最 终 终 结 于 Target 这 种 抽 象 关 系 足 以 描 述 医 学 影 像 处 理 与 分 析 这 类 以 数 据 处 理 为 中 心 的 应 用 程 序 的 需 求, 并 且 在 概 念 上 非 常 简 洁 清 晰, 可 以 提 供 一 个 统 一 的 模 型 来 设 计 MITK 的 整 个 计 算 框 架 数 据 模 型 数 据 表 达 是 数 据 流 模 型 中 的 一 个 核 心 的 内 容, 数 据 对 象 起 着 连 接 算 法 流 水 线 的 重 要 作 用 考 虑 到 MITK 的 设 计 目 标 之 一 是 有 限 目 标, 只 关 注 于 医 学 影 像 这 个 特 定 的 领 域, 此 处 就 简 化 了 数 据 模 型 的 建 立 通 过 对 医 学 影 像 数 据 进 行 仔 细 的 分 析 以 后,MITK 中 的 数 据 对 象 (Data) 可 以 被 具 体 化 为 Volume 和 Mesh 两 种 不 同 的 具 体 数 据 对 象,Data 的 类 继 承 关 系 如 图 2-2 所 示 : 图 2-2 MITK 的 数 据 模 型 在 图 中,Volume 用 来 表 达 一 个 医 学 影 像 数 据 集, 提 供 一 个 多 维 ( 包 括 一 15
23 2 MITK 的 总 体 设 计 二 三 维 ) 多 模 态 (CT MRI 等 ) 的 规 则 数 据 场 的 抽 象 Volume 提 供 了 丰 富 的 接 口 来 给 算 法 对 象 使 用, 图 中 给 出 了 一 部 分 最 常 用 的 函 数 接 口, 这 些 接 口 可 以 使 得 其 它 的 对 象 可 以 很 方 便 地 访 问 其 内 部 的 实 际 数 据 Volume 是 MITK 中 的 核 心 对 象 之 一 Mesh 用 来 表 达 一 个 几 何 数 据, 尤 其 是 以 三 角 面 片 网 格 形 式 表 达 的 几 何 模 型 Mesh 内 部 的 结 构 基 于 半 边 数 据 结 构 [6], 它 提 供 了 对 一 维 线 段 二 维 平 面 图 形 三 维 三 角 网 格 以 及 通 用 多 边 形 的 支 持, 提 供 了 丰 富 的 接 口 来 给 相 关 的 算 法 对 象 使 用, 上 图 中 给 出 了 一 部 分 最 常 用 的 函 数 接 口 不 同 于 Volume,Mesh 并 不 对 应 于 医 学 影 像 设 备 所 直 接 采 集 得 到 的 数 据, 而 是 对 医 学 影 像 数 据 集 处 理 后 得 到 的 结 果 所 依 赖 的 表 达 形 式, 它 是 可 视 化 算 法 中 面 绘 制 和 某 些 分 割 算 法 所 处 理 的 对 象, 是 MITK 中 的 核 心 对 象 之 一 算 法 模 型 有 了 一 个 设 计 良 好 的 数 据 模 型, 下 一 步 就 是 算 法 模 型 的 抽 象 了 因 为 Source 和 Target 只 是 特 殊 目 的 的 算 法 对 象, 真 正 的 算 法 是 由 Filter 对 象 来 代 表 的 按 照 一 个 Filter 的 输 入 和 输 出 数 据 对 象 的 类 型, 它 可 以 被 具 体 化 为 四 种 不 同 的 算 法 种 类, Filter 的 类 继 承 关 系 如 图 2-3 所 示 : 图 2-3 MITK 的 算 法 模 型 从 图 中 可 以 看 出 Filter 被 具 体 化 为 四 种 不 同 的 种 类 :VolumeToVolumeFilter 指 的 是 输 入 数 据 和 输 出 数 据 都 为 Volume 数 据 对 象 的 算 法, 在 其 下 可 以 更 细 分 出 图 像 处 理 算 法 图 像 配 准 算 法 一 部 分 图 像 分 割 算 法 等, 在 第 六 七 章 中 实 现 的 算 法 就 全 部 是 从 VolumeToVolumeFilter 继 承 下 来 的 ;VolumeToMeshFilter 指 的 是 输 入 数 据 为 Volume 数 据 对 象, 而 输 出 数 据 为 Mesh 数 据 对 象 的 算 法, 包 括 三 维 可 视 化 中 的 面 重 建 算 法 一 部 分 图 像 分 割 算 法, 在 第 三 章 中 实 现 的 算 法 就 是 16
24 2 MITK 的 总 体 设 计 从 VolumeToMeshFilter 继 承 下 来 的 ;MeshToMeshFilter 指 的 是 输 入 数 据 为 Mesh 数 据 对 象, 而 输 出 数 据 为 Mesh 数 据 对 象 的 算 法, 包 括 三 维 可 视 化 中 的 面 片 网 格 化 简 平 滑 细 分 等 数 字 几 何 处 理 算 法 ;MeshToVolumeFilter 指 的 是 输 入 数 据 为 Mesh 数 据 对 象, 而 输 出 数 据 为 Volume 数 据 对 象 的 算 法, 包 括 三 维 可 视 化 中 的 基 于 距 离 场 的 算 法 隐 式 曲 面 算 法 等 这 四 种 Filter 也 只 是 一 种 高 层 的 抽 象, 其 中 每 一 个 种 类 下 面 都 包 含 很 多 具 体 的 算 法, 但 是 这 些 具 体 的 算 法 的 实 现 将 只 能 在 高 层 的 模 型 规 定 的 框 架 下 进 行 通 过 这 样 的 算 法 模 型,MITK 不 仅 为 可 视 化 分 割 配 准 等 算 法 提 供 了 一 个 框 架, 而 且 可 以 很 方 便 地 往 里 面 扩 充 新 的 算 法, 而 不 需 要 改 变 上 层 的 接 口 除 了 Filter 所 规 定 的 四 种 不 同 的 算 法 大 类 以 外, 在 MITK 里 面 还 有 一 种 特 殊 的 算 法, 就 是 体 绘 制 (Volume Rendering) 算 法, 它 的 输 入 是 一 个 Volume, 通 过 算 法 直 接 将 整 个 Volume 绘 制 到 最 终 的 图 像 中, 在 屏 幕 的 窗 口 ( 属 于 一 个 Target) 里 面 显 示 在 第 四 章 中 将 详 细 介 绍 MITK 中 的 体 绘 制 算 法 的 框 架 2.3 MITK 的 基 础 设 施 搭 建 遵 循 面 向 对 象 设 计 的 准 则, 整 个 MITK 的 类 层 次 结 构 从 mitkobject 开 始 开 枝 散 叶, 另 外,MITK 框 架 里 面 的 类 的 命 名 遵 循 一 定 的 原 则, 每 个 类 都 以 小 写 的 mitk 为 前 缀, 后 面 再 跟 上 自 己 这 个 类 的 名 字, 每 个 单 层 的 第 一 个 字 母 大 写, 例 如 mitkvolumetovolumefilter 在 下 面 正 文 里 面 的 描 述 中, 为 了 方 便 起 见, 在 不 引 起 混 淆 的 情 况 下, 通 常 把 类 名 字 前 面 的 mitk 前 缀 省 掉 Object 提 供 的 服 务 为 了 给 整 个 MITK 框 架 中 的 类 提 供 基 础 服 务, 在 Object 里 面 实 现 了 五 大 基 础 设 施, 分 别 是 RTTI(Run-Time Type Information, 运 行 时 信 息 ) 调 试 信 息 对 象 引 用 计 数 (Reference Counting) 智 能 指 针 (Smart Pointer) 观 察 者 (Observer), 下 面 分 别 给 予 介 绍 (1) RTTI( 运 行 时 信 息 ) 为 了 提 供 更 高 的 效 率,MITK 中 没 有 使 用 ANSI C++ 语 言 所 提 供 的 RTTI 特 性, 而 是 自 己 实 现 了 一 套 RTTI 的 机 制, 提 供 对 象 在 运 行 时 查 询 自 己 的 类 型 信 息 Object 里 面 与 RTTI 有 关 的 函 数 如 图 2-4 所 示, 其 中 GetClassname 函 数 以 字 符 串 的 17
25 2 MITK 的 总 体 设 计 形 式 返 回 当 前 类 的 名 字, 比 如 mitkobject 类 返 回 的 就 是 mitkobject ;IsTypeOf 和 IsA 函 数 判 断 当 前 的 类 是 否 是 与 typename 同 种 类 型 的 类 或 者 是 其 子 类, 两 者 的 差 异 在 于 IsTypeOf 是 静 态 函 数, 可 以 在 没 有 对 象 生 成 的 时 候 调 用, 而 IsA 是 个 成 员 函 数, 只 能 在 对 象 存 在 的 时 候 被 调 用 ;SafeDownCast 将 object 安 全 地 转 换 为 自 身 的 类 型 为 了 实 现 这 些 基 础 服 务,Object 要 求 它 的 每 个 子 类 都 必 须 实 现 GetClassname 和 IsA 这 两 个 虚 函 数, 以 及 IsTypeOf 和 SafeDownCast 这 两 个 静 态 函 数 为 了 减 少 子 类 的 工 作 量,MITK 里 面 用 C++ 的 宏 作 为 代 码 生 成 器 来 自 动 为 子 类 生 成 这 些 函 数, 这 个 宏 的 代 码 如 下 : #define MITK_TYPE(thisClass,superclass) \ virtual const char *GetClassname() const return #thisclass; \ static int IsTypeOf(const char *type) \ \ if (!strcmp(#thisclass,type) ) \ \ return 1; \ \ return superclass::istypeof(type); \ \ virtual int IsA(const char *type) \ \ return this->thisclass::istypeof(type); \ \ static thisclass* SafeDownCast(mitkObject *o) \ \ if ( o && o->isa(#thisclass) ) \ \ return static_cast<thisclass *>(o); \ \ return NULL;\ 在 这 个 宏 中,thisClass 是 类 自 身 的 类 名, 而 superclass 是 父 类 的 类 名 有 了 这 个 宏, 在 Object 的 子 类 里 面 就 只 需 写 下 这 个 宏 的 名 字 就 行 了, 省 去 很 多 烦 琐 的 工 作 比 如 DataObject 是 Object 的 一 个 子 类, 那 么 在 DataObject 的 声 明 里 面 18
26 2 MITK 的 总 体 设 计 就 只 用 写 下 下 面 的 代 码 即 可 : MITK_TYPE(mitkDataObject,mitkObject) 图 2-4 Object 提 供 的 RTTI 服 务 (2) 调 试 信 息 Object 里 面 还 为 算 法 提 供 了 调 试 信 息 的 支 持, 与 调 试 信 息 有 关 的 函 数 如 图 2-5 所 示, 其 中 SetDebug 函 数 用 来 设 置 是 否 打 开 调 试 信 息 支 持 ; 而 GetDebug 函 数 用 来 获 得 当 前 调 试 信 息 支 持 是 否 打 开 ;DebugOn 函 数 用 来 打 开 调 试 信 息 支 持 ; 而 DebugOff 函 数 用 来 关 闭 调 试 信 息 支 持 ;Print 函 数 用 来 将 对 象 内 部 的 状 态 输 送 到 os 指 定 的 设 备 上 显 示, 其 内 部 要 调 用 虚 函 数 PrintSelf, 将 实 际 调 试 信 息 的 输 出 工 作 委 托 给 PrintSelf 函 数 来 实 现, 所 以 Object 的 每 个 子 类 必 须 实 现 虚 函 数 PrintSelf 除 了 提 供 这 些 接 口 函 数 以 外,MITK 还 定 义 了 一 些 宏 来 输 出 调 试 信 息 或 者 出 错 信 息, 这 些 宏 的 定 义 如 下 : #define mitkgenericmessage(x) \ char *mitkmsgbuff; \ ostrstream mitkmsg; \ mitkmsg << x << "\n" << ends; \ mitkmsgbuff = mitkmsg.str(); \ mitkdisplaymessage(mitkmsgbuff);\ mitkmsg.rdbuf()->freeze(0); #define mitkdebugmessage(x) \ if (this->m_debug ) \ char *mitkmsgbuff; \ ostrstream mitkmsg; \ 19
27 2 MITK 的 总 体 设 计 mitkmsg << "Debug: In " FILE ", line " << LINE << "\n" << this->getclassname() << " (" << this << "): " << x << "\n\n" << ends; \ mitkmsgbuff = mitkmsg.str(); \ mitkdisplaymessage(mitkmsgbuff);\ mitkmsg.rdbuf()->freeze(0); #define mitkwarningmessage(x) \ char *mitkmsgbuff; \ ostrstream mitkmsg; \ mitkmsg << "Warning: In " FILE ", line " << LINE << "\n" << this->getclassname() << " (" << this << "): " << x << "\n\n" << ends; \ mitkmsgbuff = mitkmsg.str(); \ mitkdisplaymessage(mitkmsgbuff);\ mitkmsg.rdbuf()->freeze(0); #define mitkerrormessage(x) \ char *mitkmsgbuff; \ ostrstream mitkmsg; \ mitkmsg << "ERROR: In " FILE ", line " << LINE << "\n" << this->getclassname() << " (" << this << "): " << x << "\n\n" << ends; \ mitkmsgbuff = mitkmsg.str(); \ mitkdisplaymessage(mitkmsgbuff);\ mitkmsg.rdbuf()->freeze(0); mitkobject::breakonerror(); 其 中 mitkgenericmessage 是 用 来 输 出 通 用 的 消 息, 其 可 以 在 任 意 位 置 被 调 用 ; 而 mitkdebugmessage,mitkwarningmessage 和 mitkerrormessage 都 只 能 在 Object 及 其 子 类 的 成 员 函 数 里 面 被 调 用, 分 别 用 来 输 出 调 试 信 息, 警 告 信 息 和 严 重 错 误 信 息 它 们 和 Object 里 面 的 相 关 函 数 一 起, 提 供 了 MITK 里 面 提 供 调 试 信 息 的 基 础 服 务 20
28 2 MITK 的 总 体 设 计 图 2-5 Object 提 供 的 调 试 信 息 服 务 (3) Reference Counting( 引 用 计 数 ) 引 用 计 数 是 实 现 内 存 管 理 的 一 种 手 段, 它 的 目 的 是 将 内 存 中 不 再 被 其 它 对 象 引 用 的 对 象 给 自 动 释 放 掉, 从 而 达 到 节 省 内 存 并 保 证 内 存 不 产 生 泄 漏 Object 里 面 与 引 用 计 数 有 关 的 函 数 如 图 2-6 所 示, 其 中 AddReference 函 数 将 本 身 被 其 它 对 象 引 用 的 次 数 加 一 ;RemoveReference 函 数 将 本 身 被 其 它 对 象 引 用 的 次 数 减 一 ;GetReferenceCount 函 数 返 回 本 身 被 其 它 对 象 引 用 的 次 数 ;Delete 函 数 将 本 身 从 内 存 中 删 除, 但 是 前 提 是 本 身 被 其 它 对 象 引 用 的 次 数 小 于 或 者 等 于 零 为 了 实 现 引 用 计 数,MITK 还 使 用 了 设 计 模 式 来 限 制 所 有 从 Object 继 承 下 来 的 子 类 都 必 须 在 堆 (Heap) 上 来 分 配 内 存, 而 不 能 从 栈 (Stack) 上 来 分 配 内 存 为 了 达 到 这 个 目 的,Object 和 所 有 从 Object 继 承 下 来 的 类 的 析 构 函 数 都 必 须 是 Protected 的 在 删 除 一 个 从 Object 继 承 下 来 的 对 象 时, 必 须 调 用 其 Delete 函 数, 而 不 能 直 接 使 用 delete 操 作 符 图 2-6 Object 提 供 的 引 用 计 数 服 务 21
29 2 MITK 的 总 体 设 计 (4) Smart Pointer( 智 能 指 针 ) 智 能 指 针 和 引 用 计 数 合 起 来 构 成 了 MITK 中 内 存 管 理 的 基 础, 所 谓 智 能 指 针, 就 是 其 行 为 模 仿 普 通 的 指 针, 但 是 可 以 通 过 运 算 符 重 载 来 进 行 一 些 必 要 的 操 作, 从 而 可 以 减 少 不 必 要 的 代 码 在 MITK 中 提 供 了 一 个 模 板 类 SmartPointer 用 来 实 现 智 能 指 针, 它 的 内 部 维 护 着 一 个 成 员 变 量 realptr, 可 以 被 具 体 的 模 板 参 数 T 来 具 体 化, 这 里 要 求 类 型 T 必 须 是 Object 的 子 类, 其 提 供 的 主 要 功 能 请 参 看 图 2-7 为 了 实 现 普 通 的 指 针 行 为, 它 重 载 了 -> 和 * 运 算 符, 分 别 返 回 实 际 指 针 的 地 址 和 引 用 ; 为 了 实 现 其 智 能 的 行 为, 它 重 载 了 = 运 算 符, 用 于 在 把 一 个 Object 的 子 类 的 指 针 赋 值 给 智 能 指 针 时, 来 自 动 地 进 行 引 用 计 数 的 工 作, 其 实 现 代 码 如 下 : template<class T> inline mitksmartpointer<t>& mitksmartpointer<t>::operator=(t* realptr) if(realptr == NULL) return *this; if(realptr == m_pointee) return *this; if(m_pointee) m_pointee->removereference(); m_pointee = realptr; m_pointee->addreference(); return *this; 图 2-7 MITK 中 的 智 能 指 针 (5) Observer( 观 察 者 ) MITK 是 一 个 底 层 的 算 法 库, 但 是 在 基 于 MITK 开 发 实 际 应 用 程 序 时, 有 时 22
30 2 MITK 的 总 体 设 计 必 须 知 道 内 部 算 法 的 状 态, 比 如 算 法 执 行 的 进 度 等 信 息 为 了 达 到 MITK 底 层 算 法 库 和 上 层 的 应 用 程 序 之 间 的 通 信,MITK 里 面 实 现 了 Observer 这 一 设 计 模 式, 其 在 Object 里 面 的 函 数 如 图 2-8 所 示,AddObserver 将 一 个 Observer 插 入 到 内 部 维 护 的 一 个 队 列 中 ;RemoveObserver 将 一 个 指 定 的 Observer 从 内 部 的 队 列 中 删 除 ;RemoveAllObservers 将 内 部 的 队 列 清 空 而 MITK 中 也 单 独 定 义 了 Observer 这 个 类 层 次, 所 有 从 Observer 继 承 的 子 类 必 须 实 现 其 虚 函 数 Update, 从 而 更 新 界 面 元 素 一 个 Observer 就 是 一 个 单 独 的 对 象, 在 外 面 观 察 一 个 MITK 对 象 的 内 部 状 态, 并 且 在 必 要 的 时 候 被 更 新 图 2-8 Object 中 提 供 的 Observer 设 计 模 式 实 现 内 存 管 理 因 为 MITK 的 结 构 比 较 复 杂, 所 以 实 现 一 个 有 效 的 内 存 管 理 方 案 是 非 常 必 要 的 上 面 介 绍 的 引 用 计 数 和 智 能 指 针 的 联 合 使 用, 形 成 了 MITK 中 的 第 一 层 内 存 管 理, 用 来 保 证 不 出 现 内 存 泄 漏 另 外,MITK 中 实 现 了 一 个 简 单 的 垃 圾 回 收 (Garbage Collection) 机 制, 用 于 在 程 序 退 出 之 前 将 所 有 可 能 泄 漏 的 内 存 释 放 掉, 此 功 能 是 在 GarbageCollection 类 里 面 实 现 的, 它 的 接 口 函 数 如 图 2-9 所 示, AddObject 函 数 将 一 个 MITK 的 对 象 的 指 针 加 入 到 内 部 维 护 的 一 张 列 表 中, 而 RemoveObject 函 数 将 指 定 的 对 象 从 内 部 的 列 表 中 删 除 由 于 GarbageCollection 在 整 个 系 统 中 只 能 存 在 一 份 实 例, 所 以 MITK 中 使 用 了 设 计 模 式 中 的 Singleton 来 达 到 这 一 目 的 有 了 GarbageCollection 类 提 供 的 基 本 功 能 以 后,Object 里 面 也 必 须 提 供 相 应 的 配 合 才 能 实 现 垃 圾 回 收 机 制 在 Object 的 构 造 函 数 里 面, 通 过 GetGarbageCollector 函 数 来 得 到 系 统 里 面 唯 一 的 GarbageCollection 实 例, 并 且 将 自 身 加 入 到 GarbageCollection 的 列 表 里 面, 代 码 如 下 : 23
31 2 MITK 的 总 体 设 计 GetGarbageCollector()->AddObject(this); 在 Object 的 析 构 函 数 里 面, 也 同 样 通 过 GetGarbageCollector 函 数 来 得 到 系 统 里 面 唯 一 的 GarbageCollection 实 例, 并 且 通 过 RemoveObject 将 自 身 从 GarbageCollection 的 列 表 里 面 删 除, 代 码 如 下 : GetGarbageCollector()->RemoveObject(this); 在 整 个 系 统 退 出 的 时 候, 系 统 里 面 唯 一 的 GarbageCollection 实 例 的 析 构 函 数 被 调 用, 检 查 自 己 维 护 的 列 表 中 是 否 为 空, 如 果 不 为 空, 则 说 明 有 内 存 泄 漏, 同 时 考 虑 到 引 用 计 数 这 一 服 务, 按 照 一 定 的 规 则 将 泄 漏 的 内 存 清 空, 代 码 如 下 所 示 : if(m_objectcollection->count() <= 0) m_objectcollection->delete(); return; mitkobject *aobject; while(m_objectcollection->count() > 0) for(m_objectcollection->inittraversal(); aobject = m_objectcollection->getnextitem(); ) if(aobject->getreferencecount() <= 0) aobject->delete(); break; 24
32 2 MITK 的 总 体 设 计 m_objectcollection->clear(); m_objectcollection->delete(); 图 2-9 MITK 中 的 垃 圾 回 收 接 口 跨 平 台 的 实 现 MITK 的 所 有 代 码 均 使 用 符 合 ANSI C++ 标 准 的 特 性, 这 保 证 了 代 码 的 可 移 植 性 但 是 对 一 些 操 作 系 统 相 关 的 代 码, 比 如 窗 口 的 事 件 处 理 虚 拟 内 存 管 理 等, 必 须 对 每 一 个 操 作 系 统 写 一 套 代 码 为 了 能 够 透 明 地 封 装 这 些 操 作 系 统 相 关 的 代 码, 得 到 一 个 优 雅 的 解 决 方 案,MITK 中 再 次 使 用 了 一 种 设 计 模 式, 名 字 为 桥 (Bridge), 下 面 以 View 这 一 对 象 为 例 进 行 介 绍 在 MITK 中,View 这 一 对 象 是 唯 一 跟 图 形 用 户 界 面 相 关 的 部 分, 给 用 户 提 供 了 一 个 用 来 显 示 图 像 或 者 三 维 图 形 的 窗 口, 这 也 就 必 然 导 致 它 跟 具 体 的 操 作 系 统 相 关 为 了 使 操 作 系 统 相 关 的 代 码 相 对 独 立, 并 且 添 加 对 新 的 操 作 系 统 的 支 持 的 时 候 也 不 影 响 到 客 户 端 的 代 码,View 这 一 部 分 的 框 架 结 构 图 如 图 2-10 所 示 25
33 2 MITK 的 总 体 设 计 图 2-10 MITK 中 View 的 结 构 从 图 中 可 以 看 出,View 里 面 维 护 着 Implementor 基 类 的 指 针, 并 且 View 把 所 有 与 操 作 系 统 相 关 的 代 码 都 委 托 给 Implementor 来 实 现,Implementor 通 过 其 子 类 来 实 现 具 体 的 代 码 通 过 这 种 结 构, 客 户 端 只 知 道 有 View, 而 不 知 道 有 Implementor 的 存 在, 因 此 降 低 了 藕 合 ; 并 且 要 添 加 对 一 个 新 的 操 作 系 统 的 支 持 的 时 候, 只 需 要 在 Implementor 分 支 下 面 再 增 加 对 应 的 子 类 即 可, 无 需 更 改 客 户 端 的 代 码 SSE 加 速 的 实 现 Intel 在 其 奔 腾 3 以 后 的 CPU 中 增 加 了 SSE 扩 展 指 令 集, 用 以 提 高 三 维 程 序 以 及 多 媒 体 程 序 的 性 能 因 为 MITK 中 三 维 可 视 化 是 一 个 比 较 重 要 的 部 分, 对 性 能 要 求 也 比 较 高, 因 此 内 部 提 供 了 对 SSE 的 支 持, 并 且 已 经 实 现 了 一 部 分 算 法 的 SSE 优 化 加 速 实 现 SSE 指 令 集 的 支 持 有 一 些 问 题 必 须 考 虑, 因 为 传 统 上 是 使 用 汇 编 语 言 对 SSE 指 令 集 进 行 编 程, 这 显 然 将 会 违 背 MITK 的 可 移 植 性 这 一 设 计 目 标 ; 另 外, 并 不 是 所 有 的 CPU 都 支 持 SSE 指 令 集, 因 此 必 须 有 一 种 手 段 能 在 运 行 时 检 测 当 前 CPU 信 息, 并 动 态 决 定 使 用 SSE 加 速 版 本 还 是 使 用 普 通 版 本 在 MITK 中, 对 第 一 个 问 题 的 解 决 方 案 是 使 用 编 译 器 提 供 的 Intrinsics 去 对 SSE 指 令 集 进 行 编 程, 这 样 虽 然 没 有 使 用 汇 编 语 言 的 效 率 高, 但 是 目 前 的 主 流 编 译 器 ( 包 括 Visual C Processor Pack,Visual C++ 7.0,gcc,Intel Compiler 等 ) 都 提 供 了 对 Intrinsics 的 支 持, 因 此 可 移 植 性 得 到 了 保 证 ; 对 第 二 个 问 题 的 解 决 方 案 是 提 供 了 两 套 分 离 的 动 态 链 接 库, 一 套 是 包 含 SSE 加 速 算 法 26
34 2 MITK 的 总 体 设 计 的, 一 套 是 非 SSE 加 速 算 法 的, 由 客 户 端 在 使 用 时 进 行 判 断 并 动 态 加 载 2.4 小 结 本 章 的 第 一 部 分 介 绍 了 MITK 的 设 计 目 标, 其 目 的 是 为 了 给 医 学 影 像 领 域 的 研 究 者 提 供 一 套 具 有 一 致 接 口 的, 可 复 用 的, 结 合 了 VTK 和 ITK 中 可 视 化 分 割 配 准 等 功 能 的 开 发 工 具 包 本 章 的 第 二 部 分 介 绍 了 MITK 的 整 体 计 算 框 架, 其 中 包 括 基 于 数 据 流 模 型 的 整 体 框 架, 数 据 模 型 和 算 法 模 型 第 三 部 分 介 绍 的 是 MITK 的 基 础 设 施 搭 建, 包 括 基 类 Object 所 提 供 的 服 务 以 及 相 关 信 息, 内 存 管 理, 跨 平 台 实 现 和 SSE 加 速 的 实 现 目 前 MITK 的 整 体 框 架 已 经 基 本 稳 定, 在 保 持 MITK 的 整 体 框 架 的 稳 定 性 的 基 础 上, 下 一 步 可 以 不 断 地 完 善 和 添 加 MITK 的 各 种 算 法, 使 其 越 来 越 丰 富, 越 来 越 实 用 接 下 来 的 几 章 将 会 介 绍 具 体 的 算 法 在 MITK 中 的 实 现 参 考 文 献 1. J. Rumbaugh, M. Blaha, W. Premerlani, et al. Object-Oriented Modeling and Design. Prentice-Hall, Meyer, B. Object-Oriented Software Construction. Prentice Hall, Erich Gamma, Richard Helm, Ralph Johnson, et al. Design Patterns, Elements of Reusable Object-Oriented Software. Pearson Education Publisher, K. Engel, M. Kraus, and T. Ertl, High-quality pre-integrated volume rendering using hardware accelerated pixel shading, in Proc. Eurographics/SIGGRAPH Workshop on Graphics Hardware C. Resk-Salama, K. Engel, M. Bauer, G. Greiner, T. Ertl, Interactive Volume Rendering on Standard PC Graphics Hardware Using Multi-Textures and Multi-Stage-Rasterization,in Proc. Eurographics/SIGGRAPH Workshop on Graphics Hardware S. Campagna, L. Kobbelt, H. P. Seidel. Directed Edges - A Scalable Representation For Triangle Meshes. ACM Journal of Graphics Tools 3 (4), 1998, pp
35 3 面 绘 制 (Surface Rendering) 的 框 架 与 实 现 面 绘 制 是 医 学 图 像 三 维 可 视 化 的 重 要 手 段 之 一, 它 通 过 对 一 系 列 的 二 维 图 像 进 行 边 界 识 别 等 分 割 处 理, 重 新 还 原 出 被 检 物 体 的 三 维 模 型, 并 以 表 面 的 方 式 显 示 出 来, 从 而 为 用 户 提 供 具 有 较 强 真 实 感 的 三 维 医 学 图 像, 便 于 医 生 从 多 角 度 多 层 次 进 行 观 察 和 分 析, 并 且 能 够 使 医 生 有 效 地 参 与 数 据 的 处 理 分 析 过 程, 在 辅 助 医 生 诊 断 手 术 仿 真 引 导 治 疗 等 方 面 都 可 以 发 挥 重 要 的 作 用 本 章 主 要 介 绍 MITK 中 的 面 绘 制 框 架 的 设 计 和 实 现, 包 括 两 大 部 分 : 表 面 重 建 和 表 面 绘 制, 在 下 面 两 节 中 将 分 别 予 以 详 细 介 绍 3.1 表 面 重 建 算 法 及 其 在 MITK 中 的 实 现 所 示 在 MITK 中, 表 面 重 建 算 法 被 抽 象 成 一 个 VolumeToMeshFilter, 如 图 3-1 Filter VolumeToVolumeFilter MeshToMeshFilter VolumeToMeshFilter m_indata : Volume m_outdata : Mesh +SetInput(in Data : Mesh) +GetOutput() : Volume MeshToVolumeFilter 图 3-2 VolumeToMeshFilter 其 输 入 数 据 是 N 张 两 维 的 切 片 生 数 据 ( 如 图 3-3 所 示 ), 表 示 为 一 个 mitkvolume; 经 处 理 后 的 输 出 数 据 是 一 个 以 三 角 网 格 来 表 示 的 三 维 表 面 模 型 ( 如 图 3-4 所 示 ), 表 示 为 mitkmesh 28
36 图 3-3 体 数 据 示 意 图 图 3-4 以 三 角 网 格 表 示 的 表 面 模 型 mitkmesh 中 的 三 角 面 片 数 据 以 点 表 和 面 表 来 组 织 点 表 是 一 个 float 型 数 组, 每 6 个 数 保 存 一 个 顶 点 的 信 息, 包 括 顶 点 坐 标 和 法 向 量, 按 (x, y, z; nx, ny, nz) 存 放 ; 面 表 是 一 个 unsigned int 型 数 组, 每 3 个 数 保 存 一 个 三 角 片 的 信 息, 分 别 是 组 成 该 三 角 片 的 3 个 顶 点 在 点 表 中 的 索 引 MITK 中 的 表 面 重 建 主 要 采 用 了 目 前 得 到 广 泛 应 用 的 Marching Cubes 算 法, 下 面 几 节 就 对 该 算 法 的 原 理 和 实 现 做 一 个 介 绍 传 统 的 Marching Cubes 算 法 Marching Cubes 算 法 是 W. Lorensen 等 人 于 1987 年 提 出 来 的 一 种 三 维 重 建 方 法 [1], 因 为 它 的 原 理 简 单, 并 且 很 容 易 实 现, 因 此 得 到 了 广 泛 的 应 用, 并 且 此 算 法 在 美 国 已 经 申 请 专 利, 它 被 认 为 是 至 今 为 止 最 流 行 的 面 显 示 算 法 之 一 Marching Cubes 算 法 是 面 显 示 算 法 中 的 一 种, 因 为 它 的 本 质 是 从 一 个 三 维 的 29
37 数 据 场 中 抽 取 出 一 个 等 值 面, 所 以 也 被 称 为 等 值 面 提 取 (Isosurface Extraction) 算 法 对 于 一 个 标 准 的 医 学 图 像 的 体 数 据 集, 它 往 往 是 由 一 系 列 的 二 维 切 片 数 据 组 成 的, 而 每 张 切 片 都 有 空 间 上 的 分 辨 率 假 设 有 一 个 体 数 据 集, 包 含 58 张 切 片, 每 张 切 片 的 分 辨 率 是 , 那 么 它 可 以 被 认 为 是 一 个 连 续 函 数 f(x,y,z) 在 x y z 三 个 方 向 上 按 一 定 的 间 隔 分 别 采 样 了 次 所 得 到 的 而 所 谓 的 等 值 面, 实 际 上 是 指 空 间 中 的 一 张 曲 面, 在 该 曲 面 上 函 数 f(x,y,z) 的 值 等 于 某 一 给 定 值 等 值 面 提 取 算 法 的 核 心 就 是 要 从 给 定 的 采 样 点 中 找 出 等 值 面 来, 这 时 最 容 易 想 到 的 方 法 就 是 首 先 由 采 样 点 恢 复 出 连 续 函 数 f(x,y,z) 来, 然 后 由 f(x,y,z) 和 某 一 给 定 的 值 ( 通 常 叫 域 值 ) 来 得 出 等 值 面, 这 种 方 法 一 般 被 称 为 显 式 的 等 值 面 提 取 算 法, 它 的 计 算 复 杂 度 比 较 高, 并 且 由 于 重 构 和 重 采 样 所 带 来 的 误 差 比 较 大, 所 以 精 度 也 得 不 到 保 证 与 此 相 反,Marching Cubes 算 法 采 用 了 隐 式 的 等 值 面 提 取 方 法, 它 不 直 接 计 算 f(x,y,z), 而 是 直 接 从 体 数 据 中 获 取 等 值 面 的 信 息 算 法 需 要 用 户 提 供 一 个 域 值, 也 就 是 所 希 望 提 取 出 来 的 物 质 的 密 度 值, 比 如 要 提 取 出 骨 骼, 域 值 就 要 相 对 大 一 些, 然 后 根 据 体 数 据 的 信 息, 就 可 以 提 取 出 来 等 值 面 的 三 角 网 格 表 达 Marching Cubes 算 法 的 过 程 可 以 描 述 如 下 : 1. 每 次 读 出 两 张 切 片, 形 成 一 层 (Layer); 2. 两 张 切 片 上 下 相 对 应 的 四 个 点 构 成 一 个 立 方 体 (Cube), 也 叫 Cell, Voxel 等, 如 图 3-5 所 示 ; 3. 从 左 至 右, 从 前 到 后 顺 序 处 理 一 层 中 的 Cubes( 抽 取 每 个 Cube 中 的 等 值 面 ), 然 后 从 下 到 上 顺 序 处 理 (n-1) 层, 算 法 就 结 束, 故 名 为 Marching Cubes 30
38 图 3-5 Cubes 示 意 图 对 于 每 一 个 Cube 而 言, 它 的 八 个 顶 点 的 灰 度 值 可 以 直 接 从 输 入 数 据 中 得 到, 要 抽 取 的 等 值 面 的 域 值 也 已 经 知 道 如 果 一 个 顶 点 的 灰 度 值 大 于 域 值, 则 将 它 标 记 为 黑 色 (Marked Vertex), 而 小 于 的 不 标 (Unmarked Vertex), 如 错 误! 未 找 到 引 用 源 所 示 图 3-6 Marked Vertex 与 Unmarked Vertex 因 为 一 个 Cube 有 8 个 顶 点, 每 个 顶 点 有 Marked Unmarked 两 种 状 态, 所 以 等 值 面 的 分 布 总 共 可 能 有 28=256 种 但 是 考 虑 到 Cube 有 旋 转 (Rotation) 对 称 性, 旋 转 不 影 响 等 值 面 的 拓 扑 结 构, 另 外, 所 有 的 Marked Vertex 变 为 Unmarked Vertex, 或 者 反 过 来 (Invertion 对 称 ), 等 值 面 的 连 接 方 式 也 不 会 改 变 考 虑 了 Rotation 和 Invertion 两 种 情 况 后, 原 作 者 总 结 了 15 种 Basic Cube, 它 们 覆 盖 了 所 有 256 种 可 能 的 情 况, 如 图 3-7 所 示 31
39 图 种 基 本 Cubes 的 拓 扑 形 状 根 据 这 15 种 基 本 的 Cubes, 可 以 造 出 一 个 查 找 表 (Look-up Table) 表 的 长 度 为 256, 记 录 了 所 有 情 况 下 的 等 值 面 连 接 方 式 所 以 此 时 只 需 分 别 比 较 一 个 Cube 的 八 个 顶 点 与 域 值 之 间 的 大 小 关 系, 即 可 得 出 一 个 之 间 的 索 引 值 (CubeIndex), 然 后 直 接 查 表 就 可 得 到 此 Cube 在 那 条 边 上 有 等 值 点, 并 且 还 能 得 到 等 值 点 的 连 接 方 式 等 信 息, 这 时 候 就 可 以 将 等 值 点 连 接 起 来 以 形 成 等 值 面 要 想 用 真 实 感 图 形 学 技 术 将 等 值 面 显 示 出 来, 除 了 要 知 道 每 个 等 值 点 的 坐 标 外, 还 必 须 知 道 每 个 等 值 点 的 法 向 量 在 计 算 Cube 某 条 边 上 的 等 值 点 坐 标 与 法 向 量 时, 有 两 种 方 法, 一 种 是 线 性 插 值, 另 外 一 种 是 中 点 选 择 (MidPoint Selection) 线 性 插 值 的 公 式 如 下 所 示 : P = P 1 + (isovalue - V 1 ) (P 2 - P 1 ) / (V 2 - V 1 ) (3-1) N = N 1 + (isovalue - V 1 ) (N 2 - N 1 ) / (V 2 - V 1 ) (3-2) 其 中 P 代 表 等 值 点 坐 标,P 1 P 2 代 表 两 个 端 点 的 坐 标,V 1 V 2 代 表 两 个 端 点 的 灰 度 值, isovalue 代 表 阈 值 ;N 代 表 等 值 点 法 向 量,N 1 N 2 代 表 两 个 端 点 的 法 向 量 中 点 选 择 的 公 式 如 下 所 示 : 32
40 P = (P 1 + P 2 )/ 2 (3-3) N = (N 1 + N 2 )/ 2 (3-4) 其 中 P 和 N P 1 P 2 N 1 N 2 所 代 表 的 意 义 同 上 中 点 选 择 所 具 有 的 优 点 是 : 1. 引 起 的 误 差 低 于 1/2 Cube 边 长, 这 在 医 学 图 像 的 解 析 度 越 来 越 高 的 情 况 下, 所 重 建 出 来 的 图 像 与 线 性 插 值 得 到 的 图 像 并 没 有 明 显 的 视 觉 上 的 差 异 2. 如 果 先 放 大 10 倍 再 进 行 运 算, 就 可 以 完 全 采 用 整 数 运 算, 避 免 浮 点 运 算 3. 可 以 使 得 局 部 表 面 更 平 坦, 有 利 于 后 续 的 化 简 过 程, 如 图 3-8 所 示 : 图 3-8 局 部 表 面 的 平 坦 化 MITK 中 的 mitkmarchingcubes 类 实 现 的 表 面 重 建 算 法 基 于 传 统 的 Marching Cubes 算 法, 它 可 以 接 受 高 低 两 个 阈 值, 把 灰 度 在 这 两 个 阈 值 之 间 的 组 织 分 界 面 提 取 出 来, 从 而 达 到 物 体 表 面 重 建 的 目 的 该 算 法 使 用 了 如 下 所 示 的 查 找 表 : short g_edgetable[256] = 0x0, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, 0x190, 0x99, 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, 0x230, 0x339, 0x33, 0x13a, 0x636, 0x73f, 0x435, 0x53c, 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, 0x3a0, 0x2a9, 0x1a3, 0xaa, 0x7a6, 0x6af, 0x5a5, 0x4ac, 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, 33
41 ; 0x460, 0x569, 0x663, 0x76a, 0x66, 0x16f, 0x265, 0x36c, 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff, 0x3f5, 0x2fc, 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55, 0x15c, 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc, 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 0xcc, 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 0x15c, 0x55, 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 0x2fc, 0x3f5, 0xff, 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, 0x36c, 0x265, 0x16f, 0x66, 0x76a, 0x663, 0x569, 0x460, 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa, 0x1a3, 0x2a9, 0x3a0, 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33, 0x339, 0x230, 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99, 0x190, 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 char g_tritable[256][16] = -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,... 34
42 1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ; 其 中,g_TriTable 比 较 大, 篇 幅 所 限 只 能 列 出 部 分 数 据 与 这 两 个 查 找 表 对 应 的 Cube 顶 点 和 边 的 位 置 关 系 如 图 3-9 所 示, 按 该 图, 可 以 很 容 易 的 生 成 这 两 个 表 z y x 图 3-9 与 查 找 表 对 应 的 Cube 各 顶 点 和 边 的 位 置 关 系 及 索 引 编 号 首 先, 每 个 顶 点 以 一 位 二 进 位 表 示, 将 0~7 八 个 顶 点 从 低 到 高 排 成 一 个 8 bits 的 整 数, 若 第 i 个 顶 点 灰 度 在 低 阈 值 和 高 阈 值 之 间, 则 第 i 位 置 1, 否 则 置 0, 这 样 就 形 成 了 一 个 索 引 值, 范 围 从 0 到 255 然 后, 根 据 这 256 种 不 同 情 况 写 出 上 面 两 表 中 的 对 应 项 对 于 g_edgetable 来 说, 其 每 一 项 是 一 个 12 bits 的 整 数, 从 第 0~11 bit 分 别 代 表 图 3-9 中 的 第 0~11 条 边, 根 据 索 引 值 所 对 应 的 顶 点 灰 度 情 况 判 断, 若 第 i 条 边 与 等 值 面 存 在 交 点 ( 一 个 端 点 灰 度 值 在 高 低 阈 值 之 间, 另 一 个 端 点 灰 度 值 在 高 低 阈 值 之 外 ), 则 第 35
43 i 位 置 1, 否 则 置 0 对 于 g_tritable, 其 每 一 项 是 一 个 由 16 个 8 bits 整 数 组 成 的 数 组, 记 录 了 与 索 引 值 对 应 的 三 角 面 片 分 割 情 况 比 如, 对 于 索 引 9( ), 顶 点 0 和 3 的 灰 度 值 在 低 阈 值 和 高 阈 值 之 间, 则 第 和 11 条 边 与 等 值 面 有 交 点,g_EdgeTable[9] 的 值 就 为 =905 16, 而 g_tritable[9] 则 以 顶 点 所 在 边 的 编 号 记 录 了 这 4 个 交 点 所 构 成 的 两 个 三 角 片 : 和 , 如 图 3-10 所 示 注 意, 生 成 的 三 角 片 正 向 须 保 持 一 致 图 3-10 索 引 值 为 9 时 对 应 表 项 示 意 算 法 在 计 算 等 值 点 坐 标 和 法 向 量 时 使 用 线 性 插 值 的 方 法 算 法 的 具 体 实 现 如 下 : template <class VT> int t_executemarchingcubes(mitkvolume *input, mitkmesh *output, mitkmarchingcubes *self, VT *vdata) // 记 录 生 成 的 顶 点 数 和 面 数 index_type vnumber = 0; index_type fnumber = 0; // 当 前 Cube 中 生 成 的 顶 点 和 面 数 int vertpos, facepos; 36
44 // 包 围 盒 的 尺 寸 float min[3]; float max[3]; min[0] = min[1] = min[2] = 30000; max[0] = max[1] = max[2] = ; // 当 前 扫 描 层 的 切 片 数 据 // 因 为 要 计 算 法 向 量, 前 后 共 设 计 4 层 切 片 数 据 VT *pslicea; VT *psliceb; VT *pslicec; VT *psliced; VT *tempslice; // 得 到 所 需 的 有 关 Volume 的 信 息 int imagewidth = input->getwidth(); int imageheight = input->getheight(); int imagesize = imagewidth * imageheight; int slicenumber = input->getimagenum(); //two adjacent pixels's distance from 3 axes float XSpace = input->getspacingx(); float YSpace = input->getspacingy(); float ZSpace = input->getspacingz(); // 得 到 两 个 灰 度 阈 值 float lowthreshold = self->getlowthreshold(); float highthreshold = self->gethighthreshold(); if (XSpace == 0) mitkgenericmessage("marching Cubes : Sorry,your data are wrong! XSpace = " << XSpace); return 0; if (YSpace == 0) mitkgenericmessage("marching Cubes : Sorry,your data are wrong! YSpace = " << YSpace); return 0; 37
45 if (ZSpace == 0) mitkgenericmessage("marching Cubes : Sorry,your data are wrong! ZSpace = " << ZSpace); return 0; pslicea = NULL; psliceb = NULL; pslicec = NULL; psliced = NULL; // 用 于 记 录 上 层 扫 描 生 成 的 顶 点, 避 免 生 成 重 复 的 顶 点 index_type *bottomxedge = new index_type[imagesize]; index_type *bottomyedge = new index_type[imagesize]; index_type *topxedge = new index_type[imagesize]; index_type *topyedge = new index_type[imagesize]; index_type *zedge = new index_type[imagesize]; tempslice = new VT[imageSize]; if ( bottomxedge == NULL bottomyedge == NULL topxedge == NULL topyedge == NULL zedge == NULL tempslice == NULL) mitkgenericmessage("marching Cubes : sorry,the assistant data structure's memory are not right allocated"); return 0; //initialize the assistant data structure memset(bottomxedge, -1, sizeof(index_type) * imagesize); memset(bottomyedge, -1, sizeof(index_type) * imagesize); memset(topxedge, -1, sizeof(index_type) * imagesize); memset(topyedge, -1, sizeof(index_type) * imagesize); memset(zedge, -1, sizeof(index_type) * imagesize); memset(tempslice, 0, sizeof(vt) * imagesize); 38
46 // 计 算 某 一 层 顶 点 和 三 角 片 时 所 需 的 一 些 变 量 // 一 些 循 环 变 量 short i, j, k, w, r; // Cube 类 型 unsigned char cubetype(0); // 用 于 计 算 法 向 量 float dx[8]; float dy[8]; float dz[8]; float squaroot; // 记 录 某 个 Cube 生 成 的 顶 点 坐 标 和 相 应 顶 点 的 法 向 量, // 对 应 一 个 Cube 的 12 条 边 // float vertpoint[6]; float vertpoint[12][6]; index_type cellverts[12]; index_type triindex[5][3]; // 用 于 记 录 已 生 成 顶 点 索 引 的 临 时 变 量 index_type offset; // 当 前 Cube 八 个 顶 点 的 灰 度 值 VT cubegrid[8]; index_type *edgegroup; //get memory data psliced = vdata; // HERE psliceb = tempslice; pslicea = tempslice; //allocate memory for output data float *pvertex; unsigned int *pface; output->setvertexnumber(100000); output->setfacenumber(100000); 39
47 pvertex = output->getvertexdata(); pface = output->getfacedata(); // 扫 描 时 4 层 切 片 的 排 列 顺 序 /* D B A C V */ for (i=0; i<=slicenumber ; ++i) pslicec = pslicea; pslicea = psliceb; psliceb = psliced; if (i >= slicenumber - 1) psliced = tempslice; else psliced += imagesize; for (j=0; j<imageheight - 1; ++j) for (k=0; k<imagewidth - 1; ++k) //calculate the cube's eight point's grey value // 得 到 当 前 立 方 体 8 顶 点 灰 度 cubegrid[0] = pslicea[j * imagewidth + k]; cubegrid[1] = pslicea[j * imagewidth + k + 1]; cubegrid[2] = pslicea[(j + 1) * imagewidth + k + 1]; cubegrid[3] = pslicea[(j + 1) * imagewidth + k]; cubegrid[4] = psliceb[j * imagewidth + k]; cubegrid[5] = psliceb[j * imagewidth + k + 1]; cubegrid[6] = psliceb[(j + 1) * imagewidth + k + 1]; cubegrid[7] = psliceb[(j + 1) * imagewidth + k]; // 计 算 Cube 的 类 型 cubetype = 0; 40
48 for (w=0; w<8; ++w) if ((cubegrid[w] > lowthreshold) && (cubegrid[w] < highthreshold)) cubetype = (1 << w); if ((cubetype == 0) (cubetype == 255)) continue; // initialize the cellverts table // and make it turn into zero table for (w=0; w<12; w++) cellverts[w] = -1; // 计 算 6 个 方 向 相 邻 点 的 像 素 差 值 ( 用 于 计 算 法 向 量 ) if (k == 0) dx[0] = pslicea[j * imagewidth + 1]; dx[3] = pslicea[(j + 1) * imagewidth + 1]; dx[4] = psliceb[j * imagewidth + 1]; dx[7] = psliceb[(j + 1) * imagewidth + 1]; else dx[0] = pslicea[j * imagewidth + k + 1] - pslicea[j * imagewidth + k - 1]; dx[3] = pslicea[(j + 1) * imagewidth + k + 1] - pslicea[(j + 1) * imagewidth + k - 1]; dx[4] = psliceb[j * imagewidth + k + 1] - psliceb[j * imagewidth + k - 1]; dx[7] = psliceb[(j + 1) * imagewidth + k + 1] - psliceb[(j + 1) * imagewidth + k - 1]; 41
49 if (k == imagewidth - 2) dx[1] = - pslicea[j * imagewidth + imagewidth - 2]; dx[2] = - pslicea[(j+1) * imagewidth + imagewidth - 2]; dx[5] = - psliceb[j * imagewidth + imagewidth - 2]; dx[6] = - psliceb[(j+1) * imagewidth + imagewidth - 2]; else dx[1] = pslicea[j * imagewidth + k + 2] - pslicea[j * imagewidth + k]; dx[2] = pslicea[(j + 1) * imagewidth + k + 2] - pslicea[(j + 1) * imagewidth + k]; dx[5] = psliceb[j * imagewidth + k + 2] - psliceb[j * imagewidth + k]; dx[6] = psliceb[(j + 1) * imagewidth + k + 2] - psliceb[(j + 1) * imagewidth + k]; if (j == 0) dy[0] = pslicea[imagewidth + k]; dy[1] = pslicea[imagewidth + k + 1]; dy[4] = psliceb[imagewidth + k]; dy[5] = psliceb[imagewidth + k + 1]; else dy[0] = pslicea[(j + 1) * imagewidth + k] - pslicea[(j - 1) * imagewidth + k]; dy[1] = pslicea[(j + 1) * imagewidth + k + 1] - pslicea[(j - 1) * imagewidth + k + 1]; dy[4] = psliceb[(j + 1) * imagewidth + k] - psliceb[(j - 1) * imagewidth + k]; dy[5] = psliceb[(j + 1) * imagewidth + k + 1] - psliceb[(j - 1) * imagewidth + k + 1]; if (j == imageheight - 2) 42
50 dy[2] = - pslicea[(imageheight-2) * imagewidth + k+1]; dy[3] = - pslicea[(imageheight-2) * imagewidth + k]; dy[6] = - psliceb[(imageheight-2) * imagewidth + k+1]; dy[7] = - psliceb[(imageheight-2) * imagewidth + k]; else dy[2] = pslicea[(j + 2) * imagewidth + k + 1] - pslicea[j * imagewidth + k + 1]; dy[3] = pslicea[(j + 2) * imagewidth + k] - pslicea[j * imagewidth + k]; dy[6] = psliceb[(j + 2) * imagewidth + k + 1] - psliceb[j * imagewidth + k + 1]; dy[7] = psliceb[(j + 2) * imagewidth + k] - psliceb[j * imagewidth + k]; dz[0] = psliceb[j * imagewidth + k] - pslicec[j * imagewidth + k]; dz[1] = psliceb[j * imagewidth + k + 1] - pslicec[j * imagewidth + k + 1]; dz[2] = psliceb[(j + 1) * imagewidth + k + 1] - pslicec[(j + 1) * imagewidth + k + 1]; dz[3] = psliceb[(j + 1) * imagewidth + k] - pslicec[(j + 1) * imagewidth + k]; dz[4] = psliced[j * imagewidth + k] - pslicea[j *imagewidth + k]; dz[5] = psliced[j * imagewidth + k + 1] - pslicea[j * imagewidth + k + 1]; dz[6] = psliced[(j + 1) * imagewidth + k + 1] - pslicea[(j + 1) * imagewidth + k + 1]; dz[7] = psliced[(j + 1) * imagewidth + k] 43
51 - pslicea[(j + 1) * imagewidth + k]; //calculate triangle vertex's coordinate value //and gradient vertpos = 0; facepos = 0; for (w=0; w<12; w++) // 根 据 g_edgetable 对 应 值 判 断 Cube 的 那 一 条 边 与 等 值 面 有 交 点 if (g_edgetable[cubetype] & (1 << w)) switch (w) case 0: offset = j * imagewidth + k; edgegroup = bottomxedge; break; case 1: offset = j * imagewidth + k + 1; edgegroup = bottomyedge; break; case 2: offset = (j+1) * imagewidth + k; edgegroup = bottomxedge; break; case 3: offset = j * imagewidth + k; edgegroup = bottomyedge; break; case 4: offset = j * imagewidth + k; edgegroup = topxedge; break; case 5: 44
52 offset = j * imagewidth + k + 1; edgegroup = topyedge; break; case 6: offset = (j+1) * imagewidth + k; edgegroup = topxedge; break; case 7: offset = j * imagewidth + k; edgegroup = topyedge; break; case 8: offset = j * imagewidth + k; edgegroup = zedge; break; case 9: offset = j * imagewidth + k + 1; edgegroup = zedge; break; case 10: offset = (j+1)*imagewidth + k+1; edgegroup = zedge; break; case 11: offset = (j+1)*imagewidth + k; edgegroup = zedge; break; // 该 边 上 的 顶 点 是 否 已 经 在 上 一 层 中 生 成 if (edgegroup[offset] == -1) int index1; int index2; 45
53 VT s1, s2, s; float x1, y1, z1, nx1, ny1, nz1; float x2, y2, z2, nx2, ny2, nz2; // 得 到 该 边 两 端 点 的 索 引 进 而 得 到 两 点 的 灰 度 值 index1 = g_coordtable[w][3]; index2 = g_coordtable[w][4]; s1 = cubegrid[index1]; s2 = cubegrid[index2]; if (s1 < highthreshold && s1 > lowthreshold) if (s2 >= highthreshold) s = highthreshold; else if (s2 <= lowthreshold) s = lowthreshold; else if (s2 < highthreshold && s2 > lowthreshold) if (s1 >= highthreshold) s = highthreshold; else if (s1 <= lowthreshold) s = lowthreshold; // 计 算 两 端 点 的 实 际 坐 标 x1 = (k + g_coordvertex[index1][0]) * XSpace; y1 = (j + g_coordvertex[index1][1]) * YSpace; z1 = (i + g_coordvertex[index1][2]) * ZSpace; x2 = (k + g_coordvertex[index2][0]) * XSpace; y2 = (j + g_coordvertex[index2][1]) * YSpace; z2 = (i + g_coordvertex[index2][2]) * ZSpace; 46
54 // 计 算 两 端 点 处 的 法 向 量 nx1 = dx[index1] / XSpace; ny1 = dy[index1] / YSpace; nz1 = dz[index1] / ZSpace; nx2 = dx[index2] / XSpace; ny2 = dy[index2] / YSpace; nz2 = dz[index2] / ZSpace; float factor = ((float)(s - s1)) / ((float)(s2 - s1)); // 插 值 计 算 交 点 坐 标 vertpoint[vertpos][0] = factor * (x2 - x1) + x1; vertpoint[vertpos][1] = factor * (y2 - y1) + y1; vertpoint[vertpos][2] = factor * (z2 - z1) + z1; // 插 值 计 算 交 点 处 法 向 量 vertpoint[vertpos][3] = factor*(nx1-nx2)-nx1; vertpoint[vertpos][4] = factor*(ny1-ny2)-ny1; vertpoint[vertpos][5] = factor*(nz1-nz2)-nz1; // 法 向 量 归 一 化 squaroot=sqrt(vertpoint[vertpos][3] * vertpoint[vertpos][3] + vertpoint[vertpos][4] * vertpoint[vertpos][4] + vertpoint[vertpos][5] * vertpoint[vertpos][5]); if (squaroot <= 0) squaroot = 1.0; vertpoint[vertpos][3] /= squaroot; vertpoint[vertpos][4] /= squaroot; vertpoint[vertpos][5] /= squaroot; // 更 新 包 围 盒 数 据 if(min[0] > vertpoint[vertpos][0]) min[0] = vertpoint[vertpos][0]; if(max[0] < vertpoint[vertpos][0]) max[0] = vertpoint[vertpos][0]; if(min[1] > vertpoint[vertpos][1]) min[1] = vertpoint[vertpos][1]; 47
55 if(max[1] < vertpoint[vertpos][1]) max[1] = vertpoint[vertpos][1]; if(min[2] > vertpoint[vertpos][2]) min[2] = vertpoint[vertpos][2]; if(max[2] < vertpoint[vertpos][2]) max[2] = vertpoint[vertpos][2]; // 记 录 新 生 成 的 顶 点 索 引 cellverts[w] = vnumber; edgegroup[offset] = cellverts[w]; vnumber ++; vertpos ++; else // 若 该 顶 点 已 经 在 上 一 层 生 成, 则 直 接 得 到 其 索 引 cellverts[w] = edgegroup[offset]; // 存 储 当 前 Cube 新 生 成 的 顶 点 及 其 法 向 量 数 据 index_type m = output->getvertexnumber(); if (vnumber > m) output->setvertexnumber(m ); pvertex = output->getvertexdata(); memcpy(pvertex + 6 * (vnumber - vertpos), vertpoint, sizeof(float) * 6 * vertpos); // 记 录 新 生 成 的 三 角 面 片 数 据 w = 0; while (g_tritable[cubetype][w]!= -1) for (r=0; r<3; r++) triindex[facepos][r] = cellverts[g_tritable[cubetype][w++]]; 48
56 facepos ++; fnumber ++; m = output->getfacenumber(); if (fnumber > m) output->setfacenumber(m ); pface = output->getfacedata(); memcpy(pface + 3 * (fnumber - facepos), triindex, sizeof(unsigned int) * 3 * facepos); memcpy(bottomxedge, topxedge, sizeof(index_type) * imagesize); memcpy(bottomyedge, topyedge, sizeof(index_type) * imagesize); memset(topxedge, -1, sizeof(index_type) * imagesize); memset(topyedge, -1, sizeof(index_type) * imagesize); memset(zedge, -1, sizeof(index_type) * imagesize); delete []tempslice; delete []bottomxedge; delete []bottomyedge; delete []topxedge; delete []topyedge; delete []zedge; // 设 置 输 出 Mesh 的 相 关 参 数 output->setvertexnumber(vnumber); output->setfacenumber(fnumber); output->setboundingbox(min[0], max[0], min[1], max[1], min[2], max[2]); return 1; 49
57 [1] 基 于 分 割 的 Marching Cubes 方 法 众 所 周 知, 医 学 图 像 具 有 模 糊 性 首 先, 医 学 图 像 具 有 灰 度 上 的 模 糊 性 在 同 一 种 组 织 中 密 度 值 会 出 现 大 幅 度 的 变 化 如 骨 骼 中 股 骨, 鼻 窦 骨 骼 和 牙 齿 的 密 度 就 有 很 大 差 别 ; 在 同 一 个 物 体 中 密 度 值 也 不 均 匀 如 股 骨 外 表 面 和 内 部 的 骨 髓 的 密 度 其 次, 医 学 图 像 具 有 几 何 上 的 模 糊 性 在 一 个 边 界 上 的 大 体 素 中 常 常 同 时 包 含 边 界 和 物 体 两 种 物 质 ; 图 像 中 物 体 的 边 缘, 拐 角 及 区 域 间 的 关 系 都 难 以 精 确 的 加 以 描 述 一 些 病 变 组 织 由 于 侵 袭 周 围 组 织 其 边 缘 无 法 明 确 界 定 然 后, 还 有 不 确 定 性 知 识 的 影 响 通 常 正 常 组 织 或 部 位 没 有 的 结 构 在 病 变 情 况 下 出 现, 如 脏 器 表 面 的 肿 物, 骨 骼 表 面 的 骨 刺, 它 的 出 现 给 建 造 模 型 带 来 困 难 医 学 图 像 的 这 些 特 点 为 它 的 三 维 重 建 带 来 很 多 困 难 基 于 以 上 考 虑, 我 们 采 用 了 基 于 分 割 的 等 值 面 生 成 算 法 (SEGMC), 该 算 法 将 分 割 结 果 作 为 MC 的 输 入, 这 样 可 以 根 据 图 像 特 征 选 择 最 恰 当 的 分 割 方 法, 利 用 分 割 结 果 构 造 等 值 面 图 像 分 割 是 进 行 表 面 重 建 的 基 础, 分 割 的 效 果 直 接 影 响 到 表 面 重 建 的 速 度 和 重 建 后 模 型 的 视 觉 效 果 通 过 分 割 可 以 帮 助 医 生 将 感 兴 趣 的 物 体 ( 病 变 组 织 等 ) 提 取 出 来, 减 少 了 三 维 体 数 据 的 数 据 量, 为 表 面 重 建 和 显 示 提 供 了 方 便, 并 使 得 医 生 能 够 对 病 变 组 织 进 行 定 性 及 定 量 的 分 析, 从 而 提 高 医 学 诊 断 的 准 确 性 和 科 学 性 这 里 所 介 绍 的 算 法 SEGMC 由 于 将 分 割 与 MC 相 结 合, 它 摆 脱 了 SMC 只 能 用 阈 值 分 割 的 局 限 性, 该 算 法 的 模 块 性 和 可 扩 充 性 好, 可 以 将 各 种 分 割 算 法 集 成 到 算 法 中 其 算 法 的 简 易 流 程 如 图 3-11 所 示 Source Volume Segmentation Filter Segmented Volume SEGMC Mesh 图 3-11 SEGMC 简 易 流 程 示 意 该 算 法 在 MITK 中 由 mitkbinmarchingcubes 实 现 mitkbinmarchingcubes 50
58 类 接 受 两 个 输 入 : 一 个 是 原 始 的 Volume, 用 于 计 算 法 向 量 ; 另 一 个 是 原 始 Volume 经 分 割 算 法 所 产 生 的 二 值 数 据, 用 于 计 算 等 值 点 坐 标, 只 要 当 前 Cube 的 某 边 两 端 点 灰 度 值 不 同, 则 认 为 该 边 与 等 值 面 相 交 交 点 坐 标 与 法 向 量 均 采 用 中 点 选 择 的 方 法 计 算 算 法 的 具 体 实 现 与 所 述 类 似, 只 是 在 判 断 有 无 交 点 及 交 点 坐 标 与 法 向 量 的 计 算 上 有 所 区 别, 这 里 就 不 再 详 细 说 明, 感 兴 趣 的 读 者 可 以 自 己 修 改 中 的 代 码 实 现 该 算 法 3.2 MITK 中 的 表 面 绘 制 框 架 表 面 绘 制 框 架 的 设 计 在 介 绍 面 绘 制 框 架 之 前, 首 先 介 绍 一 下 MITK 中 的 绘 制 模 型 MITK 中 的 绘 制 模 型 如 图 3-12 所 示, 其 中,View 是 Target 的 一 种, 用 于 将 计 算 所 得 的 结 果 显 示 在 计 算 机 屏 幕 上 它 本 身 并 不 做 实 际 的 绘 制 工 作, 而 只 是 提 供 一 个 显 示 环 境 所 有 计 算 结 果 的 绘 制 实 际 上 由 MITK 中 的 不 同 Model 来 实 现, Model 在 概 念 上 代 表 场 景 中 的 一 个 待 画 的 物 体, 可 以 是 一 幅 图 像, 也 可 以 是 三 维 图 形, 其 实 际 的 绘 制 由 相 应 的 Model 来 实 现 View 中 维 护 了 一 个 Model 的 列 表, 可 以 通 过 View 提 供 的 接 口 向 这 个 列 表 中 添 加 或 删 除 Model, 而 在 View 的 绘 制 函 数 OnDraw() 里 遍 历 列 表 中 的 每 一 个 Model, 调 用 其 虚 函 数 Render() 将 其 最 终 绘 制 出 来 View +AddModel(in amodel : Model) +RemoveModel(in amodel : Model) +OnDraw() m_models Model +Render() m_models ->AddModel (amodel) For all models in m_models : amodel->render() SurfaceModel +Render() VolumeModel +Render() 图 3-12 MITK 的 绘 制 模 型 MITK 中 跟 面 绘 制 相 关 的 Model 是 SurfaceModel, 其 主 要 任 务 是 实 现 父 类 里 规 定 的 接 口 Render() 来 绘 制 表 面 重 建 算 法 生 成 的 三 角 网 格 数 据 但 是, 绘 制 三 角 片 51
59 网 格 的 方 法 多 种 多 样, 而 且 加 光 照 模 型 时 设 置 表 面 材 质 属 性 的 参 数 众 多, 特 别 是 针 对 不 同 级 别 的 硬 件 配 置 有 不 同 的 硬 件 加 速 算 法, 而 这 些 算 法 往 往 不 是 通 用 的 为 了 最 大 程 度 的 提 升 面 绘 制 的 整 体 性 能 并 且 保 证 最 大 限 度 的 灵 活 性, 我 们 设 计 了 如 图 3-13 所 示 的 面 绘 制 框 架 Mesh +GetVertexNumber() +GetFaceNumber() +GetVertexData() +GetFaceData() SurfaceProperty +SetRepresentationType() +SetAmbientColor() +SetDiffuseColor() +SetSpecularColor() +SetEmissionColor() +SetSpecularPower() +SetOpacity()... m_data m_property SurfaceModel +Render() +SetData() +SetRenderer() +SetProperty() +GetRenderer() +GetProperty() SurfaceRendererStandard +Render() SurfaceRenderer +Render() m_renderer m_renderer ->Render() SurfaceRendererUseVA +Render() SurfaceRendererUseVBO +Render() 图 3-13 MITK 的 面 绘 制 框 架 从 图 中 可 以 看 出,SurfaceModel 拥 有 三 个 类 成 员 :Mesh SurfaceProperty 和 SurfaceRenderer Mesh 提 供 对 生 成 的 三 角 面 片 数 据 的 访 问 ;SurfaceProperty 维 护 表 面 模 型 的 材 质 属 性, 并 且 提 供 给 用 户 修 改 这 些 属 性 参 数 的 接 口 ; 而 SurfaceRenderer 则 负 责 最 终 实 际 的 绘 制 工 作 SurfaceModel 的 Render() 函 数 并 未 做 任 何 实 际 的 绘 制, 而 是 通 过 m_renderer 成 员 调 用 SurfaceRenderer 的 Render() 函 数 来 实 现 对 本 Model 的 绘 制, 具 体 的 绘 制 方 法 又 由 SurfaceRenderer 的 子 类 实 现 SurfaceRenderer 的 各 个 子 类 代 表 了 不 同 的 面 绘 制 方 法, 目 前 MITK 中 提 供 了 三 种 面 绘 制 的 Renderer: 标 准 的 Renderer(SurfaceRendererStandard) 采 用 OpenGL 的 顶 点 数 组 加 速 绘 制 的 Renderer(SurfaceRendererUseVA) 和 采 用 OpenGL 的 VBO 扩 展 加 速 绘 制 的 Renderer(SurfaceRendererUseVBO), 这 些 具 体 的 方 法 将 在 下 一 节 中 做 详 细 的 介 绍 这 种 模 块 化 的 面 绘 制 框 架 使 得 往 里 面 添 加 新 的 绘 制 方 法 变 得 非 常 简 单, 只 要 52
60 从 SurfaceModel 再 派 生 出 相 应 的 子 类 就 可 以 了, 而 通 过 SurfaceModel 的 SetRenderer() 接 口 可 以 随 意 更 换 当 前 SurfaceModel 的 绘 制 方 法 表 面 绘 制 框 架 的 实 现 (1) SurfaceModel 的 实 现 SurfaceModel 中 包 含 三 个 成 员 变 量 : mitkrcptr<mitkmesh> m_data; mitkrcptr<mitksurfaceproperty> m_property; mitkrcptr<mitksurfacerenderer> m_renderer; 它 们 均 采 用 Smart Pointer 包 装, 为 的 是 在 SurfaceModel 存 在 的 过 程 中 始 终 保 持 这 些 指 针 指 向 有 效 的 对 象 通 过 如 下 接 口 可 以 方 便 地 对 它 们 进 行 访 问 : void SetRenderer(mitkSurfaceRenderer *renderer); mitksurfacerenderer* GetRenderer(); void SetProperty(mitkSurfaceProperty *prop); mitksurfaceproperty* GetProperty(); void SetData(mitkMesh *data); mitkmesh* GetData(); 其 中,SetRenderer() SetProperty() 只 是 简 单 的 赋 值 操 作,GetData() 直 接 返 回 m_data, 没 什 么 好 说 的 余 下 的 GetRenderer() GetProperty() 和 SetData() 则 需 要 一 些 额 外 的 处 理, 其 代 码 如 下 所 示 : mitksurfacerenderer* mitksurfacemodel::getrenderer() if (m_renderer == NULL) // 要 用 Vertex Array 或 Vertex Buffer Object, // 必 须 保 证 OpenGL 版 本 在 1.1 以 上 #ifdef GL_VERSION_1_1 if (g_isextensionsupported("gl_arb_vertex_buffer_object")) // 如 果 支 持 VBO 扩 展, // 则 选 用 mitksurfacerendererusevbo 作 为 Renderer m_renderer = new mitksurfacerendererusevbo; else // 否 则, 选 用 mitksurfacerendereruseva 作 为 Renderer 53
61 m_renderer = new mitksurfacerendereruseva; #else // 最 坏 的 情 况, 只 能 用 标 准 的 也 是 速 度 最 慢 的 Renderer m_renderer = new mitksurfacerendererstandard; #endif return m_renderer; // mitksurfaceproperty* mitksurfacemodel::getproperty() if (m_property == NULL) // 若 无 SurfaceProperty 对 象 则 立 即 产 生 一 个 m_property = new mitksurfaceproperty; return m_property; // void mitksurfacemodel::setdata(mitkmesh *data) m_data = data; if (data == NULL) return; // 得 到 包 围 盒 m_data->getboundingbox(m_bounds); // 重 新 计 算 模 型 中 心 位 置 m_origin[0] = (m_bounds[1] + m_bounds[0]) / 2.0f; m_origin[1] = (m_bounds[3] + m_bounds[2]) / 2.0f; m_origin[2] = (m_bounds[5] + m_bounds[4]) / 2.0f; // 设 置 更 改 标 志, 让 View 重 新 计 算 相 机 位 置 m_datachanged = true; // 设 置 更 改 标 志, 以 更 新 Model 所 维 护 的 模 型 变 换 矩 阵 m_matrixmodified = 1; //
62 可 以 看 到, 在 GetRenderer() 中, 根 据 当 前 硬 件 的 配 置 选 用 了 最 合 适 的 Renderer 来 绘 制 这 个 SurfaceModel, 以 求 得 到 比 较 好 的 面 绘 制 性 能 当 然, 用 户 也 可 以 在 自 己 程 序 的 任 何 地 方 通 过 SetRenderer() 函 数 直 接 更 换 MITK 配 置 的 Renderer 其 中 的 g_isextensionsupported() 用 于 检 测 OpenGL 的 某 项 扩 展 是 否 被 当 前 硬 件 支 持, 其 原 型 在 mitkopenglexam.h 中 在 SurfaceModel 的 Render() 函 数 中, 只 是 调 用 其 SurfaceRenderer 成 员 m_renderer 的 Render() 函 数 来 实 现 Model 的 绘 制 : int mitksurfacemodel::render(mitkview *view) if(m_data == NULL) return 0; this->getrenderer()->render(view, this); return 1; (2) SurfaceProperty 的 实 现 SurfaceProperty 中 包 含 一 组 数 据 成 员, 记 录 了 表 面 材 质 属 性 的 参 数 值 : float m_color[4]; float m_ambientcolor[4]; float m_diffusecolor[4]; float m_specularcolor[4]; float m_emissioncolor[4]; float m_edgecolor[4]; float m_ambient; float m_diffuse; float m_specular; float m_emission; float m_specularpower; float m_opacity; float m_pointsize; float m_linewidth; // 表 面 颜 色 // 环 境 光 颜 色 // 散 射 光 颜 色 // 反 射 光 颜 色 // 发 射 光 颜 色 // 采 用 线 框 方 式 绘 制 时 线 框 的 颜 色 // 环 境 光 系 数 // 散 射 光 系 数 // 反 射 光 系 数 // 发 射 光 系 数 // 发 射 光 强 度 // 不 透 明 度 // 采 用 顶 点 方 式 绘 制 时 点 的 大 小 // 采 用 线 框 方 式 绘 制 时 线 的 宽 度 55
63 int m_representationtype; // 绘 制 方 式 int m_interpolationtype; // 插 值 方 式 int m_linestipplerepeatfactor; // 点 划 线 重 复 因 子 unsigned short m_linestipplepattern; // 点 划 线 模 板 unsigned short m_modified; // 参 数 是 否 被 更 改 每 种 颜 色 值 按 RGBA 四 个 分 量 存 储, 其 中,m_Color 的 值 由 如 下 算 法 得 到 : float* mitksurfaceproperty::getcolor() float norm, total = m_ambient + m_diffuse + m_specular + m_emission; if ( total > 0 ) norm = 1.0f / total; else norm = 0.0f; for (int i=0; i<4; ++i) m_color[i] = m_ambientcolor[i] * m_ambient * norm + m_diffusecolor[i] * m_diffuse * norm + m_specularcolor[i] * m_specular * norm + m_emissioncolor[i] * m_emission * norm; return m_color; m_opacity 只 是 为 方 便 而 提 供, 实 际 绘 制 中 其 含 意 与 Diffuse Color 的 Alpha 分 量 ( 即 m_diffusecolor[3]) 相 同, 均 代 表 物 体 表 面 的 不 透 明 程 度, 其 值 也 始 终 保 持 一 致, 值 为 0 表 示 完 全 透 明,1 表 示 完 全 不 透 明 m_representationtype 记 录 三 角 面 片 数 据 的 绘 制 方 式, 其 值 为 如 下 三 个 值 之 一 :MITK_MESH_POINTS( 仅 绘 制 顶 点 ) MITK_MESH_WIREFRAME( 绘 制 线 框 模 型 ) 和 MITK_MESH_SURFACE( 绘 制 加 光 照 的 表 面 模 型 ) 56
64 m_interpolationtype 记 录 绘 制 表 面 模 型 时 的 顶 点 着 色 的 插 值 方 式, 其 值 为 如 下 三 个 值 之 一 :MITK_SURFACE_FLAT( 平 面 着 色 方 式, 计 算 最 简 便 但 效 果 最 差 ),MITK_SURFACE_GOURAUD(Gouraud 着 色, 计 算 复 杂 但 效 果 较 好 ), MITK_SURFACE_PHONG(Phong 着 色, 计 算 非 常 耗 时, 但 可 以 得 到 更 加 平 滑 的 效 果 ) 其 中,Phong 着 色 并 未 在 目 前 MITK 提 供 的 Renderer 中 实 现, 而 只 是 采 取 与 Gouraud 着 色 相 同 的 方 式 进 行 处 理, 所 以 实 际 上 只 有 两 种 插 值 方 式 在 工 作 m_linestipplerepeatfactor 和 m_linestipplepattern 指 定 当 采 用 线 框 方 式 绘 制 模 型 并 采 用 点 划 线 时 点 划 线 的 线 型 其 中,m_LineStipplePattern 为 一 个 16 位 整 型 数, 它 的 二 进 制 位 模 板 决 定 在 绘 制 线 段 时 哪 个 片 断 将 被 绘 出 ( 二 进 制 位 为 1 的 绘 出, 为 0 的 则 不 绘 出 ),m_linestipplerepeatfactor 则 指 定 绘 制 模 板 中 每 个 二 进 制 位 的 重 复 次 数, 比 如 其 值 为 3 时, 模 板 中 每 个 二 进 制 位 将 重 复 使 用 3 次 后 才 使 用 下 一 位 SurfaceProperty 提 供 了 丰 富 的 Set/Get 接 口 来 访 问 上 述 参 数, 接 口 函 数 请 参 考 mitksurfaceproperty.h, 这 里 就 不 再 多 说 了 具 体 的 Renderer 将 根 据 上 述 参 数 来 决 定 如 何 绘 制 其 所 在 的 SurfaceModel 在 实 际 的 使 用 过 程 中, 当 要 调 整 某 一 个 SurfaceModel 的 部 分 绘 制 参 数 时, 一 般 情 况 下 我 们 并 不 提 倡 额 外 保 留 一 个 指 向 SurfaceProperty 的 指 针 来 对 其 中 的 参 数 进 行 设 置, 而 是 直 接 通 过 SurfaceModel 的 GetProperty() 接 口 实 现, 如 下 所 示, 以 免 出 现 不 一 致 的 情 况 而 发 生 错 误 : asurfmodel->getproperty()->setambientcolor(0.75f, 0.75f, 0.75f, 1.0f); asurfmodel->getproperty()->setdiffusecolor(1.0f, 0.57f, 0.04f, 1.0f); asurfmodel->getproperty()->setspecularcolor(1.0f, 1.0f, 1.0f, 1.0f); asurfmodel->getproperty()->setspecularpower(100.0f); asurfmodel->getproperty()->setemissioncolor(0.0f, 0.0f, 0.0f, 0.0f); 当 然 也 可 以 预 先 生 成 多 个 不 同 情 况 下 绘 制 所 对 应 的 SurfaceProperty, 然 后 在 需 要 的 时 候 通 过 SetProperty() 直 接 替 换 SurfaceModel 的 Property (3) 实 现 具 体 的 SurfaceRenderer SurfaceRenderer 是 面 绘 制 的 关 键, 所 有 的 绘 制 工 作 都 在 SurfaceRenderer 的 子 类 中 完 成 57
65 SurfaceRenderer 继 承 自 Renderer,Renderer 中 维 护 了 一 个 裁 剪 平 面 的 列 表, 如 果 额 外 的 裁 剪 平 面 被 激 活, 则 其 子 类 中 将 根 据 其 中 记 录 的 裁 剪 平 面 实 施 具 体 的 裁 剪 操 作 SurfaceRenderer 的 子 类 则 通 过 实 现 父 类 中 的 虚 函 数 Render() 完 成 最 终 的 绘 制 下 面 通 过 MITK 中 已 经 实 现 的 三 个 SurfaceRenderer 子 类 来 看 一 下 如 何 实 现 一 个 具 体 的 SurfaceRenderer SurfaceRendererStandard 这 是 一 个 标 准 的 SurfaceRenderer, 采 用 传 统 的 OpenGL 绘 制 方 式, 没 有 做 任 何 加 速 的 尝 试, 但 是 其 兼 容 性 是 最 好 的, 可 以 在 任 何 支 持 OpenGL 的 计 算 机 上 运 行 其 Render() 函 数 如 下 : void mitksurfacerendererstandard::render(mitkview *view, mitksurfacemodel *surf) // 得 到 需 要 绘 制 的 Mesh 数 据 mitkmesh *mesh = surf->getdata(); if (mesh == NULL) return; // 取 得 指 向 点 表 和 面 表 的 指 针 float *vertexbuf = mesh->getvertexdata(); unsigned int *facebuf = mesh->getfacedata(); if ((vertexbuf == NULL) (facebuf == NULL)) return; // 得 到 顶 点 和 三 角 片 的 数 量 unsigned int vertexnum = mesh->getvertexnumber(); unsigned int facenum = mesh->getfacenumber(); // 得 到 SurfaceModel 的 Property mitksurfaceproperty *prop = surf->getproperty(); 58
66 // 是 否 需 要 alpha 融 合 ( 半 透 明 显 示 ) bool blend = ( prop->getopacity() < 1.0f ); // 线 框 显 示 时 是 否 采 用 点 划 方 式 bool stipple = ( prop->getlinestipplepattern()!= 0xFFFF ); if (prop->ismodified()) // 若 材 质 属 性 被 修 改, 则 重 新 设 置 材 质 属 性 this->_setmaterial(prop); prop->setunmodified(); gldisable(gl_texture_2d); glmatrixmode(gl_modelview); glpushmatrix(); // 模 型 的 几 何 变 换 glmultmatrixf((glfloat *)surf->getmodelmatrix()); // 如 果 附 加 裁 剪 平 面 被 激 活 则 设 定 裁 剪 平 面 if (this->getclipping()) mitkplane *aplane = NULL; int count = 0; mitklist *planes = this->getclippingplanes(); double equation[4]; float nx, ny, nz, ox, oy, oz; for (planes->inittraversal(); (aplane = (mitkplane *)planes->getnextitem()) && (count<6);) aplane->getnormal(nx, ny, nz); aplane->getorigin(ox, oy, oz); equation[0] = (double)nx; equation[1] = (double)ny; equation[2] = (double)nz; equation[3] = - (double)(nx*ox + ny*oy + nz*oz); glclipplane(gl_clip_plane0+count,equation); 59
67 glenable(gl_clip_plane0+count); count ++; // 根 据 不 同 的 显 示 方 式 绘 制 SurfaceModel switch(prop->getrepresentationtype()) case MITK_MESH_POINTS: // 点 显 示 gldisable(gl_lighting); this->_drawpoints(vertexnum, vertexbuf); break; case MITK_MESH_WIREFRAME: // 线 框 显 示 if (stipple) glenable(gl_line_stipple); else gldisable(gl_line_stipple); gldisable(gl_lighting); glcolor4fv(prop->getedgecolor()); this->_drawwireframe(facenum, vertexbuf, facebuf); if (stipple) gldisable(gl_line_stipple); break; case MITK_MESH_SURFACE: // 面 显 示 glenable(gl_depth_test); if (blend) glenable(gl_blend); gldepthmask(gl_false); glenable(gl_lighting); glenable(gl_normalize); if (mesh->isclockwise()) glfrontface(gl_cw); this->_drawsurface(facenum, vertexbuf, facebuf); if (blend) gldisable(gl_blend); if (mesh->isclockwise()) glfrontface(gl_ccw); if (blend) gldisable(gl_blend); gldepthmask(gl_true); 60
68 gldisable(gl_normalize); gldisable(gl_lighting); gldisable(gl_depth_test); break; glpopmatrix(); 其 中, 集 中 设 置 表 面 材 质 属 性 的 函 数 _setmaterial() 如 下 所 示 : void mitksurfacerendererstandard::_setmaterial(mitksurfaceproperty *prop) GLenum method, face; // 设 置 插 值 方 式 switch (prop->getinterpolationtype()) case MITK_SURFACE_FLAT: method = GL_FLAT; break; case MITK_SURFACE_GOURAUD: case MITK_SURFACE_PHONG: method = GL_SMOOTH; break; default: method = GL_SMOOTH; glshademodel(method); // 点 和 线 段 的 绘 制 属 性 设 置 glpointsize(prop->getpointsize()); gllinewidth(prop->getlinewidth()); gllinestipple(prop->getlinestipplerepeatfactor(), prop->getlinestipplepattern()); // 表 面 材 质 属 性 设 置 face = GL_FRONT_AND_BACK; 61
69 glmaterialfv(face, GL_AMBIENT, prop->getambientcolor()); glmaterialfv(face, GL_DIFFUSE, prop->getdiffusecolor()); glmaterialfv(face, GL_SPECULAR, prop->getspecularcolor()); glmaterialfv(face, GL_EMISSION, prop->getemissioncolor()); glmaterialf(face, GL_SHININESS, prop->getspecularpower()); // 设 置 Alpha 融 合 运 算 方 式 ( 用 于 表 面 的 半 透 明 显 示 ) glblendfunc(gl_src_alpha, GL_ONE_MINUS_SRC_ALPHA); MITK 为 三 维 模 型 的 绘 制 环 境 维 护 了 三 个 变 换 矩 阵 及 其 逆 矩 阵, 它 们 是 模 型 变 换 矩 阵 及 其 逆 矩 阵 视 图 变 换 矩 阵 及 其 逆 矩 阵 和 投 影 变 换 矩 阵 及 其 逆 矩 阵, 这 里 对 模 型 的 几 何 变 换 ( 包 括 平 移 旋 转 缩 放 ) 即 通 过 直 接 乘 上 模 型 变 换 矩 阵 来 实 现, 而 没 有 通 过 调 用 gltranslatef() glrotatef() 等 函 数 实 现, 这 样 不 仅 简 化 代 码 书 写, 而 且 在 一 定 程 度 上 提 高 了 绘 制 的 效 率 三 种 显 示 方 式 的 绘 制 实 际 是 在 _drawpoints() _drawwireframe() 和 _drawsurface() 三 个 函 数 中 实 现 的, 其 代 码 如 下 : void mitksurfacerendererstandard::_drawpoints(unsigned int vertexnum, float *vertexdata) glbegin(gl_points); for (unsigned int i=0; i<vertexnum; ++i) glvertex3fv(&vertexdata[6*i]); glend(); // void mitksurfacerendererstandard::_drawwireframe(unsigned int facenum, float *vertexdata, unsigned int *facedata) glbegin(gl_lines); for (unsigned int i=0; i<facenum; ++i) glvertex3fv(&vertexdata[6*facedata[3*i]]); glvertex3fv(&vertexdata[6*facedata[3*i+1]]); glvertex3fv(&vertexdata[6*facedata[3*i+1]]); glvertex3fv(&vertexdata[6*facedata[3*i+2]]); glvertex3fv(&vertexdata[6*facedata[3*i+2]]); 62
70 glvertex3fv(&vertexdata[6*facedata[3*i]]); glend(); // void mitksurfacerendererstandard::_drawsurface(unsigned int facenum, float *vertexdata, unsigned int *facedata) glbegin(gl_triangles); for (unsigned int i=0; i<facenum; ++i) glnormal3fv(vertexdata + 6*faceData[3*i] + 3); glvertex3fv(vertexdata + 6*faceData[3*i]); glnormal3fv(vertexdata + 6*faceData[3*i+1] + 3); glvertex3fv(vertexdata + 6*faceData[3*i+1]); glnormal3fv(vertexdata + 6*faceData[3*i+2] + 3); glvertex3fv(vertexdata + 6*faceData[3*i+2]); glend(); // 可 以 看 出, 所 有 的 绘 制 都 是 采 用 最 基 本 的 OpenGL 代 码, 虽 然 慢, 但 是 可 以 适 应 绝 大 多 数 的 绘 制 环 境 SurfaceRendererUseVA [7][8] 这 个 SurfaceRenderer 使 用 了 OpenGL 中 的 顶 点 数 组 (Vertex Array) 来 加 速 三 角 面 片 的 绘 制 采 用 顶 点 数 组, 只 需 将 指 向 顶 点 数 据 的 指 针 传 给 OpenGL, 并 在 绘 制 时 告 诉 OpenGL 顶 点 数 据 的 排 列 方 式, 由 OpenGL 决 定 按 何 种 顺 序 以 及 如 何 绘 制 这 些 顶 点, 这 样 就 可 以 避 免 绘 制 每 个 顶 点 时 glvertex*() glnormal*() 等 函 数 调 用 的 开 销, 从 而 大 大 提 高 绘 制 速 度 该 类 包 含 了 指 向 顶 点 数 组 的 指 针 m_vertices 以 及 指 向 边 表 和 面 表 的 指 针 m_edges 和 m_faces, 其 中 边 表 和 面 表 均 存 放 构 成 边 和 面 的 顶 点 在 顶 点 数 组 中 的 索 引 顶 点 数 组 和 面 表 可 以 直 接 从 Mesh 中 得 来, 而 边 表 还 需 创 建 理 想 情 况 下, 63
71 边 表 所 存 储 的 边 应 该 是 无 重 复 的, 但 是 考 虑 到 要 创 建 这 样 的 边 表, 算 法 复 杂 度 比 较 高, 故 采 用 直 接 从 面 表 创 建 的 方 法, 这 样, 被 两 个 面 共 用 的 边 会 出 现 两 次, 占 用 的 存 储 空 间 最 多 是 理 想 情 况 的 两 倍, 但 创 建 的 速 度 要 快 的 多 这 部 分 的 工 作 在 _buildarrays() 中 完 成, 如 下 所 示 : bool mitksurfacerendereruseva::_buildarrays(mitkmesh *mesh) // 清 除 旧 的 数 据 this->_cleararrays(); // 由 Mesh 直 接 得 到 顶 点 数 组 和 面 表 数 据 m_vertnum = mesh->getvertexnumber(); m_facenum = mesh->getfacenumber(); m_vertices = mesh->getvertexdata(); m_faces = mesh->getfacedata(); if (m_vertices==null m_faces==null) mitkerrormessage("something wrong with mesh data!"); return false; // 创 建 边 表 m_edgenum = m_facenum * 3; m_edges = new unsigned int[m_edgenum*2]; if (m_edges == NULL) mitkerrormessage("not enough memory for building edge table using vertex array!"); return false; unsigned int *curface = m_faces; unsigned int *curedge = m_edges; for (unsigned long i=0; i<m_facenum; ++i) curedge[0] = curface[0]; curedge[1] = curface[1]; curedge[2] = curface[0]; 64
72 curedge[3] = curface[2]; curedge[4] = curface[1]; curedge[5] = curface[2]; curface += 3; curedge += 6; return true; 最 后, 该 类 的 Render() 函 数 如 下 所 示, 框 架 基 本 与 SurfaceRendererStandard 同, 因 此 只 在 不 同 之 处 加 以 注 释 : void mitksurfacerendereruseva::render(mitkview *view, mitksurfacemodel *surf) mitkmesh *mesh = surf->getdata(); if (mesh == NULL) return; if (surf->getdatamodifystatus()) // 若 SurfaceModel 包 含 的 Mesh 数 据 有 所 改 变, 则 重 新 创 建 相 关 数 组 if (!this->_buildarrays(mesh)) return; mitksurfaceproperty *prop = surf->getproperty(); bool blend = ( prop->getopacity() < 1.0f ); bool stipple = ( prop->getlinestipplepattern()!= 0xFFFF ); if (prop->ismodified()) this->_setmaterial(prop); prop->setunmodified(); 65
73 gldisable(gl_texture_2d); glenableclientstate(gl_vertex_array); // 启 用 顶 点 数 组 glenableclientstate(gl_normal_array); // 启 用 法 向 量 数 组 glmatrixmode(gl_modelview); glpushmatrix(); glmultmatrixf((glfloat *)surf->getmodelmatrix()); if (this->getclipping()) mitkplane *aplane = NULL; int count = 0; mitklist *planes = this->getclippingplanes(); double equation[4]; float nx, ny, nz, ox, oy, oz; for (planes->inittraversal(); (aplane = (mitkplane *)planes->getnextitem()) && (count<6);) aplane->getnormal(nx, ny, nz); aplane->getorigin(ox, oy, oz); equation[0] = (double)nx; equation[1] = (double)ny; equation[2] = (double)nz; equation[3] = - (double)(nx*ox + ny*oy + nz*oz); glclipplane(gl_clip_plane0+count,equation); glenable(gl_clip_plane0+count); count ++; switch(prop->getrepresentationtype()) case MITK_MESH_POINTS: gldisable(gl_lighting); // 指 定 顶 点 数 组 并 按 顶 点 方 式 绘 制 glvertexpointer(3, GL_FLOAT, 6*sizeof(float), m_vertices); gldrawarrays(gl_points, 0, m_vertnum); 66
74 break; case MITK_MESH_WIREFRAME: if (stipple) glenable(gl_line_stipple); else gldisable(gl_line_stipple); gldisable(gl_lighting); glcolor4fv(prop->getedgecolor()); // 指 定 顶 点 数 组 并 按 索 引 方 式 绘 制 所 有 的 边 glvertexpointer(3, GL_FLOAT, 6*sizeof(float), m_vertices); gldrawelements(gl_lines, m_edgenum * 2, GL_UNSIGNED_INT, m_edges); if (stipple) gldisable(gl_line_stipple); break; case MITK_MESH_SURFACE: glenable(gl_depth_test); if (blend) glenable(gl_blend); gldepthmask(gl_false); glenable(gl_lighting); glenable(gl_normalize); if (mesh->isclockwise()) glfrontface(gl_cw); // 指 定 顶 点 数 组 及 法 向 量 数 组 并 按 索 引 方 式 绘 制 所 有 三 角 片 glvertexpointer(3, GL_FLOAT, 6*sizeof(float), m_vertices); glnormalpointer(gl_float, 6*sizeof(float), m_vertices+3); gldrawelements(gl_triangles, m_facenum * 3, GL_UNSIGNED_INT, m_faces); if (blend) gldisable(gl_blend); if (mesh->isclockwise()) glfrontface(gl_ccw); if (blend) gldisable(gl_blend); gldepthmask(gl_true); 67
75 gldisable(gl_normalize); gldisable(gl_lighting); gldisable(gl_depth_test); break; gldisableclientstate(gl_vertex_array); // 关 闭 顶 点 数 组 gldisableclientstate(gl_normal_array); // 关 闭 法 向 量 数 组 glpopmatrix(); 该 绘 制 方 法 仅 被 OpenGL 1.1 及 以 上 版 本 支 持 SurfaceRendererUseVBO [9] 这 个 SurfaceRenderer 使 用 了 OpenGL 的 VBO(Vertex Buffer Object) 扩 展 来 加 速 三 角 面 片 的 绘 制 VBO 扩 展 的 工 作 方 式 和 顶 点 数 组 很 像, 唯 一 的 区 别 就 是 VBO 将 数 据 直 接 加 载 到 显 卡 的 高 性 能 显 存 里, 省 去 了 系 统 内 存 与 显 存 之 间 频 繁 的 数 据 传 输, 因 此 大 大 提 高 了 渲 染 速 度 该 扩 展 是 依 赖 于 较 新 的 硬 件 的, 所 以 我 们 在 使 用 它 之 前 必 须 确 保 当 前 所 使 用 的 显 卡 支 持 这 一 扩 展, 方 法 是 使 用 glgetstring(gl_extensions) 得 到 所 有 当 前 被 支 持 的 OpenGL 扩 展 的 字 符 串, 然 后 查 询 其 中 是 否 包 含 GL_ARB_vertex_buffer_object 这 一 功 能 已 被 封 装 进 全 局 函 数 g_isextensionsupported() 中, 其 原 型 为 : bool g_isextensionsupported(const char *extension); 它 接 受 扩 展 的 名 称 字 符 串 作 为 参 数, 返 回 一 个 bool 值 表 示 该 扩 展 是 否 被 支 持 由 于 Microsoft 在 Windows 系 列 操 作 系 统 中 提 供 的 OpenGL 库 文 件 到 现 在 还 停 留 在 1.1 版 本, 所 以 如 果 显 卡 支 持 VBO 扩 展, 我 们 还 需 要 用 wglgetprocaddress() 直 接 从 由 显 卡 驱 动 提 供 的 最 新 的 DLL 文 件 中 得 到 使 用 VBO 扩 展 所 需 的 4 个 函 数 的 指 针 : glgenbuffersarb = (PFNGLGENBUFFERSARBPROC)wglGetProcAddress("glGenBuffersARB"); 68
76 glbindbufferarb = (PFNGLBINDBUFFERARBPROC)wglGetProcAddress("glBindBufferARB"); glbufferdataarb = (PFNGLBUFFERDATAARBPROC)wglGetProcAddress("glBufferDataARB"); gldeletebuffersarb = (PFNGLDELETEBUFFERSARBPROC)wglGetProcAddress("glDeleteBuffersARB"); 关 于 PFNGLGENBUFFERSARBPROC PFNGLBINDBUFFERARBPROC PFNGLBUFFERDATAARBPROC 和 PFNGLDELETEBUFFERSARBPROC 的 声 明 参 见 OpenGL 扩 展 的 头 文 件 glext.h, 该 文 件 的 最 新 版 本 可 以 在 得 到 这 些 初 始 化 工 作 在 构 造 函 数 中 完 成 : mitksurfacerendererusevbo::mitksurfacerendererusevbo() glgenbuffersarb = NULL; glbindbufferarb = NULL; glbufferdataarb = NULL; gldeletebuffersarb = NULL; m_vertvbo = m_facevbo = m_edgevbo = 0; m_vertices = NULL; m_faces = NULL; m_edges = NULL; m_isvbosupported = g_isextensionsupported("gl_arb_vertex_buffer_object"); m_vbobuilt = false; m_normalreversed = false; if (m_isvbosupported) glgenbuffersarb = (PFNGLGENBUFFERSARBPROC)wglGetProcAddress("glGenBuffersARB"); glbindbufferarb = (PFNGLBINDBUFFERARBPROC)wglGetProcAddress("glBindBufferARB"); glbufferdataarb = (PFNGLBUFFERDATAARBPROC)wglGetProcAddress("glBufferDataARB"); gldeletebuffersarb = (PFNGLDELETEBUFFERSARBPROC)wglGetProcAddress("glDeleteBuffersARB"); 69
77 在 SurfaceRendererUseVBO 中, 我 们 为 顶 点 数 组 面 表 和 边 表 分 别 创 建 了 一 个 VBO 对 象 :m_vertvbo m_facevbo 和 m_edgevbo, 其 创 建 工 作 在 _buildvbos() 中 完 成 : bool mitksurfacerendererusevbo::_buildvbos(mitkmesh *mesh) if (!this->_buildarrays(mesh)) return false; if (!m_vbobuilt) // 创 建 VBO glgenbuffersarb(1, &m_vertvbo); glgenbuffersarb(1, &m_facevbo); glgenbuffersarb(1, &m_edgevbo); m_vbobuilt = true; // 绑 定 顶 点 VBO 对 象 并 向 显 卡 传 送 顶 点 数 组 数 据 glbindbufferarb(gl_array_buffer_arb, m_vertvbo); glbufferdataarb(gl_array_buffer_arb, m_vertnum*6*sizeof(float), m_vertices, GL_STATIC_DRAW_ARB); m_normalreversed = mesh->isnormalreversed(); // 绑 定 面 表 VBO 对 象 并 向 显 卡 传 送 面 表 索 引 数 据 glbindbufferarb(gl_element_array_buffer_arb, m_facevbo); glbufferdataarb(gl_element_array_buffer_arb, m_facenum*3*sizeof(unsigned int), m_faces, GL_STATIC_DRAW_ARB); // 绑 定 边 表 VBO 对 象 并 向 显 卡 传 送 边 表 索 引 数 据 glbindbufferarb(gl_element_array_buffer_arb, m_edgevbo); glbufferdataarb(gl_element_array_buffer_arb, m_edgenum*2*sizeof(unsigned int), m_edges, GL_STATIC_DRAW_ARB); // 因 为 数 据 均 已 传 送 到 显 卡 显 存, // 所 以 临 时 申 请 的 存 放 边 表 的 系 统 内 存 可 以 释 放 了 this->_cleararrays(); 70
78 return true; 其 中 的 _buildarrays() 与 SurfaceRendererUseVA 中 的 相 同, 不 再 赘 述 最 后, 来 看 一 下 Render() 函 数 中 的 绘 制 过 程, 同 样 的, 只 在 与 前 面 不 同 之 处 加 以 注 释 : void mitksurfacerendererusevbo::render(mitkview *view, mitksurfacemodel *surf) if (!m_isvbosupported) return; mitkmesh *mesh = surf->getdata(); if (mesh == NULL) return; if (surf->getdatamodifystatus()) // 创 建 VBO 对 象 if (!this->_buildvbos(mesh)) return; if (m_normalreversed!= mesh->isnormalreversed()) // 如 果 法 线 方 向 改 变, 则 重 新 传 送 顶 点 数 组 数 据 m_vertices = mesh->getvertexdata(); glbindbufferarb(gl_array_buffer_arb, m_vertvbo); glbufferdataarb(gl_array_buffer_arb, m_vertnum*6*sizeof(float), m_vertices, GL_STATIC_DRAW_ARB); m_vertices = NULL; m_normalreversed = mesh->isnormalreversed(); mitksurfaceproperty *prop = surf->getproperty(); bool blend = ( prop->getopacity() < 1.0f ); 71
79 bool stipple = ( prop->getlinestipplepattern()!= 0xFFFF ); if (prop->ismodified()) this->_setmaterial(prop); prop->setunmodified(); gldisable(gl_texture_2d); glenableclientstate(gl_vertex_array); glenableclientstate(gl_normal_array); glmatrixmode(gl_modelview); glpushmatrix(); glmultmatrixf((glfloat *)surf->getmodelmatrix()); if (this->getclipping()) mitkplane *aplane = NULL; int count = 0; mitklist *planes = this->getclippingplanes(); double equation[4]; float nx, ny, nz, ox, oy, oz; for (planes->inittraversal(); (aplane = (mitkplane *)planes->getnextitem()) && (count<6);) aplane->getnormal(nx, ny, nz); aplane->getorigin(ox, oy, oz); equation[0] = (double)nx; equation[1] = (double)ny; equation[2] = (double)nz; equation[3] = - (double)(nx*ox + ny*oy + nz*oz); glclipplane(gl_clip_plane0+count,equation); glenable(gl_clip_plane0+count); count ++; 72
80 switch(prop->getrepresentationtype()) case MITK_MESH_POINTS: gldisable(gl_lighting); // 首 先 要 绑 定 所 需 使 用 的 VBO glbindbufferarb(gl_array_buffer_arb, m_vertvbo); glvertexpointer(3, GL_FLOAT, 6*sizeof(float), NULL); gldrawarrays(gl_points, 0, m_vertnum); break; case MITK_MESH_WIREFRAME: if (stipple) glenable(gl_line_stipple); else gldisable(gl_line_stipple); gldisable(gl_lighting); glcolor4fv(prop->getedgecolor()); // 首 先 要 绑 定 所 需 使 用 的 VBO // 在 指 定 顶 点 数 组 数 据 头 指 针 时 因 为 数 据 均 已 传 送 进 显 卡, // 所 以 只 使 用 NULL( 实 际 上 表 示 了 相 对 位 移 0) glbindbufferarb(gl_array_buffer_arb, m_vertvbo); glvertexpointer(3, GL_FLOAT, 6*sizeof(float), NULL); glbindbufferarb(gl_element_array_buffer_arb, m_edgevbo); gldrawelements(gl_lines, m_edgenum * 2, GL_UNSIGNED_INT, NULL); if (stipple) gldisable(gl_line_stipple); break; case MITK_MESH_SURFACE: glenable(gl_depth_test); if (blend) glenable(gl_blend); gldepthmask(gl_false); glenable(gl_lighting); glenable(gl_normalize); 73
81 if (mesh->isclockwise()) glfrontface(gl_cw); // 首 先 要 绑 定 所 需 使 用 的 VBO // 因 为 顶 点 和 法 向 量 是 放 在 一 块 存 储 的, // 所 以 要 指 定 两 顶 点 数 据 之 间 的 间 隔 6*sizeof(float), // 并 且 在 指 定 法 向 量 数 组 头 指 针 时, 相 对 的 从 // (const void *)(3*sizeof(float)) 开 始 glbindbufferarb(gl_array_buffer_arb, m_vertvbo); glvertexpointer(3, GL_FLOAT, 6*sizeof(float), NULL); glnormalpointer(gl_float, 6*sizeof(float), (const void *)(3*sizeof(float))); glbindbufferarb(gl_element_array_buffer_arb, m_facevbo); gldrawelements(gl_triangles, m_facenum * 3, GL_UNSIGNED_INT, NULL); if (blend) gldisable(gl_blend); if (mesh->isclockwise()) glfrontface(gl_ccw); if (blend) gldisable(gl_blend); gldepthmask(gl_true); gldisable(gl_normalize); gldisable(gl_lighting); gldisable(gl_depth_test); break; gldisableclientstate(gl_vertex_array); gldisableclientstate(gl_normal_array); glpopmatrix(); 另 外, 在 析 构 函 数 中 归 还 VBO 对 象 所 占 用 的 资 源 : mitksurfacerendererusevbo::~mitksurfacerendererusevbo() this->_cleararrays(); if (m_vbobuilt) 74
82 gldeletebuffersarb(1, &m_vertvbo); gldeletebuffersarb(1, &m_facevbo); gldeletebuffersarb(1, &m_edgevbo); 3.3 小 结 MITK 所 实 现 的 面 绘 制 功 能 采 用 了 改 进 后 的 Marching Cubes 算 法 进 行 表 面 重 建, 并 提 供 了 一 个 灵 活 的 易 于 扩 充 的 绘 制 框 架 来 显 示 重 建 所 得 的 表 面 图 3-14 就 是 运 用 MITK 所 提 供 的 面 绘 制 功 能 做 出 的 一 些 多 层 表 面 重 建 及 显 示 的 实 例 图 3-14 多 层 表 面 重 建 及 绘 制 实 例 参 考 文 献 1. 田 捷, 包 尚 联, 周 明 全. 医 学 影 像 处 理 与 分 析. 北 京 : 电 子 工 业 出 版 社, W. Lorensen and H. Cline. Marching cubes: a high resolution 3D surface construction algorithm. ACM Computer Graphics, 21(4): pp , M. J. Durst. Letters: Additional reference to Marching Cubes. ACM Computer Graphics, 22(4): pp ,
83 4. Nielson G. M.,Hamann B. The Asymptotic Decider: Resolving the Ambiguity in Marching Cubes. IEEE Proceedings of Visualization 91, pp J. Wilhelms and A. Van Gelder. Octrees for faster isosurface generation. ACM Computer Graphics, 24(5): pp ,Nov Mingchang Zhao, Jie Tian, Xun Zhu, Jian Xue, Zhanglin Cheng, Hua Zhao, The Design and Implementation of a C++ Toolkit for Integrated Medical Image Processing and Analyzing, Proc. of SPIE Medical Imaging Mason Woo, Jackie Neider, Tom Davis, et al. OpenGL Programming Guide: The Official Guide to Learning OpenGL, Version 1.2 (3rd Edition). Addison-Wesley Professional, Dave Shreiner, OpenGL Architecture Review. OpenGL Reference Manual: The Official Reference Document to OpenGL, Version 1.2 (3rd Edition). Addison-Wesley Professional, NVIDIA Corporation. White Paper: Using Vertex Buffer Objects (VBOs). NVIDIA Corporation,
84 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 体 绘 制 算 法 是 可 视 化 算 法 中 非 常 重 要 的 一 种 方 法, 体 绘 制 的 研 究 出 现 于 上 个 世 纪 80 年 代 末, 传 统 的 算 法 可 以 分 为 三 个 大 类 : 图 像 空 间 (Image Space) 的 绘 制 算 法 物 体 空 间 (Object Space) 的 绘 制 算 法 以 及 图 像 和 物 体 空 间 混 合 (Hybrid) 绘 制 算 法 4.1 体 绘 制 算 法 综 述 Ray Casting[1][2] 是 图 像 空 间 的 经 典 绘 制 算 法, 它 是 从 投 影 平 面 的 每 一 个 像 素 点 发 射 出 一 条 光 线, 穿 过 三 维 体 数 据 场, 并 计 算 光 的 传 输 方 程, 得 到 最 后 的 图 像, 这 种 方 法 得 到 的 绘 制 效 果 比 较 好, 并 且 可 以 很 方 便 地 实 现 一 些 插 值 算 法 和 光 线 的 提 取 终 止, 但 是 算 法 的 速 度 比 较 慢, 目 前 还 不 能 达 到 实 时 的 绘 制 目 的 Splatting[3][4] 是 物 体 空 间 的 经 典 绘 制 算 法, 它 的 思 路 与 Ray Casting 完 全 不 同, 它 按 照 物 体 顺 序 扫 描 每 个 体 素, 并 将 其 投 影 到 图 像 平 面 上, 由 于 每 个 体 素 要 影 响 到 图 像 平 面 上 的 好 几 个 像 素, 所 以 算 法 的 名 字 被 称 为 Splatting Splatting 算 法 尽 管 是 按 照 物 体 顺 序 来 访 问 三 维 数 据 集, 可 以 充 分 利 用 现 代 CPU 的 Cache 机 制, 但 仍 要 牵 涉 到 非 常 复 杂 的 投 影 核 心 的 计 算, 所 以 计 算 量 也 非 常 大, 传 统 的 算 法 仍 然 达 不 到 实 时 绘 制 的 目 的 在 文 献 [5] 里 面 提 出 了 一 种 优 化 的 算 法, 可 以 将 算 法 速 度 提 高 三 到 五 倍, 但 是 在 目 前 的 硬 件 水 平 上 尚 无 法 实 现 实 时 绘 制 Shear Warp[6][7] 是 综 合 了 图 像 空 间 和 物 体 空 间 优 点 的 混 合 绘 制 算 法, 是 目 前 为 止 基 于 软 件 实 现 的 最 快 的 体 绘 制 算 法 它 首 先 将 三 维 体 数 据 集 进 行 错 切 (Shear) 变 换, 使 其 和 图 像 平 面 平 行, 然 后 再 进 行 投 影 计 算, 由 于 此 时 图 像 平 面 和 数 据 集 的 特 殊 位 置 关 系, 使 得 投 影 计 算 量 大 大 降 低, 最 后 通 过 一 个 两 维 的 图 像 变 形, 得 到 最 终 的 结 果 由 于 此 算 法 既 可 以 很 好 地 利 用 CPU 的 Cache 来 获 得 内 存 访 问 性 能, 又 能 够 利 用 提 前 射 线 终 止 等 图 像 空 间 算 法 的 优 点, 并 且 所 有 的 计 算 都 被 降 到 两 维 来 完 成, 因 此 可 以 实 现 非 常 快 的 绘 制 速 度 虽 然 在 绘 制 速 度 上 非 常 快, 但 这 是 建 立 在 牺 牲 图 像 质 量 的 前 提 基 础 之 上 的, 在 文 献 [8] 中 提 出 了 一 些 提 高 绘 制 质 量 的 方 法, 同 时 尽 可 能 减 少 算 法 在 速 度 上 的 损 失, 在 文 献 [9] 中 将 Pre-Integration 集 成 进 原 始 的 Shear Warp 算 法 框 架, 进 一 步 提 高 算 法 的 绘 制 质 量 77
85 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 上 面 所 述 的 三 大 类 算 法 都 是 基 于 纯 软 件 的 算 法, 而 近 年 随 着 普 通 显 卡 里 面 三 维 加 速 功 能 的 逐 渐 强 大, 基 于 图 形 硬 件 的 体 绘 制 算 法 正 逐 渐 称 为 主 流 自 从 1994 年 Brian Cabral 等 人 提 出 使 用 纹 理 映 射 [10] 来 加 速 体 绘 制, 直 至 1998 年, 由 于 此 算 法 只 能 运 行 在 昂 贵 的 图 形 工 作 站 的 显 卡 上, 这 期 间 研 究 人 员 一 直 没 有 认 识 到 这 个 算 法 的 重 要 性 随 着 三 维 游 戏 等 娱 乐 市 场 的 不 断 需 求, 普 通 PC 机 上 装 配 的 显 卡 的 三 维 加 速 能 力 越 来 越 强, 同 时 纹 理 映 射 所 需 要 的 二 维 光 栅 的 处 理 能 力 也 越 来 越 强, 使 用 图 形 硬 件 来 作 体 绘 制 也 变 得 更 为 可 行 在 1998 年 的 SigGraph 文 章 [11] 中,Westermann 等 人 基 于 [10] 的 思 想, 并 结 合 当 前 的 显 卡 所 提 供 的 功 能, 实 现 了 一 个 比 较 实 用 的 基 于 硬 件 的 体 绘 制 算 法, 并 且 进 一 步 完 善 [12], 加 入 了 基 于 硬 件 加 速 的 分 类 和 光 照 计 算 另 外 值 得 一 提 的 是 在 2000 年 的 Graphics Hardware 年 会 的 最 佳 论 文 [13] 中, 作 者 总 结 了 前 人 所 作 过 的 工 作, 使 用 显 卡 中 新 提 供 的 多 纹 理 功 能, 在 普 通 的 PC 机 上 实 现 了 实 时 体 绘 制 ; 在 第 二 年 (2001 年 ) 的 Graphics Hardware 年 会 的 最 佳 论 文 仍 然 是 基 于 硬 件 的 体 绘 制 [14], 作 者 将 Pre-Integration 集 成 到 基 于 硬 件 的 体 绘 制 算 法 中, 从 而 显 著 地 提 高 了 绘 制 质 量 ; 同 样, 在 2003 年 的 Visualization 年 会 上, 最 佳 论 文 又 是 基 于 硬 件 的 体 绘 制 算 法 [15], 作 者 基 于 图 形 硬 件 实 现 了 多 维 的 传 递 函 数 的 调 节, 从 而 可 以 更 好 地 探 索 三 维 体 数 据 的 内 部 结 构, 尤 其 是 边 界 信 息 这 三 篇 最 佳 论 文 带 来 了 体 绘 制 算 法 研 究 的 春 天, 给 原 本 比 较 沉 闷 的 这 一 研 究 领 域 带 来 了 新 的 活 力 随 着 现 在 NVidia 和 ATI 对 三 维 显 卡 的 不 断 更 新 换 代, 目 前 显 卡 已 经 具 备 了 编 程 能 力 ( 被 称 为 GPU), 并 且 具 有 很 强 的 计 算 能 力 现 在 最 活 跃 的 研 究 领 域 是 彻 底 在 GPU 上 实 现 Ray Casting 算 法 [16][17][18], 从 而 实 现 基 于 硬 件 的 体 绘 制 算 法 可 以 达 到 和 基 于 软 件 的 算 法 同 样 的 绘 制 效 果 尽 管 基 于 硬 件 的 体 绘 制 算 法 有 很 多 优 越 性, 但 是 目 前 还 是 受 到 很 多 因 素 的 限 制, 最 大 的 一 个 问 题 就 是 有 限 的 纹 理 存 储 容 量, 因 此 对 于 比 较 大 的 三 维 数 据 集 来 说, 不 能 被 加 载 到 纹 理 内 存 中 进 行 处 理 ; 另 外 一 个 问 题 就 是 计 算 精 度, 由 于 显 卡 的 绘 制 引 擎 一 般 在 内 部 使 用 定 点 运 算, 因 此 最 终 的 精 度 会 受 到 影 响, 不 过 随 着 更 新 显 卡 的 出 现, 在 不 远 的 将 来 应 该 能 实 现 全 IEEE 32 位 浮 点 的 绘 制 引 擎, 那 样 计 算 精 度 的 问 题 就 会 得 到 缓 解 4.2 MITK 中 的 体 绘 制 算 法 框 架 由 上 面 给 出 的 综 述, 可 以 看 出 体 绘 制 算 法 的 多 样 性, 另 外, 体 绘 制 算 法 本 身 78
86 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 的 实 现 也 是 比 较 复 杂 的, 里 面 还 牵 涉 到 各 种 光 学 参 数 和 绘 制 参 数 的 调 节, 因 此 要 实 现 一 个 非 常 实 用 灵 活 可 扩 充 的 算 法 框 架 非 常 困 难 MITK 中 的 体 绘 制 算 法 框 架 是 建 立 在 VTK 的 体 绘 制 算 法 框 架 之 上, 并 进 行 了 扩 充 和 完 善 而 得 到 的, 它 的 目 标 是 在 一 个 统 一 的 框 架 里 面 能 够 集 成 不 同 的 体 绘 制 算 法, 集 成 不 同 的 参 数 调 节 算 法, 从 而 使 得 以 后 扩 充 新 的 算 法 非 常 容 易 在 介 绍 体 绘 制 的 算 法 框 架 之 前, 先 介 绍 MITK 中 的 绘 制 模 型, 如 图 4-1 所 示 View 是 一 个 Target 的 子 类, 用 来 将 计 算 得 到 的 图 像 或 者 三 维 图 形 显 示 在 屏 幕 上, 其 维 护 着 一 个 Model 的 数 组 m_models, 可 以 往 里 面 添 加 Model 在 View 的 绘 制 函 数 OnDraw 里 面, 遍 历 每 一 个 Model, 并 且 调 用 每 个 Model 的 虚 函 数 Render Model 在 概 念 上 代 表 着 场 景 中 一 个 待 画 的 物 体, 可 以 是 一 个 图 像, 也 可 以 是 三 维 图 形, 具 体 由 Model 的 子 类 来 实 现, 其 中 跟 体 绘 制 关 系 密 切 的 就 是 VolumeModel 图 4-1 MITK 的 绘 制 模 型 VolumeModel 的 主 要 职 责 是 实 现 父 类 里 面 规 定 的 接 口 Render 函 数, 但 是 体 绘 制 有 很 多 种 不 同 的 算 法, 又 有 很 多 参 数 需 要 调 节, 尤 其 是 阻 光 度 传 递 函 数 (Transfer Function), 其 选 择 的 好 坏 直 接 影 响 着 体 绘 制 的 结 果 因 此 为 了 实 现 一 个 灵 活 的 框 架,VolumeModel 的 结 构 图 如 图 4-2 所 示 79
87 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 图 4-2 MITK 的 体 绘 制 框 架 80
88 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 从 图 中 可 见,VolumeModel 拥 有 三 个 类 成 员 :Volume VolumeProperty 和 VolumeRenderer Volume 主 要 是 提 供 对 体 数 据 的 访 问,VolumeProperty 主 要 是 提 供 对 体 绘 制 算 法 的 一 些 参 数 的 调 整, 而 VolumeRenderer 则 负 责 实 际 的 绘 制 工 作 在 VolumeModel 的 Render 函 数 里 面, 绘 制 的 工 作 被 委 托 给 了 VolumeRenderer 的 Render 函 数, 又 由 其 子 类 来 负 责 具 体 的 实 现 VolumeRenderer 的 各 个 子 类 代 表 了 各 种 不 同 的 体 绘 制 算 法, 目 前 在 MITK 里 面 实 现 了 上 一 节 中 介 绍 的 Ray Casting 算 法, 以 及 Shear Warp 算 法, 并 且 还 实 现 了 硬 件 加 速 的 基 于 纹 理 映 射 的 体 绘 制 算 法 由 于 MITK 中 采 用 了 这 样 灵 活 的 绘 制 架 构, 以 后 往 里 面 添 加 新 的 算 法 也 非 常 方 便, 只 用 再 增 加 一 个 VolumeRenderer 的 子 类 即 可 VolumeProperty 除 了 提 供 体 绘 制 算 法 所 必 须 的 光 照 计 算 的 参 数 以 外, 最 重 要 的 一 个 目 的 就 是 提 供 阻 光 度 的 传 递 函 数, 为 了 提 供 足 够 的 灵 活 性, VolumeProperty 里 面 拥 有 一 个 TransferFunctionGenerator 基 类 对 象 的 指 针, 用 来 生 成 传 递 函 数 而 TransferFunctionGenerator 的 子 类 则 负 责 实 现 各 种 不 同 的 传 递 函 数 的 生 成 算 法, 包 括 手 工 的 和 半 自 动 的 方 法 TransferFunctionGenerator 的 输 出 为 TransferFunction, 而 考 虑 到 现 在 国 际 上 已 经 开 始 研 究 多 维 的 传 递 函 数 [19], TransferFunction 通 过 子 类 来 实 现 一 二 三 维 的 传 递 函 数 (TransferFunction1D, TransferFunction2D,TransferFunction3D) 的 支 持, 并 且 要 求 所 有 的 Renderer 也 支 持 多 维 的 传 递 函 数 4.3 体 绘 制 算 法 在 MITK 中 的 实 现 有 了 整 个 体 绘 制 的 框 架 以 后, 理 解 整 个 体 绘 制 算 法 在 MITK 中 的 实 现 就 比 较 容 易 了 这 里 为 了 完 整 起 见, 也 涉 及 到 View 中 绘 制 时 的 操 作, 但 侧 重 点 在 整 个 体 绘 制 流 程 的 实 现 以 及 具 体 体 绘 制 算 法 的 实 现 View 中 绘 制 操 作 的 实 现 因 为 整 个 绘 制 过 程 是 由 View 得 到 重 绘 消 息 以 后 发 起 的, 所 以 为 了 更 好 地 理 解 整 个 绘 制 过 程, 这 里 简 单 给 出 View 中 绘 制 操 作 的 实 现 由 图 4-1 可 以 得 知, 在 View 中 有 一 个 Model 的 列 表,View 负 责 维 护 这 个 列 表 ( 添 加 删 除 等 ), 并 且 在 绘 制 的 时 候 也 是 绘 制 这 些 Model, 因 此 Model 的 角 色 81
89 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 就 相 当 于 被 绘 制 的 物 体 加 上 一 些 属 性 在 View 中 跟 维 护 Model 和 绘 制 相 关 的 变 量 和 函 数 声 明 如 下 面 代 码 所 示 : class mitkview : public mitktarget public: // 维 护 模 型 列 表 的 相 关 函 数 void AddModel(mitkModel *model); void RemoveModel(mitkModel *model); void RemoveAllModel(void); mitkmodel* GetModel(int i); mitklist* GetModels(); int GetModelCount(); // 绘 制 操 作 virtual void OnDraw(); protected: ; mitklist *m_models; // 存 储 模 型 列 表 mitkmodel **m_visiblemodels; int m_numberofpropsrendered; unsigned char m_visiblemodelcount; AddModel() 函 数 往 模 型 列 表 中 添 加 一 个 模 型,RemoveModel() 函 数 将 一 个 指 定 的 模 型 从 模 型 列 表 中 删 除,RemoveAllModel() 函 数 清 空 模 型 列 表,GetModel() 函 数 得 到 指 定 索 引 位 置 的 模 型 指 针,GetModels() 函 数 直 接 得 到 整 个 模 型 列 表 的 指 针,GetModelCount() 函 数 得 到 模 型 列 表 中 模 型 的 个 数, 这 些 函 数 的 实 现 如 下 所 示 : void mitkview::addmodel(mitkmodel *model) if(model) model->addreference(); 82
90 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 m_models->add(model); void mitkview::removemodel(mitkmodel *model) if(model) int indinmodels = m_models->find(model); if(indinmodels >= 0) m_models->remove(indinmodels); model->removereference(); void mitkview::removeallmodel(void) mitkmodel *amodel = NULL; for(m_models->inittraversal(); (amodel = (mitkmodel*) m_models->getnextitem()); ) amodel->removereference(); m_models->clear(); mitkmodel* mitkview::getmodel(int i) return (mitkmodel*) m_models->item(i); mitklist* mitkview::getmodels() return m_models; int mitkview::getmodelcount() 83
91 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 return m_models->count(); OnDraw() 函 数 是 View 在 接 收 到 重 绘 消 息 以 后 调 用 的, 其 循 环 遍 历 每 个 Model, 并 且 调 用 它 们 的 虚 函 数 Render, 来 使 其 子 类, 也 就 是 具 体 的 模 型 来 绘 制 自 己 其 实 现 代 码 如 下 所 示 : void mitkview::ondraw() // 初 始 化 屏 幕 颜 色 glclearcolor(m_backcolor[0], m_backcolor[1], m_backcolor[2], m_backcolor[3]); glcleardepth(1.0f); glclear(gl_color_buffer_bit GL_DEPTH_BUFFER_BIT); if(m_models->count() <= 0) return; int i; mitkmodel *amodel; m_visiblemodelcount = 0; m_visiblemodels = new mitkmodel* [m_models->count()]; // 循 环 遍 历 模 型 列 表, 找 出 可 见 模 型, 并 将 其 存 储 在 数 组 中 for (m_models->inittraversal(); (amodel = (mitkmodel*) m_models->getnextitem()); ) if(amodel->getvisibility()) m_visiblemodels[m_visiblemodelcount++] = amodel; if(m_visiblemodelcount == 0) return; // 循 环 遍 历 模 型 列 表, 先 画 不 透 明 物 体 m_numberofpropsrendered = 0; for(i = 0; i < m_visiblemodelcount; i++) 84
92 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 if(m_visiblemodels[i]->isopaque()) m_numberofpropsrendered += m_visiblemodels[i]->render(this); // 循 环 遍 历 模 型 列 表, 再 画 透 明 物 体 for(i = 0; i < m_visiblemodelcount; i++) if(!m_visiblemodels[i]->isopaque()) m_numberofpropsrendered += m_visiblemodels[i]->render(this); delete []m_visiblemodels; m_visiblemodels = NULL; 从 上 面 的 代 码 可 以 看 出, 实 际 的 绘 制 工 作 被 View 委 托 给 了 各 个 Model 的 Render 函 数 并 且 为 了 实 现 表 面 绘 制 和 体 绘 制 透 明 物 体 和 不 透 明 物 体 的 同 时 显 示, 这 里 先 循 环 遍 历 所 有 模 型, 画 出 不 透 明 的 物 体, 然 后 再 循 环 遍 历 一 次, 画 出 透 明 的 物 体 VolumeModel 由 于 体 绘 制 算 法 的 本 质 特 点, 决 定 了 它 是 一 个 透 明 的 物 体 VolumeModel 的 实 现 VolumeModel 是 Model 的 一 个 子 类, 它 的 最 重 要 任 务 是 实 现 Model 所 规 定 的 Render 接 口, 从 而 将 自 身 绘 制 出 来 为 了 能 达 到 这 一 目 的,VolumeModel 里 面 存 储 有 三 个 对 象 指 针 m_data, m_property, m_renderer, 分 别 指 向 Volume VolumeProperty VolumeRenderer 的 具 体 实 例 VolumeModel 的 声 明 如 下 所 示 : class mitkvolumemodel : public mitkdatamodel public: // 设 置 新 的 体 绘 制 算 法 void SetRenderer(mitkVolumeRenderer *renderer); // 得 到 当 前 的 体 绘 制 算 法 mitkvolumerenderer* GetRenderer(void); 85
93 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 // 设 置 一 个 新 的 体 数 据 属 性 void SetProperty(mitkVolumeProperty *prop); // 得 到 当 前 的 体 数 据 属 性 mitkvolumeproperty* GetProperty(void); // 设 置 新 的 体 数 据 void SetData(mitkVolume *data); // 得 到 当 前 的 体 数 据 mitkvolume* GetData(void); // 必 须 实 现 的 接 口, 绘 制 自 身 virtual int Render(mitkView *view); // 得 到 当 前 模 型 的 边 界 盒 virtual float* GetBounds(); // 得 到 当 前 模 型 是 否 为 不 透 明 // 对 于 体 绘 制 来 说, 始 终 是 透 明 的 virtual bool IsOpaque() return false; protected: ; mitkrcptr<mitkvolume> m_data; // 数 据 指 针 mitkrcptr<mitkvolumeproperty> m_property; // 属 性 指 针 mitkrcptr<mitkvolumerenderer> m_renderer; // 算 法 指 针 m_data, m_property, m_renderer 这 三 个 成 员 变 量 都 是 通 过 智 能 指 针 RCPtr 来 实 现 的, 它 们 可 以 被 当 做 普 通 的 指 针 使 用, 这 里 无 需 去 理 会 RCPtr 的 实 现 细 节 通 过 一 系 列 的 Get 和 Set 函 数,VolumeModel 可 以 很 方 便 地 在 运 行 时 切 换 不 同 的 数 据, 属 性 甚 至 绘 制 算 法, 这 就 使 得 整 个 框 架 非 常 灵 活 这 些 Get 和 Set 函 数 的 实 现 都 非 常 简 单, 如 下 面 代 码 所 示 : void mitkvolumemodel::setrenderer(mitkvolumerenderer *renderer) 86
94 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 m_renderer = renderer; mitkvolumerenderer* mitkvolumemodel::getrenderer(void) if(m_renderer == NULL) m_renderer = new mitkvolumerendererraycasting; return m_renderer; void mitkvolumemodel::setproperty(mitkvolumeproperty *prop) m_property = prop; mitkvolumeproperty* mitkvolumemodel::getproperty(void) if(m_property == NULL) m_property = new mitkvolumeproperty; return m_property; void mitkvolumemodel::setdata(mitkvolume *data) m_data = data; if(m_data == NULL) return; m_bounds[0] = 0.0f; m_bounds[1] = m_data->getwidth() - 1.0f; m_bounds[2] = 0.0f; m_bounds[3] = m_data->getheight() - 1.0f; m_bounds[4] = 0.0f; m_bounds[5] = m_data->getimagenum() - 1.0f; m_origin[0] = (m_bounds[0] + m_bounds[1]) / 2.0f; 87
95 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 m_origin[1] = (m_bounds[2] + m_bounds[3]) / 2.0f; m_origin[2] = (m_bounds[4] + m_bounds[5]) / 2.0f; mitkvolume* mitkvolumemodel::getdata(void) return m_data; 其 中 在 GetRenderer() 函 数 中, 如 果 当 前 还 没 有 合 适 的 绘 制 算 法, 则 缺 省 地 生 成 Ray Casting 绘 制 算 法 的 对 象 在 SetData() 函 数 中, 还 要 设 置 好 合 适 的 边 界 和 原 点 等 信 息, 总 之, 上 面 的 这 些 代 码 比 较 容 易 理 解, 这 里 就 不 过 多 解 释 了 接 下 来 比 较 重 要 的 一 个 辅 助 函 数 是 GetBounds() 函 数, 它 负 责 返 回 当 前 模 型 的 边 界 盒 信 息, 并 且 是 在 世 界 坐 标 系 中 的 边 界 盒 因 此 它 需 要 调 用 父 类 中 的 函 数 ModelToWorld() 函 数 将 模 型 坐 标 系 中 的 坐 标 转 换 为 世 界 坐 标 系 中 的 坐 标, 最 后 返 回 世 界 坐 标 系 中 的 边 界 盒, 其 实 现 代 码 如 下 : float* mitkvolumemodel::getbounds() if(m_data == NULL m_matrixmodified == 0) return m_bounds; float minx, miny, minz, maxx, maxy, maxz; minx = 0.0f; maxx = m_data->getwidth() - 1.0f; miny = 0.0f; maxy = m_data->getheight() - 1.0f; minz = 0.0f; maxz = m_data->getimagenum() - 1.0f; float modelpoint[4]; float worldpoint[4]; //First point modelpoint[0] = minx; modelpoint[1] = miny; 88
96 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 modelpoint[2] = minz; modelpoint[3] = 1.0f; this->modeltoworld(modelpoint, worldpoint); m_bounds[0] = m_bounds[1] = worldpoint[0]; m_bounds[2] = m_bounds[3] = worldpoint[1]; m_bounds[4] = m_bounds[5] = worldpoint[2]; //Second point modelpoint[0] = maxx; modelpoint[1] = miny; modelpoint[2] = minz; modelpoint[3] = 1.0f; this->modeltoworld(modelpoint, worldpoint); if(worldpoint[0] < m_bounds[0]) m_bounds[0] = worldpoint[0]; if(worldpoint[0] > m_bounds[1]) m_bounds[1] = worldpoint[0]; if(worldpoint[1] < m_bounds[2]) m_bounds[2] = worldpoint[1]; if(worldpoint[1] > m_bounds[3]) m_bounds[3] = worldpoint[1]; if(worldpoint[2] < m_bounds[4]) m_bounds[4] = worldpoint[2]; if(worldpoint[2] > m_bounds[5]) m_bounds[5] = worldpoint[2]; //Third point modelpoint[0] = maxx; modelpoint[1] = maxy; modelpoint[2] = minz; modelpoint[3] = 1.0f; this->modeltoworld(modelpoint, worldpoint); if(worldpoint[0] < m_bounds[0]) m_bounds[0] = worldpoint[0]; if(worldpoint[0] > m_bounds[1]) m_bounds[1] = worldpoint[0]; if(worldpoint[1] < m_bounds[2]) m_bounds[2] = worldpoint[1]; if(worldpoint[1] > m_bounds[3]) m_bounds[3] = worldpoint[1]; if(worldpoint[2] < m_bounds[4]) m_bounds[4] = worldpoint[2]; if(worldpoint[2] > m_bounds[5]) m_bounds[5] = worldpoint[2]; //Fourth point modelpoint[0] = minx; modelpoint[1] = maxy; modelpoint[2] = minz; modelpoint[3] = 1.0f; this->modeltoworld(modelpoint, worldpoint); if(worldpoint[0] < m_bounds[0]) m_bounds[0] = worldpoint[0]; 89
97 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 if(worldpoint[0] > m_bounds[1]) if(worldpoint[1] < m_bounds[2]) if(worldpoint[1] > m_bounds[3]) if(worldpoint[2] < m_bounds[4]) if(worldpoint[2] > m_bounds[5]) m_bounds[1] = worldpoint[0]; m_bounds[2] = worldpoint[1]; m_bounds[3] = worldpoint[1]; m_bounds[4] = worldpoint[2]; m_bounds[5] = worldpoint[2]; //Fifth point modelpoint[0] = minx; modelpoint[1] = miny; modelpoint[2] = maxz; modelpoint[3] = 1.0f; this->modeltoworld(modelpoint, worldpoint); if(worldpoint[0] < m_bounds[0]) m_bounds[0] = worldpoint[0]; if(worldpoint[0] > m_bounds[1]) m_bounds[1] = worldpoint[0]; if(worldpoint[1] < m_bounds[2]) m_bounds[2] = worldpoint[1]; if(worldpoint[1] > m_bounds[3]) m_bounds[3] = worldpoint[1]; if(worldpoint[2] < m_bounds[4]) m_bounds[4] = worldpoint[2]; if(worldpoint[2] > m_bounds[5]) m_bounds[5] = worldpoint[2]; //Sixth point modelpoint[0] = maxx; modelpoint[1] = miny; modelpoint[2] = maxz; modelpoint[3] = 1.0f; this->modeltoworld(modelpoint, worldpoint); if(worldpoint[0] < m_bounds[0]) m_bounds[0] = worldpoint[0]; if(worldpoint[0] > m_bounds[1]) m_bounds[1] = worldpoint[0]; if(worldpoint[1] < m_bounds[2]) m_bounds[2] = worldpoint[1]; if(worldpoint[1] > m_bounds[3]) m_bounds[3] = worldpoint[1]; if(worldpoint[2] < m_bounds[4]) m_bounds[4] = worldpoint[2]; if(worldpoint[2] > m_bounds[5]) m_bounds[5] = worldpoint[2]; //Seventh point modelpoint[0] = maxx; modelpoint[1] = maxy; modelpoint[2] = maxz; modelpoint[3] = 1.0f; this->modeltoworld(modelpoint, worldpoint); if(worldpoint[0] < m_bounds[0]) m_bounds[0] = worldpoint[0]; if(worldpoint[0] > m_bounds[1]) m_bounds[1] = worldpoint[0]; 90
98 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 if(worldpoint[1] < m_bounds[2]) if(worldpoint[1] > m_bounds[3]) if(worldpoint[2] < m_bounds[4]) if(worldpoint[2] > m_bounds[5]) m_bounds[2] = worldpoint[1]; m_bounds[3] = worldpoint[1]; m_bounds[4] = worldpoint[2]; m_bounds[5] = worldpoint[2]; //Eighth point modelpoint[0] = minx; modelpoint[1] = maxy; modelpoint[2] = maxz; modelpoint[3] = 1.0f; this->modeltoworld(modelpoint, worldpoint); if(worldpoint[0] < m_bounds[0]) m_bounds[0] = worldpoint[0]; if(worldpoint[0] > m_bounds[1]) m_bounds[1] = worldpoint[0]; if(worldpoint[1] < m_bounds[2]) m_bounds[2] = worldpoint[1]; if(worldpoint[1] > m_bounds[3]) m_bounds[3] = worldpoint[1]; if(worldpoint[2] < m_bounds[4]) m_bounds[4] = worldpoint[2]; if(worldpoint[2] > m_bounds[5]) m_bounds[5] = worldpoint[2]; return m_bounds; 最 后, 该 到 最 重 要 的 Render() 函 数 了, 也 许 你 以 为 这 是 一 个 最 复 杂 的 函 数 了, 可 是 这 里 考 虑 到 框 架 的 需 求, 并 没 有 将 复 杂 的 逻 辑 都 交 给 VolumeModel 去 处 理, 而 是 委 托 给 VolumeRenderer 去 作 绘 制 工 作, 这 样,Render() 的 代 码 就 比 较 简 单 了, 如 下 所 示 : int mitkvolumemodel::render(mitkview *view) if(m_data == NULL) return 0; this->getrenderer()->render(view, this); return 1; 91
99 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 VolumeProperty 的 实 现 上 面 已 经 提 到 过,VolumeModel 并 没 有 大 包 大 揽 地 将 所 有 复 杂 的 事 务 都 自 己 处 理, 而 是 分 散 给 Volume VolumeProperty 和 VolumeRenderer 来 分 别 处 理, 它 们 各 司 其 职, 共 同 协 作 来 完 成 复 杂 的 任 务 VolumeProperty 的 主 要 任 务 就 是 提 供 体 绘 制 各 种 参 数 的 调 节, 其 中 分 为 两 大 类 : 传 递 函 数 的 调 节 和 光 照 效 果 参 数 的 调 节 对 于 传 递 函 数 来 说,VolumeProperty 里 面 支 持 两 种 风 格 的 传 递 函 数 : Classical 和 MultiDimensional, 对 于 Classical 风 格 的 传 递 函 数, 又 分 为 灰 度 值 - 颜 色 传 递 函 数 灰 度 值 - 阻 光 度 传 递 函 数 梯 度 值 - 阻 光 度 传 递 函 数, 而 对 于 MultiDimensional 风 格 的 传 递 函 数, 只 使 用 一 个 通 用 的 类 型 mitktransferfunction 来 表 示 即 可, 它 可 以 表 示 一 维 两 维 和 三 维 的 传 递 函 数 对 于 光 照 效 果 参 数 来 说,VolumeProperty 可 以 控 制 是 否 打 开 光 照 计 算, 以 及 分 别 控 制 各 个 光 照 分 量 的 多 少 VolumeProperty 的 相 关 声 明 如 下 所 示 : class mitkvolumeproperty : public mitkobject public: // 设 置 灰 度 值 - 颜 色 传 递 函 数 void SetScalarColor(mitkColorTransferFunction *scfunction); // 设 置 灰 度 值 - 阻 光 度 传 递 函 数 void SetScalarOpacity(mitkTransferFunction1D *sofunction); // 设 置 梯 度 值 - 阻 光 度 传 递 函 数 void SetGradientOpacity(mitkTransferFunction1D *gofunction); // 得 到 灰 度 值 - 颜 色 传 递 函 数 mitkcolortransferfunction* GetScalarColor(); // 得 到 灰 度 值 - 阻 光 度 传 递 函 数 mitktransferfunction1d* GetScalarOpacity(); // 得 到 梯 度 值 - 阻 光 度 传 递 函 数 mitktransferfunction1d* GetGradientOpacity(); // 设 置 梯 度 值 - 阻 光 度 传 递 函 数 是 否 打 开 92
100 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 void SetGradientOpacityCalculation(bool goenable) m_goenable = goenable; // 得 到 梯 度 值 - 阻 光 度 传 递 函 数 是 否 打 开 int GetGradientOpacityCalculation(void) return m_goenable; // 设 置 通 用 传 递 函 数 ( 可 以 是 高 维 的 ) void SetOpacityTransferFunction(mitkTransferFunction *mofunction); // 得 到 通 用 传 递 函 数 ( 可 以 是 高 维 的 ) mitktransferfunction* GetOpacityTransferFunction(); // TransferFunctionStyle 指 定 传 递 函 数 的 类 型 enum TransferFunctionStyle Classical, MultiDimensional ; // 设 置 传 递 函 数 的 类 型 void SetTransferFunctionStyle(TransferFunctionStyle tfstyle) m_transferfunctionstyle = tfstyle; // 得 到 传 递 函 数 的 类 型 TransferFunctionStyle GetTransferFunctionStyle(void) return m_transferfunctionstyle; // 设 置 是 否 打 开 Shading( 光 照 效 果 ) void SetShade(bool shade) m_shade = shade; // 得 到 当 前 的 Shading 效 果 是 否 打 开 int GetShade(void) return m_shade; // 设 置 环 境 光 系 数 void SetAmbient(float value) m_ambient = value; 93
101 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 // 得 到 环 境 光 系 数 float GetAmbient() return m_ambient; // 设 置 散 射 光 系 数 void SetDiffuse(float value) m_diffuse = value; // 得 到 散 射 光 系 数 float GetDiffuse() return m_diffuse; // 设 置 高 光 系 数 void SetSpecular(float value) m_specular = value; // 得 到 高 光 系 数 float GetSpecular() return m_specular; // 设 置 高 光 指 数 void SetSpecularPower(float value) m_specularpower = value; // 得 到 高 光 指 数 float GetSpecularPower() return m_specularpower; protected: mitkrcptr<mitkcolortransferfunction> m_scalarcolor; mitkrcptr<mitktransferfunction1d> m_scalaropacity; mitkrcptr<mitktransferfunction1d> m_gradientopacity; mitkrcptr<mitktransferfunction> m_opacity; ; bool m_shade; bool m_goenable; float m_ambient; float m_diffuse; float m_specular; float m_specularpower; TransferFunctionStyle m_transferfunctionstyle; 这 些 函 数 都 是 设 置 一 些 状 态, 因 此 实 现 相 对 比 较 简 单, 有 一 些 函 数 已 经 内 联 94
102 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 实 现 在 声 明 里 面, 其 它 的 函 数 的 实 现 如 下 所 示 : void mitkvolumeproperty::setscalarcolor(mitkcolortransferfunction *scfunction) m_scalarcolor = scfunction; void mitkvolumeproperty::setscalaropacity(mitktransferfunction1d *sofunction) m_scalaropacity = sofunction; void mitkvolumeproperty::setgradientopacity(mitktransferfunction1d *gofunction) m_gradientopacity = gofunction; mitkcolortransferfunction* mitkvolumeproperty::getscalarcolor() if(m_scalarcolor == NULL) m_scalarcolor = new mitkcolortransferfunction; return m_scalarcolor; mitktransferfunction1d* mitkvolumeproperty::getscalaropacity(void) if(m_scalaropacity == NULL) m_scalaropacity = new mitktransferfunction1d; return m_scalaropacity; 95
103 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 mitktransferfunction1d* mitkvolumeproperty::getgradientopacity(void) if(m_gradientopacity == NULL) m_gradientopacity = new mitktransferfunction1d; return m_gradientopacity; void mitkvolumeproperty::setopacitytransferfunction(mitktransferfunction *mofunction) m_opacity = mofunction; mitktransferfunction* mitkvolumeproperty::getopacitytransferfunction() if(m_opacity == NULL) m_opacity = new mitktransferfunction1d; return m_opacity; VolumeRenderer 的 实 现 VolumeRenderer 的 职 责 是 实 现 VolumeModel 委 托 给 它 的 Render 接 口, 也 就 是 将 模 型 绘 制 出 来, 为 了 允 许 其 它 算 法 的 无 缝 集 成, 所 以 VolumeRenderer 仍 然 只 是 个 虚 基 类, 各 个 具 体 的 算 法 通 过 继 承 VolumeRenderer 来 实 现 Render 接 口 VolumeRenderer 为 其 子 类 提 供 了 两 个 方 面 的 能 力 : 设 置 不 同 的 分 类 方 法, 设 置 裁 剪 平 面 在 体 绘 制 算 法 中, 如 果 分 类 方 法 是 PreClassification, 那 么 就 是 先 使 用 阻 光 度 传 递 函 数 进 行 分 类, 然 后 再 插 值 计 算 ; 如 果 分 类 方 法 是 PostClassification, 那 么 就 是 先 进 行 插 值 计 算, 然 后 再 根 据 阻 光 度 传 递 函 数 进 行 分 类 另 外,VolumeRenderer 也 支 持 平 面 裁 剪, 用 一 系 列 的 平 面 去 裁 剪 Volume, 从 而 可 以 得 到 切 割 效 果, 为 了 和 OpenGL 保 持 兼 容, 这 里 规 定 裁 剪 平 面 最 多 不 能 超 过 六 个 因 为 分 类 方 法 和 裁 剪 平 面 是 所 有 体 绘 制 算 法 都 公 用 的 东 西, 因 此 96
104 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 将 它 们 提 在 基 类 VolumeRenderer 中 实 现, 减 少 子 类 的 负 担, 从 而 使 得 子 类 只 用 关 注 于 实 现 自 己 的 Render 接 口 就 行 了 VolumeRenderer 中 这 两 个 部 分 的 相 关 的 函 数 和 变 量 声 明 如 下 所 示 : class mitkvolumerenderer : public mitkrenderer public: // 设 置 体 绘 制 算 法 的 分 类 方 法 void SetClassifyMethod(int classifymethod) m_classifymethod = classifymethod; // 得 到 体 绘 制 算 法 的 分 类 方 法 int GetClassifyMethod() return m_classifymethod; // 设 置 体 绘 制 算 法 的 分 类 方 法 为 PreClassification void SetClassifyMethodToPreClassification() m_classifymethod = MITK_PRE_CLASSIFICATION; // 设 置 体 绘 制 算 法 的 分 类 方 法 为 PostClassification void SetClassifyMethodToPostClassification() m_classifymethod = MITK_POST_CLASSIFICATION; // 添 加 一 个 裁 剪 平 面 void AddClippingPlane(mitkPlane *plane); // 删 除 一 个 裁 剪 平 面 void RemoveClippingPlane(mitkPlane *plane); // 删 除 所 有 的 裁 剪 平 面, 清 空 裁 剪 平 面 列 表 void RemoveAllClippingPlanes(); // 得 到 裁 剪 平 面 列 表 97
105 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 mitklist* GetClippingPlanes(void) return m_clippingplanes; // 得 到 指 定 位 置 的 裁 剪 平 面 mitkplane* GetClippingPlane(int planeindex); // 得 到 裁 剪 平 面 的 个 数 int GetClippingPlaneCount(void) m_clippingplanes->count(); return // 允 许 裁 剪 void ClippingOn() m_enableclipping = true; // 关 闭 裁 剪 void ClippingOff() m_enableclipping = false; // 设 置 是 否 打 开 裁 剪 void SetClipping(bool enableclipping) m_enableclipping = enableclipping; // 得 到 当 前 裁 剪 是 否 打 开 bool GetClipping() return m_enableclipping; // 纯 虚 函 数, 规 定 Render 接 口 virtual void Render(mitkView *view, mitkvolumemodel *vol) = 0; protected: int m_classifymethod; ; mitklist *m_clippingplanes; bool m_enableclipping; 设 置 分 类 方 法 的 相 关 函 数 都 已 经 作 为 内 联 函 数 实 现 了, 设 置 裁 剪 平 面 的 函 数 的 实 现 代 码 如 下 所 示, 因 为 代 码 相 对 简 单, 这 里 就 不 过 多 解 释 了 void mitkvolumerenderer::addclippingplane(mitkplane *plane) m_clippingplanes->add(plane); 98
106 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 void mitkvolumerenderer::removeclippingplane(mitkplane *plane) m_clippingplanes->remove(plane); void mitkvolumerenderer::removeallclippingplanes() m_clippingplanes->removeall(); mitkplane* mitkvolumerenderer::getclippingplane(int planeindex) return (mitkplane*) m_clippingplanes->getitem(planeindex); Ray Casting 算 法 的 实 现 VolumeRenderer 只 是 规 定 了 一 个 抽 象 的 Render 接 口, 具 体 的 体 绘 制 算 法 由 具 体 的 子 类 从 VolumeRenderer 继 承, 并 实 现 Render() 函 数 来 实 现 算 法 在 Render() 函 数 中, 综 合 利 用 VolumeModel 中 的 各 项 信 息, 并 充 分 利 用 VolumeRenderer 父 类 提 供 的 公 用 函 数, 来 将 体 数 据 绘 制 出 来 在 MITK 中 目 前 实 现 了 几 种 不 同 的 体 绘 制 算 法, 但 是 考 虑 到 篇 幅, 这 里 只 给 出 最 常 用 也 是 最 经 典 的 Ray Casting 算 法 的 实 现 MITK 中 Ray Casting 算 法 在 类 mitkvolumerendererraycasting 中 实 现, 其 相 关 类 声 明 如 下 所 示 : class mitkvolumerendererraycasting : public mitkvolumerenderer public: // 必 须 实 现 的 接 口 virtual void Render(mitkView *view, mitkvolumemodel *vol); // 设 置 沿 着 射 线 方 向 的 采 样 距 离 void SetSampleDistance(float fval) m_sampledistance = fval; 99
107 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 // 得 到 沿 着 射 线 方 向 的 采 样 距 离 float GetSampleDistance()return m_sampledistance; // 设 置 在 图 像 平 面 上 的 采 样 距 离 void SetImageSampleDistance(float fval) m_imagesampledistance = fval; // 得 到 在 图 像 平 面 上 的 采 样 距 离 float GetImageSampleDistance() return m_imagesampledistance; protected: mitkvolumemodel **m_rendervolumetable; mitkview **m_renderviewtable; // 一 些 变 换 矩 阵 mitkmatrix *m_perspectivematrix; mitkmatrix *m_viewtoworldmatrix; mitkmatrix *m_viewtovoxelsmatrix; mitkmatrix *m_voxelstoviewmatrix; mitkmatrix *m_worldtovoxelsmatrix; mitkmatrix *m_voxelstoworldmatrix; int m_imageviewportsize[2]; int m_imagememorysize[2]; int m_imageinusesize[2]; int m_imageorigin[2]; unsigned char *m_image; int *m_rowbounds; int *m_oldrowbounds; int m_rendertablesize; int m_rendertableentries; float m_sampledistance; float m_imagesampledistance; 100
108 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 float m_worldsampledistance; int m_scalardatatype; void *m_scalardatapointer; mitkray *m_ray; void _updateshadingtables(mitkview *view, mitkvolumemodel *vol); void _rendertexture(mitkvolumemodel *vol, mitkview *view); int _computerowbounds(mitkvolumemodel *vol, mitkview *view); int _cliprayagainstvolume(mitkray *rayinfo, float bounds[6] ); int _cliprayagainstclippingplanes(int planecount, float *planeequs, mitkray *rayinfo); void _castraysclipoff(mitkview *view, mitkvolumemodel *vol); void _castraysclipon(mitkview *view, mitkvolumemodel *vol); ; void _castray(mitkray *rayinfo); 其 中,Render() 函 数 是 必 须 实 现 的 接 口, 而 Ray Casting 算 法 自 身 也 有 一 些 参 数 可 以 设 置, 比 如 SetSampleDistance() 函 数 和 SetImageSampleDistance() 函 数 可 以 用 来 控 制 最 后 图 像 的 质 量 和 绘 制 速 度 的 折 衷, 采 样 距 离 设 置 的 越 大, 最 后 图 像 的 质 量 就 越 差, 但 是 绘 制 速 度 会 大 大 加 快 另 外, 在 mitkvolumerendererraycasting 中 还 用 到 了 很 多 变 换 矩 阵,mitkMatrix 提 供 了 对 4 4 矩 阵 的 封 装, 可 以 简 化 很 多 操 作 为 了 实 现 复 杂 的 算 法, mitkvolumerendererraycasting 中 还 有 很 多 受 保 护 的 函 数, 来 实 现 内 部 的 结 构 化, 每 个 都 实 现 其 具 体 的 功 能, 它 们 将 具 体 在 下 面 被 描 述 为 了 理 清 Ray Casting 算 法, 还 是 让 我 们 从 Render() 函 数 来 开 始 入 手, 其 代 码 如 下 所 示 : void mitkvolumerendererraycasting::render(mitkview *view, mitkvolumemodel *vol) mitkvolume *input = vol->getdata(); if (input == NULL input->getdata() == NULL) 101
109 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 mitkerrormessage("no Input!"); return; // 得 到 体 数 据 的 属 性 mitkvolumeproperty *volproperty = vol->getproperty(); // 得 到 相 机 mitkcamera *cam = view->getcamera(); // 初 始 化 Ray 的 一 些 属 性 m_ray->m_scalardatapointer = input->getdata(); m_ray->m_scalardatatype = input->getdatatype(); input->getincrements(m_ray->m_dataincrement); input->getdimensions(m_ray->m_datasize); input->getspacings(m_ray->m_dataspacing); vol->getorigin(m_ray->m_dataorigin); m_ray->m_classificationmethod = this->m_classifymethod; m_ray->m_color[0] = 0.0f; m_ray->m_color[1] = 0.0f; m_ray->m_color[2] = 0.0f; m_ray->m_color[3] = 0.0f; m_ray->m_maximizeopacity = 1; if(volproperty->gettransferfunctionstyle() == mitkvolumeproperty::classical) m_ray->m_tf_dimension = 1; m_ray->m_tf_scalaropacitymaxx = volproperty->getscalaropacity()->getmaxx(); m_ray->m_tf_scalaropacity = volproperty->getscalaropacity()->getdata(); m_ray->m_tf_scalarcolorred = volproperty->getscalarcolor()->getrdata(); m_ray->m_tf_scalarcolorgreen = volproperty->getscalarcolor()->getgdata(); 102
110 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 m_ray->m_tf_scalarcolorblue = volproperty->getscalarcolor()->getbdata(); if(volproperty->getgradientopacitycalculation()) m_ray->m_tf_gradientopacity = volproperty->getgradientopacity()->getdata(); else m_ray->m_tf_gradientopacity = NULL; else m_ray->m_tf_dimension = volproperty->getopacitytransferfunction()->getdimension(); // 计 算 Shading Table this->_updateshadingtables(view, vol); //Voxel(Logical)----->Volume(Physical)---->World---->Camera----> //View //Voxel to Volume transformation = // translate(orgin)*scale(sapcings)*translate(-orgin) mitkmatrix tempmatrix; tempmatrix.ele[0] = m_ray->m_dataspacing[0]; tempmatrix.ele[1] = tempmatrix.ele[2] = tempmatrix.ele[3] = tempmatrix.ele[4] = 0.0f; tempmatrix.ele[5] = m_ray->m_dataspacing[1]; tempmatrix.ele[6] = tempmatrix.ele[7] = tempmatrix.ele[8] = tempmatrix.ele[9] = 0.0f; tempmatrix.ele[10] = m_ray->m_dataspacing[2]; tempmatrix.ele[11] = 0.0f; tempmatrix.ele[12] = m_ray->m_dataorigin[0]*(1.0f - m_ray->m_dataspacing[0]); 103
111 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 tempmatrix.ele[13] = m_ray->m_dataorigin[1]*(1.0f - m_ray->m_dataspacing[1]); tempmatrix.ele[14] = m_ray->m_dataorigin[2]*(1.0f - m_ray->m_dataspacing[2]); tempmatrix.ele[15] = 1.0f; vol->getmodelmatrix(m_voxelstoworldmatrix); *m_voxelstoworldmatrix *= tempmatrix; //Volume to Voxel transformation = //translate(orgin)*scale(1/sapcings)*translate(-orgin) float invsx = 1.0f / m_ray->m_dataspacing[0]; float invsy = 1.0f / m_ray->m_dataspacing[1]; float invsz = 1.0f / m_ray->m_dataspacing[2]; tempmatrix.ele[0] = invsx; tempmatrix.ele[1] = tempmatrix.ele[2] = tempmatrix.ele[3] = tempmatrix.ele[4] = 0.0f; tempmatrix.ele[5] = invsy; tempmatrix.ele[6] = tempmatrix.ele[7] = tempmatrix.ele[8] = tempmatrix.ele[9] = 0.0f; tempmatrix.ele[10] = invsz; tempmatrix.ele[11] = 0.0f; tempmatrix.ele[12] = m_ray->m_dataorigin[0]*(1.0f - invsx); tempmatrix.ele[13] = m_ray->m_dataorigin[1]*(1.0f - invsy); tempmatrix.ele[14] = m_ray->m_dataorigin[2]*(1.0f - invsz); tempmatrix.ele[15] = 1.0f; vol->getinverseofmodelmatrix(m_worldtovoxelsmatrix); *m_worldtovoxelsmatrix = tempmatrix * *m_worldtovoxelsmatrix; //World to View(Clipping Spacing) Transformation = Projection * View cam->getprojectionmatrix(m_perspectivematrix); cam->getviewmatrix(&tempmatrix); *m_perspectivematrix *= tempmatrix; //View(Clipping Spacing) to World Transformation = InverseOfView * //InverseOfProjection cam->getinverseofprojectionmatrix(&tempmatrix); cam->getinverseofviewmatrix(m_viewtoworldmatrix); *m_viewtoworldmatrix *= tempmatrix; 104
112 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 //Voxels to View = World to View * Voxels to World *m_voxelstoviewmatrix = *m_perspectivematrix * *m_voxelstoworldmatrix; //View to Voxels = World to Voxels * View to World *m_viewtovoxelsmatrix = *m_worldtovoxelsmatrix * *m_viewtoworldmatrix; // 根 据 图 像 采 样 距 离, 计 算 最 终 图 像 的 大 小 m_imageviewportsize[0] = (int) ((float) view->getwidth() / m_imagesampledistance); m_imageviewportsize[1] = (int) ((float) view->getheight() / m_imagesampledistance); if (this->_computerowbounds(vol, view)) bool enableclipping = m_enableclipping & (this->getclippingplanecount() > 0); if(!enableclipping) _castraysclipoff(view, vol); else _castraysclipon(view, vol); this->_rendertexture(vol, view); Render() 函 数 的 一 开 始, 从 VolumeModel 取 得 体 数 据 (Volume), 数 据 属 性 (VolumeProperty), 从 View 取 得 当 前 的 相 机, 之 后 就 要 初 始 化 射 线 m_ray 的 一 些 公 有 的 属 性 ( 也 就 是 不 随 每 条 射 线 变 化 的 属 性 ) 这 里 必 须 提 到 的 是,m_Ray 是 一 个 类 型 为 mitkray 的 结 构, 记 录 了 投 射 一 条 射 线 所 必 须 的 信 息, 其 声 明 如 下 所 示 : struct mitkray float m_color[4]; 105
113 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 // 射 线 起 点 mitkvector *m_raystart; // 射 线 终 点 mitkvector *m_rayend; // 射 线 方 向 mitkvector *m_raydirection; // 射 线 在 三 个 方 向 的 增 量 mitkvector *m_rayincrement; int m_numberofstepstotake; int m_numberofstepstaken; // 辅 助 信 息 int m_scalardatatype; void *m_scalardatapointer; int m_dataincrement[3]; int m_datasize[3]; float m_dataspacing[3]; float m_dataorigin[3]; // 所 用 的 分 类 方 法 int m_classificationmethod; int int m_maximizeopacity; m_scalarvalue; // 计 算 光 照 效 果 需 要 用 到 的 信 息 int m_shading; float *m_reddiffuseshadingtable; float *m_greendiffuseshadingtable; float *m_bluediffuseshadingtable; float *m_redspecularshadingtable; float *m_greenspecularshadingtable; float *m_bluespecularshadingtable; unsigned short *m_encodednormals; unsigned char *m_gradientmagnitudes; // 传 递 函 数 相 关 信 息 106
114 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 ; int m_tf_dimension; int m_tf_scalaropacitymaxx; int m_tf_scalaropacitymaxy; float *m_tf_scalaropacity; float *m_tf_gradientopacity; float *m_tf_scalarcolorred; float *m_tf_scalarcolorgreen; float *m_tf_scalarcolorblue; 之 后, 计 算 跟 光 照 相 关 的 Shading 表, 然 后 是 一 些 矩 阵 变 换, 这 部 分 的 代 码 虽 然 看 起 来 很 长, 但 是 本 身 并 不 复 杂, 它 们 的 功 能 也 只 是 完 成 每 一 帧 绘 制 的 前 期 准 备 工 作 在 进 行 矩 阵 变 换 时, 这 里 遵 循 的 是 OpenGL 的 坐 标 空 间 命 名 规 则 _computerowbounds() 这 个 保 护 的 成 员 函 数 的 任 务 是 计 算 在 当 前 的 变 换 矩 阵 下, 体 数 据 投 影 到 图 像 平 面 所 占 的 区 域, 这 个 区 域 的 表 示 方 式 是 使 用 一 条 一 条 的 扫 描 线 来 记 录 的, 每 条 扫 描 线 记 录 它 的 起 点 和 终 点 x 坐 标 _computerowbounds() 函 数 的 实 现 代 码 如 下, 其 每 一 步 都 给 出 了 比 较 详 细 的 注 释 : int mitkvolumerendererraycasting::_computerowbounds(mitkvolumemodel *vol, mitkview *view) mitkvector voxelpoint; mitkvector viewpoint[8]; unsigned char *ucptr; // 需 要 注 意 的 是, 这 里 遵 循 OpenGL 的 标 准, 经 过 投 影 变 换 后, //x y z 的 坐 标 范 围 都 在 -1.0~1.0 之 间 float minx, miny, maxx, maxy, minz, maxz; float bounds[6]; int dim[3]; int i, j; vol->getdata()->getdimensions(dim); // 逻 辑 坐 标 系 (voxel) 里 面 的 包 围 盒 bounds[0] = bounds[2] = bounds[4] = 0.0; bounds[1] = dim[0] - 1.0f; bounds[3] = dim[1] - 1.0f; 107
115 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 bounds[5] = dim[2] - 1.0f; float campos[3]; float worldbounds[6]; int insideflag = 0; // 得 到 世 界 坐 标 系 (world) 里 面 的 包 围 盒 vol->mitkmodel::getbounds(worldbounds); view->getcamera()->getposition(campos); if (campos[0] >= worldbounds[0] && campos[0] <= worldbounds[1] && campos[1] >= worldbounds[2] && campos[1] <= worldbounds[3] && campos[2] >= worldbounds[4] && campos[2] <= worldbounds[5]) // 相 机 在 Volume 内 部 insideflag = 1; // 将 包 围 盒 投 影 到 图 像 平 面 上, 找 出 投 影 后 所 占 的 空 间 范 围 以 及 图 像 的 大 小 // 分 别 计 算 包 围 盒 的 八 个 顶 点 // 如 果 相 机 在 Volume 内 部, 则 认 为 Volume 充 满 整 个 图 像 平 面 if (insideflag) minx = -1.0; maxx = 1.0; miny = -1.0; maxy = 1.0; minz = -1.0f; maxz = 1.0f; else //First point voxelpoint.ele[0] = bounds[0]; voxelpoint.ele[1] = bounds[2]; voxelpoint.ele[2] = bounds[4]; 108
116 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 voxelpoint.ele[3] = 1.0f; viewpoint[0] = *m_voxelstoviewmatrix * voxelpoint; viewpoint[0] *= (1.0f/viewPoint[0].ele[3]); minx = maxx = viewpoint[0].ele[0]; miny = maxy = viewpoint[0].ele[1]; minz = maxz = viewpoint[0].ele[2]; //Second point voxelpoint.ele[0] = bounds[1]; viewpoint[1] = *m_voxelstoviewmatrix * voxelpoint; viewpoint[1] *= (1.0f/viewPoint[1].ele[3]); minx = (viewpoint[1].ele[0] < minx)? (viewpoint[1].ele[0]) : (minx); miny = (viewpoint[1].ele[1] < miny)? (viewpoint[1].ele[1]) : (miny); maxx = (viewpoint[1].ele[0] > maxx)? (viewpoint[1].ele[0]) : (maxx); maxy = (viewpoint[1].ele[1] > maxy)? (viewpoint[1].ele[1]) : (maxy); minz = (viewpoint[1].ele[2] < minz)? (viewpoint[1].ele[2]) : (minz); maxz = (viewpoint[1].ele[2] > maxz)? (viewpoint[1].ele[2]) : (maxz); //Third point voxelpoint.ele[1] = bounds[3]; viewpoint[2] = *m_voxelstoviewmatrix * voxelpoint; viewpoint[2] *= (1.0f/viewPoint[2].ele[3]); minx = (viewpoint[2].ele[0] < minx)? (viewpoint[2].ele[0]) : (minx); miny = (viewpoint[2].ele[1] < miny)? (viewpoint[2].ele[1]) : (miny); maxx = (viewpoint[2].ele[0] > maxx)? (viewpoint[2].ele[0]) : (maxx); maxy = (viewpoint[2].ele[1] > maxy)? (viewpoint[2].ele[1]) : (maxy); minz = (viewpoint[2].ele[2] < minz)? (viewpoint[2].ele[2]) : (minz); maxz = (viewpoint[2].ele[2] > maxz)? (viewpoint[2].ele[2]) : (maxz); 109
117 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 //Fourth point voxelpoint.ele[0] = bounds[0]; viewpoint[3] = *m_voxelstoviewmatrix * voxelpoint; viewpoint[3] *= (1.0f/viewPoint[3].ele[3]); minx = (viewpoint[3].ele[0] < minx)? (viewpoint[3].ele[0]) : (minx); miny = (viewpoint[3].ele[1] < miny)? (viewpoint[3].ele[1]) : (miny); maxx = (viewpoint[3].ele[0] > maxx)? (viewpoint[3].ele[0]) : (maxx); maxy = (viewpoint[3].ele[1] > maxy)? (viewpoint[3].ele[1]) : (maxy); minz = (viewpoint[3].ele[2] < minz)? (viewpoint[3].ele[2]) : (minz); maxz = (viewpoint[3].ele[2] > maxz)? (viewpoint[3].ele[2]) : (maxz); //Fifth point voxelpoint.ele[2] = bounds[5]; viewpoint[4] = *m_voxelstoviewmatrix * voxelpoint; viewpoint[4] *= (1.0f/viewPoint[4].ele[3]); minx = (viewpoint[4].ele[0] < minx)? (viewpoint[4].ele[0]) : (minx); miny = (viewpoint[4].ele[1] < miny)? (viewpoint[4].ele[1]) : (miny); maxx = (viewpoint[4].ele[0] > maxx)? (viewpoint[4].ele[0]) : (maxx); maxy = (viewpoint[4].ele[1] > maxy)? (viewpoint[4].ele[1]) : (maxy); minz = (viewpoint[4].ele[2] < minz)? (viewpoint[4].ele[2]) : (minz); maxz = (viewpoint[4].ele[2] > maxz)? (viewpoint[4].ele[2]) : (maxz); //Sixth point voxelpoint.ele[0] = bounds[1]; viewpoint[5] = *m_voxelstoviewmatrix * voxelpoint; viewpoint[5] *= (1.0f/viewPoint[5].ele[3]); 110
118 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 minx = (viewpoint[5].ele[0] < minx)? (viewpoint[5].ele[0]) : (minx); miny = (viewpoint[5].ele[1] < miny)? (viewpoint[5].ele[1]) : (miny); maxx = (viewpoint[5].ele[0] > maxx)? (viewpoint[5].ele[0]) : (maxx); maxy = (viewpoint[5].ele[1] > maxy)? (viewpoint[5].ele[1]) : (maxy); minz = (viewpoint[5].ele[2] < minz)? (viewpoint[5].ele[2]) : (minz); maxz = (viewpoint[5].ele[2] > maxz)? (viewpoint[5].ele[2]) : (maxz); //Seventh point voxelpoint.ele[1] = bounds[2]; viewpoint[6] = *m_voxelstoviewmatrix * voxelpoint; viewpoint[6] *= (1.0f/viewPoint[6].ele[3]); minx = (viewpoint[6].ele[0] < minx)? (viewpoint[6].ele[0]) : (minx); miny = (viewpoint[6].ele[1] < miny)? (viewpoint[6].ele[1]) : (miny); maxx = (viewpoint[6].ele[0] > maxx)? (viewpoint[6].ele[0]) : (maxx); maxy = (viewpoint[6].ele[1] > maxy)? (viewpoint[6].ele[1]) : (maxy); minz = (viewpoint[6].ele[2] < minz)? (viewpoint[6].ele[2]) : (minz); maxz = (viewpoint[6].ele[2] > maxz)? (viewpoint[6].ele[2]) : (maxz); //Eighth point voxelpoint.ele[0] = bounds[0]; viewpoint[7] = *m_voxelstoviewmatrix * voxelpoint; viewpoint[7] *= (1.0f/viewPoint[7].ele[3]); minx = (viewpoint[7].ele[0] < minx)? (viewpoint[7].ele[0]) : (minx); miny = (viewpoint[7].ele[1] < miny)? (viewpoint[7].ele[1]) : (miny); maxx = (viewpoint[7].ele[0] > maxx)? (viewpoint[7].ele[0]) : (maxx); 111
119 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 maxy = (viewpoint[7].ele[1] > maxy)? (viewpoint[7].ele[1]) : (maxy); minz = (viewpoint[7].ele[2] < minz)? (viewpoint[7].ele[2]) : (minz); maxz = (viewpoint[7].ele[2] > maxz)? (viewpoint[7].ele[2]) : (maxz); if (minz < f maxz > f ) minx = -1.0f; maxx = 1.0f; miny = -1.0f; maxy = 1.0f; insideflag = 1; // 转 换 成 屏 幕 坐 标, 留 有 一 定 的 裕 量 minx = (minx + 1.0f) * 0.5f * m_imageviewportsize[0] - 2.0f; miny = (miny + 1.0f) * 0.5f * m_imageviewportsize[1] - 2.0f; maxx = (maxx + 1.0f) * 0.5f * m_imageviewportsize[0] + 2.0f; maxy = (maxy + 1.0f) * 0.5f * m_imageviewportsize[1] + 2.0f; minz = (minz + 1.0f) * 0.5f; maxz = (maxz + 1.0f) * 0.5f; m_minimumviewdistance = (minz < 0.001f)? (0.001f) : ((minz > 0.999f)? (0.999f) : (minz)); // 进 行 裁 剪 if ((minx < 0 && maxx < 0) (miny < 0 && maxy < 0) (minx > (m_imageviewportsize[0] - 1) && maxx > (m_imageviewportsize[0] - 1)) (miny > (m_imageviewportsize[1] - 1) && maxy > (m_imageviewportsize[1] - 1))) return 0; int oldimagememorysize[2]; 112
120 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 oldimagememorysize[0] = m_imagememorysize[0]; oldimagememorysize[1] = m_imagememorysize[1]; // Swap the row bounds // 如 果 是 第 一 次 进 来, 则 m_rowbounds 和 m_oldrowbounds 都 为 NULL, 要 被 重 新 分 配 // 如 果 前 一 帧 比 本 帧 小, 则 m_rowbounds 和 m_oldrowbounds 也 需 要 被 重 新 分 配 // 如 果 前 一 帧 比 本 帧 大, 但 是 又 不 是 太 大, 则 需 要 重 用 m_image, 也 仅 在 这 种 情 况 // 下,m_RowBounds 和 m_oldrowbounds 的 前 后 帧 关 联 才 被 用 上 int *tmpptr; tmpptr = m_rowbounds; m_rowbounds = m_oldrowbounds; m_oldrowbounds = tmpptr; // 进 行 必 要 的 裁 剪 minx = (minx < 0.0f)? (0.0f) : (minx); miny = (miny < 0.0f)? (0.0f) : (miny); maxx = (maxx > m_imageviewportsize[0] - 1.0f)? (m_imageviewportsize[0] - 1.0f) : (maxx); maxy = (maxy > m_imageviewportsize[1] - 1.0f)? (m_imageviewportsize[1] - 1.0f) : (maxy); // 得 到 新 图 像 的 实 际 大 小 m_imageinusesize[0] = (int) (maxx - minx + 1.0f); m_imageinusesize[1] = (int) (maxy - miny + 1.0f); // 计 算 新 图 像 在 内 存 里 面 的 大 小, 需 要 是 2 的 倍 数 // 主 要 是 为 了 后 续 使 用 OpenGL 纹 理 绘 制 方 便 m_imagememorysize[0] = 32; m_imagememorysize[1] = 32; while(m_imagememorysize[0] < m_imageinusesize[0]) m_imagememorysize[0] *= 2; while(m_imagememorysize[1] < m_imageinusesize[1]) m_imagememorysize[1] *= 2; m_imageorigin[0] = (int) minx; m_imageorigin[1] = (int) miny; 113
121 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 // 如 果 旧 的 图 像 大 小 太 大, 则 不 复 用 前 一 帧 的 结 果 if (oldimagememorysize[0] > 2*m_ImageMemorySize[0] oldimagememorysize[1] > 2*m_ImageMemorySize[1]) oldimagememorysize[0] = 0; if (oldimagememorysize[0] >= m_imagememorysize[0] && oldimagememorysize[1] >= m_imagememorysize[1]) m_imagememorysize[0] = oldimagememorysize[0]; m_imagememorysize[1] = oldimagememorysize[1]; // 清 除 前 一 帧 的 if (!m_image m_imagememorysize[0] > oldimagememorysize[0] m_imagememorysize[1] > oldimagememorysize[1]) if (m_image) delete [] m_image; delete [] m_rowbounds; delete [] m_oldrowbounds; m_image = new unsigned char[m_imagememorysize[0]*m_imagememorysize[1]*4]; m_rowbounds = new int [2*m_ImageMemorySize[1]]; m_oldrowbounds = new int [2*m_ImageMemorySize[1]]; for(i = 0; i < m_imagememorysize[1]; i++) m_rowbounds[i*2] = m_imagememorysize[0]; m_rowbounds[i*2 + 1] = -1; m_oldrowbounds[i*2] = m_imagememorysize[0]; m_oldrowbounds[i*2 + 1] = -1; 114
122 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 ucptr = m_image; memset(ucptr, 0, m_imagememorysize[0]*m_imagememorysize[1]*4); if (insideflag) for (i = 0; i < m_imageinusesize[1]; i++) m_rowbounds[i*2] = 0; m_rowbounds[i*2 + 1] = m_imageinusesize[0] - 1; else // 计 算 每 条 扫 描 线 的 起 点 和 终 点 float lines[12][4]; float x1, y1, x2, y2; int xlow, xhigh; int lineindex[12][2] = 0,1, 2,3, 4,5, 6,7, 0,3, 2,1,6,5, 4,7, 0,7, 6,1, 2,5, 4,3; for(i = 0; i < 8; i++) viewpoint[i].ele[0] = (viewpoint[i].ele[0] + 1.0f)*0.5f*m_ImageViewportSize[0] - m_imageorigin[0]; viewpoint[i].ele[1] = (viewpoint[i].ele[1] + 1.0f)*0.5f*m_ImageViewportSize[1] - m_imageorigin[1]; for (i = 0; i < 12; i++) x1 = viewpoint[lineindex[i][0]].ele[0]; y1 = viewpoint[lineindex[i][0]].ele[1]; x2 = viewpoint[lineindex[i][1]].ele[0]; y2 = viewpoint[lineindex[i][1]].ele[1]; if ( y1 < y2 ) 115
123 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 lines[i][0] = x1; lines[i][1] = y1; lines[i][2] = x2; lines[i][3] = y2; else lines[i][0] = x2; lines[i][1] = y2; lines[i][2] = x1; lines[i][3] = y1; for(j = 0; j < m_imageinusesize[1]; j++) m_rowbounds[j*2] = m_imagememorysize[0]; m_rowbounds[j*2 + 1] = -1; for(i = 0; i < 12; i++) if(j >= lines[i][1] && j <= lines[i][3] && (lines[i][1]!= lines[i][3])) x1 = lines[i][0] + ((float)(j) - lines[i][1]) / (lines[i][3] - lines[i][1]) * (lines[i][2] - lines[i][0]); xlow = (int) (x f); xhigh = (int) (x1-1.0f); xlow = (xlow < 0.0f)? (0.0f) : (xlow); xlow = (xlow > m_imageinusesize[0] - 1.0f)? (m_imageinusesize[0] - 1.0f) : (xlow); xhigh = (xhigh < 0.0f)? (0.0f) : (xhigh); xhigh = (xhigh > m_imageinusesize[0] - 1.0f)? (m_imageinusesize[0] - 1.0f) : (xhigh); if (xlow < m_rowbounds[j*2]) 116
124 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 m_rowbounds[j*2] = xlow; if (xhigh > m_rowbounds[j*2 + 1]) m_rowbounds[j*2 + 1] = xhigh; if(m_rowbounds[j*2] == m_rowbounds[j*2 + 1]) m_rowbounds[j*2] = m_imagememorysize[0]; m_rowbounds[j*2 + 1] = -1; // 区 域 之 外 的 填 充 成 -1 for (j = m_imageinusesize[1]; j < m_imagememorysize[1]; j++) m_rowbounds[j*2] = m_imagememorysize[0]; m_rowbounds[j*2 + 1] = -1; for(j = 0; j < m_imagememorysize[1]; j++) if(m_rowbounds[j*2+1] < m_oldrowbounds[j*2] m_rowbounds[j*2] > m_oldrowbounds[j*2+1]) ucptr = m_image + 4*(j*m_ImageMemorySize[0] + m_oldrowbounds[j*2]); if((m_oldrowbounds[j*2 + 1] - m_oldrowbounds[j*2]) >= 0) memset(ucptr, 0, 4*(m_OldRowBounds[j*2 + 1] - m_oldrowbounds[j*2] + 1)); else if((m_rowbounds[j*2] - m_oldrowbounds[j*2]) > 0) 117
125 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 ucptr = m_image + 4*(j*m_ImageMemorySize[0] + m_oldrowbounds[j*2]); memset(ucptr, 0, 4*(m_RowBounds[j*2] - m_oldrowbounds[j*2])); if((m_oldrowbounds[j*2+1] - m_rowbounds[j*2+1]) > 0) ucptr = m_image + 4*(j*m_ImageMemorySize[0] + m_rowbounds[j*2 + 1] + 1); memset(ucptr, 0, 4*(m_OldRowBounds[j*2+1] - m_rowbounds[j*2+1])); return 1; 在 计 算 完 投 影 区 域 以 后, 该 完 成 的 准 备 工 作 已 经 都 差 不 多 了, 需 要 注 意 的 是, 到 目 前 为 止 算 法 最 复 杂 的 多 层 循 环 还 没 有 开 始, 前 面 的 都 是 一 些 循 环 外 层 的 初 始 工 作 在 进 入 主 要 循 环 之 前, 程 序 还 分 两 种 情 况 : 支 持 平 面 裁 剪 和 不 支 持 平 面 裁 剪, 如 果 设 置 了 支 持 平 面 裁 剪 并 且 当 前 存 在 裁 剪 平 面, 那 么 程 序 调 用 _castraysclipon() 函 数 来 进 行 实 际 的 光 线 投 射 计 算 工 作, 如 果 没 有 打 开 平 面 裁 剪, 那 么 程 序 调 用 _castraysclipoff() 函 数 来 进 行 实 际 的 光 线 投 射 计 算 工 作 因 为 _castraysclipon() 比 _castraysclipoff() 更 复 杂, 这 里 为 了 避 免 大 量 的 代 码 段 出 现, 只 给 出 _castraysclipon() 的 实 现, 如 下 所 示 : void mitkvolumerendererraycasting::_castraysclipon(mitkview *view, mitkvolumemodel *vol) int i, j; unsigned char *ucptr; float camerathickness = view->getcamera()->getthickness(); // 得 到 射 线 的 必 要 信 息 118
126 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 mitkvector *raystart = m_ray->m_raystart; mitkvector *rayend = m_ray->m_rayend; mitkvector *raydirection = m_ray->m_raydirection; mitkvector *raystep = m_ray->m_rayincrement; // 初 始 化 裁 剪 平 面 (Clipping Plane) // 得 到 裁 剪 平 面 (Clipping Plane) 的 Ax + By + Cz + D = 0 的 表 达 式 mitkplane *oneplane; int count; float *clippingplane, *tempplanepointer; float *planeorigin; count = this->getclippingplanecount(); clippingplane = new float[4*count]; // 必 须 在 用 完 时 释 放 tempplanepointer = clippingplane; for(m_clippingplanes->inittraversal(); (oneplane = (mitkplane*) m_clippingplanes->getnextitem()); ) // 此 处 的 Clipping Plane 是 在 Voxel Space 中 指 定 的, 所 以 无 需 转 换 oneplane->getnormal(tempplanepointer[0], tempplanepointer[1], tempplanepointer[2]); planeorigin = (float*) oneplane->getorigin()->ele; tempplanepointer[3] = -(tempplanepointer[0]*planeorigin[0] + tempplanepointer[1]*planeorigin[1] + tempplanepointer[2]*planeorigin[2]); tempplanepointer += 4; float norm; mitkvector viewray(0.0f, 0.0f, -1.0f); mitkvector raycenter; float absstep[3]; mitkvector voxelpoint; // 在 View(NDC) 坐 标 系 中 的 近 平 面 的 中 心 点 // 转 换 到 Voxel(Logical) 坐 标 系 中 *raystart = *m_viewtovoxelsmatrix * viewray; *raystart *= (1.0f / raystart->ele[3]); // 在 View(NDC) 坐 标 系 中 的 远 平 面 的 中 心 点 // 转 换 到 Voxel(Logical) 坐 标 系 中 119
127 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 viewray.ele[2] = 1.0f; voxelpoint = *m_viewtovoxelsmatrix * viewray; voxelpoint *= (1.0f / voxelpoint.ele[3]); // 得 到 中 心 射 线 的 方 向 向 量 raycenter = voxelpoint - *raystart; // 标 准 化 中 心 射 线 的 方 向 向 量 raycenter *= (1.0f / camerathickness); // centerscale = Len(rayCenter) / ( far - near ) float centerscale = raycenter.length(); raycenter.normalize(); float bounds[6]; int dim[3]; float val; vol->getdata()->getdimensions(dim); bounds[0] = bounds[2] = bounds[4] = 0.0; bounds[1] = dim[0]-1; bounds[3] = dim[1]-1; bounds[5] = dim[2]-1; float offsetx = 1.0f / (float) (m_imageviewportsize[0]); float offsety = 1.0f / (float) (m_imageviewportsize[1]); // 主 要 循 环 开 始, 遍 历 投 影 图 像 的 每 一 条 扫 描 线 for (j = 0; j < m_imageinusesize[1]; j++) ucptr = m_image + 4*(j*m_ImageMemorySize[0] + m_rowbounds[j*2]); // 计 算 NDC 空 间 里 面 的 y 坐 标 viewray.ele[1] = (((float)(j + m_imageorigin[1])) / (float) m_imageviewportsize[1]) * 2.0f - 1.0f + offsety; // 内 层 循 环 开 始, 遍 历 扫 描 线 上 起 点 和 终 点 之 间 的 每 一 个 点 for(i = m_rowbounds[j*2]; i <= m_rowbounds[j*2+1]; i++) ucptr[0] = 0; 120
128 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 ucptr[1] = 0; ucptr[2] = 0; ucptr[3] = 0; // 计 算 NDC 空 间 里 面 的 x 坐 标 viewray.ele[0] = (((float)(i + m_imageorigin[0])) / (float) m_imageviewportsize[0]) * 2.0f - 1.0f + offsetx; // 在 NDC 空 间 中, 每 条 射 线 都 是 平 行 的, 并 且 近 平 面 就 是 图 像 平 面 // 射 线 起 始 于 图 像 平 面, 终 止 于 远 平 面, 或 者 终 止 于 最 近 的 不 透 明 // 物 体 前 ( 由 zbuffer 决 定 ) // 先 算 起 点 坐 标, 将 其 转 换 至 Voxel 空 间 中 viewray.ele[2] = -1.0f; *raystart = *m_viewtovoxelsmatrix * viewray; *raystart *= (1.0f / raystart->ele[3]); // 再 算 终 点 坐 标, 将 其 转 换 至 Voxel 空 间 中 viewray.ele[2] = (m_zbuffer)? (this->_getzbuffervalue(i,j)) : (1.0f); *rayend = *m_viewtovoxelsmatrix * viewray; *rayend *= (1.0f / rayend->ele[3]); // 得 到 射 线 的 方 向 *raydirection = *rayend - *raystart; // 本 射 线 相 对 于 Volume 和 Clipping Plane 进 行 裁 剪 if(this->_cliprayagainstvolume(m_ray, bounds) && this->_cliprayagainstclippingplanes(count, clippingplane, m_ray)) // 重 新 计 算 射 线 方 向 *raydirection = *rayend - *raystart; // 归 一 化 *raystep = *raydirection; raystep->normalize(); val = *raystep * raycenter; //Cos(theta) norm = (val!= 0.0f)? (1.0f / val) : (1.0f); 121
129 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 // 此 处 应 该 注 意 的 是,sampleDistance / ( far - near ) 是 NDC // 空 间 的 采 样 距 离, 它 保 证 在 NDC 空 间 是 等 间 距 采 样 的, 但 是 在 Voxel // 空 间, 每 条 射 线 的 采 样 距 离 就 不 相 等 了 在 Voxel 空 间 的 采 样 距 离 // 为 :0.5 * Len(rayCenter) * sampledistance / ( far - // near ), 因 为 centerscale = Len(rayCenter) / ( far - // near ), 所 以 上 式 为 :0.5 * sampledistance * centerscale. // 这 是 在 中 心 线 上 的 采 样 距 离, 本 算 法 中 采 用 平 行 于 图 像 平 面 的 平 面 去 采 // 样, 而 没 有 用 半 球 面 上 的 采 样, 所 以 最 终 的 采 样 距 离 为 : //0.5 * sampledistance * centerscale / cos(theta), 也 即 //0.5 * sampledistance * centerscale * norm *raystep *= 0.5f * norm * m_sampledistance * centerscale; absstep[0] = (raystep->ele[0] < 0.0f)? (-raystep->ele[0]) : (raystep->ele[0]); absstep[1] = (raystep->ele[1] < 0.0f)? (-raystep->ele[1]) : (raystep->ele[1]); absstep[2] = (raystep->ele[2] < 0.0f)? (-raystep->ele[2]) : (raystep->ele[2]); if ( absstep[0] >= absstep[1] && absstep[0] >= absstep[2] ) m_ray->m_numberofstepstotake = (int) ((rayend->ele[0] - raystart->ele[0]) / raystep->ele[0]); else if ( absstep[1] >= absstep[2] && absstep[1] >= absstep[0] ) m_ray->m_numberofstepstotake = (int) ((rayend->ele[1] - raystart->ele[1]) / raystep->ele[1]); else m_ray->m_numberofstepstotake = (int) ((rayend->ele[2] - raystart->ele[2]) / raystep->ele[2]); // 最 内 层 循 环 在 此 函 数 内 完 成 this->_castray(m_ray); 122
130 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 // 得 到 这 条 射 线 最 后 的 颜 色 if(m_ray->m_color[3] > 0.0f) val = (m_ray->m_color[0] / m_ray->m_color[3]) * 255.0f; val = (val > 255.0f)? (255.0f) : (val); val = (val < 0.0f)? ( 0.0f) : (val); ucptr[0] = (unsigned char) (val); val = (m_ray->m_color[1] / m_ray->m_color[3]) * 255.0f; val = (val > 255.0f)? (255.0f) : (val); val = (val < 0.0f)? ( 0.0f) : (val); ucptr[1] = (unsigned char) (val); val = (m_ray->m_color[2] / m_ray->m_color[3]) * 255.0f; val = (val > 255.0f)? (255.0f) : (val); val = (val < 0.0f)? ( 0.0f) : (val); ucptr[2] = (unsigned char) (val); val = m_ray->m_color[3] * 255.0f; val = (val > 255.0f)? (255.0f) : (val); val = (val < 0.0f)? ( 0.0f) : (val); ucptr[3] = (unsigned char) (val); ucptr += 4; delete []clippingplane; 可 以 看 到, 在 上 面 的 代 码 中 主 要 是 一 个 双 层 循 环, 外 层 循 环 遍 历 我 们 在 _computerowbounds() 函 数 中 计 算 出 来 的 投 影 区 域 的 每 一 条 扫 描 线, 而 内 层 循 环 则 遍 历 这 条 扫 描 线 上 从 起 点 到 终 点 的 每 一 个 像 素 点, 对 于 每 一 个 像 素 点, 计 算 出 从 其 投 射 的 光 线 的 起 始 点 和 终 止 点 坐 标 射 线 方 向 等 信 息 ( 在 Voxel 空 间 ), 然 后 再 调 用 _castray() 函 数 来 完 成 最 后 的 计 算 工 作 123
131 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 _castray() 函 数 内 部 实 际 上 也 是 一 个 循 环, 它 对 传 进 来 的 射 线 按 照 一 定 的 步 长 采 样, 这 个 循 环 就 是 要 遍 历 每 一 个 采 样 点, 所 以 实 际 上 Ray Casting 算 法 的 本 质 是 个 三 重 循 环 _castray() 函 数 处 于 最 内 层 循 环, 它 接 收 一 条 射 线 信 息, 计 算 出 这 条 射 线 穿 过 Volume 后 累 积 得 到 的 颜 色 值, 其 代 码 实 现 如 下 所 示 : void mitkvolumerendererraycasting::_castray(mitkray *rayinfo) void *data_ptr = rayinfo->m_scalardatapointer; if(rayinfo->m_shading == 0) // 没 有 光 照 效 果 (Shading) // 判 断 数 据 类 型, 并 分 发 给 模 板 函 数 去 处 理 switch(rayinfo->m_scalardatatype) case MITK_UNSIGNED_CHAR: t_castrayunshaded((unsigned char *)data_ptr, rayinfo); break; case MITK_UNSIGNED_SHORT: t_castrayunshaded((unsigned short *)data_ptr, rayinfo); break; else // 有 光 照 效 果 (Shading) // 判 断 数 据 类 型, 并 分 发 给 模 板 函 数 去 处 理 switch(rayinfo->m_scalardatatype) case MITK_UNSIGNED_CHAR: t_castrayshaded((unsigned char *)data_ptr, rayinfo); break; case MITK_UNSIGNED_SHORT: t_castrayshaded((unsigned short *)data_ptr, rayinfo); break; 124
132 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 在 _castray() 函 数 中, 要 处 理 有 光 照 效 果 和 无 光 照 效 果 两 种 情 况, 并 且 还 要 处 理 不 同 的 数 据 类 型 这 里 使 用 模 板 函 数 来 处 理 不 同 的 数 据 类 型, 避 免 代 码 的 重 复 : 模 板 函 数 t_castrayunshaded() 用 来 计 算 无 光 照 效 果 下 的 结 果, 而 模 板 函 数 t_castrayshaded() 用 来 计 算 有 光 照 效 果 下 的 结 果, 它 们 都 接 收 一 个 参 数 化 的 数 据 指 针, 和 一 个 mitkray 类 型 的 指 针 ( 提 供 射 线 信 息 ) 为 了 避 免 重 复, 这 里 只 给 出 比 较 复 杂 的 有 光 照 效 果 计 算 的 函 数 t_castrayshaded() 的 实 现, 其 代 码 如 下 所 示 : // 要 计 算 光 照 效 果 (Shading), 必 须 付 出 的 额 外 的 内 存 代 价 就 是 Normal 的 存 储 空 间 // 这 里 将 Normal 压 缩 编 码 成 16bits // 并 且 Shading_Table 可 以 直 接 接 收 一 个 16bits 编 码 的 Normal 作 为 输 入, // 输 出 对 应 的 Diffuse 和 Specular 光 照 颜 色 // 这 里 最 终 颜 色 的 计 算 公 式 如 下 : //FinalColor = Diffuse * Color + Specular template <class T> static void t_castrayshaded(t *data_ptr, mitkray *rayinfo) // 局 部 变 量 定 义 int value = 0; unsigned char *grad_mag_ptr = NULL; float accum_red_intensity; float accum_green_intensity; float accum_blue_intensity; float remaining_opacity; float opacity = 0.0f; float opacity_mul; float *diff_shade_red; float *diff_shade_green; float *diff_shade_blue; float *spec_shade_red; float *spec_shade_green; float *spec_shade_blue; unsigned short *encoded_normals; int encoded_normal; int loop; int xinc, yinc, zinc; 125
133 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 int voxel[3]; float ray_position[3]; int tf_dim; float *SOTF; float *SCTF_R; float *SCTF_G; float *SCTF_B; float *GOTF; int offset = 0; int steps_this_ray = 0; int num_steps; float *ray_start, *ray_increment; // 将 必 须 的 信 息 拷 贝 到 局 部 变 量 中, 为 编 译 器 优 化 提 供 条 件 num_steps = rayinfo->m_numberofstepstotake; ray_start = rayinfo->m_raystart->ele; ray_increment = rayinfo->m_rayincrement->ele; SOTF = rayinfo->m_tf_scalaropacity; GOTF = rayinfo->m_tf_gradientopacity; SCTF_R = rayinfo->m_tf_scalarcolorred; SCTF_G = rayinfo->m_tf_scalarcolorgreen; SCTF_B = rayinfo->m_tf_scalarcolorblue; tf_dim = rayinfo->m_tf_dimension; diff_shade_red = rayinfo->m_reddiffuseshadingtable; diff_shade_green = rayinfo->m_greendiffuseshadingtable; diff_shade_blue = rayinfo->m_bluediffuseshadingtable; spec_shade_red = rayinfo->m_redspecularshadingtable; spec_shade_green = rayinfo->m_greenspecularshadingtable; spec_shade_blue = rayinfo->m_bluespecularshadingtable; encoded_normals = rayinfo->m_encodednormals; xinc = rayinfo->m_dataincrement[0]; yinc = rayinfo->m_dataincrement[1]; zinc = rayinfo->m_dataincrement[2]; ray_position[0] = ray_start[0]; ray_position[1] = ray_start[1]; 126
134 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 ray_position[2] = ray_start[2]; voxel[0] = mitkroundfuncmacro( ray_position[0] ); voxel[1] = mitkroundfuncmacro( ray_position[1] ); voxel[2] = mitkroundfuncmacro( ray_position[2] ); accum_red_intensity accum_green_intensity accum_blue_intensity remaining_opacity = 0.0f; = 0.0f; = 0.0f; = 1.0f; // 如 果 需 要 计 算 Gradient-Opacity, 则 得 到 Volume 的 梯 度 场 if(gotf) grad_mag_ptr = rayinfo->m_gradientmagnitudes; // 循 环 遍 历 本 条 射 线, 最 内 层 循 环 开 始 for(loop = 0; loop < num_steps && remaining_opacity > MITK_REMAINING_OPACITY; loop++ ) steps_this_ray++; // 得 到 Scalar Value offset = voxel[2] * zinc + voxel[1] * yinc + voxel[0] * xinc; value = data_ptr[offset]; // 计 算 Opacity if(tf_dim == 1) // 一 维 的 TF opacity = SOTF[value]; if(opacity && grad_mag_ptr) opacity *= GOTF[grad_mag_ptr[offset]]; else if(tf_dim == 2) // 二 维 的 TF else // 三 维 的 TF 127
135 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 // 累 加 颜 色 值, 由 前 向 后 opacity_mul = opacity * remaining_opacity; encoded_normal = encoded_normals[offset]; accum_red_intensity += (opacity_mul * (diff_shade_red[encoded_normal] * SCTF_R[value] + spec_shade_red[encoded_normal])); accum_green_intensity += (opacity_mul * (diff_shade_green[encoded_normal] * SCTF_G[value] + spec_shade_green[encoded_normal])); accum_blue_intensity += (opacity_mul * (diff_shade_blue[encoded_normal] * SCTF_B[value] + spec_shade_blue[encoded_normal])); remaining_opacity *= (1.0 - opacity); // 计 算 下 一 个 Voxel 的 位 置 ray_position[0] += ray_increment[0]; ray_position[1] += ray_increment[1]; ray_position[2] += ray_increment[2]; voxel[0] = mitkroundfuncmacro( ray_position[0] ); voxel[1] = mitkroundfuncmacro( ray_position[1] ); voxel[2] = mitkroundfuncmacro( ray_position[2] ); if(accum_red_intensity > 1.0f) accum_red_intensity = 1.0f; if(accum_green_intensity > 1.0f) accum_green_intensity = 1.0f; if(accum_blue_intensity > 1.0f) accum_blue_intensity = 1.0f; if(remaining_opacity < MITK_REMAINING_OPACITY) 128
136 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 remaining_opacity = 0.0f; // 储 存 颜 色 信 息 rayinfo->m_color[0] = accum_red_intensity; rayinfo->m_color[1] = accum_green_intensity; rayinfo->m_color[2] = accum_blue_intensity; rayinfo->m_color[3] = remaining_opacity; rayinfo->m_numberofstepstaken = steps_this_ray; 至 此 为 止, 整 个 Ray Casting 绘 制 算 法 的 主 要 步 骤 的 实 现 细 节 都 已 经 给 出 了, 还 有 一 些 其 它 的 辅 助 函 数 的 实 现 这 里 没 有 详 细 给 出, 不 过 都 是 一 些 枝 节 性 的 东 西 深 入 理 解 了 Ray Casting 算 法 的 实 现 以 后, 对 于 其 它 类 型 的 体 绘 制 算 法 也 可 以 很 快 地 理 解 并 给 出 自 己 的 实 现 4.4 小 结 本 节 给 出 了 MITK 中 体 绘 制 算 法 的 框 架 和 实 现, 在 第 一 部 分, 简 要 介 绍 了 体 绘 制 算 法 的 发 展 历 史, 以 及 体 绘 制 算 法 的 分 类 ; 在 第 二 部 分, 给 出 了 MITK 中 体 绘 制 算 法 的 整 体 框 架, 目 的 是 为 了 得 到 灵 活 性 易 于 扩 充 性 ; 在 第 三 部 分, 以 实 例 代 码 的 形 式 给 出 了 MITK 中 体 绘 制 算 法 框 架 各 个 部 分 的 详 细 实 现, 并 且 用 Ray Casting 算 法 为 例, 给 出 了 此 算 法 在 MITK 框 架 里 面 的 详 细 实 现 读 者 在 阅 读 完 本 章 以 后, 应 该 对 体 绘 制 的 整 个 过 程 有 一 个 比 较 深 入 的 理 解, 并 且 对 算 法 的 实 现 细 节 也 有 比 较 好 的 掌 握 为 了 使 复 杂 的 体 绘 制 算 法 能 够 被 容 易 地 把 握, 本 章 不 吝 笔 墨, 给 出 了 大 量 的 实 现 细 节, 包 括 实 际 的 代 码, 因 为 我 们 相 信 最 终 代 码 最 能 说 明 问 题 希 望 读 者 能 够 反 复 地 阅 读 这 些 代 码, 彻 底 理 解 体 绘 制 的 工 作 流 程, 这 样 可 以 在 自 己 的 工 作 中, 举 一 反 三, 设 计 出 自 己 的 体 绘 制 算 法, 丰 富 可 用 的 算 法 仓 库 参 考 文 献 1. M. Levoy, Display of surfaces from volume data, IEEE Transaction on Computer Graphics and Applications, 1988, 8(3):
137 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 2. R. Drebin, L. Carpenter, P. Hanrahan. Volume Rendering. Computer Graphics, Vol. 22, No. 4, pp , Aug L. Westover. Footprint evaluation for volume rendering. Computer Graphics (SIGGRAPH '90 Proceedings), 24(4): , August D. Laur and P. Hanrahan, Hierarchical Splatting: A Progressive Refinement Algorithm for Volume Rendering, ACM Computer Graphics, Proc. SIGGRAPH 93, 25(4): , July J. Huang, K. Mueller, N. Shareef, et al. FastSplats: Optimized Splatting on Rectilinear Grids. In Proceedings of IEEE Visualization 2000, pp , October P. Lacroute and M. Levoy, Fast volume rendering using a shear-warp factorization of the viewing transformation, Proc. SIGGRAPH 94, pp , P. Lacroute, M. Levoy. Real-time volume rendering on shared-memory multiprocessors using the shear-warp factorization. In Proc. of Parallel Rendering Symposium 95, pp , J. Sweeney, K. Mueller, Shear-Warp Deluxe: The shear-warp algorithm revisited. In Proceeding of Joint Eurographics - IEEE TCVG Symposium on Visualization 2002, Barcelona, Spain, May 2002, pp J. P. Schulze, M. Kraus, U. Lang, et al. Integrating Pre-Integration into the Shear-Warp Algorithm. In Proceedings of the Third International Workshop on Volume Graphics, Tokyo, July 7-8, 2003, pp Brian Cabral, Nancy Cam, Jim Foran. Accelerated Volume Rendering and Tomographic Reconstruction Using Texture Mapping Hardware. In Proc. Symposium on Volume Visualization 94, pages ACM SIGGRAPH, R. Westermann, T. Ertl. Efficiently Using Graphics Hardware in Volume Rendering Applications. In Computer Graphics, SigGraph Annual Conference Series, pages , M. Meißner, U. Hoffmann, W Straßer. Enabling Classification and Shading for 3D Texture Based Volume Rendering Using OpenGL and Extensions. In Proc. of Visualization 99, C. Resk-Salama, K. Engel, M. Bauer, G. Greiner, T. Ertl, Interactive Volume Rendering on Standard PC Graphics Hardware Using Multi-Textures and Multi-Stage-Rasterization,in Proc. Eurographics/SIGGRAPH Workshop on Graphics Hardware K. Engel, M. Kraus, and T. Ertl, High-quality pre-integrated volume rendering using hardware accelerated pixel shading, in Proc. Eurographics/SIGGRAPH Workshop on 130
138 4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 Graphics Hardware Joe Kniss, Gordon Kindlmann, Charles Hansen. Multidimensional Transfer Functions for Interactive Volume Rendering. IEEE Transactions on Visualization and Computer Graphics, Volume 8, Issue 3, pp , Roettger S., Guthe S., Weiskopf D., et al. Smart hardware accelerated volume rendering. In EUROGRAPHICS/IEEE Symposium on Visualization (2003), pp J. Krüger, R. Westermann. Acceleration Techniques for GPU-based Volume Rendering. In Proc. of IEEE Visualization 2003, pp Eric B. Lum, Brett Wilson, Kwan-Liu Ma. High-Quality Lighting and Efficient Pre-Integration for Volume Rendering. In Proc. of Joint EUROGRAPHICS - IEEE TCVG Symposium on Visualization (2004). 19. Joe Kniss, Gordon Kindlmann, Charles Hansen. Multidimensional Transfer Functions for Interactive Volume Rendering. IEEE Transactions on Visualization and Computer Graphics, Volume 8, Issue 3, pp ,
139 5 三 维 人 机 交 互 的 设 计 与 实 现 5 三 维 人 机 交 互 的 设 计 与 实 现 随 着 计 算 机 硬 件 和 软 件 的 飞 速 发 展, 传 统 的 二 维 用 户 接 口 (User Interfaces) 已 经 无 法 适 应 人 机 交 互 的 需 求, 这 一 现 象 突 出 的 反 应 在 虚 拟 现 实 技 术 中, 在 虚 拟 的 三 维 场 景 中 操 纵 三 维 物 体, 传 统 的 二 维 交 互 模 式 显 然 不 能 胜 任, 为 此, 人 们 开 始 将 更 多 的 注 意 力 转 移 到 三 维 交 互 技 术 上 来 5.1 背 景 介 绍 对 三 维 交 互 的 研 究 由 来 已 久, 从 设 备 角 度 讲, 这 方 面 的 研 究 主 要 有 两 个 方 向, 一 是 使 用 三 维 的 输 入 输 出 设 备 模 拟 三 维 交 互, 比 如 三 维 跟 踪 球 数 据 手 套 头 盔 显 示 器 等, 但 由 于 此 类 设 备 价 格 昂 贵 应 用 面 窄 且 操 作 复 杂 而 无 法 普 及 ; 二 是 用 传 统 的 二 维 输 入 输 出 设 备 ( 鼠 标 键 盘 普 通 显 示 器 等 ) 模 拟 三 维 交 互, 虽 然 在 很 多 的 情 况 下 难 以 得 到 十 分 精 确 的 控 制, 但 由 于 其 设 备 简 单 且 普 及 面 广 而 得 到 广 泛 应 用 在 这 方 面 比 较 突 出 的 是 Brookshire D. Conner 等 在 1992 年 首 先 提 出 的 3D Widgets[1],MITK 所 包 含 的 三 维 交 互 设 计 框 架 中 最 核 心 的 元 素 也 即 来 源 于 此 在 [1] 中, 作 者 给 出 了 3D Widgets 的 定 义, 即 一 种 封 装 了 三 维 几 何 形 状 及 行 为 的 实 体, 其 中 的 三 维 几 何 形 状 即 指 3D Widgets 本 身 的 外 观, 行 为 包 括 了 3D Widgets 对 三 维 场 景 中 其 他 物 体 的 控 制 和 对 其 他 物 体 信 息 的 显 示 此 后, 围 绕 3D Widgets 展 开 了 一 系 列 的 研 究, 如 [2] 提 出 了 一 套 称 为 导 轨 (racks) 的 3D Widgets 可 用 于 控 制 三 维 物 体 的 形 变 ;[3] 提 出 了 一 种 称 为 阴 影 (shadows) 的 3D Widgets, 可 以 清 楚 地 展 示 三 维 空 间 中 物 体 间 的 相 对 位 置 并 可 对 物 体 进 行 直 接 的 定 位 ;[4] 中 更 是 在 Widgets 中 增 加 了 时 间 的 维 度, 使 其 可 以 控 制 三 维 物 体 在 空 间 中 的 运 动 随 着 研 究 的 深 入,3D Widgets 也 越 来 越 显 示 出 其 在 三 维 交 互 中 的 重 要 价 值, 比 如 Joe Kniss 等 在 [5] 中 将 3D Widgets 用 于 交 互 式 体 绘 制 中 对 多 维 传 递 函 数 的 调 节, 极 大 地 降 低 了 手 工 调 节 传 递 函 数 的 难 度 ;[6] 和 [7] 分 别 将 3D Widgets 用 于 体 数 据 的 浏 览 和 医 学 数 据 虚 拟 现 实 的 应 用 同 时, 各 种 针 对 3D Widgets 的 开 发 包 也 在 不 断 涌 现, 如 [8][9] 等 尽 管 如 此, 从 总 体 上 来 说, 目 前 在 三 维 人 机 交 互 的 设 计 上 并 没 有 一 个 得 到 普 遍 公 认 的 框 架 或 标 准, 这 主 要 是 由 三 维 交 互 本 身 的 复 杂 性 所 决 定 的 而 从 应 用 132
140 5 三 维 人 机 交 互 的 设 计 与 实 现 的 角 度 来 讲, 三 维 人 机 交 互 在 可 视 化 领 域 具 有 非 常 重 要 的 价 值, 尤 其 在 医 学 影 像 可 视 化 中 三 维 交 互 对 于 更 方 便 准 确 地 使 用 可 视 化 的 结 果 辅 助 医 生 进 行 诊 断 和 手 术 模 拟 具 有 十 分 重 要 的 意 义 因 此 在 MITK 中 设 计 并 实 现 一 个 实 用 的 模 块 化 的 可 扩 展 的 三 维 交 互 框 架 是 十 分 必 要 的 5.2 以 3D Widgets 为 核 心 的 三 维 人 机 交 互 的 框 架 设 计 D Widgets 的 设 计 准 则 Scott S. Snibbe 等 在 [2] 中 提 出 了 3D Widgets 设 计 中 应 遵 循 的 一 些 基 本 原 则 : (1) 行 为 自 明 即 Widget 的 外 观 应 该 能 够 直 观 的 反 映 出 它 的 行 为 ; (2) 去 除 不 必 要 的 自 由 度 即 对 Widget 的 行 为 方 式 做 尽 可 能 多 的 限 定, 这 样 不 仅 能 极 大 地 降 低 实 现 难 度, 而 且 便 于 用 户 理 解 和 使 用 该 Widget 此 外, 对 于 不 同 应 用,Widget 的 设 计 要 求 也 是 不 同 的, 比 如 [2] 中 提 到 对 于 一 般 的 用 于 展 示 或 设 计 用 途 的 造 型 工 具,Widget 的 设 计 只 要 能 达 到 让 需 展 示 的 三 维 物 体 看 起 来 对 就 可 以 了 ; 而 在 机 械 或 工 业 制 造 领 域,Widget 必 须 能 够 精 确 地 调 节 零 件 的 参 数 值, 并 且 对 任 何 参 数 的 更 改 必 须 是 可 再 现 的 而 医 学 影 像 可 视 化 中 3D Widgets 的 设 计 要 求 更 接 近 于 后 者, 一 方 面, 让 受 控 于 Widget 的 三 维 物 体 正 确 地 显 示 是 基 本 要 求 ; 另 一 方 面, 更 重 要 的 是 Widget 所 反 映 或 调 节 的 三 维 物 体 的 物 理 参 量 必 须 达 到 足 够 的 精 度 以 3D Widgets 为 核 心 的 三 维 交 互 框 架 总 体 结 构 MITK 中 所 实 现 的 三 维 交 互 框 架 以 3D Widgets 为 核 心, 其 总 体 结 构 如 图 5-1 所 示 其 中, 组 成 这 个 框 架 的 所 有 模 块 均 继 承 自 同 一 个 根 Object View 用 于 控 制 整 个 三 维 场 景 的 最 终 显 示, 所 有 在 三 维 场 景 (View) 中 显 示 的 物 体 都 称 为 Model, 处 于 核 心 地 位 的 WidgetModel 从 概 念 上 来 说 也 是 属 于 Model 的 一 种, 因 为 它 本 身 也 提 供 了 可 显 示 的 外 观 而 除 了 WidgetModel, 在 三 维 场 景 中 还 存 在 另 外 一 种 Model, 即 观 察 对 象, 称 为 DataModel, 是 WidgetModel 的 操 纵 对 象 但 本 身 就 处 于 三 维 场 景 中 的 WidgetModel 无 法 自 行 触 发 对 DataModel 的 控 制, 在 它 和 View 之 间 需 要 一 个 沟 通 的 桥 梁, 这 个 桥 梁 就 是 Manipulator 模 块, 它 就 像 是 一 个 驱 动 器, 接 受 View 截 获 的 命 令, 然 后 驱 动 WidgetModel 对 DataModel 实 施 控 制 此 外, 还 133
141 5 三 维 人 机 交 互 的 设 计 与 实 现 有 一 个 Observer 模 块 对 Model 进 行 观 察, 随 时 反 映 Model 状 态 的 改 变, 比 如 用 于 测 量 的 WidgetModel 就 通 过 Observer 将 测 量 结 果 返 回 给 用 户 Object Observer Model Manipulator View DataModel WidgetModel 图 5-1 以 3D Widgets 为 核 心 的 三 维 交 互 框 架 结 构 以 3D Widgets 为 核 心 的 三 维 交 互 框 架 设 计 整 个 三 维 交 互 框 架 以 3D Widgets 为 核 心, 但 要 顺 利 完 成 整 个 交 互 过 程, 其 他 模 块 的 支 持 是 必 不 可 少 的 首 先, 是 widgets 所 操 纵 的 对 象 DataModel, 没 有 它,widgets 就 没 有 存 在 的 必 要 具 体 来 说,WidgetModel 和 DataModel 之 间 存 在 一 种 依 赖 关 系, 一 般 情 况 下 一 个 WidgetModel 总 是 与 唯 一 的 一 个 DataModel 相 关 联, 这 由 其 DataModel 成 员 m_sourcemodel 指 出, 而 一 个 DataModel 可 以 同 时 与 一 组 WidgetModel 产 生 关 系, 这 由 DataModel 中 的 一 个 WidgetModel 数 组 成 员 m_widgetmodels 维 护 当 然, 这 些 关 系 并 非 是 必 然 存 在 的, 比 如 一 个 WidgetModel 完 全 可 以 不 与 任 何 一 个 DataModel 关 联 其 次, 需 要 由 Manipulator 来 驱 动 WidgetModel 实 现 对 DataModel 的 操 纵, 为 此, 该 Manipulator 必 须 具 备 从 View 中 拣 选 出 当 前 鼠 标 所 指 WidgetModel 的 能 力, 并 能 够 将 控 制 权 移 交 给 该 WidgetModel, 而 WidgetModel 也 必 须 提 供 给 Manipulator 一 个 Select() 接 口 实 现 拣 选 操 作, 以 及 相 应 的 OnMouseDown() OnMouseUp() 和 OnMouseMove() 接 口 来 接 受 控 制 权 的 转 移 并 实 现 具 体 的 控 制 行 为 此 外,WidgetModel 还 需 提 供 Pick () 和 Release() 接 口 给 Manipulator 以 控 制 选 中 或 释 放 时 自 身 状 态 的 更 新 134
142 5 三 维 人 机 交 互 的 设 计 与 实 现 第 三, 需 要 一 个 开 放 的 Observer 提 供 给 用 户 以 各 种 合 适 的 方 式 显 示 WidgetModel 所 提 取 的 相 关 DataModel 的 一 些 几 何 或 物 理 参 量 Observer 是 一 个 高 度 抽 象 的 类, 它 只 提 供 一 个 Update() 接 口 而 没 有 任 何 具 体 内 容, 每 一 个 自 Object 继 承 的 类 都 具 有 添 加 多 个 Observer 的 能 力 (Observer 本 身 除 外 ),WidgetModel 当 然 也 不 例 外, 当 WidgetModel 参 数 更 新 之 后 即 调 用 所 有 与 之 关 联 的 Observer 的 Update() 接 口 通 知 Observer 更 新 所 要 显 示 的 数 据, 而 具 体 的 显 示 方 式 由 用 户 在 其 具 体 实 现 的 Update() 接 口 中 给 出 最 终, 由 View 提 供 给 DataModel 和 WidgetModel 展 示 的 舞 台, 显 示 最 后 的 结 果, 同 时,View 还 肩 负 着 与 操 作 系 统 打 交 道 的 任 务, 负 责 截 获 窗 口 的 鼠 标 和 键 盘 消 息, 根 据 不 同 的 消 息 组 合 调 用 Manipulator 相 应 的 接 口, 启 动 三 维 交 互 的 整 个 过 程 上 述 这 些 模 块 及 它 们 之 间 的 相 互 作 用 构 成 了 整 个 三 维 人 机 交 互 的 框 架, 如 图 5-2 所 示 Object m_observers Observer +Update() Model +Render() +Select() m_models View +OnDraw() +OnMouseDown() +OnMouseMove() +OnMouseUp() m_mani DataModel +Render() m_widgetmodels WidgetModel +Render() +Select() +Pick() +Release() +OnMouseDown() +OnMouseMove() +OnMouseUp() Manipulator +OnMouseDown() +OnMouseMove() +OnMouseUp() #_pick() m_pickedwidgets.owner m_sourcemodel 图 5-2 三 维 人 机 交 互 框 架 中 各 模 块 之 间 的 关 系 135
143 5 三 维 人 机 交 互 的 设 计 与 实 现 从 图 中 可 以 清 晰 地 描 绘 出 整 个 三 维 交 互 的 过 程 : (1) View 显 示 三 维 场 景 中 的 各 个 Model, 接 收 鼠 标 和 键 盘 消 息, 根 据 不 同 消 息 组 合 驱 动 Manipulator; (2) Manipulator 在 合 适 的 时 机 ( 比 如 OnMouseDown() 被 触 发 时 ) 根 据 当 前 鼠 标 位 置 在 场 景 中 进 行 拣 选 操 作, 若 有 WidgetModel 被 选 中, 则 将 控 制 权 移 交 到 选 中 的 WidgetModel, 否 则 进 行 一 般 的 处 理 ; (3) 掌 握 了 控 制 权 的 WidgetModel 将 实 现 其 预 先 定 义 的 行 为, 其 行 为 的 影 响 最 终 反 映 在 View 及 Observer 中 整 个 三 维 交 互 的 过 程 就 是 (1)(2)(3) 不 断 重 复 的 过 程 5.3 以 3D Widgets 为 核 心 的 三 维 人 机 交 互 的 实 现 在 上 述 的 设 计 框 架 中, 与 交 互 功 能 的 实 现 密 切 相 关 的 核 心 部 分 是 Manipulator 和 继 承 自 WidgetModel 的 具 体 的 Widgets, 下 面 对 交 互 框 架 具 体 实 现 的 介 绍 将 主 要 围 绕 这 两 方 面 展 开 而 View 和 Obserser 在 整 个 交 互 过 程 中 并 不 起 决 定 性 的 作 用, 并 且 设 计 相 对 简 单, 这 里 就 不 再 赘 述 Manipulator 的 实 现 Manipulator 的 关 键 是 拣 选 功 能, 由 于 整 个 交 互 框 架 采 用 OpenGL 绘 制 Model, 因 此 很 自 然 的 拣 选 功 能 最 终 也 将 基 于 OpenGL 提 供 的 选 择 和 反 馈 机 制 交 互 操 作 绝 大 部 分 情 况 下 采 用 鼠 标 作 为 输 入 设 备, 对 各 种 鼠 标 事 件, Manipulator 将 做 如 下 处 理 : 当 OnMouseDown() 被 触 发 时 ( 鼠 标 按 键 按 下 ): 首 先 判 断 当 前 是 否 有 WidgetModel 处 于 选 中 状 态, 若 有 则 调 用 其 Release() 接 口 释 放 它 ; 然 后 在 OpenGL 选 择 模 式 下 绘 制 整 个 场 景, 即 遍 历 场 景 中 的 每 个 Model, 依 次 调 用 其 Select() 接 口,Model 基 类 中 缺 省 的 Select() 什 么 也 不 做, 只 有 可 被 选 择 的 Model 才 实 现 它 自 己 的 Select(), 在 这 里 就 是 直 接 调 用 Render(); 如 果 选 择 模 式 的 绘 制 过 程 返 回 了 某 个 WidgetModel, 则 调 用 其 Pick() 接 口 选 中 它, 然 后 调 用 其 OnMouseDown() 接 口 转 移 控 制, 否 则 按 常 规 处 理 当 OnMouseMove() 被 触 发 时 ( 移 动 鼠 标 ): 若 当 前 有 WidgetModel 处 于 选 136
144 5 三 维 人 机 交 互 的 设 计 与 实 现 中 状 态 则 调 用 其 OnMouseMove() 接 口 转 移 控 制, 否 则 按 常 规 处 理 当 OnMouseUp() 被 触 发 时 ( 鼠 标 按 键 松 开 ): 若 当 前 有 WidgetModel 处 于 选 中 状 态 则 调 用 其 OnMouseUp() 接 口 转 移 控 制, 否 则 按 常 规 处 理 上 述 实 现 是 将 鼠 标 按 键 按 下 的 事 件 作 为 触 发 拣 选 操 作 的 时 机, 这 并 不 是 唯 一 的 实 现 方 案, 这 个 三 维 交 互 框 架 为 用 户 提 供 了 最 大 程 度 的 自 由, 用 户 完 全 可 以 采 用 自 己 的 方 式 来 实 现 三 维 交 互 的 Manipulator, 只 要 其 具 体 实 现 符 合 整 个 三 维 交 互 框 架 的 接 口 规 范 即 可, 具 体 的 说 就 是 从 mitkmanipulator 派 生 出 自 己 的 类, 在 里 面 实 现 几 个 需 要 实 现 的 虚 函 数 就 可 以 了, 主 要 是 OnMouseDown() OnMouseUp() 和 OnMouseMove() 三 个 函 数 至 于 拣 选 操 作, 你 可 以 按 照 自 己 的 想 法 来 实 现 它,MITK 对 此 并 未 作 出 任 何 限 定 但 是, 如 果 你 觉 得 这 样 很 麻 烦 ( 编 程 实 现 拣 选 操 作 确 实 也 比 较 复 杂 ) 或 者 对 OpenGL 的 选 择 和 反 馈 机 制 并 不 熟 悉, 没 有 关 系,MITK 已 经 为 你 提 供 了 一 个 实 现 了 拣 选 操 作 的 基 类 : mitkpickmanipulator, 它 提 供 了 一 个 保 护 成 员 _pick() 来 实 现 拣 选 操 作, 其 函 数 原 型 为 : bool _pick(int xpos, int ypos); 该 函 数 接 受 当 前 鼠 标 在 View 内 的 坐 标 为 输 入 参 数, 返 回 一 个 bool 值 以 示 有 无 WidgetModel 被 选 中 若 返 回 true, 则 选 中 物 体 的 相 关 信 息 可 以 在 如 下 的 一 个 结 构 中 得 到 : typedef struct _widget_names mitkwidgetmodel *owner; GLuint name1; GLuint name2; GLuint name3; WidgetNames; 该 结 构 的 定 义 在 mitkwidgetmodel.h 中 其 中 owner 指 向 选 中 的 WidgetModel, 这 是 我 们 在 编 写 自 己 的 三 维 交 互 Manipulator 时 所 需 要 的 ; 下 面 name1 到 name3 标 明 了 被 选 中 的 是 该 WidgetModel 的 哪 一 个 组 成 部 件, 这 是 被 选 中 的 WidgetModel 在 实 施 具 体 的 控 制 时 所 需 要 的 信 息, 目 前 只 用 到 了 name3, name1 和 name2 作 为 将 来 扩 展 功 能 用 在 mitkpickmanipulator 有 一 个 此 结 构 的 成 员 变 量 :m_pickedwidgets, 若 有 WidgetModel 被 选 中, 被 选 中 的 WidgetModel 137
145 5 三 维 人 机 交 互 的 设 计 与 实 现 的 信 息 就 被 填 入 到 此 结 构 中, 我 们 只 需 将 其 作 为 参 数, 通 过 调 用 m_pickedwidgets.owner->pick(m_pickedwidgets) 就 可 以 将 必 要 的 信 息 传 给 被 选 中 的 WidgetModel 此 后, 只 要 通 过 owner 这 个 指 针 调 用 WidgetModel 的 OnMouseDown() OnMouseUp() 及 OnMouseMove(), 即 可 将 控 制 权 转 移 到 具 体 的 WidgetModel MITK 中 已 经 提 供 了 一 个 标 准 的 带 三 维 交 互 功 能 的 Manipulator: mitkwidgetsviewmanipulator, 该 类 即 继 承 自 mitkpickmanipulator, 下 面 是 其 三 个 主 要 虚 函 数 的 实 现, 可 以 体 会 一 下 如 何 使 用 mitkpickmanipulator 提 供 的 拣 选 功 能 : void mitkwidgetsviewmanipulator::onmousedown(int mousebutton, bool ctrldown, bool shiftdown, int xpos, int ypos) if (m_view == NULL) return; switch(mousebutton) case MITK_LEFTBUTTON: m_buttondown[mitk_leftbutton] = true; m_oldx[mitk_leftbutton] = xpos; m_oldy[mitk_leftbutton] = ypos; if (this->_pick(xpos, ypos)) // 有 Widgets 被 选 中 // 通 知 WidgetModel 被 选 中 并 传 递 必 要 信 息 m_pickedwidgets.owner->pick(m_pickedwidgets); // 将 控 制 权 转 移 到 选 中 的 WidgetModel m_pickedwidgets.owner->onmousedown(mousebutton, ctrldown, shiftdown, xpos, ypos); m_view->update(); break; case MITK_MIDDLEBUTTON: 138
146 5 三 维 人 机 交 互 的 设 计 与 实 现 m_buttondown[mitk_middlebutton] = true; m_oldx[mitk_middlebutton] = xpos; m_oldy[mitk_middlebutton] = ypos; break; case MITK_RIGHTBUTTON: m_buttondown[mitk_rightbutton] = true; m_oldx[mitk_rightbutton] = xpos; m_oldy[mitk_rightbutton] = ypos; break; // void mitkwidgetsviewmanipulator::onmouseup(int mousebutton, bool ctrldown, bool shiftdown, int xpos, int ypos) if (m_view == NULL) return; switch(mousebutton) case MITK_LEFTBUTTON: m_buttondown[mitk_leftbutton] = false; break; case MITK_MIDDLEBUTTON: m_buttondown[mitk_middlebutton] = false; break; case MITK_RIGHTBUTTON: m_buttondown[mitk_rightbutton] = false; break; // 如 果 当 前 有 选 中 的 WidgetModel 则 转 移 控 制 权 if (m_pickedwidgets.owner) m_pickedwidgets.owner->onmouseup(mousebutton, ctrldown, shiftdown, xpos, ypos); 139
147 5 三 维 人 机 交 互 的 设 计 与 实 现 // void mitkwidgetsviewmanipulator::onmousemove(bool ctrldown, bool shiftdown, int xpos, int ypos) if(m_view == NULL) return; if(m_buttondown[mitk_leftbutton] == true) if (m_pickedwidgets.owner!= NULL) // 有 选 中 则 转 移 控 制 m_pickedwidgets.owner->onmousemove(ctrldown, shiftdown, xpos, ypos, xpos - m_oldx[mitk_leftbutton], m_oldy[mitk_leftbutton] - ypos); else // 否 则 按 常 规 处 理 m_view->rotate(m_oldy[mitk_leftbutton] - ypos, xpos - m_oldx[mitk_leftbutton], 0.0f); m_oldx[mitk_leftbutton] = xpos; m_oldy[mitk_leftbutton] = ypos; else if(m_buttondown[mitk_middlebutton] == true) m_view->translate(xpos - m_oldx[mitk_middlebutton], m_oldy[mitk_middlebutton] - ypos, 0.0f); m_oldx[mitk_middlebutton] = xpos; m_oldy[mitk_middlebutton] = ypos; else if(m_buttondown[mitk_rightbutton] == true) m_view->scale(m_oldy[mitk_rightbutton] - ypos); m_oldy[mitk_rightbutton] = ypos; else return; m_view->update(); //
148 5 三 维 人 机 交 互 的 设 计 与 实 现 实 现 具 体 的 WidgetModel 在 整 个 三 维 交 互 框 架 中,WidgetModel 部 分 的 继 承 结 构 如 图 5-3 所 示 考 虑 到 整 个 框 架 的 弹 性, 有 必 要 使 其 向 下 兼 容 二 维 的 医 学 图 像, 而 二 维 的 View 和 三 维 的 View 存 在 一 定 差 异, 如 果 让 同 一 个 WidgetModel 同 时 支 持 二 维 和 三 维 的 View, 单 个 WidgetModel 的 实 现 难 度 和 复 杂 度 将 大 大 增 加, 因 此 从 框 架 的 弹 性 和 扩 展 的 难 易 程 度 考 虑, 将 WidgetModel 分 为 二 维 和 三 维 两 类 并 且 这 样 做 还 有 利 于 从 WidgetModel 扩 展 出 更 高 维 的 Widget, 很 容 易 想 到 的 是 将 时 间 的 维 度 加 进 去, 实 现 动 态 的 Widget[4] 当 然, 下 面 的 重 点 是 讨 论 跟 三 维 交 互 直 接 相 关 的 三 维 WidgetModel 的 实 现 WidgetModel WidgetModel2D WidgetModel3D LineWidgetModel2D AngleWidgetModel2D LineWidgetModel3D AngleWidgetModel3D PseudocolorWidgetModel ClippingPlaneWidgetModel 图 5-3 WidgetModel 的 继 承 结 构 所 有 Widget 的 基 类 是 mitkwidgetmodel, 这 个 类 规 定 了 一 些 如 下 一 些 统 一 的 接 口 : virtual void Pick(const WidgetNames &names) = 0; virtual void Release() = 0; virtual void Select(mitkView *view); virtual void OnMouseDown(int mousebutton, bool ctrldown, bool shiftdown, int xpos, int ypos) = 0; virtual void OnMouseUp(int mousebutton, bool ctrldown, bool shiftdown, int xpos, int ypos) = 0; virtual void OnMouseMove(bool ctrldown, bool shiftdown, int xpos, int ypos, int deltax, int deltay) = 0; virtual void SetSourceModel(mitkDataModel *model); 141
149 5 三 维 人 机 交 互 的 设 计 与 实 现 mitkdatamodel* GetSourceModel() return m_sourcemodel; virtual void SetView(mitkView *view) = 0; virtual bool IsOpaque() return m_isopaque; virtual bool IsActive() return m_isactive; void UpdateObservers() this->_updateobservers(); 其 中 Pick() 的 作 用 不 用 多 说 了,Release() 的 作 用 与 Pick() 相 对, 这 一 对 操 作 在 子 类 中 必 须 实 现, 其 实 现 的 范 例 如 下 : void mitklinewidgetmodel3d::pick(const WidgetNames &names) // 得 到 选 中 部 件 的 id // 一 个 Widget 通 常 由 几 个 基 本 的 部 件 构 成, 每 个 部 件 对 应 一 个 无 符 号 整 型 id, // 其 含 意 由 具 体 的 WidgetModel 定 义 m_pickedobj = names.name3; // 一 般 在 选 中 后 将 其 设 为 不 透 明 显 示 // 该 参 数 实 际 上 与 你 的 WidgetModel 实 际 绘 制 是 不 是 透 明 没 有 必 然 的 联 系, // 它 的 作 用 是 给 View 提 供 一 个 绘 制 顺 序 的 参 考, 在 View 绘 制 它 所 包 含 的 所 有 Model // 时 将 根 据 IsOpaque() 的 返 回 值 先 绘 制 所 有 不 透 明 的 物 体, 再 绘 制 所 有 透 明 物 体, // 这 样, 整 个 场 景 看 上 去 才 比 较 正 常, 如 果 你 需 要 确 保 自 己 实 现 的 WidgetModel 的 // 绘 制 顺 序 比 较 靠 后, 那 完 全 可 以 将 其 设 为 false, 而 不 必 管 是 不 是 真 的 要 将 这 个 // WidgetModel 绘 制 成 透 明 的 m_isopaque = true; // 标 志 本 WidgetModel 进 入 活 动 状 态 m_isactive = true; // 通 知 Observer 状 态 已 更 新 this->_updateobservers(); // void mitklinewidgetmodel3d::release() // 将 选 中 部 件 置 为 unknown, 表 示 无 部 件 被 选 中 m_pickedobj = unknown; // 可 以 在 释 放 后 设 为 透 明 显 示 m_isopaque = false; // 标 志 本 WidgetModel 离 开 活 动 状 态 142
150 5 三 维 人 机 交 互 的 设 计 与 实 现 m_isactive = false; // 通 知 Observer 状 态 已 更 新 this->_updateobservers(); // 以 上 是 在 Pick() 和 Release() 里 面 需 要 完 成 的 一 些 基 本 工 作, 你 也 可 以 根 据 自 己 的 需 要 在 里 面 更 新 一 些 自 定 义 的 状 态 变 量 其 中 涉 及 的 m_pickedobj 是 以 无 符 号 整 型 数 表 示 的 当 前 选 中 的 组 成 这 个 Widget 的 某 一 个 部 件 的 id, 各 部 件 的 id 一 般 是 在 具 体 的 WidgetModel 中 定 义 的 比 如 在 MITK 中 的 mitklinewidgetmodel3d 就 定 义 了 这 样 一 组 id: enum unknown, arrow0, arrow1, line ; 可 以 看 出 该 WidgetModel 是 由 两 个 以 箭 头 表 示 的 端 点 和 一 条 线 段 组 成 的, 这 三 个 组 件 可 以 分 别 被 选 中, 在 WidgetModel 中 根 据 选 中 的 部 件 做 相 应 的 操 作 Select() 功 能 是 检 测 本 WidgetModel 是 否 被 选 中, 一 般 是 在 选 择 操 作 中 遍 历 地 调 用 所 有 WidgetModel 的 Select() 接 口, 以 判 断 哪 一 个 被 选 中 了, 这 个 在 基 类 中 已 经 有 缺 省 的 实 现, 如 果 你 不 想 自 己 实 现 选 择 功 能 就 不 需 要 动 这 个 函 数 OnMouseDown() OnMouseUp() 和 OnMouseMove() 都 是 在 子 类 中 必 须 实 现 的,WidgetModel 所 有 的 鼠 标 交 互 功 能 尽 在 这 三 个 函 数 中 体 现 ( 同 时 还 可 以 搭 配 键 盘 上 Shift Ctrl 键 作 为 组 合 键 使 用 ) 在 实 现 一 个 WidgetModel 时, 根 据 具 体 的 鼠 标 和 键 盘 操 作, 在 这 三 个 函 数 调 整 WidgetModel 本 身 及 其 所 控 制 的 Source Model 的 显 示 参 数 以 达 到 交 互 功 能 以 实 现 测 量 三 维 Model 内 任 意 两 点 间 距 离 的 Widget 为 例, 我 们 将 首 先 在 OnMouseDown() 触 发 时 记 录 当 前 线 段 两 个 端 点 的 坐 标, 然 后 鼠 标 移 动 时 在 OnMouseMove() 中 根 据 当 时 的 鼠 标 位 置 即 时 更 新 鼠 标 所 选 中 的 端 点 部 件 在 三 维 空 间 中 的 坐 标, 最 终 表 现 为 该 端 点 跟 随 鼠 标 指 针 移 动, 同 时 在 GetLineLength() 函 数 中 根 据 端 点 坐 标 以 及 该 Widget 控 制 对 象 (Source Model) 的 一 些 几 何 信 息 计 算 出 当 前 线 段 的 实 际 长 度 返 回 给 用 户 143
151 5 三 维 人 机 交 互 的 设 计 与 实 现 余 下 的 几 个 函 数 在 基 类 中 均 已 实 现, 一 般 情 况 下 子 类 也 不 需 要 再 动 它 们 了 最 后 还 有 一 个 很 重 要 的 函 数 Render(), 这 是 继 承 自 mitkmodel 的 函 数, WidgetModel 所 有 的 绘 制 工 作 均 在 这 里 面 完 成 以 下 就 是 Render() 函 数 在 OpenGL 绘 制 框 架 下 的 一 个 范 例 : int MyWidgetModel::Render(mitkView *view) // 开 启 深 度 检 测 和 光 照 glenable(gl_depth_test); glenable(gl_lighting); // 关 闭 所 有 裁 剪 平 面 ( 如 果 你 不 想 让 你 的 WidgetModel 也 被 裁 剪 的 话 ) this->_disableclippingplanes(); // 采 用 缺 省 的 材 质 设 定 ( 当 然 可 以 替 换 成 自 定 义 的 设 定 ) this->_defaultmaterial(); // MODELVIEW 矩 阵 压 栈, 以 免 对 其 他 Model 的 绘 制 产 生 影 响 glmatrixmode(gl_modelview); glpushmatrix(); // 一 般 是 直 接 从 SourceModel 得 到 模 型 变 换 矩 阵, // 以 使 WidgetModel 能 跟 随 SourceModel 做 全 局 的 几 何 变 换 if (m_sourcemodel!= NULL) glmultmatrixf(*m_sourcemodel->getmodelmatrix()); // 每 个 部 件 实 际 的 绘 制 // 注 意 : 如 果 你 想 采 用 MITK 提 供 的 选 择 功 能 的 话, // 在 绘 制 每 个 部 件 之 前, 调 用 glloadname( 部 件 id) 来 登 记 所 绘 部 件 的 id, // 选 择 程 序 将 把 选 中 部 件 的 id 返 回, 而 WidgetModel 将 根 据 返 回 的 id 判 断 是 // 哪 个 部 件 被 选 中 glloadname(id1);... glloadname(id2);... glloadname(id3);
152 5 三 维 人 机 交 互 的 设 计 与 实 现 // 对 应 于 上 面 的 glpushmatrix() glpopmatrix(); // 返 回 1 表 示 绘 制 成 功 return 1; 值 得 一 提 的 是 MITK 为 三 维 模 型 的 绘 制 环 境 维 护 了 三 个 变 换 矩 阵 及 其 逆 矩 阵, 它 们 是 模 型 变 换 矩 阵 及 其 逆 矩 阵 视 图 变 换 矩 阵 及 其 逆 矩 阵 和 投 影 变 换 矩 阵 及 其 逆 矩 阵 模 型 变 换 矩 阵 及 其 逆 矩 阵 可 以 通 过 mitkmodel 类 提 供 的 GetModelMatrix() 及 GetInverseOfModelMatrix() 得 到, 视 图 变 换 矩 阵 和 投 影 变 换 矩 阵 及 其 逆 矩 阵 可 以 通 过 mitkcamera 类 提 供 的 GetViewMatrix() GetInverseOfViewMatrix() GetProjectionMatrix() 及 GetInverseOfProjectionMatrix() 得 到, 而 指 向 mitkcamera 的 指 针 可 以 通 过 mitkview 的 GetCamera() 得 到 前 面 两 个 矩 阵 的 乘 积 就 相 当 于 OpenGL 中 的 MODELVIEW MATRIX, 最 后 一 个 矩 阵 和 OpenGL 中 的 投 影 矩 阵 意 义 相 同 有 了 这 三 个 矩 阵 和 它 们 的 逆 矩 阵, 不 仅 可 以 很 方 便 地 得 到 屏 幕 坐 标 和 模 型 空 间 三 维 坐 标 之 间 的 对 应 关 系 ( 这 一 点 在 的 第 一 条 中 有 更 具 体 的 体 现 ), 而 且 在 绘 制 时 不 需 要 再 繁 琐 地 调 用 gltranslate*() glrotate*() 等 函 数 来 对 模 型 进 行 几 何 变 换, 而 只 要 将 所 需 变 换 的 矩 阵 作 为 参 数 调 用 一 次 glmultmatrixf() 就 可 以 了, 正 如 上 面 给 出 的 例 子 所 示 另 外, 在 mitkwidgetmodel2d 及 mitkwidgetmodel3d 中 均 已 实 现 了 一 些 标 准 部 件 的 绘 制, 比 如 mitkwidgetmodel3d 中 的 圆 锥 (_drawcone()) 圆 柱 (_drawcylinder()) 球 (_drawsphere()) 等, 如 果 直 接 用 这 些 部 件 构 成 Widget, 在 绘 制 每 一 个 部 件 时 就 不 需 要 再 显 式 地 调 用 glloadname(id) 了, 而 只 要 把 id 当 作 参 数 传 给 标 准 部 件 的 绘 制 函 数 就 可 以 了 由 上 述 介 绍 可 见, 与 Manipulator 相 比, 实 现 具 体 的 WidgetModel 要 显 得 更 自 由 一 些, 在 遵 循 三 维 交 互 框 架 的 接 口 规 范 的 前 提 下, 其 行 为 的 定 义 完 全 是 开 放 的 实 现 具 体 的 WidgetModel, 其 关 键 在 于 在 现 有 的 框 架 下, 如 何 使 WidgetModel 的 行 为 效 果 与 其 预 定 义 的 一 致, 下 面 通 过 MITK 中 几 个 已 经 实 现 的 3D Widgets 来 说 明 这 一 点 145
153 5 三 维 人 机 交 互 的 设 计 与 实 现 (1) 测 量 距 离 的 Widget(mitkLineWidgetModel3D) 该 Widget 的 功 能 是 取 得 模 型 空 间 ( 坐 标 未 经 模 型 视 图 及 投 影 变 换 ) 中 任 意 两 点 之 间 的 距 离, 外 观 即 为 两 端 带 有 箭 头 的 线 段, 箭 头 是 可 用 鼠 标 控 制 的 端 点 该 Widget 在 对 重 建 的 三 维 物 体 进 行 测 量 时 经 常 用 到 为 达 到 测 量 距 离 的 目 的, 必 须 能 在 模 型 空 间 任 意 移 动 线 段 的 两 个 端 点 以 及 整 条 线 段, 而 通 过 鼠 标 所 获 得 的 屏 幕 坐 标 是 二 维 的, 不 可 能 直 接 用 它 去 控 制 线 段 端 点 在 三 维 空 间 中 的 位 置, 也 不 可 能 光 靠 鼠 标 实 现 具 有 三 个 自 由 度 的 端 点 ( 仅 看 作 一 个 几 何 意 义 上 的 点 的 话 ) 在 空 间 的 自 由 移 动, 这 时 通 常 需 要 其 他 设 备 ( 如 键 盘 ) 的 辅 助 来 完 成 端 点 的 自 由 移 动, 但 这 样 做 无 疑 会 使 操 作 复 杂 化 权 衡 利 弊, 我 们 采 用 另 一 种 方 式 来 达 到 目 的, 即 限 制 在 鼠 标 控 制 下 端 点 移 动 的 自 由 度, 合 理 的 做 法 是 当 鼠 标 选 中 某 个 端 点 时, 让 它 只 能 在 其 当 前 所 在 的 与 屏 幕 平 行 的 平 面 上 移 动, 这 样 只 用 两 个 坐 标 就 可 以 确 定 端 点 的 当 前 位 置 ( 第 三 个 坐 标 固 定 ), 只 用 鼠 标 就 能 进 行 操 作, 而 且 在 视 觉 反 馈 上 也 是 可 以 接 受 的, 然 后 辅 助 以 整 个 模 型 空 间 的 旋 转 ( 改 变 第 三 个 坐 标 ), 就 可 以 达 到 端 点 自 由 移 动 的 目 的 要 实 现 如 上 描 述 的 鼠 标 控 制 下 端 点 的 移 动, 关 键 是 如 何 求 得 与 当 前 鼠 标 在 屏 幕 上 的 位 置 相 对 应 的 原 模 型 空 间 中 的 三 维 点 坐 标, 为 此 Widget 必 须 知 道 以 下 一 些 信 息 : 选 中 端 点 未 移 动 前 在 模 型 空 间 中 的 坐 标 (x objold,y objold,z objold ); 模 型 变 换 矩 阵 M 视 图 变 换 矩 阵 V 和 投 影 变 换 矩 阵 P; 当 前 鼠 标 在 屏 幕 上 的 位 置 (x snew,y snew ); 当 前 视 区 的 位 置 和 大 小 (o x,o y,w,h) 其 中 M V 和 P 均 为 4 4 矩 阵, 在 OpenGL 中 M 和 V 合 并 为 一 个 矩 阵, 但 在 我 们 的 三 维 交 互 框 架 中 可 以 通 过 SourceModel( 或 WidgetModel 本 身 ) 和 View 分 别 得 到 这 两 个 矩 阵 根 据 上 述 信 息 即 可 推 算 出 移 动 后 的 端 点 在 模 型 空 间 中 的 坐 标 (x objnew,y objnew, z objnew ): 首 先, 由 146
154 5 三 维 人 机 交 互 的 设 计 与 实 现 x y z w sold sold sold x = y PVM z 1.0 objold objold objold. (5-1) 可 以 得 到 原 坐 标 经 变 换 后 在 屏 幕 坐 标 空 间 中 的 z 坐 标 z sold, 要 限 制 端 点 只 能 在 与 屏 幕 平 行 的 平 面 移 动, 只 需 保 持 屏 幕 空 间 中 z 坐 标 不 变 即 可, 因 此 只 需 将 x sold 和 y sold 分 别 用 x snew 和 y snew 替 换 然 后 反 推 回 去 就 可 以 了, 但 有 一 点 需 要 注 意, 通 过 上 式 计 算 所 得 到 的 屏 幕 坐 标 并 非 真 正 意 义 上 的 屏 幕 坐 标, 这 些 坐 标 的 值 均 被 归 一 化 到 [-1.0,1.0), 根 据 当 前 视 区 的 位 置 和 大 小 可 以 将 其 化 到 正 常 的 屏 幕 坐 标, 但 是 这 一 步 计 算 实 际 上 是 没 有 必 要 的, 只 需 在 反 推 前 将 新 的 屏 幕 坐 标 根 据 当 前 视 区 的 位 置 和 大 小 归 一 化 到 [-1.0,1.0) 就 可 以 了 (x objnew,y objnew,z objnew ) 的 计 算 如 下 所 示 : x y z w objnew objnew objnew = M 1 V ( xsnew ox ) ( y w ) 1 snew oy P 1.0 h zsold 1.0 (5-2) 上 述 M 及 其 逆 可 通 过 SourceModel( 或 WidgetModel 本 身 ) 直 接 得 到,V 和 P 及 其 逆 可 通 过 View 中 的 Camera 直 接 得 到, 当 然 也 可 以 用 OpenGL 的 API 函 数 计 算 但 采 用 前 者 效 率 更 高, 并 且 有 利 于 提 高 整 个 模 块 与 底 层 平 台 的 无 关 性 对 于 整 条 线 段 的 移 动, 两 个 端 点 的 坐 标 要 同 时 更 新, 而 所 有 计 算 基 于 偏 移 量 进 行, 最 后 将 原 坐 标 加 上 偏 移 量 即 为 新 坐 标 当 mitklinewidgetmodel3d 的 OnMouseMove() 被 触 发 时, 通 过 上 述 计 算 即 可 即 时 更 新 端 点 坐 标 采 用 上 述 方 法 可 以 保 证 端 点 紧 随 鼠 标 指 针 移 动, 从 而 达 到 精 确 控 制 端 点 位 置 的 目 的 同 时,Observer 可 以 通 过 GetLineLength() 接 口 得 到 当 前 线 段 的 长 度 由 于 坐 标 均 为 原 始 模 型 空 间 中 的 坐 标, 只 要 给 予 足 够 准 确 的 原 始 数 据 的 信 147
155 5 三 维 人 机 交 互 的 设 计 与 实 现 息, 该 Widget 便 能 根 据 坐 标 以 及 各 坐 标 轴 方 向 上 的 单 位 长 度 ( 由 SourceModel 提 供 ) 给 出 在 三 维 重 建 的 模 型 空 间 中 对 应 于 原 始 物 体 的 比 较 准 确 的 测 量 长 度, 保 证 了 测 量 的 精 度 (2) 测 量 角 度 的 Widget(mitkAngleWidgetModel3D) 该 Widget 的 功 能 是 取 得 模 型 空 间 中 任 意 三 点 组 成 的 角 的 角 度 值, 其 外 观 即 为 由 两 条 线 段 和 三 个 控 制 点 ( 两 个 箭 头 和 一 个 球 状 的 端 点 ) 构 成 的 一 个 张 开 的 角 采 用 与 mitklinewidgetmodel3d 相 同 的 方 式 可 以 自 由 地 控 制 三 个 端 点 的 位 置 从 而 准 确 地 作 出 所 需 的 角, 同 时 Observer 可 以 通 过 GetAngleInDegree() 或 GetAngleInRadian() 得 到 以 角 度 或 弧 度 表 示 的 角 度 值 (3) 裁 剪 平 面 Widget(mitkClippingPlaneWidgetModel) 该 Widget 的 功 能 是 对 当 前 场 景 中 显 示 的 三 维 物 体 在 任 意 位 置 和 任 意 方 向 上 进 行 裁 剪, 其 外 观 包 括 一 个 由 4 个 圆 柱 体 和 4 个 球 体 围 成 的 边 框 和 边 框 内 一 个 半 透 明 的 平 面 该 Widget 对 于 观 察 三 维 物 体 的 内 部 构 造 比 较 有 用 圆 柱 体 和 球 体 都 是 控 制 点, 四 个 球 体 分 别 实 现 四 种 对 裁 剪 平 面 的 控 制, 包 括 : 绕 中 心 的 任 意 旋 转, 外 框 相 对 中 心 的 的 缩 放, 垂 直 于 平 面 的 平 移 以 及 沿 平 面 的 平 移, 圆 柱 体 也 是 实 现 沿 平 面 的 平 移 其 中 缩 放 比 较 简 单, 而 限 制 裁 剪 平 面 的 平 移 路 径 ( 垂 直 于 裁 剪 平 面 或 平 行 于 裁 剪 平 面 ) 则 要 费 一 番 功 夫, 这 里 采 用 的 方 式 是 先 反 推 出 鼠 标 在 屏 幕 上 的 平 移 向 量 所 对 应 的 在 原 模 型 空 间 中 的 平 移 向 量, 计 算 方 法 基 本 上 与 mitklinewidgetmodel 计 算 端 点 坐 标 的 方 法 相 同, 然 后 将 平 移 向 量 在 移 动 路 径 方 向 ( 裁 剪 平 面 的 法 线 方 向 或 裁 剪 平 面 本 身 ) 上 进 行 投 影, 所 得 结 果 即 为 最 终 的 平 移 向 量, 将 裁 剪 平 面 的 中 心 坐 标 加 上 这 个 平 移 向 量 即 可 此 外, 为 保 证 旋 转 基 本 上 与 鼠 标 的 移 动 保 持 一 致, 这 里 采 用 了 一 个 虚 拟 跟 踪 球 来 跟 踪 和 控 制 裁 剪 平 面 的 旋 转 [10] 对 物 体 进 行 裁 剪 的 功 能 是 通 过 激 活 OpenGL 中 的 附 加 裁 剪 平 面 实 现 的 激 活 后 将 该 Widget 的 平 面 法 线 方 向 和 中 心 点 坐 标 作 为 OpenGL 中 裁 剪 平 面 的 参 数, 就 可 以 达 到 裁 剪 的 目 的 148
156 5 三 维 人 机 交 互 的 设 计 与 实 现 5.4 三 维 交 互 的 应 用 实 例 mitklinewidgetmodel3d 的 应 用 实 例 图 5-4 给 出 了 一 组 使 用 LineWidgetModel3D 对 用 等 值 面 提 取 算 法 进 行 表 面 重 建 的 结 果 进 行 测 量 的 实 例, 原 始 数 据 是 部 分 头 部 的 CT 扫 描 切 片 图 像, 其 中 Observer 采 用 类 似 Tool Tip 的 方 式 实 时 返 回 测 量 的 结 果 图 5-4 mitklinewidgetmodel3d 的 使 用 实 例 mitkanglewidgetmodel3d 的 应 用 实 例 图 5-5 给 出 了 一 组 使 用 AngleWidgetModel3D 对 用 等 值 面 提 取 算 法 进 行 表 面 重 建 的 结 果 进 行 测 量 的 实 例, 原 始 数 据 与 4.1 中 的 相 同,Observer 也 使 用 相 同 的 方 式 返 回 结 果 149
157 5 三 维 人 机 交 互 的 设 计 与 实 现 图 5-5 mitkanglewidgetmodel3d 的 使 用 实 例 mitkclippingplanewidget 的 应 用 实 例 图 5-6 给 出 了 一 组 使 用 ClippingPlaneWidget 对 用 等 值 面 提 取 算 法 进 行 表 面 重 建 的 结 果 进 行 裁 剪 的 实 例, 原 始 数 据 与 4.1 中 所 用 的 相 同 裁 剪 平 面 采 用 半 透 明 显 示, 这 样 可 以 使 物 体 之 间 的 前 后 位 置 关 系 显 得 更 清 楚 一 些, 当 需 要 更 清 楚 地 观 察 被 裁 剪 物 体 的 内 部 时 可 以 将 其 透 明 度 调 至 最 大 ( 即 完 全 透 明 ) 图 5-6 mitkclippingplanewidget 的 使 用 实 例 150
158 5 三 维 人 机 交 互 的 设 计 与 实 现 5.5 小 结 本 章 主 要 介 绍 了 MITK 中 基 于 3D Widgets 的 三 维 人 机 交 互 的 设 计 与 实 现, 集 中 讨 论 了 以 3D Widgets 为 核 心 的 三 维 人 机 交 互 框 架 的 设 计 思 路 及 其 具 体 实 现 整 个 三 维 交 互 的 框 架 采 用 模 块 化 的 设 计 思 路, 将 功 能 分 解 到 各 个 模 块 中, 使 整 个 框 架 具 有 很 强 的 弹 性 和 可 维 护 性 同 时, 整 个 框 架 开 放 式 的 结 构 给 用 户 提 供 了 最 大 限 度 的 自 由, 在 遵 循 框 架 接 口 规 范 的 前 提 下, 用 户 可 以 根 据 自 己 的 需 求 通 过 具 现 化 WidgetModel 来 实 现 各 种 不 同 的 三 维 交 互 功 能 MITK 的 3D Widgets 目 前 仍 处 于 不 断 的 完 善 与 发 展 之 中, 以 其 为 核 心 的 三 维 交 互 框 架 也 有 待 于 进 一 步 的 完 善 和 增 强, 特 别 是 向 框 架 中 添 加 各 种 实 用 的 3D Widgets, 从 而 增 强 整 个 框 架 的 三 维 交 互 功 能, 最 终 使 其 成 为 一 个 简 单 易 用 灵 活 可 靠 并 且 可 扩 展 的 三 维 人 机 交 互 平 台 参 考 文 献 1. Brookshire D. Conner, Scott S. Snibbe, Kenneth P. Herndon, Daniel C. Robbins, Robert C. Zeleznik, Andries van Dam. Three-dimensional widgets. Proceedings of Interactive 3D graphics Symposium, 1992, pp Scott S. Snibbe, Kenneth P. Herndon, Daniel C. Robbins, Brookshire D. Conner, Andries van Dam. Using deformations to explore 3D widget design. Proceedings of SIGGRAPH 92, 1992, ACM, pp Kenneth P. Herndon, Robert C. Zeleznik, Daniel C. Robbins, D. Brookshire Conner, Scott S. Snibbe, Andries van Dam. Interactive shadows. Proceedings of UIST 92, 1992, ACM, pp J. Döllner, K. Hinrichs. Interactive, Animated 3D Widgets. Proceedings of Computer Graphics International 98, 1998, IEEE, pp Joe Kniss, Gordon Kindlmann, Charles Hansen. Multidimensional Transfer Functions for Interactive Volume Rendering. IEEE Transactions on Visualization and Computer Graphics, Volume 8, Issue 3, pp , Michael J. McGuffin, Liviu Tancau, Ravin Balakrishnan. Using Deformations for Browsing Volumetric Data. Proceedings of the IEEE Visualization Conference, 2003, pp Fred Dech, Jonathan C. Silverstein. Rigorous Exploration of Medical Data in 151
159 5 三 维 人 机 交 互 的 设 计 与 实 现 Collaborative Virtual Reality Applications. Sixth International Conference on Information Visualisation, 2002, pp Marc P. Stevens, Robert C. Zeleznik, John F. Hughes. An architecture for an extensible 3D interface toolkit. Proceedings of UIST 94, 1994, ACM, pp Robert C. Zeleznik, Kenneth P. Herndon, Daniel C. Robbins, Nate Huang, Tom Meyer, Noah Parker, John F. Hughes. An interactive 3D toolkit for constructing 3D widgets. Proceedings of SIGGRAPH 93, 1993, ACM, New York, NY, USA, pp Michael Chen, S. Joy Mountford, Abigail Sellen. A study in interactive 3-D rotation using 2-D control devices. Proceedings of SIGGRAPH 88, 1988, ACM, pp Mingchang Zhao, Jie Tian, Xun Zhu, Jian Xue, Zhanglin Cheng, Hua Zhao. The Design and Implementation of a C++ Toolkit for Integrated Medical Image Processing and Analyzing. Proceedings of SPIE Medical Imaging Kenneth P. Herndon, Tom Meyer. 3D widgets for exploratory scientific visualization. Proceedings of UIST , ACM, pp Mark R. Mine. Virtual environment interaction techniques. UNC Chapel Hill CS Dept.: Technical Report TR Doug A. Bowman, Ernst Kruijff, Joseph J. LaViola, Jr., Ivan Poupyrev. An Introduction to 3-D User Interface Design. Presence, Vol. 10, No. 1, 2001, pp Paul S. Strauss, Rikk Carey. An object-oriented 3D graphics toolkit. Proceedings of SIGGRAPH 92, 1992, ACM, pp Kim MH, Choi SM, Rhee SM, Kwon DY, Kim HS. A Guided Interaction Approach for Architectural Design in a Table-Type VR Environment. 3rd IEEE Pacific Rim Conference on Multimedia (PCM 2002), Robert W. Lindeman, John L. Sibert, James N. Templeman. The Effect of 3D Widget Representation and Simulated Surface Constraints on Interaction in Virtual Environments. Proceedings of Virtual Reality Annual International Symposium, IEEE, 2001, pp
160 6 分 割 算 法 的 设 计 与 实 现 6 分 割 算 法 的 设 计 与 实 现 MITK 的 设 计 初 衷 是 为 了 给 医 学 影 像 领 域 的 研 究 者 提 供 一 套 具 有 一 致 接 口 的 可 复 用 的 包 括 可 视 化 分 割 配 准 功 能 的 集 成 化 的 医 学 影 像 开 发 包, 弥 补 VTK 和 ITK 的 一 些 缺 憾, 并 引 入 一 些 新 的 特 性, 使 得 MITK 能 够 成 为 除 了 VTK+ITK 以 外 的 另 外 一 个 选 择 医 学 影 像 分 割 是 医 学 影 像 处 理 与 分 析 中 的 一 个 重 点 课 题 和 难 点, 分 割 的 结 果 是 三 维 可 视 化 和 定 量 分 析 等 后 续 处 理 的 基 础 近 几 年 来 虽 然 仍 然 有 很 多 研 究 人 员 致 力 于 图 像 分 割 的 研 究, 发 表 了 很 多 的 研 究 成 果, 但 由 于 问 题 本 身 的 困 难 性, 与 八 十 年 代 相 比 并 没 有 取 得 多 少 实 质 性 的 进 展 本 章 旨 在 将 分 割 算 法 集 成 在 一 个 统 一 的 框 架 内, 力 图 设 计 一 个 具 有 良 好 可 扩 充 性 和 可 重 用 性 的 算 法 包, 使 得 算 法 开 发 人 员 能 利 用 算 法 开 发 包 深 入 研 究 并 改 进 各 种 算 法, 工 程 技 术 人 员 能 利 用 开 发 包 方 便 的 生 成 自 己 的 应 用 程 序 目 前 MITK 中 主 要 提 供 了 一 些 主 流 算 法, 如 level set 算 法 fast marching 算 法 live wire 算 法 区 域 增 长 算 法 交 互 式 分 割 算 法 和 阈 值 分 割 算 法 下 面 详 细 介 绍 各 个 算 法 的 设 计 与 实 现, 对 于 每 种 算 法, 将 从 如 下 三 方 面 加 以 阐 述 : 原 理 概 述 该 部 分 将 简 要 介 绍 该 算 法 的 理 论 依 据 数 学 方 法 该 算 法 开 发 包 的 设 计 与 实 现 该 部 分 主 要 介 绍 该 算 法 的 算 法 流 程 分 割 算 法 开 发 包 的 设 计 和 实 现 方 法, 重 点 在 于 如 何 按 照 面 向 对 象 的 软 件 设 计 方 法 来 组 织 各 个 类, 以 使 算 法 模 块 具 有 最 大 的 灵 活 性 可 重 用 性 和 可 扩 充 性 实 验 结 果 该 部 分 以 系 统 界 面 的 形 式 给 出 每 个 算 法 的 分 割 结 果 6.1 MITK 中 的 分 割 算 法 框 架 分 割 算 法 开 发 包 既 可 以 作 为 MITK 的 一 部 分, 也 可 以 独 立 出 来, 作 为 一 个 153
161 6 分 割 算 法 的 设 计 与 实 现 单 独 的 开 发 包 使 用 它 是 一 个 基 于 面 向 对 象 方 法 的 ANSI C++ 开 发 包, 目 标 是 为 用 户 提 供 一 个 具 有 一 致 接 口 的 算 法 包 我 们 采 用 了 基 于 设 计 模 式 的 软 件 开 发 方 法, 这 种 方 法 强 调 代 码 模 块 化 和 对 象 间 相 互 独 立 性, 使 得 软 件 具 有 很 高 的 灵 活 性 同 时, 由 于 开 发 包 使 用 标 准 C++ 来 书 写, 它 还 大 量 使 用 了 C++ 标 准 模 板 库 STL 来 简 化 一 些 繁 琐 的 编 程 工 作 算 法 包 主 要 分 为 四 大 模 块 : 数 据 模 块 数 据 获 取 模 块 数 据 输 出 模 块 和 数 据 处 理 模 块 他 们 分 别 负 责 数 据 表 示 文 件 读 取 文 件 输 出 和 数 据 处 理 工 作 定 义 类 mitkobject 作 为 开 发 包 中 所 有 类 的 基 类, 在 mitkobject 中 定 义 了 开 发 包 中 所 有 类 的 一 些 共 同 属 性 行 为 和 接 口, 如 有 关 类 自 身 的 一 些 信 息 调 试 信 息 和 内 存 管 理 接 口 等 数 据 模 块 数 据 模 块 主 要 用 于 数 据 的 表 示 分 割 算 法 处 理 的 数 据 主 要 是 一 些 有 多 张 切 片 组 成 的 体 数 据, 在 分 割 算 法 包 中 用 类 mitkvolume 为 他 们 提 供 一 个 统 一 的 表 示 方 法 mitkobject mitkvolume -m_width -m_height -m_imagenum -m_datatype -m_data SetWidth() +SetHeight() +SetImageNum() +SetDataType() +SetData() +GetWidth() +GetHeight() +GetImageNum() +GetDataType() +GetData() +...() 图
162 6 分 割 算 法 的 设 计 与 实 现 如 图 6-1 所 示,mitkVolume 继 承 于 mitkobject, 它 封 装 了 体 数 据 的 一 些 特 性, 如 体 数 据 的 宽 度 (m_width) 体 数 据 的 高 度 (m_height) 体 数 据 的 切 片 数 (m_imagenum) 体 数 据 的 数 据 类 型 (m_datatype) 以 及 体 数 据 数 值 (m_data) 等 用 户 通 过 mitkvolume 提 供 的 一 些 公 用 接 口 来 设 置 和 获 取 体 数 据 的 属 性 如 : 用 SetWidth() 方 法 来 设 置 体 数 据 的 宽 度, 用 GetWidth() 方 法 来 获 取 体 数 据 的 宽 度 分 割 算 法 包 处 理 的 数 据 类 型 可 以 是 二 维 的 也 可 以 是 三 维 的 当 mitkvolume 的 m_imagenum 值 为 2 时, 代 表 二 维 体 数 据 即 图 像 数 据 ; 当 mitkvolume 的 m_imagenum 值 为 3 时, 代 表 三 维 体 数 据 数 据 获 取 模 块 数 据 获 取 模 块 的 作 用 是 从 磁 盘 上 读 取 输 入 文 件, 将 其 写 入 内 存, 并 转 化 为 统 一 的 表 示 模 式 mitkvolume 图 像 文 件 有 很 多 种 存 贮 格 式, 医 学 图 像 中 比 较 常 用 的 文 件 格 式 有 Dicom 文 件 Im0 文 件 Tiff 文 件 和 生 数 据 文 件 等 本 章 的 分 割 算 法 平 台 除 支 持 以 上 文 件 格 式 的 读 取 外, 还 支 持 一 些 常 见 的 文 件 格 式 如 :jpeg bmp 文 件 的 读 取 mitkobject mitkreader +AddFileName() +Run() +GetOutput() <<uses>> mitkvolume mitkdicomreader mitkim0reader mitktiffreader mitkrawdatareader mitkbmpreader mitkjepgreader +Run() +Run() +Run() +Run() +Run() +Run() 图 6-2 如 图 6-2 所 示,mitkReader 为 数 据 获 取 模 块 定 义 了 一 个 统 一 的 接 口 用 户 通 过 AddFileName() 指 定 将 要 读 取 的 文 件 在 磁 盘 上 的 路 径 ;Run() 函 数 为 虚 函 数, mitkreader 的 子 类 通 过 重 载 Run() 函 数 实 现 不 同 文 件 类 型 的 读 取 ; 用 户 通 过 155
163 6 分 割 算 法 的 设 计 与 实 现 GetOutput() 获 取 mitkreader 的 输 出 结 果 一 个 mitkvolume 类 型 的 数 据 例 如, 欲 将 路 径 为 e:\ttt.im0 的 Im0 类 型 的 文 件 读 取 到 volume 中, 可 用 如 下 代 码 : mitkim0reader *reader = new mitkim0reader; reader->addfile( e:\\ttt.im0 ); reader->run(); mitkvolume *volume = reader->getoutput(); 数 据 输 出 模 块 数 据 输 出 模 块 的 作 用 是 将 内 存 中 的 mitkvolume 数 据 写 入 磁 盘 的 指 定 路 径 同 数 据 获 取 模 块 一 样, 数 据 输 出 模 块 支 持 Dicom 文 件 Im0 文 件 Tiff 文 件 生 数 据 文 件 jpeg 文 件 和 bmp 文 件 的 输 出 mitkobject mitkwriter +SetInput() +AddFileName() +Run() <<uses>> mitkvolume mitkdicomwriter mitkim0writer mitktiffwriter mitkrawdatawriter mitkbmpwriter mitkjepgwriter +Run() +Run() +Run() +Run() +Run() +Run() 图 6-3 如 图 6-3 所 示,mitkWriter 为 数 据 输 出 模 块 定 义 了 一 个 统 一 的 接 口 用 户 通 过 SetInput 输 入 要 保 存 的 数 据 ; 通 过 AddFileName() 指 定 将 要 输 出 的 文 件 在 磁 盘 上 的 路 径 ;Run() 函 数 为 虚 函 数,mitkWriter 的 子 类 通 过 重 载 Run() 函 数 实 现 不 同 文 件 类 型 的 输 出 例 如, 欲 将 内 存 中 的 volume 数 据 写 入 路 径 为 e:\ttt.im0 的 文 件 中, 可 用 156
164 6 分 割 算 法 的 设 计 与 实 现 如 下 代 码 : mitkim0writer *writer = new mitkim0writer; writer ->AddFile( e:\\ttt.im0 ); writer->setinput(volume); writer ->Run(); 数 据 处 理 模 块 数 据 处 理 模 块 是 分 割 算 法 包 的 核 心 部 分 它 的 输 入 是 一 个 mitkvolume 型 的 数 据, 输 出 也 是 一 个 mitkvolume 型 的 数 据, 因 此 可 以 称 它 为 一 个 过 滤 器 (Filter), 它 的 作 用 是 对 输 入 的 数 据 进 行 处 理, 将 结 果 写 入 输 出 的 数 据 如 图 6-4 所 示, 因 为 数 据 处 理 模 块 的 输 入 和 输 出 数 据 都 是 mitkvolume 类 型, 因 此 定 义 mitkvolumetovolumefilter 为 所 有 Filter 的 基 类, 在 其 中 将 函 数 Run() 定 义 为 虚 函 数, 它 的 子 类 可 以 通 过 重 载 Run() 函 数 实 现 不 同 的 Filter 功 能 mitvolumetovolumefilter 可 以 包 含 实 现 各 种 功 能 的 子 类, 如 : 实 现 滤 波 功 能 的 Filter, 实 现 距 离 函 数 功 能 的 Filter, 和 实 现 各 种 分 割 功 能 的 Filter 等 mitkobject mitkvolumetovolumefilter <<uses>> mitkvolume +SetInput() +GetOutput() +virtual Run() mitkgausefilter mitkdistancemapfilter mitkfinitdifferencefilter mitk...filter +virtual Run() +virtual Run() +virtual Run() +virtual Run() 图 6-4 例 如, 要 对 InputVolume 数 据 做 阈 值 分 割, 高 阈 值 和 低 阈 值 分 别 为 vh 和 vl, 并 将 分 割 后 的 结 果 写 入 OutputVolume 数 据, 可 用 如 下 代 码 : mitkthresholdsegmentfilter *filter = new mitkthresholdsegmentfilter; filter->setinput(inputvolume); filter->setlowthreshold(vl); 157
165 6 分 割 算 法 的 设 计 与 实 现 filter->sethighthreshold(vh); filter->run(); OutputVolume = filter->getoutput(); 如 图 6-5 所 示, 一 个 Filter 的 输 出 可 以 作 为 另 一 个 Filter 的 输 入,N 个 Filter 可 以 组 合 成 一 个 大 的 Filter 图 基 于 阈 值 的 分 割 算 法 在 MITK 中 的 实 现 原 理 概 述 阈 值 分 割 是 最 常 见 的 并 行 的 直 接 检 测 区 域 的 分 割 方 法 [1] 如 果 只 用 选 取 一 个 阈 值 称 为 单 阈 值 分 割, 它 将 图 像 分 为 目 标 和 背 景 两 大 类 ; 如 果 用 多 个 阈 值 分 割 称 为 多 阈 值 方 法, 图 像 将 被 分 割 为 多 个 目 标 区 域 和 背 景, 为 区 分 目 标, 还 需 要 对 各 个 区 域 进 行 标 记 阈 值 分 割 方 法 基 于 对 灰 度 图 像 的 一 种 假 设 : 目 标 或 背 景 内 的 相 邻 象 素 间 的 灰 度 值 是 相 似 的, 但 不 同 目 标 或 背 景 的 象 素 在 灰 度 上 有 差 异, 反 映 在 图 像 直 方 图 上, 不 同 目 标 和 背 景 则 对 应 不 同 的 峰 选 取 的 阈 值 应 位 于 两 个 峰 之 间 的 谷, 从 而 将 各 个 峰 分 开 ( 见 图 6-6) 图 6-6 阈 值 分 割 示 意 图 158
166 6 分 割 算 法 的 设 计 与 实 现 阈 值 分 割 的 优 点 是 简 单, 同 时 对 于 不 同 类 的 物 体 灰 度 值 或 其 他 特 征 值 相 差 很 大 时, 它 能 很 有 效 的 对 图 像 进 行 分 割 阈 值 分 割 通 常 作 为 预 处 理, 在 其 后 应 用 其 他 一 系 列 分 割 方 法 进 行 处 理, 它 常 被 用 于 CT 图 像 中 皮 肤 骨 骼 的 分 割 阈 值 分 割 的 缺 点 是 不 适 用 于 多 通 道 图 像 和 特 征 值 相 差 不 大 的 图 像, 对 于 图 像 中 不 存 在 明 显 的 灰 度 差 异 或 各 物 体 的 灰 度 值 范 围 有 较 大 重 叠 的 图 像 分 割 问 题 难 以 得 到 准 确 的 结 果 另 外, 由 于 它 仅 仅 考 虑 了 图 像 的 灰 度 信 息 而 不 考 虑 图 像 的 空 间 信 息, 阈 值 分 割 对 噪 声 和 灰 度 不 均 匀 很 敏 感 针 对 阈 值 分 割 方 法 的 缺 点, 不 少 学 者 提 出 了 许 多 改 进 方 法 在 噪 声 图 像 的 分 割 中, 一 些 阈 值 分 割 方 法 还 利 用 了 一 些 象 素 邻 域 的 局 部 信 息, 如 基 于 过 渡 区 的 方 法 [2], 还 有 利 用 像 素 点 空 间 位 置 信 息 的 变 化 阈 值 法 [3], 结 合 局 部 灰 度 [4] 和 连 通 信 息 [5] 的 阈 值 方 法 阈 值 分 割 算 法 开 发 包 设 计 与 实 现 mitkthresholdimagefilter +SetLowThreshold() +SetHighThreshold() +Run() 图 6-7 阈 值 分 割 类 示 意 图 mitkthresholdimagefilter() 用 来 实 现 阈 值 分 割 函 数 SetLowThreshold(): 设 置 低 阈 值 ; 函 数 SetHighThreshold(): 设 置 高 阈 值 ; 函 数 Run(): 启 动 与 之 分 割 算 法 像 素 值 在 低 阈 值 和 高 阈 值 之 间 的 保 留 原 值, 其 他 的 赋 零 值 159
167 6 分 割 算 法 的 设 计 与 实 现 阈 值 分 割 结 果 示 意 图 图 6-8 阈 值 分 割 结 果 实 验 结 果 如 图 6-8 以 系 统 界 面 的 形 式 给 出 了 与 之 分 割 的 结 果 6.3 区 域 增 长 算 法 在 MITK 中 的 实 现 原 理 概 述 区 域 生 长 是 典 型 的 串 行 区 域 分 割 方 法, 其 特 点 是 将 分 割 过 程 分 解 为 多 个 顺 序 的 步 骤, 其 中 后 续 步 骤 要 根 据 前 面 步 骤 的 结 果 进 行 判 断 而 确 定 区 域 生 长 的 基 本 思 想 是 将 具 有 相 似 性 质 的 像 素 集 中 起 来 构 成 区 域, 该 方 法 需 要 先 选 取 一 个 种 子 点, 然 后 依 次 将 种 子 像 素 周 围 的 相 似 像 素 合 并 到 种 子 像 素 所 160
168 6 分 割 算 法 的 设 计 与 实 现 在 的 区 域 中 区 域 生 长 算 法 的 研 究 重 点 一 是 特 征 度 量 和 区 域 增 长 规 则 的 设 计, 二 是 算 法 的 高 效 性 和 准 确 性 区 域 生 长 算 法 的 优 点 是 计 算 简 单, 特 别 适 用 于 分 割 小 的 结 构 如 肿 瘤 和 伤 疤 [6] 与 阈 值 分 割 类 似, 区 域 生 长 也 很 少 单 独 使 用, 往 往 是 与 其 他 分 割 方 法 一 起 使 用 图 6-9 区 域 生 长 算 法 示 意 图 区 域 生 长 的 缺 点 是 它 需 要 人 工 交 互 以 获 得 种 子 点, 这 样 使 用 者 必 须 在 每 个 需 要 抽 取 出 的 区 域 中 植 入 一 个 种 子 点 同 时, 区 域 生 长 方 法 也 对 噪 声 敏 感, 导 致 抽 取 出 的 区 域 有 空 洞 或 者 在 局 部 体 效 应 的 情 况 下 将 原 本 分 开 的 区 域 连 接 起 来 为 了 解 决 这 些 缺 点,J.F. Mangin 等 提 出 了 一 种 同 伦 的 (homotopic) 区 域 生 长 方 法 [7], 以 保 证 初 始 区 域 和 最 终 抽 取 出 的 区 域 的 拓 扑 结 构 相 同 另 外, 模 糊 连 接 度 理 论 与 区 域 生 长 相 结 合 也 是 一 个 发 展 方 向 [8] 在 区 域 合 并 方 法 中, 输 入 图 像 往 往 先 被 分 为 多 个 相 似 的 区 域, 然 后 类 似 的 相 邻 区 域 根 据 某 种 判 断 准 则 迭 代 地 进 行 合 并 在 区 域 分 裂 技 术 中, 整 个 图 像 先 被 看 成 一 个 区 域, 然 后 区 域 不 断 被 分 裂 为 四 个 矩 形 区 域, 直 到 每 个 区 域 内 部 都 是 相 似 的 在 区 域 的 分 裂 合 并 方 法 中 [9], 先 从 整 幅 图 像 进 行 分 裂, 然 后 将 相 邻 的 区 域 进 行 合 并 分 裂 合 并 方 法 不 需 要 预 先 指 定 种 子 点, 它 的 研 究 重 点 是 分 裂 和 合 并 规 则 的 设 计 但 是, 分 裂 可 能 会 使 分 割 区 域 的 边 界 被 破 坏 区 域 生 长 算 法 开 发 包 的 设 计 与 实 现 (1) 算 法 流 程 如 图 6-10 给 出 了 区 域 生 长 算 法 的 算 法 流 程, 主 要 分 为 两 个 模 块 : 初 始 化 部 分 : 该 部 分 主 要 工 作 有 1. 由 用 户 选 取 初 始 种 子 点 ; 2. 初 始 化 堆 栈, 将 种 子 点 压 入 队 列 ; 161
169 6 分 割 算 法 的 设 计 与 实 现 循 环 部 分 : 该 部 分 主 要 工 作 有 1. 循 环 结 束 条 件 : 队 列 为 空 时 循 环 结 束 ; 2. 从 队 列 中 取 出 队 顶 元 素, 获 取 它 的 邻 域 点, 对 于 二 维 图 像, 取 八 邻 域, 对 于 三 维 图 像, 取 六 邻 域 ; 3. 相 似 度 条 件 : 有 很 多 种 定 义 方 法, 这 里 采 取 比 较 简 单 的 定 义 方 法, 设 当 前 队 列 顶 端 元 素 的 灰 度 值 为 gc, 当 前 邻 点 灰 度 值 为 gn, 种 子 点 的 灰 度 值 为 gs, nv cv 为 用 户 设 定 的 值, 定 义 当 gc gn < nv 且 gs gn < cv 时, 满 足 相 似 度 条 件 ; 4. 如 果 邻 点 满 足 相 似 度 条 件, 则 将 其 压 入 堆 栈 继 续 循 环, 如 果 不 满 足 则 跳 出 循 环, 结 束 程 序 ; 162
170 6 分 割 算 法 的 设 计 与 实 现 图 6-10 区 域 生 长 算 法 流 程 (2) 类 协 作 图 如 图 6-11 给 出 了 区 域 生 长 算 法 的 类 协 作 图 mitkregiongrowimagefilter 定 义 了 按 照 图 6-10 定 义 了 区 域 生 长 算 法 的 主 框 架 流 程 ; mitk2dregiongrowimagefilter 和 mitk3dregiongrowimagefilter 是 mitkregiongrowimagefilter 的 子 类, 分 别 定 义 了 二 维 和 三 维 区 域 生 长 算 法 的 框 架 ;mitkregiongrowtemplatefunction 为 所 有 区 域 生 长 算 法 中 的 相 似 度 function 定 义 了 一 个 基 类 ;mitkregiongrowfunction 是 mitkregiongrowtemplatefunction 的 一 163
171 6 分 割 算 法 的 设 计 与 实 现 个 子 类, 它 定 义 了 一 种 相 似 度 函 数 的 具 体 实 现 mitkvolumetovolumefilter mitkregiongrowtemplatefunction mitkregiongrowimagefilter mitkregiongrowfunction mitk2dregiongrowimagefilter mitk3dregiongrowimagefilter 图 6-11 区 域 生 长 算 法 类 协 作 图 (3) 类 组 成 结 构 详 解 mitkregiongrowimagefilter -m_queue -m_function +Run() +SetSeedPoint() +SetFunction() -virtual Initialize() -virtual GetNeighbor() <<uses>> this->initialize(); while(!m_queue->empty()) currentpoint = m_queue->top(); while(still have other neighbors) neighbor = this->getneighbor; if (m_function->similar()) m_queue->push(neighbor); else Jump out; mitkregiongrow2dimagefilter mitkregoingrow3dimagefilter mitkregiongrowtemplatefunction +GetNeighbor() +virtual Initialze() +virtual Similar() mitkregiongrowfunction +virtual Initialze() +virtual Similar() 图 6-12 Region Grow 算 法 开 发 包 内 部 结 构 图 如 图 6-12 给 出 了 Region Grow 算 法 开 发 包 的 内 部 结 构, 算 法 的 输 入 数 据 为 原 始 图 像, 输 出 数 据 为 分 割 后 的 图 像 mitkregiongrowimagefilter 按 照 图 6-10 定 义 了 区 域 生 长 算 法 的 主 框 架 流 程 164
172 6 分 割 算 法 的 设 计 与 实 现 m_queue: 指 向 队 列 的 指 针,m_Queue 保 存 了 当 前 活 动 点 ; m_function: 它 是 一 个 mitkregiongrowtemplatefunctino 类 型 的 指 针, 用 来 进 行 相 似 度 判 断, 程 序 运 行 时, 用 户 需 将 一 个 具 体 的 mitkregiongrowtemplatefunction 的 子 类 用 SetFunction() 函 数 指 定 ; 函 数 SetSeedPoint(): 指 定 种 子 点 的 位 置 ; 函 数 Initialize(): 主 要 是 做 一 些 初 始 化 工 作, 初 始 化 队 列, 将 种 子 点 压 入 堆 栈 ; 函 数 GetNeighbor(): 获 取 当 前 点 的 邻 域 点, 可 以 由 子 类 重 载, 以 实 现 不 同 的 获 取 方 式 ; 函 数 Run(): 由 用 户 调 用, 以 启 动 区 域 生 长 算 法, 如 图 6-12 所 示, 他 起 重 要 调 用 m_function 以 进 行 相 似 度 判 断 ; mitkregiongrow2dimagefilter 实 现 两 维 图 像 的 区 域 生 长 算 法 函 数 GetNeighbor(): 由 基 类 重 载 而 来, 获 得 当 前 点 的 八 邻 域 点 ; mitkregiongrow3dimagefilter 实 现 三 维 图 像 的 区 域 生 长 算 法 函 数 GetNeighbor(): 由 基 类 重 载 而 来, 获 得 当 前 点 的 四 邻 域 ; mitkregiongrowtemplatefunction 为 区 域 生 长 算 法 的 相 似 度 函 数 定 义 了 一 个 基 类 函 数 Initialize(): 做 一 些 初 始 化 函 数, 由 子 类 重 载 ; 函 数 Similar(): 判 断 相 似 条 件 是 否 满 足, 满 足 则 返 回 值 为 真, 不 满 足 返 回 值 为 假 ; mitkregiongrowfunction: 定 义 了 一 种 具 体 的 判 断 相 似 度 的 方 法 函 数 similar(): 详 见 上 一 小 节, 否 则 返 回 值 为 假 ; gc gn < nv 且 gs gn < cv 时 返 回 值 为 真, 区 域 生 长 分 割 结 果 下 面 以 系 统 界 面 的 形 式 给 出 分 割 结 果 : 165
173 6 分 割 算 法 的 设 计 与 实 现 图 6-13 脑 肿 瘤 的 二 维 分 割 结 果 图 6-14 脑 肿 瘤 三 维 分 割 重 建 后 的 结 果 166
174 6 分 割 算 法 的 设 计 与 实 现 图 6-15 脑 肿 瘤 三 维 分 割 中 的 一 张 切 片 167
175 6 分 割 算 法 的 设 计 与 实 现 图 6-16 膝 盖 骨 质 的 二 维 分 割 结 果 6.4 交 互 式 分 割 在 MITK 中 的 实 现 原 理 概 述 本 节 所 指 的 交 互 式 分 割 指 : 由 用 户 指 定 一 些 作 为 多 边 形 的 顶 点, 系 统 自 动 将 多 边 形 内 部 填 充 作 为 分 割 结 果 填 充 多 边 形 时 要 用 到 图 形 学 中 的 边 填 充 算 法, 下 面 做 以 简 要 介 绍 边 填 充 算 法 的 基 本 思 想 是 : 对 于 每 一 条 扫 描 线 和 每 条 多 边 形 边 的 交 点 ( y 1 1 x, ), 将 该 扫 描 线 上 交 点 右 方 的 所 有 像 素 取 补 对 多 边 行 的 每 条 边 做 此 处 理, 多 边 行 的 顺 序 随 意 如 图 6-17 所 示, 为 应 用 最 简 单 的 边 填 充 算 法 填 充 一 个 多 边 形 的 示 意 图 168
176 6 分 割 算 法 的 设 计 与 实 现 图 6-17 边 填 充 算 法 示 意 图 交 互 式 分 割 算 法 开 发 包 的 设 计 与 实 现 (1) 算 法 流 程 如 图 6-18 给 出 了 交 互 式 分 割 算 法 的 算 法 流 程 图, 算 法 的 输 入 是 原 始 图 像 和 一 组 多 边 形 的 顶 点, 输 出 是 分 割 好 的 图 像 算 法 分 为 三 大 模 块 : 初 始 化 部 分, 该 部 分 主 要 进 行 如 下 操 作 : 1. 初 始 化 图 像 : 将 标 记 图 像 的 所 有 点 初 始 化 为 零 点 ; 2. 离 散 化 每 条 边 : 设 扫 描 线 的 方 向 为 水 平 方 向, 相 邻 两 条 扫 描 线 间 的 间 距 为 图 像 垂 直 方 向 的 单 位 距 离 如 图 6-19 所 示, 求 出 每 条 扫 描 线 与 多 边 形 各 边 的 交 点 P1 P2 P3 P4 并 将 他 们 标 值 为 非 零 值 ; 多 边 形 填 充 部 分, 该 部 分 按 照 如 下 为 代 码 工 作 : Inside = FALSE; For ( 扫 描 线 上 的 每 个 像 素 ) if ( 该 像 素 值 为 非 零 ) Inside =!Inside; 169
177 6 分 割 算 法 的 设 计 与 实 现 If (Inside == true) 将 该 像 素 赋 值 非 零 ; 图 6-18 边 填 充 算 法 流 程 170
178 6 分 割 算 法 的 设 计 与 实 现 图 6-19 边 的 离 散 化 后 处 理 部 分, 生 成 分 割 结 果 对 于 标 记 图 像 上 的 非 零 点, 输 出 图 像 的 像 素 点 等 于 输 入 图 像 上 对 应 点 的 值, 对 于 标 记 图 像 上 的 零 点, 输 出 图 像 上 的 对 应 点 赋 值 为 零 ; (2) 交 互 分 割 算 法 开 发 包 的 设 计 与 实 现 mitkvolumetovolumefilter mitkinteractiveimagefiler +SetPoints() +Run() +Initialize() +PolyFill() +PostProcess() Initialize(); PolyFill(); PostProcess(); 图 6-20 交 互 分 割 算 法 开 发 包 内 部 结 构 如 图 6-20 给 出 了 区 域 生 长 算 法 开 发 包 内 部 结 构 图 : mitkinteractiveimagefilter 实 现 了 一 种 交 互 式 分 割 算 法 函 数 SetPoints(): 用 于 指 定 多 边 形 的 各 个 定 点 ; 函 数 Run(): 由 用 户 调 用, 用 于 启 动 交 互 式 分 割 算 法, 开 始 分 割 在 其 中 依 次 调 用 函 数 Initialize() PolyFill() 和 PostProcess() 以 具 体 实 现 图 4.46 内 列 出 的 各 个 步 骤 ; 171
179 6 分 割 算 法 的 设 计 与 实 现 交 互 式 分 割 算 法 的 分 割 结 果 图 6-21 交 互 式 分 割 结 果 一 图 6-22 交 互 式 分 割 结 果 二 172
180 6 分 割 算 法 的 设 计 与 实 现 图 6-23 交 互 式 分 割 结 果 三 6.5 Live Wire 算 法 在 MITK 中 的 实 现 live wire 算 法 是 用 来 确 定 图 像 中 轮 廓 线 的 方 法, 如 果 想 把 轮 廓 线 外 或 轮 廓 线 内 的 部 分 分 割 出 来, 还 要 用 到 种 子 点 填 充 算 法 因 此 在 下 面 的 原 理 概 述 和 算 法 包 的 设 计 部 分, 将 对 他 们 分 别 予 以 阐 述 原 理 概 述 (1) live wire 基 本 原 理 live wire 方 法 属 于 交 互 式 分 割 广 义 上 讲, 图 像 分 割 可 分 为 两 大 类 : 自 动 分 割 和 交 互 式 分 割 自 动 分 割 方 法 可 以 避 免 用 户 的 交 互, 但 很 难 保 证 他 们 总 是 有 效 的 在 交 互 式 分 割 方 法 中, 有 的 完 全 需 要 有 由 用 户 来 完 成, 如 那 些 由 用 户 来 话 轮 廓 线 的 分 割 方 式, 有 的 只 需 要 很 少 的 用 户 交 互, 如 本 节 要 介 绍 的 live wire 分 割 方 法 自 动 分 割 目 前 已 被 广 泛 采 用, 但 当 一 些 新 的 图 像 出 现 时, 他 们 往 往 不 能 顺 利 工 作, 这 方 面 还 有 大 量 的 研 究 工 作 要 做 在 这 种 情 况 下, 往 往 应 用 交 173
181 6 分 割 算 法 的 设 计 与 实 现 互 式 分 割, 它 能 够 使 用 户 完 全 控 制 分 割 过 程, 而 且 在 任 何 情 况 下 他 都 可 以 正 常 工 作 在 live wire 算 法 中, 将 图 像 看 成 是 一 个 连 通 图, 图 像 中 的 像 素 当 作 连 通 图 中 的 节 点, 相 邻 像 素 间 的 边 当 作 连 接 节 点 的 边 在 边 上 定 义 一 个 代 价 函 数, 使 强 边 缘 具 有 较 小 的 代 价 值, 非 边 缘 具 有 较 大 的 代 价 值, 两 个 节 点 间 的 距 离 可 用 代 价 值 表 示 然 后 通 过 图 搜 索 来 找 物 体 的 边 界, 把 用 户 指 定 的 物 体 边 界 上 的 两 点 之 间 的 最 短 路 径 ( 即 该 条 路 径 上 所 有 边 的 代 价 值 总 和 最 小 ) 当 作 物 体 的 边 界 一 般 用 动 态 规 划 来 查 找 连 通 图 中 两 点 之 间 的 最 短 路 径, 如 图 6-24 所 示 图 6-24 连 接 两 个 点 之 间 的 最 短 路 径 由 上 面 的 分 析 可 知, 图 像 中 目 标 物 体 的 边 缘 跟 踪 问 题 可 以 被 转 换 成 赋 权 图 的 最 优 路 径 搜 索 问 题 为 此, 需 对 一 幅 n n 的 灰 度 图 像 进 行 如 下 处 理 一 幅 n n 的 图 像 被 描 述 成 具 有 4 邻 域 象 素 的 象 素 阵 列, 每 个 象 素 被 描 述 成 一 个 正 方 形, 相 邻 象 素 有 一 条 公 共 边, 称 为 元 边 对 于 G 中 的 每 一 条 元 边, 根 据 一 定 的 规 则 赋 予 其 相 应 的 特 征 值, 用 以 描 述 该 元 边 属 于 物 体 边 缘 的 可 能 性, 元 边 的 特 征 值 经 过 特 征 转 换 函 数 转 变 成 一 定 的 代 价 值 我 们 定 义 图 中 任 意 两 个 节 点 间 的 最 优 路 径, 由 两 个 节 点 间 累 积 代 价 和 最 小 的 连 续 元 边 组 成 此 时, 该 图 像 确 定 了 一 个 赋 权 图 : G=(V,E) 其 中 V 为 图 像 的 象 素 点 集 合,E 为 元 边 的 集 合 174
182 6 分 割 算 法 的 设 计 与 实 现 175 图 6-25 相 邻 像 素 如 图 6-25 所 示, 相 邻 象 素 p 与 q 之 间 的 公 共 边 b,b 的 特 征 值 根 据 图 像 的 边 缘 信 息 确 定, 边 缘 信 息 越 强, 特 征 值 越 大 利 用 特 征 转 换 函 数 可 以 将 特 征 值 转 换 为 边 的 代 价 值 文 献 [9] 用 如 下 几 式 来 确 定 边 的 特 征 值, 和 特 征 转 换 函 数 特 征 值 为 : ) ( ) ( 1 q g p g f = (6-1) ) ( ) ( ) ( ) ( ) ( ) ( w g u g q g v g t g p g f + + = (6-2) ) ( 2 1 ) ( 2 1 ) ( ) ( 2 1 ) ( 2 1 ) ( w g u g q g v g t g p g f + + = (6-3) ( ) ) ( ) ( ) ( ) ( ) ( ) ( ) ( ) ( q g v g w g p g q g t g u g p g f = (6-4) 特 征 转 换 函 数 为 : > < + + = , 2, ) 2( 2 1, ) ( l f l f a l l f a a l f f c (6-5) 其 中 a 1 l 和 2 l 为 自 由 参 数, 他 们 分 别 取 如 下 值 : ) min( 1 f l = (6-6) ) max( 2 f l = (6-7)
183 6 分 割 算 法 的 设 计 与 实 现 [ ] l 2 l a = 1 (6-8) 100 目 标 物 体 的 边 缘 并 不 总 是 强 边 缘, 而 且 在 目 标 物 体 边 缘 的 周 围 可 能 存 在 具 有 强 边 缘 的 物 体, 或 存 在 具 有 强 特 征 的 噪 音 干 扰, 采 用 上 述 特 征 转 换 函 数 会 将 某 物 体 的 强 边 缘 或 噪 音 误 判 为 目 标 物 体 的 边 缘 因 此 考 虑 到 高 斯 函 数 的 特 性, 采 用 高 斯 函 数 的 一 种 变 体 作 为 特 征 转 换 函 数 : c 2 = 1 e ( f mean) 2σ 2 2 (6-9) 其 中 mean 表 示 赋 权 图 中 期 望 的 元 边 的 均 值,σ 表 示 赋 权 图 中 期 望 的 元 边 的 方 差 这 样, 对 于 具 有 不 同 特 征 值 的 期 望 边 缘, 其 代 价 值 都 可 取 得 较 小 值 特 征 转 换 函 数 c 1 c 2 的 粗 略 函 数 曲 线 图 如 图 6-26 所 示 : 由 图 6-26 中 c 2 的 粗 略 函 数 曲 线 图 可 知, 当 元 边 特 征 值 为 均 值 mean 时 其 代 价 值 最 小 因 此 在 分 割 前 用 户 通 过 鼠 标 交 互 式 的 选 定 特 征 较 弱 的 物 体 边 缘 区 域 作 为 训 练 区 域, 并 以 该 训 练 区 域 的 样 本 梯 度 均 值 和 样 本 梯 度 方 差 分 别 作 为 特 征 转 换 函 数 c 2 的 mean 值 和 σ 值, 这 样 即 使 目 标 物 体 边 缘 存 在 具 有 强 特 征 的 噪 音, 但 因 为 期 望 的 物 体 边 缘 代 价 值 较 小, 分 割 不 再 会 受 噪 音 的 干 扰 而 导 致 错 误 的 结 果 cost cost c1 feature mean feature c2 图 6-26 代 价 函 数 176
184 6 分 割 算 法 的 设 计 与 实 现 (2) 种 子 点 填 充 基 本 原 理 种 子 点 填 充 是 图 形 学 中 的 算 法, 是 轮 廓 提 取 算 法 的 逆 过 程 它 首 先 假 定 封 闭 轮 廓 线 内 某 点 是 已 知 的, 然 后 算 法 开 始 搜 索 与 种 字 点 相 邻 且 位 于 轮 廓 线 内 的 点 如 果 相 邻 点 不 再 轮 廓 线 内, 那 么 就 到 达 轮 廓 线 的 边 界 ; 如 果 相 邻 点 位 于 轮 廓 线 之 内, 那 么 这 一 点 就 成 为 新 的 种 子 点, 然 后 继 续 搜 索 下 去 种 子 点 填 充 区 域 的 连 通 情 况 又 有 四 连 通 和 八 连 通 之 分 live Wire 算 法 包 的 设 计 与 实 现 (1) 算 法 流 程 live wire 算 法 流 程 由 上 面 的 介 绍 可 知,live wire 算 法 的 关 键 在 于 如 何 确 定 边 的 特 征 值 和 特 征 转 换 函 数 在 live wire 算 法 包 中, 采 用 式 (6-1)-(6-4) 来 确 定 边 的 特 征 值, 采 用 式 (6-5) 来 确 定 边 的 特 征 转 换 函 数 如 图 6-27 给 出 了 live wire 算 法 的 框 架 图 他 的 输 入 是 原 始 图 像, 输 出 是 一 幅 二 值 图, 从 初 始 点 到 终 止 点 的 最 优 路 径 上 的 点 为 非 零 值, 其 他 的 点 为 零 值 有 图 中 可 见, 该 算 法 主 要 分 为 两 大 模 块, 初 始 化 部 分 和 动 态 规 划 部 分 : 177
185 6 分 割 算 法 的 设 计 与 实 现 图 6-27 live wire 算 法 框 架 图 初 始 化 部 分 : 该 部 分 主 要 完 成 如 下 工 作 计 算 由 图 6-26 确 定 的 每 条 边 的 代 价 值 c (b) ; 用 与 输 入 图 像 大 小 相 同 的 一 幅 图 像 记 录 图 像 中 各 个 点 的 累 积 代 价 值 成 这 幅 图 像 为 代 价 图 像 在 代 价 图 像 中, 将 初 始 节 点 v s 的 累 积 代 价 值 cc (v s ) 设 为 0, 其 余 节 点 的 累 积 代 价 值 为 ; 初 始 化 队 列 Q, 把 初 始 节 点 v s 置 入 待 处 理 的 节 点 队 列 Q 中 ; 178
186 6 分 割 算 法 的 设 计 与 实 现 动 态 规 划 部 分 : 该 部 分 主 要 完 成 如 下 工 作 设 置 循 环 终 止 条 件 : 当 堆 栈 为 空 时 循 环 终 止 从 Q 中 移 出 一 个 累 积 代 价 值 最 小 的 节 点 v, 并 将 v 置 入 队 列 L; 对 于 v 的 每 一 个 4 邻 节 点 v', v' L, 计 算 cc ( v) + c( b ) 其 中 b ' 为 节 点 v ' 到 节 点 v 的 元 边 如 果 cc ( v') > cc( v) + c( b' ) 且 T CC > cc (v) + c ( b ) 则 使 cc ( v') = cc( v) + c( b' ), 并 将 节 点 v 的 方 向 信 息 保 存 在 dir (v') 中 如 果 v' Q 则 将 v ' 插 入 到 Q 中 ' 如 果 v 点 为 终 止 点 则 跳 出 循 环 从 终 止 点 开 始, 根 据 dir (v') 的 纪 录, 找 到 最 优 路 径 种 子 点 填 充 算 法 流 程 对 于 种 子 点 填 充 算 法, 我 们 可 以 用 堆 栈 的 方 法, 对 边 界 定 义 的 区 域 进 行 填 充 基 本 流 程 是 : 试 ; 种 子 点 像 素 压 入 堆 栈 ; 当 堆 栈 非 空 时, 从 堆 栈 中 推 出 一 个 像 素, 并 将 该 像 素 设 置 成 所 要 的 值 ; 对 每 个 于 当 前 像 素 邻 接 的 得 四 连 通 或 八 连 通 像 素, 进 行 上 述 两 部 分 内 容 的 测 若 所 测 试 的 像 素 在 区 域 内 没 有 被 填 充 过, 则 将 该 像 素 压 入 堆 栈 ; (2) 类 协 作 图 179
187 6 分 割 算 法 的 设 计 与 实 现 mitkvolumetovolumefilter mitkwirecostfunction mitklivewireimagefilter mitkwirecostconcretefunction mitkgradientfilter 图 6-28 live wire 算 法 包 类 协 作 图 如 图 6-28 给 出 了 live wire 算 法 包 的 类 协 作 图 mitklivewireimagefilter 定 义 了 live wire 算 法 的 主 框 架 流 程, 也 就 是 一 个 动 态 规 划 过 程 ;mitkwirecostfunction 为 所 有 计 算 边 代 价 值 的 方 法 提 供 了 一 个 基 类 ;mitklivewirecostconcretefunction 提 供 了 一 种 计 算 边 代 价 值 的 方 法 ;mitkgradientfilter 用 来 计 算 图 像 的 梯 度 场 mitklivewireimagefilter 拥 有 指 向 mitkwirecostfunction 和 mitkgradientfilter 的 指 针 (3) 类 内 部 组 成 结 构 详 解 mitklivewirefunction <<uses>> +Initialize() +CostCompute() ComputeWireCost(); InitCostImage(); InitStack(); while(stack is not empty) DynamincFind(); BackFind(); mitklivewireimagefilter -m_function -m_gradientfilter +SetFunction() +SetGradientFilter() +Run() -ComputeWireCost() -InitCostImage() -InitStack() -DynamicFind() -BackFind() <<uses>> mitklivewireconcretefunction +Initialize() +CostCompute() mitkgradientfilter 图 6-29 类 内 部 组 成 结 构 详 解 180
188 6 分 割 算 法 的 设 计 与 实 现 如 图 6-29 给 出 了 live wire 算 法 包 的 内 部 组 成 结 构 mitklivewirefunction: 他 为 所 有 计 算 边 代 价 的 数 学 方 法 提 供 了 一 个 基 类 用 户 通 过 为 他 添 加 不 同 的 子 类 以 实 现 不 同 的 计 算 边 代 价 的 方 法 函 数 Initialize(): 主 要 做 一 些 初 始 化 工 作 函 数 CostCompute(): 计 算 每 条 边 的 代 价 值 这 两 个 函 数 都 为 虚 函 数, 由 子 类 重 载 mitklivewireconcretefunction: 由 mitklivewirefunction 继 承 而 来, 根 据 式 实 现 了 一 种 计 算 边 代 价 的 方 法 mitklivewireimagefilter: 依 据 图 6-27 定 义 了 live wire 主 框 架 流 程, 主 要 是 定 义 了 动 态 规 划 的 实 现 过 程 它 包 含 了 指 向 mitklivewirefunction 和 mitkgradientfilter 的 指 针 m_function 和 m_gradientfilter, 用 以 计 算 边 代 价 值 和 求 图 像 的 梯 度 场 函 数 SetFunction() 用 来 制 定 所 采 用 的 function, 这 些 function 都 是 mitklivewirefunction 的 子 类 函 数 SetGradientFilter() 用 来 指 定 所 采 用 的 求 梯 度 的 方 法 函 数 Run() 为 由 用 户 调 用, 用 来 启 动 mitklivewireimagefilter, 在 这 个 函 数 中, 一 次 调 用 与 图 6-27 对 应 的 各 个 虚 函 数, 以 实 现 动 态 规 划 过 程 函 数 ComputeWireCost() 用 来 计 算 边 代 价 值 : 首 先 用 m_gradientfilter 计 算 图 像 的 梯 度 场, 然 后 用 m_function 计 算 各 个 边 的 代 价 值 函 数 InitCostImage() 用 来 初 始 化 代 价 图 像 函 数 InitStack() 用 来 初 始 化 堆 栈 函 数 DynamicFind() 用 来 实 现 动 态 规 划 的 过 程 函 数 BackFind() 从 终 止 点 向 前 回 溯, 找 到 最 优 路 径 181
189 6 分 割 算 法 的 设 计 与 实 现 mitkseedfillfilter +SetSeedPoint() +Run() 图 6-30 如 图 对 于 种 子 点 填 充 算 法, 用 类 mitkseedfill 来 实 现 输 入 图 像 是 一 幅 二 值 图, 轮 廓 线 上 的 点 非 零, 其 他 点 为 零 点 进 行 填 充, 输 出 图 像 也 为 二 值 图, 被 填 充 的 部 分 值 为 非 零, 其 他 部 分 为 零 值 live wire 分 割 结 果 图 6-31 给 定 起 始 点 和 终 止 点 下 找 出 的 轮 廓 线 图 6-31 给 出 了 在 如 图 所 示 的 起 始 点 和 终 止 点 下,live wire 算 法 找 出 的 轮 廓 线 ; 182
190 6 分 割 算 法 的 设 计 与 实 现 图 6-32 用 live wire 算 法 生 成 的 几 条 轮 廓 线 图 6-32 给 出 了 几 条 用 live wire 算 法 找 出 的 轮 廓 线 ; 183
191 6 分 割 算 法 的 设 计 与 实 现 图 6-33 对 轮 廓 线 进 行 种 子 点 填 充 后 的 分 割 结 果 图 6-33 给 出 了 种 子 点 选 在 各 轮 廓 线 间 时 的 种 子 点 填 充 结 果 6.6 Fast Marching 算 法 在 MITK 中 的 实 现 原 理 概 述 Fast Marching 算 法 是 一 种 基 于 几 何 形 变 模 型 的 医 学 影 像 分 割 方 法 几 何 形 变 模 型 是 由 Irasel 的 Caselles 等 [10] 和 Florida 的 Malladi 等 [11] 提 出 的, 这 些 模 型 的 理 论 基 础 是 曲 线 演 化 (curve evolution) 理 论 [12] [13] [14] [15] 和 水 平 集 (level set) 方 法 [16] [17] 几 何 形 变 模 型 的 基 本 思 想 是 将 曲 线 的 形 状 变 化 用 曲 线 演 化 理 论 来 描 述, 即 用 曲 率 或 法 向 量 等 几 何 度 量 表 示 曲 线 或 曲 面 演 化 的 速 度 函 数, 并 将 速 度 函 数 与 图 像 数 据 关 联 起 来, 从 而 使 曲 线 在 对 象 边 缘 处 停 止 演 化 由 于 曲 线 的 演 化 与 参 数 无 关, 几 何 形 变 模 型 能 被 自 动 处 理 对 象 拓 扑 的 变 化, 演 化 过 程 中 的 184
192 6 分 割 算 法 的 设 计 与 实 现 曲 线 和 曲 面 只 能 被 隐 含 表 示 为 一 个 更 高 维 函 数 的 一 个 水 平 集, 因 此 曲 线 演 化 过 程 采 用 了 水 平 集 方 法 加 以 实 现 另 一 种 跟 踪 运 动 的 曲 线 或 曲 面 的 方 法 是 固 定 曲 线 或 者 曲 面 的 演 化 方 向, 也 就 是 说, 曲 线 或 者 曲 面 只 能 收 缩 或 者 扩 张, 这 就 是 Fast Marching 方 法 (1) 曲 线 进 化 理 论 曲 线 演 化 理 论 的 研 究 目 的 是 利 用 几 何 度 量 描 述 曲 线 的 形 状 随 着 时 间 的 变 化, 几 何 度 量 如 单 位 法 向 量 和 曲 率, 而 不 是 一 些 与 参 数 有 关 的 数 量 ( 如 任 意 参 数 曲 线 的 导 数 ), 如 图 6-34 所 示 试 想 一 条 运 动 中 的 曲 线 X ( s, t) = [ X ( s, t), Y ( s, t)], 其 中 s 是 任 意 参 数,t 是 时 间 变 量,N 是 向 内 的 单 位 法 向 量,k 是 曲 率 曲 线 沿 着 法 线 方 向 的 形 变 可 用 以 下 偏 微 分 方 程 描 述 : X t = V ( k) N (6-10) 图 6-34 曲 线 演 化 其 中 V(k) 被 称 为 速 度 函 数 (speed function), 因 为 它 决 定 了 曲 线 演 化 的 速 度 注 意, 曲 线 沿 任 意 方 向 的 运 动 都 可 以 重 新 参 数 化 后 再 用 公 式 (6-10) 表 示 [15] 此 事 实 的 直 观 解 释 就 是, 切 线 方 向 的 形 变 只 影 响 曲 线 的 参 数, 而 不 改 变 形 状 和 几 何 特 征 在 曲 线 形 变 理 论 中 研 究 得 最 多 的 是 曲 率 形 变 (curvature deformation) 和 常 数 形 变 (constant deformation) 曲 率 形 变 用 一 个 几 何 热 方 程 (geometric heat equation) 表 示 : X = α kn (6-11) t 其 中 α 是 一 个 正 常 数 此 方 程 将 使 一 条 曲 线 平 滑 并 最 终 收 缩 为 一 个 圆 点 使 用 曲 率 形 变 的 效 果 类 似 于 在 参 数 形 变 模 型 中 使 用 弹 性 内 力 常 数 形 变 表 示 为 : 185
193 6 分 割 算 法 的 设 计 与 实 现 X t = V N 0 (6-12) 其 中 V 0 是 决 定 形 变 速 度 和 方 向 的 系 数 常 数 形 变 所 起 的 作 用 与 参 数 形 变 模 型 中 的 压 力 相 同 曲 率 形 变 和 常 数 形 变 的 性 质 是 互 补 的, 曲 率 形 变 通 过 平 滑 曲 线 除 去 了 奇 异 点, 而 常 数 形 变 可 以 在 初 始 平 滑 曲 线 上 创 造 奇 异 点 (2) Fast Marching 方 法 考 虑 一 种 界 面 运 动 的 特 殊 情 况, 那 就 是 界 面 的 运 动 速 度 F>0 或 者 F<0, 也 就 是 说 运 动 的 界 面 或 者 只 能 进 行 扩 张, 或 者 只 能 进 行 收 缩 如 图 6-35 所 示, 图 中 的 F>0, 运 动 的 界 面 蓝 色 的 圆 圈 只 能 向 外 扩 张, 而 不 能 收 缩 图 6-35 运 动 的 界 面 假 定 T 是 界 面 经 过 一 个 指 定 点 (x, y) 的 时 间, 这 样 T 就 满 足 如 下 的 方 程 : T F = 1 (6-13) 这 个 公 式 简 单 的 说 明 了 到 达 时 间 的 梯 度 和 界 面 的 运 动 速 度 成 反 比 从 广 义 上 说, 有 两 种 方 法 可 以 用 来 近 似 运 动 的 界 面 随 时 间 变 化 的 位 置 : 一 种 是 通 过 迭 代 和 数 字 近 似 公 式 微 商 来 解 决 ; 另 一 种 是 构 建 公 式 (6-13) 中 到 达 时 间 T 的 解 决 方 案 而 Fast Marching 方 法 依 赖 于 后 一 种 方 法 Fast Marching 算 法 开 发 包 的 设 计 与 实 现 (1) Fast Marching 的 算 法 流 程 公 式 (6-13) 是 著 名 的 Eikonal 方 程 的 一 种 形 式,Sethian 在 文 [19] 中 指 出, 要 得 186
194 6 分 割 算 法 的 设 计 与 实 现 到 公 式 6-14) 中 的 到 达 时 间 T, 等 价 于 求 解 下 面 的 二 次 方 程, 有 关 它 的 具 体 的 近 似 解 决 方 案 请 参 见 文 [20] [21] 2 2 x + x max( D ij T,0) + min( D ij T,0) + max( D,0) min(,0) y 2 + y 2 ij T + D ij T 这 里 D 和 D + 分 别 是 后 向 差 分 和 前 向 差 分 算 子 1/2 = 1/ F i, j (6-14) + x Txy (, + 1) Txy (, ) x Txy (, ) Txy (, 1) D T= & 2 D T= 2 + y Tx ( 1, y) Txy (, ) y + Txy (, ) Tx ( 1, y) D T= & T 2 D = 2 (6-15) 下 面 给 出 分 割 算 法 包 中 该 算 法 的 主 框 架 流 程 187
195 6 分 割 算 法 的 设 计 与 实 现 图 6-36 Fast Marching 算 法 主 框 架 流 程 该 算 法 的 输 入 为 一 幅 速 度 图 像, 该 图 像 上 每 个 像 素 点 的 值 代 表 轮 廓 线 在 该 点 的 扩 散 速 度 ; 输 出 为 一 幅 时 间 图, 该 图 像 上 每 个 像 素 点 的 值 代 表 轮 廓 线 扩 散 到 该 点 所 需 的 时 间 如 图 6-36 所 示, 算 法 主 要 分 为 两 大 模 块 : 初 始 化 部 分 和 循 环 部 分 初 始 化 部 分 : 设 置 活 动 点 : 活 动 点 就 是 所 有 网 格 点 中 时 间 T 固 定 的 点 可 以 有 多 种 指 定 方 法, 在 分 割 算 法 包 目 前 提 供 的 算 法 中, 也 就 是 用 户 指 定 的 种 子 点, 时 间 T(x, y)=0 188
196 6 分 割 算 法 的 设 计 与 实 现 设 置 窄 带 : 窄 带 是 指 活 动 点 附 近 的 点 在 运 算 时 只 更 新 窄 带 中 的 点, 以 提 高 计 算 速 度 在 分 割 算 法 包 目 前 提 供 的 算 法 中, 也 就 是 所 有 种 子 点 的 邻 接 点, 时 间 T(x, y)= 1/F(x, y) 标 记 远 点 : 除 了 活 动 点 和 窄 带 点 外, 所 有 其 它 的 网 格 点 为 远 离 点,T(x, y)= TIME_MAX 循 环 部 分 : 循 环 结 束 条 件 : 可 以 有 多 种 结 束 条 件, 由 具 体 的 子 类 指 定 此 处 采 用 的 结 束 条 件 为, 当 当 前 像 素 的 时 间 值 大 于 给 定 的 停 止 时 间 值 时 停 止 循 环 ; 处 理 当 前 点 : 具 体 处 理 方 法 由 子 类 定 义 本 节 中 该 步 骤 的 主 要 工 作 是, 从 最 小 化 堆 栈 中 取 出 栈 顶 元 素, 检 查 它 的 合 法 性, 并 获 得 它 的 每 个 邻 点 处 理 邻 域 点 : 更 新 当 前 点 的 每 个 邻 点 的 值, 具 体 更 新 方 法 由 子 类 指 定 (2) 类 协 作 图 分 割 算 法 开 发 包 中 Fast Marching 算 法 部 分 主 要 类 的 协 作 图 如 下 : mitkvolumetovolumefilter mitkfastmarchingtemplate mitkfastmarchingtemplatefunction mitkfastmarchingfunction mitkfastmarchingimagefilter mitkminimumheap 图 6-37 Fast Marching 算 法 部 分 主 要 类 协 作 图 mitkfastmarchingtemplate 定 义 了 图 6-37 所 示 的 Fast Marching 算 法 的 主 要 流 程 框 架 ;mitkfastmarchingimagefilter 是 mitkfastmarchingtemplate 的 一 个 子 类, 它 定 义 了 Fast Marching 主 框 架 的 一 种 具 体 实 现 方 法 ; mitkfastmarchingtemplatefunction 为 解 方 程 6-13 的 所 有 方 法 提 供 了 一 个 基 类 ; mitkfastmarchingfunction 定 义 了 一 个 解 方 6-13 的 一 种 具 体 方 法 ; mitkminimumheap 是 这 部 分 中 很 重 要 的 一 个 类, 它 定 义 了 一 个 最 小 堆 栈, 它 自 189
197 6 分 割 算 法 的 设 计 与 实 现 动 对 栈 内 的 所 有 元 素 排 序, 以 保 证 每 次 弹 出 的 元 素 都 是 栈 内 最 小 的 元 素 (3) 类 内 部 组 成 结 构 详 解 mitkminimumheap -m_nodelink +top() +pop() +push() +empty() <<uses>> mitknodelink 图 6-38 mitkminimumheap 主 要 接 口 图 6-38 给 出 了 类 mitkminimumheapde 的 内 部 结 构 值 mitknodelink: 定 义 了 一 个 链 表, 它 的 每 个 节 点 包 含 了 该 点 的 坐 标 值 和 时 间 mitkminimumheap: 它 定 义 了 一 个 最 小 化 堆 栈, 自 动 对 栈 内 元 素 排 序, 保 证 栈 顶 元 素 为 站 内 最 小 的 元 素, 堆 栈 内 部 的 排 序 过 程 对 用 户 来 说 是 不 可 见 的 : m_nodelink 是 一 个 mitknodelink 类 型 的 成 员 变 量, 对 栈 中 的 所 有 元 素 都 是 以 链 表 形 式 存 贮 的 函 数 push(): 将 一 个 节 点 压 入 堆 栈 中 ; 函 数 top(): 用 来 得 到 堆 栈 中 值 最 小 的 节 点 即 栈 顶 元 素 ; 函 数 pop(): 将 栈 顶 元 素 弹 出 栈 ; 函 数 empty(): 用 来 判 断 堆 栈 是 否 为 空 图 4.6 给 出 了 fast marching 算 法 包 中 其 他 类 的 内 部 结 构 mitkfastmarchingtemplate 定 义 了 算 法 的 主 要 流 程, 它 的 成 员 函 数 分 别 对 应 于 图 4.3 中 的 每 一 个 步 骤 函 数 SetActivePoints(): 用 来 为 算 法 设 置 活 动 点 ; 函 数 SetTrialPoints(): 用 来 为 算 法 设 置 窄 带 ; 函 数 Run(): 由 用 户 调 用, 用 于 启 动 fast marchig 算 法 如 图 6-39 所 示, 在 Run() 函 数 中, 他 分 别 调 用 虚 函 数 ProcessActivePoints() ProcessTrialPoints() 190
198 6 分 割 算 法 的 设 计 与 实 现 ProcessFarpoints() Halt() GetCurrentNode() ProcessCurrentNode() 和 Update() 这 些 虚 函 数 在 mitkfastmarchingtemplate 中 没 有 定 义, 子 类 通 过 重 载 这 些 函 数 来 来 完 成 改 算 法 的 一 种 实 现 ; mitkfastmarchigntemplatefunction 为 所 有 的 function 提 供 了 一 个 基 类 函 数 Update(): 用 来 更 新 当 前 点 的 时 间 值, 为 虚 函 数, 由 子 类 重 载, 以 实 现 一 种 数 学 方 法 ; mitkfastmarchingfunction 为 mitkfastmarchingtemplatefunction 的 一 个 子 类 函 数 Update(): 定 义 了 一 种 解 方 程 6-13 的 方 法 mitkfastmarchingimagefilter 是 mitkfastmarchingtemplate 的 一 个 子 类, 重 载 了 它 的 一 部 分 虚 函 数 : m_nodeheap: 是 一 个 mitkminimumheap 类 型 的 指 针 191
199 6 分 割 算 法 的 设 计 与 实 现 mitkfastmarchingtemplate +SetActivePoints() +SetTrialPoints() +Run() -virtual ProcessActivePoints() -virtual ProcessTrialPoints() -virtual ProcessFarPoints() -virtual Halt() -virtual GetCurrentNode() -virtual ProcessCurrentNode() -virtual UpdateValue() <<uses>> this->processactivepoints(); this->processtrialpoints(); this->processfarpoints(); while(!this->halt()) currentnode = this->getcurrentnode(); this->processcurrentnode(); for(each neighbor of currentnode) UpdateValue(); mitkminimumheap mitkfastmarchingimagefilter -m_nodeheap -m_function +SetFunction() -virtual ProcessActivePoints() -virtual ProcessTrialPoints() -virtual ProcessFarPoints() -virtual Halt() -virtual GetCurrentNode() -virtual ProcessCurrentNode() -virtual UpdateValue() <<uses>> mitkfastmarchingtemplatefunction +virtual Update() mitkfastmarchingfunction +Update() 图 6-39 m_function: 是 一 个 mitkfastmarchingtemplatefunction 类 型 的 指 针 SetFunction(): 用 来 为 m_function 指 定 一 个 function 类 型, 它 必 须 是 mitkfastmarchingtemplatefunction 的 子 类 ; ProcessActivePoints(): 将 活 动 点 的 T(x, y) 值 设 为 零 ProcessTrialPoints(): 将 窄 带 内 点 的 T(x, y) 值 设 为 1/F(x, y) ProcessFarPoints() : 将 既 非 活 动 点 也 非 窄 带 内 的 点 的 T(x, y) 值 设 为 TIME_MAX Halt(): 当 m_nodeheap->empty() == true 即 最 小 堆 栈 为 空 时, 循 环 结 束 192
200 6 分 割 算 法 的 设 计 与 实 现 GetCurrentNode(): 返 回 m_nodeheap 的 栈 顶 元 素 ProcessCurrentNode(): 更 新 当 前 点, 如 果 当 前 点 的 值 大 于 已 经 设 定 的 停 止 值, 跳 出 循 环 UpdateValue(): 调 用 m_function, 计 算 各 邻 域 点 的 值 如 图 6-40 所 示, 分 割 算 法 开 发 包 最 终 提 供 给 用 户 的 类 是 mitkfastmarchingpacketfilter, 它 里 面 封 装 了 三 个 类 :mitkspeedfilter 将 用 户 输 入 的 原 始 图 像 根 据 它 们 的 梯 度 图 像 转 化 为 速 度 图 像 ;mitkfastmarchingimagefilter 对 速 度 图 像 执 行 FastMarching 算 法 ;mitktimetransferfilter 将 时 间 图 像 转 化 为 要 输 出 的 分 割 结 果 mitkfastmarchingpacketfilter -m_speedfilter -m_fastmarchingfilter -m_timetransfer <<uses>> +Run() <<uses>> <<uses>> m_speedfilter->setinput(this->getinput); m_fastmarchingfilter->setinput(m_speedfilter->getoutput); m_timetransfer->setinput(m_fastmarchingfilter->getoutput); this->getoutput() = m_timetransfer->getoutput(); mitktimetransferfilter mitkfastmarchingimagefilter mitkspeedimagefilter <<uses>> mitkgradientfilter -m_gradientfilter 图 6-40 封 装 后 的 Fast Marching Filter 193
201 6 分 割 算 法 的 设 计 与 实 现 Fast Marching 分 割 结 果 图 6-41 Fast Marching 分 割 结 果 一 194
202 6 分 割 算 法 的 设 计 与 实 现 图 6-42 Fast Marching 插 件 分 割 结 果 6.7 Level Set 算 法 在 MITK 中 的 实 现 原 理 概 述 下 面 介 绍 曲 线 演 化 的 水 平 集 实 现 方 法 水 平 集 方 法 用 于 解 决 拓 扑 的 自 动 变 化, 它 也 提 供 了 几 何 形 变 模 型 的 数 学 实 现 基 础 Osher and Sethian 最 先 将 水 平 集 方 法 用 于 实 现 曲 线 的 演 化 [16] [21] [22] 图 6-43 将 一 条 曲 线 嵌 入 为 一 个 水 平 集 195
203 6 分 割 算 法 的 设 计 与 实 现 在 水 平 集 方 法 中, 曲 线 隐 含 表 示 为 一 个 更 高 维 曲 面 函 数 的 一 个 水 平 集 (level set), 该 高 维 函 数 称 为 水 平 集 函 数 (level set function), 其 定 义 域 通 常 为 图 像 空 间 水 平 集 是 由 那 些 水 平 集 函 数 值 相 等 的 点 组 成 的 集 合 图 6-43 所 示 为 一 条 嵌 入 为 零 水 平 集 的 曲 线 与 参 数 形 变 模 型 不 同, 水 平 集 方 法 没 有 跟 踪 不 同 时 刻 曲 线 的 运 动 情 况, 而 是 在 固 定 坐 标 系 中 更 新 不 同 时 刻 下 的 水 平 集 函 数 来 模 拟 曲 线 的 演 化 图 6-43 中, 一 条 圆 形 曲 线 被 嵌 入 为 一 个 水 平 集 函 数 的 零 水 平 集, 当 圆 扩 大 时, 水 平 集 函 数 相 应 地 发 生 了 变 化, 但 新 曲 线 仍 对 应 为 新 水 平 集 函 数 的 零 水 平 集 在 嵌 入 的 曲 线 改 变 其 拓 扑 时, 水 平 集 函 数 依 然 保 持 是 一 个 有 效 的 函 数, 见 图 6-44 图 6-44 曲 线 的 运 动 对 应 着 水 平 集 函 数 Φ 的 变 化 图 6-45 水 平 集 分 裂 为 两 条 曲 线 时 水 平 集 函 数 仍 保 持 有 效 给 定 一 个 水 平 集 函 数 Φ ( x, y, t), 其 零 水 平 集 对 应 为 轮 廓 曲 线 X ( s, t), 则 有 : Φ( X ( s, t), t) = 0 (6-16) 使 用 链 规 则 令 此 方 程 对 t 求 导, 可 得 到 : 196
204 6 分 割 算 法 的 设 计 与 实 现 φ X + φ t t = 0 (6-17) 其 中 φ 代 表 φ 的 梯 度 不 妨 假 设 在 零 水 平 集 内 φ 是 负 的, 在 外 是 正 的 因 此, 水 平 集 曲 线 的 向 内 的 单 位 法 向 量 可 以 表 示 为 : φ N = (6-18) φ 因 此 有 : φ = V (k) φ t 其 中 零 水 平 集 对 应 曲 线 的 曲 率 k 为 : (6-19) 2 2 φ φxxφ y 2φ xφ φ + φ yyφx k = = (6-20) φ ( φ + φ 2 x y xy 2 3/ 2 y ) 为 实 现 几 何 形 变 模 型 需 要 解 决 三 个 问 题 : (1) 初 始 化 函 数 φ ( x, y, t = 0) 的 构 造, 必 须 使 其 零 水 平 集 对 应 于 初 始 轮 廓 的 位 置 通 常 做 法 是 设 置 φ ( x, y,0) = D( x, y), 其 中 D ( x, y) 是 从 每 个 网 格 点 到 零 水 平 集 的 符 号 距 离 (2) 速 度 函 数 的 设 计 :Caselles 等 [Caselles 1993] 和 Malladi 等 [Malladi 1995] 提 出 的 几 何 形 变 轮 廓 采 用 如 下 公 式 : φ = c( k + V0) φ, t 1 c = 1 + ( G * I ) (6-21) σ 正 V 0 使 曲 线 收 缩, 负 V 0 使 曲 线 扩 展 曲 线 演 化 通 过 一 个 乘 数 停 止 项 c 与 图 像 数 据 关 联 此 方 法 对 于 分 割 对 比 度 高 的 对 象 效 果 不 错 但 是, 当 对 象 的 边 缘 不 清 晰 或 者 有 狭 窄 缺 口 (gap) 时, 几 何 形 变 模 型 可 能 出 错, 而 且 一 旦 曲 线 跨 过 边 缘, 它 不 会 被 拉 回 到 正 确 的 边 缘 位 置 为 解 决 后 一 个 问 题,Caselles 等 [23] [24] 和 Kichenassamy 等 [25] [26] 使 用 一 个 能 量 最 小 化 公 式 设 计 速 度 函 数, 通 过 在 速 度 函 数 中 加 入 额 外 的 停 止 项, 能 使 模 型 即 使 跨 过 边 缘 也 能 被 拉 回 来, 而 且 也 不 会 跨 过 边 缘 上 的 小 缺 口 197
205 6 分 割 算 法 的 设 计 与 实 现 (3) 常 数 形 变 通 常 被 用 于 解 决 大 尺 度 的 形 变 和 发 现 窄 边 缘 锯 齿 (indentation) 和 突 起 (protrusion) 但 是, 常 数 形 变 使 曲 线 可 能 从 初 始 光 滑 的 零 水 平 集 形 变 成 锋 利 的 角 点 (corner) 一 旦 出 现 角 点, 由 于 其 法 向 量 方 向 有 二 义 性, 如 何 继 续 形 变 就 不 明 确 了 解 决 二 义 性 的 方 法 有 Sethian 提 出 的 熵 条 件 (entropy condition) [Sethian 1982] 和 由 Osher and Sethian 提 出 的 entropy satisfying 数 值 方 法 [Osher 1988] Level Set 方 法 自 提 出 以 来, 已 在 图 像 处 理 和 计 算 机 视 觉 等 领 域 得 到 广 泛 的 应 用 : 如 Sethian 和 Osher [16] 等 用 Level Set 去 除 图 像 噪 声 ;Malladi [19] 将 其 应 用 于 图 像 分 割, 特 别 是 医 学 图 像 的 分 割 和 重 建 中 ;Bertalmio [27] 等 将 Level Set 应 用 于 图 像 变 形 和 破 损 图 像 修 复 中 ;Masouri [28] 将 Level Set 运 用 于 运 动 目 标 跟 踪 领 域 ; Parogios 和 Deriche [29] 用 Level Set 方 法 进 行 纹 理 分 割 以 及 运 动 目 标 分 割 和 跟 踪 ; Samson [30] 等 人 用 Level Set 方 法 实 现 图 像 分 类 等 level set 算 法 开 发 包 的 设 计 与 实 现 (1) Level Set 算 法 流 程 Level Set 方 法 的 基 本 思 想 是 将 平 面 闭 合 曲 线 隐 含 的 表 达 为 二 维 曲 面 函 数 的 水 平 集, 即 具 有 相 同 函 数 值 的 点 集, 通 过 Level Set 函 数 曲 面 的 进 化 隐 含 的 求 解 曲 线 的 运 动 尽 管 这 种 转 化 使 得 问 题 在 形 式 上 变 得 复 杂, 但 在 问 题 的 求 解 上 带 来 很 多 优 点, 其 最 大 的 优 点 在 于 曲 线 的 拓 扑 变 化 能 够 得 到 很 自 然 的 处 理, 而 且 可 以 获 得 唯 一 的 满 足 熵 条 件 的 弱 解 ; 另 外, 界 面 的 几 何 属 性 ( 法 向 量 曲 率 等 ) 可 以 很 容 易 的 得 到 ; 最 后, 这 种 思 想 可 以 不 做 任 何 改 变 推 广 到 高 维 空 间 Level Set 函 数 的 演 化 满 足 如 下 的 基 本 方 程 : Φ + F Φ = 0 t (6-22) 其 中 Φ 为 Level Set 函 数, 其 零 水 平 集 表 示 目 标 轮 廓 曲 线, 即 : Γ () t = x Φ ( x,) t = 0 (6-23) Φ 表 示 Level Set 函 数 的 梯 度 范 数 ;F 为 曲 面 法 线 方 向 上 的 速 度 函 数, 控 制 曲 线 的 运 动, 一 般 F 包 括 三 项 : 与 图 像 有 关 的 项 ( 如 梯 度 信 息 等 ), 与 曲 线 的 几 何 形 状 有 关 的 项 ( 如 曲 线 的 曲 率 等 ) 以 及 附 加 的 演 化 项 (Additional propagation terms) 198
206 6 分 割 算 法 的 设 计 与 实 现 Φ t Φ t + 1 t i, j i, j Δt Φ (6-24) ( ) t t t t Φ Φ Φ Φ Φ Φ 2 i+ 1, j i 1, j ij, + 1 ij, 1 Φ + + x y 2Δx 2Δy (6-25) 我 们 可 以 得 到 下 面 的 离 散 方 程 : ( ) 2 2 t t t t + Φi+ 1, j Φi 1, j Φi, j+ 1 Φi, j 1 = + + Δt t 1 t Φi, j Φ F K i, j 2Δx 2Δy (6-26) 水 平 集 方 法 虽 然 在 处 理 拓 扑 变 化 方 面 具 有 参 数 形 变 模 型 所 无 可 比 拟 的 优 势, 但 是 由 于 它 是 将 问 题 转 化 到 高 一 维 空 间 来 处 理, 必 然 会 带 来 计 算 量 方 面 的 大 大 增 加 窄 带 (Narrow Band) 算 法 是 水 平 集 方 法 实 现 中 常 用 的 算 法, 其 主 要 思 想 是 只 更 新 零 水 平 集 附 近 点 的 水 平 集 函 数 值, 而 无 须 计 算 整 个 图 像 空 间 中 每 一 点 的 水 平 集 函 数 值, 以 此 来 提 高 水 平 集 方 法 的 计 算 效 率 如 图 6-46 所 示, 左 边 的 图 表 示 设 置 的 窄 带, 在 这 个 窄 带 以 外 的 像 素 点, 就 不 需 要 再 进 行 符 号 距 离 的 计 算, 从 而 大 大 的 减 少 了 计 算 量 随 着 时 间 演 化, 当 我 们 跟 踪 的 前 端 演 化 到 图 6-46 右 边 的 情 况 时, 也 就 是 前 端 的 部 分 像 素 点 已 经 或 者 非 常 接 近 窄 带 的 边 缘 时, 要 停 止 演 化, 要 进 行 重 新 初 始 化 操 作 和 重 新 设 置 窄 带 宽 度 对 于 窄 带 算 法 而 言, 如 何 选 取 窄 带 宽 度 成 为 提 高 窄 带 算 法 效 率 的 关 键, 过 宽 必 然 加 大 计 算 量, 过 窄 必 然 影 响 水 平 集 函 数 值 的 差 分 迭 代 199
207 6 分 割 算 法 的 设 计 与 实 现 图 6-46 窄 带 算 法 示 意 图 开 始 初 始 化 初 始 化 是 否 满 足 停 止 条 件? 循 环 计 算 变 化 应 用 更 新 后 处 理 后 处 理 结 束 图 6-47 Level Set 算 法 主 框 架 图 6-47 Level Set 算 法 主 框 架 给 出 了 level set 算 法 的 主 框 架 流 程 该 算 法 的 输 200
208 6 分 割 算 法 的 设 计 与 实 现 入 数 据 为 距 离 图, 距 离 图 是 这 样 定 义 的, 给 定 一 条 轮 廓 线, 图 像 上 每 个 像 素 的 值 表 示 该 点 到 轮 廓 线 上 最 近 一 点 的 距 离, 如 果 该 点 在 轮 廓 线 外 部, 其 值 为 正, 如 果 在 内 部, 其 值 为 负 输 出 数 据 仍 为 一 幅 距 离 图, 只 是 零 水 平 线 已 经 被 更 新 过 该 算 法 主 要 分 为 三 个 模 块 : 初 始 化 部 分 : 一 般 在 这 个 部 分 主 要 对 输 入 图 像 进 行 处 理, 找 出 零 水 平 线 和 窄 带, 初 始 化 的 方 法 有 很 多 种, 可 以 通 过 不 同 的 子 类 来 定 义 循 环 部 分 : 这 部 分 一 般 包 含 三 块 循 环 终 止 条 件 : 一 般 用 循 环 次 数 来 定 义 当 循 环 次 数 小 于 指 定 循 环 次 数 时 继 续 循 环, 大 于 时 终 止 循 环 这 个 条 件 由 子 类 具 体 定 义 计 算 窄 带 内 的 点 的 更 新 值 : 应 用 数 学 公 式, 更 新 窄 带 内 每 一 点 的 值 更 新 窄 带 : 根 据 上 一 步 计 算 出 来 的 更 新 值, 重 新 计 算 零 水 平 线 和 窄 带, 具 体 方 法 由 子 类 定 义 后 处 理 部 分 : 这 一 部 分 可 有 可 无, 如 果 需 要 的 话, 可 以 在 子 类 中 定 义 (2) 类 协 作 图 mitkvolumetovolumefilter mitkfinitedifferencefunction mitkfinitedifferenceimagefilter mitklevelsetfunction mitklevelsetimagefilter mitkzerocrossingimagefilter mitkdistancemapfilter 图 6-48 level set 算 法 包 类 协 作 图 如 图 6-48 给 出 了 level set 算 法 包 中 各 类 的 协 作 图 mitkfinitedifferenceimagefilter 依 据 图 6-48 定 义 了 level set 算 法 的 主 框 架 流 程 事 实 上, 它 可 作 为 所 有 有 限 元 差 分 算 法 的 基 类, 为 他 们 提 供 一 个 统 一 的 框 架 类 mitkfinitedifferencefunction 是 一 个 纯 虚 类, 他 为 实 现 level set 算 法 的 各 种 数 学 方 法 提 供 了 一 个 基 类 mitkfinitedifferenceimagefilter 中 保 存 了 一 个 指 向 201
209 6 分 割 算 法 的 设 计 与 实 现 mitkfinitedifferencefunction 的 指 针 类 mitklevelsetimagefilter 定 义 了 一 个 level set 算 法 的 具 体 实 现, 他 拥 有 指 向 mitkzerocrossingimagefilter 和 mitkdistancemapfilter 的 指 针, 用 以 实 现 零 水 平 集 的 查 找 和 距 离 变 换 mitklevelsetfunction 定 义 了 一 种 依 据 公 式 4.17 给 出 了 level set 算 法 中 所 用 到 的 数 学 方 法 的 一 种 实 现 (3) 类 内 部 组 成 结 构 详 解 mitkfinitedifferenceimagefilter -m_function +Run() +SetFunction() -virtual Initialize() -virtual Halt() -virtual CalculateChange() -virtual ApplyUpdate() +virtual PostProcess() mitklevelsetimagefilter -m_zerocrossingfilter -m_distancefilter +SetZeroCrossingFilter() +SetDistanceFilter() -virtual Initialize() -virtual Halt() -virtual CalculateChange() -virtual ApplyUpdate() +virtual PostProcess() -UpdateActivePoints() -UpdateNarrowBand() <<uses>> <<uses>> this->initialize(); while(!this->halt()) this->calculatechange(); this->applyupdate(); this->postprocess(); mitkfinitedifferencefunction +SetInput() +virtual Initialize() +virtual Update() mitklevelsetfunction <<uses>> -m_distancefilter -m_gausefilter +virtual Initialize() +virtual Update() <<uses>> mitkzerocrossingimagefilter mitkgausefilter mitkgradientfilter <<uses>> mitkdistancemapfilter 图 6-49 level set 算 法 包 内 部 组 成 结 构 202
210 6 分 割 算 法 的 设 计 与 实 现 如 图 6-49 给 出 了 level set 算 法 包 的 内 部 组 成 结 构 : mitkfinitedifferenceimagefilter 给 出 了 level set 算 法 的 主 框 架 : 函 数 Run(): 为 外 部 可 见 的, 用 来 启 动 level set 算 法 在 Run 函 数 中 依 次 调 用 虚 函 数 Initialize() Halt() CalculateChange() ApplyUpdate() 和 PostProcess(), 这 些 函 数 对 应 于 图 6-47 中 的 各 个 步 骤, 在 mitkfinitdifferenceimagefilter 中 没 有 定 义 他 们, 子 类 通 过 重 载 各 个 虚 函 数, 可 以 完 成 不 同 的 实 现 方 法 函 数 SetFunction() : 指 定 所 采 用 的 function 的 类 型, 他 们 必 须 是 mitkfinitedifferencefunction 的 子 类 ; 类 mitkfinitedifferencefunction 为 level set 算 法 所 采 用 的 数 学 方 法 提 供 了 一 个 基 类, 他 根 据 输 入 数 据, 计 算 每 个 像 素 的 更 新 值 虚 函 数 Initialize() 和 Update() 由 子 类 具 体 定 义 函 数 SetInput():mitkFinitDifferenceFunction 的 输 入 数 据 是 图 像 的 原 始 数 据, 由 函 数 SetInput() 指 定 ; mitkzerocrossingimagefilter 定 义 了 一 种 求 零 水 平 线 的 方 法 他 的 输 入 是 距 离 图, 其 中 包 含 了 一 条 零 水 平 线, 但 这 条 水 平 线 是 隐 含 的, mitkzerocrossingimagefilter 的 作 用 就 是 找 到 这 条 零 水 平 线, 将 水 平 线 上 所 有 的 点 赋 零 值, 并 写 到 输 出 的 距 离 图 像 中 mitkdistancemapfilter 的 作 用 是 将 的 二 值 图 转 化 为 距 离 图, 他 的 输 入 是 一 幅 二 值 图, 二 值 图 的 边 界 为 给 定 的 轮 廓 线, 轮 廓 线 内 部 的 值 为 零, 轮 廓 线 外 部 的 值 为 非 零 输 出 是 一 幅 距 离 图, 轮 廓 线 上 的 距 离 值 为 零, 轮 廓 线 内 部 的 距 离 值 为 负, 轮 廓 线 外 部 的 距 离 值 为 正 mitklevelsetimagefilter 定 义 了 一 种 level set 算 法 的 具 体 实 现 m_zerocrossingfilter: 是 一 个 mitkzerocrossingimagefilter 类 型 的 指 针, 用 来 查 找 令 水 平 线 ; m_distancefilter: 是 一 个 mitkdistancemapfilter 类 型 的 指 针, 用 来 进 行 距 离 变 换 ; 函 数 SetZeroCrossingFilter(): 用 来 指 定 m_zerocrossingfilter 的 类 型 ; 203
211 6 分 割 算 法 的 设 计 与 实 现 函 数 SetDistanceFilter(): 用 来 指 定 m_distancefilter 的 类 型 函 数 Initialize(): 做 一 些 初 始 化 工 作 : 调 用 m_zerocrossingfilter 在 输 入 的 距 离 图 中 找 到 隐 含 的 水 平 线 ; 调 用 UpDateActivePoints() 函 数 处 理 活 动 点 ; 调 用 UpdateNarrowBand() 函 数 处 理 窄 带 中 的 点 ; 函 数 Halt() 为 循 环 终 止 条 件, 在 本 节 的 定 义 为, 当 循 环 次 数 小 于 用 户 指 定 的 循 环 次 数 时 继 续 循 环, 大 于 时 终 止 ; 函 数 CalculateChange() 调 用 m_function 来 计 算 窄 带 内 每 个 点 的 更 新 值, 对 于 窄 带 内 的 点, 可 以 在 该 函 数 内 更 新 多 次, 更 新 的 次 数 由 用 户 指 定 ; 函 数 ApplyUpdate() 利 用 CalculateChange() 中 更 新 后 的 窄 带, 定 义 一 幅 二 值 图, 将 值 为 负 的 点 定 义 为 零 点, 将 非 负 的 点 定 义 为 非 零 点, 然 后 调 用 m_zerocrossingfilter 找 出 新 的 水 平 线, 调 用 UpdateActivePoints() 更 新 活 动 点, 调 用 UpdateNarrowBand() 更 新 窄 带 点 ; 函 数 PostProcess() 主 要 做 一 些 后 处 理 工 作, 将 更 新 后 的 距 离 图 按 照 给 定 的 格 式 写 入 输 出 数 据 ; mitklevelsetfunction 根 据 式 6-25 给 出 了 level set 算 法 所 采 用 的 一 种 数 学 方 法 m_gausefilter: 一 个 mitkgausefilter 类 型 的 指 针, 用 来 对 图 像 滤 波 ; m_gradientfilter: 一 个 GradientFilter 类 型 的 指 针, 用 来 计 算 图 像 的 梯 度 值 ; 函 数 Initialize(): 首 先 调 用 m_gausefilter 对 输 入 的 图 像 进 行 高 斯 滤 波, 再 调 用 m_gradientfilter 求 出 滤 波 后 的 图 像 的 梯 度 图 像 ; 函 数 Update() 应 用 式 6-20 求 出 每 个 像 素 点 更 新 后 的 值 ; 同 上 一 节 介 绍 的 fast marching 算 法 一 样,level set 算 法 开 发 包 同 样 为 那 些 对 该 算 法 不 太 了 解 的 人 提 供 了 一 个 封 装, 使 得 输 入 数 据 为 原 始 图 像 数 据, 输 出 数 据 为 分 割 好 的 图 像 如 图 6-50,mitkLevelSetPacket 是 封 装 好 的 level set 算 法 包, 它 包 含 了 指 向 mitkdistancefilter mitklevelsetimagefilter 和 mitkdistanceconvertfilter 的 指 针 m_distancefilter m_levelsetfilter 和 m_distanceconverfilter m_distancefilter 204
212 6 分 割 算 法 的 设 计 与 实 现 将 输 入 数 据 根 据 给 定 的 轮 廓 线 转 化 为 距 离 图 像, 作 为 m_levelsetimagefilter 的 输 入,m_DistanceConvertFilter 将 m_levelsetimagefilter 输 出 的 距 离 图 像 转 化 为 分 割 后 的 图 像 mitklevelsetpacket -m_distancefilter -m_levelsetfilter -m_distanceconvertfilter +Run() <<uses>> <<uses>> <<uses>> this->setfunction(); this->getfunction()->setinput(); m_distancefilter->setinput(this->getinput()); m_levelsetfilter->setinput(m_distancefilter->getoutput()); m_distanceconvertfilter->setinput(m_levelsetfilter->getoutput()); mitkdistancefilter mitklevelsetimagefilter mitkdistanceconvertfilter level set 分 割 结 果 图 6-50 图 4.19 封 装 后 的 level set 开 发 包 图 6-51 level set 插 件 分 割 结 果 205
213 6 分 割 算 法 的 设 计 与 实 现 图 6-51 以 系 统 界 面 的 形 式 给 出 了 level set 插 件 的 分 割 结 果, 其 中 fast marching 算 法 的 阈 值 设 为 750, level set 内 循 环 的 次 数 为 3, 外 循 环 的 次 数 为 3, 窄 带 的 宽 度 为 小 结 医 学 图 像 分 割 是 医 学 影 像 处 理 与 分 析 中 的 一 个 重 点 课 题 和 难 点, 分 割 的 结 果 是 三 维 可 视 化 和 定 量 分 析 等 后 续 处 理 的 基 础 本 章 针 对 医 学 图 像 中 的 一 些 主 流 分 割 算 法, 对 其 原 理 进 行 了 介 绍, 并 给 出 了 类 的 结 构 框 图 说 明 其 是 如 何 在 MITK 的 框 架 中 实 现 的 本 章 介 绍 的 几 个 算 法 各 有 特 点, 就 其 实 用 性 来 说, 阈 值 分 割 与 区 域 增 长 最 简 单 易 用, 交 互 式 分 割 最 准 确, 但 是 耗 时 长 就 发 展 方 向 来 说,Level Set 得 到 了 越 来 越 多 的 重 视, 通 过 它 可 以 直 接 将 三 维 模 型 提 取 出 来 更 进 一 步, 人 机 交 互 的 分 割 策 略 将 会 成 为 分 割 算 法 的 主 流 关 键 是 如 何 将 人 的 知 识 转 化 为 模 型, 让 计 算 机 能 够 理 解, 从 而 引 导 分 割 策 略 我 们 应 当 认 识 到, 尽 管 对 医 学 图 像 分 割 方 法 的 研 究 已 有 三 十 余 年 的 历 史, 但 是 到 目 前 为 止 尚 不 存 在 一 个 通 用 的 解 决 方 法, 现 有 的 任 何 一 种 单 独 的 图 像 分 割 算 法 都 难 以 对 一 般 图 像 取 得 令 人 满 意 的 分 割 结 果, 因 而 人 们 在 继 续 致 力 于 将 新 的 概 念, 新 的 方 法 引 入 图 像 分 割 领 域 的 同 时, 更 加 重 视 多 种 分 割 算 法 的 有 效 结 合, 近 几 年 来 提 出 的 方 法 大 多 数 是 结 合 了 多 种 算 法 的 采 取 什 么 样 的 结 合 方 式 才 能 体 现 各 种 方 法 的 优 点, 取 得 好 的 效 果 成 为 人 们 关 注 的 问 题 参 考 文 献 1. P.K.Sahoo, S.Soltani, A.K.C.Wang, and Y.C.Chen. A survey of thresholding techniques. Computer Vision, Graphics, and Image Processing, 1988, 41: Y. J. Zhang, J. J. Gerbrands. Transition region determination based thresholding. Pattern Recognition Letter, 1991, 12: Y. Nakagawa, A. Rosenfeld. Some experiments on variable thresholding. Pattern Recognition, 1979, 11:191~ H.D. Li, M. Kallergi, L.P. Clarke, V.K. Jain, R.A. Clark. Markov random field for tumor detection in digital mammagraphy. IEEE Trans On Medical Imaging, 1995, 14: C. Lee, S. Hun, T.A. Ketter, and M. Unser. Unsupervised connectivity-based thresholding 206
214 6 分 割 算 法 的 设 计 与 实 现 segmentation of midsaggital brain MR images. Comput. Biol. Med., 1998, 28: S. Pohlman, K.A. Powell, N.A. Obuchowski, W.A. Chilcote, and S. Grundfest-Broniatowski. Quantitative classification of breast tumors in digitzed mamograms. Medical Physics, 1996, 23: J.F. Mangin, V. Frouin, I. Bloch, J. Regis, J. Lopez-Krahe. From 3D magnetic resonance images to structural representations of the cortex topography using topology preserving deformations. J. Math. Imag. Vis., 1995, 5: J. K. Udupa, and S. Samarasekera. Fuzzy connectedness and object definition: theory, algorithms, and applications in image segmentation. Graphical Model and Image Processing, 1995, 58(3): I.N. Manousakas, P.E. Undrill, G.G. Cameron, T.W. Redpath. Split-and-merge segmentation of magnetic resonance medical images: performance evaluation and extension to three dimensions. Computers and Biomedical Research, 1998, 31: V. Caselles, F. Catte, T. Coll, and F. Dibos. A geometric model for active contours. Numerische Mathematik, 1993, 66: R. Malladi, J.Sethian, and B.Vemuri. Shape modeling with front propagation: A level set approach. IEEE Trans. on Pattern Analysis and Machine Intelligence, 1995, 17(2): L. Alvarez, F. Guichard, P. L. Lions, and J. M. Morel. Axioms and fundamental equations of image processing. Archive for Rational Mechanics and Analysis, 1993, 123(3): G. Sapiro and A. Tannenbaum. Affine invariant scale-space. Proc. Intl. Conf. on Computer Vision, 1993, 11(1): R. Kimmel, A. Amir, and A. M. Bruckstein. Finding shortest paths on surfaces using level sets propagation. IEEE Transaction On Pattern Analysis and Machine Intelligence, 1995, 17(6): B. B. Kimia, A. R. Tannenbaum, and S. W. Zucker. Shapes, shocks, and deformations I: the components of two-dimensional shape and the reaction-diffusion space. Int l J. Comp. Vis., 1995, 15: S. Osher and J. A. Sethian. Fronts propagating with curvature-dependent speed: algorithms based on Hamilton-Jacobi formulations. Journal of Computational Physics, 1988, 79: J. A. Sethian, Level Set Methods and Fast Marching Methods: Evolving Interfaces in Computational Geometry, Fluid Mechanics, Computer Vision, and Material Science. Cambridge, UK: Cambridge University Press, 2nd ed., J.A.Sethian. A fast marching level set method for monotonically advancing fronts. Proc. Natl. Acad. Sci. USA, 93(1996): pp
215 6 分 割 算 法 的 设 计 与 实 现 19. R.Malladi and J.A.Sethian. An O(N log(n)) Algorithm for Shape Modeling. In Proceedings of National Academy of Sciences, USA, Sept. 1996,Vol. 93: pp J. A. Sethian. Level Set Methods. Cambridge University Press, J. A. Sethian. Curvature and evolution of fronts. Communication: Mathematics & Physics, 1985, 101: J. A. Sethian. A review of recent numerical algorithms for hypersurfaces moving with curvature dependent speed. Journal of Differential Geometry, 1989, 31: V. Caselles, R. Kimmel, and G. Sapiro. Geodesic active contours. in Proc. 5th Int l Conf. Computer Vision, 1995, V. Caselles, R.Kimmel, and G.Sapiro. Geodesic active contours. International Journal of Computer Vision, 1997, 22(1): A. Kichenassamy, A.Kumar, P.Olver, A. Tannenbaum and A. Yezzi. Gradient flows and geometric active contour models. Proceedings of IEEE International Conference on Computer Vision, 1995, A. Yezzi, S. Kichenassamy, A. Kumar, P. Olver, and A. Tennenbaum. A geometric snake model for segmentation of medical imagery. IEEE Trans. On Medical Imaging, 1997, 16: Bertalmio M, Sapiro G, Randall G. Region tracking on level-set methods. IEEE Transactions on Medical Imaging, 1999, 18(5): Masouri A-R, Sirivong B, Konrad J. Multiple motion segmentation with level sets. Proceedings of SPIE, 2000, Vol.3974, pp Paragios N, Deriche R. Geodesic active contours and level sets for the detection and tracking of moving objects. IEEE Transactions on Pattern Analysis and Machine Intelligence, 2000, 22(3): Samon C, Blanc-Feraud L, Aubert G, Josiane Z. Level set model for image classification. International Journal of computer Vision, 2000, 40(3):
216 7 配 准 算 法 的 设 计 与 实 现 7 配 准 算 法 的 设 计 与 实 现 7.1 配 准 算 法 简 介 图 像 配 准 (Image Registration) 技 术 现 已 广 泛 应 用 应 用 于 模 式 识 别 计 算 机 视 觉 医 学 影 像 处 理 遥 感 数 据 处 理 等 诸 多 领 域 [1] 本 章 中 我 们 主 要 考 虑 其 在 医 学 领 域 的 应 用 20 世 纪 以 来 医 学 成 像 技 术 经 历 了 一 个 从 静 态 到 动 态 从 形 态 到 功 能 从 平 面 到 立 体 的 发 展 过 程, 尤 其 在 计 算 机 技 术 高 度 发 达 之 后, 医 学 成 像 技 术 的 发 展 给 临 床 医 学 提 供 了 从 X 线, 超 声, 计 算 机 断 层 成 像 (CT), 数 字 减 影 血 管 造 影 (DSA), 单 光 子 发 射 断 层 成 像 (SPECT), 磁 共 振 成 像 (MRI), 数 字 荧 光 造 影 (DF), 正 电 子 发 射 断 层 成 像 (PET) 等 形 态 和 功 能 的 影 像 信 息 根 据 医 学 图 像 所 提 供 的 信 息 内 涵, 可 将 这 些 信 息 分 为 两 大 类 : 解 剖 结 构 图 像 (CT MRI B 超 等 ) 和 功 能 图 像 (SPECT PET 等 ) 这 两 类 图 像 各 有 其 优 缺 点 : 功 能 图 像 分 辨 率 较 差, 但 它 提 供 的 脏 器 功 能 代 谢 信 息 是 解 剖 图 像 所 不 能 替 代 的 ; 解 剖 图 像 以 较 高 的 分 辨 率 提 供 了 脏 器 的 解 剖 形 态 信 息 ( 功 能 图 像 无 法 提 供 脏 器 或 病 灶 的 解 剖 细 节 ), 但 无 法 反 映 脏 器 的 功 能 情 况 目 前 这 两 类 成 像 设 备 的 研 究 都 已 取 得 了 很 大 的 进 步, 图 像 的 空 间 分 辨 率 和 图 像 质 量 有 很 大 的 提 高, 但 由 成 像 原 理 不 同 所 造 成 的 图 像 信 息 局 限 性, 使 得 单 独 使 用 某 一 类 图 像 的 效 果 并 不 理 想, 而 多 种 图 像 的 利 用 又 必 须 借 助 医 生 的 空 间 构 想 和 推 测 去 综 合 判 定 他 们 所 要 的 信 息, 其 准 确 性 受 到 主 观 影 响, 更 主 要 的 是 一 些 信 息 将 可 能 被 忽 视 解 决 这 个 问 题 的 最 有 效 方 法, 就 是 以 医 学 图 像 配 准 技 术 为 基 础, 利 用 信 息 融 合 技 术, 将 这 两 种 图 像 结 合 起 来, 利 用 各 自 的 信 息 优 势, 在 一 幅 图 像 上 同 时 表 达 来 自 人 体 的 多 方 面 信 息 使 人 体 内 部 的 结 构 功 能 等 多 方 面 的 状 况 通 过 影 像 反 映 出 来, 从 而 更 加 直 观 地 提 供 了 人 体 解 剖 生 理 及 病 理 等 信 息 其 中 图 像 配 准 技 术 是 图 像 融 合 的 关 键 和 难 点 [2] 20 世 纪 80 年 代 以 来, 医 学 图 像 配 准 (Medical Image Registration) 技 术 研 究 取 得 了 显 著 的 进 展 从 基 于 外 部 特 征 (Extrinsic) 到 基 于 内 部 特 征 (Intrinsic), 从 二 维 领 域 到 三 维 领 域, 从 刚 性 配 准 到 非 刚 性 配 准, 从 单 模 态 (Monomodal) 图 像 到 多 模 态 (Multimodal) 图 像, 研 究 领 域 不 断 扩 大, 配 准 算 法 日 益 丰 富, 特 别 是 计 算 机 硬 件 的 飞 速 发 展 更 进 一 步 推 动 了 图 像 配 准 技 术 的 研 究, 产 生 了 许 多 成 功 的 临 床 209
217 7 配 准 算 法 的 设 计 与 实 现 应 用 目 前, 医 学 图 像 配 准 已 发 展 成 为 图 像 处 理 领 域 里 一 个 比 较 活 跃 的 分 支, 每 年 相 关 的 期 刊 会 议 上 发 表 了 大 量 关 于 医 学 图 像 配 准 的 论 文 图 像 配 准 (Image Registration) 实 际 上 是 指 在 两 幅 图 像 相 应 点 之 间 建 立 一 一 映 射 的 过 程, 也 就 是 说, 将 两 幅 图 像 中 对 应 于 空 间 同 一 位 置 的 点 联 系 起 来, 这 里 的 映 射 一 般 称 为 变 换 (Transform) 图 7-1 简 单 说 明 了 一 个 二 维 图 像 配 准 的 概 念 (a) 和 (b) 是 对 应 于 同 一 物 体 同 一 位 置 的 两 幅 图 像 这 两 幅 图 有 两 点 明 显 的 不 同, 第 一 是 方 向 上 有 差 异 ( 图 (a) 已 经 被 旋 转 了 一 个 角 度 以 便 更 好 的 观 察 与 (b) 在 方 向 上 的 差 别 ), 第 二 是 形 状 上 (b) 少 了 一 部 分 我 们 可 以 认 为 它 们 由 不 同 的 成 像 设 备 得 到 ((b) 中 少 的 那 部 分 代 表 两 种 成 像 设 备 成 像 模 式 的 差 异 ), 也 可 以 认 为 它 们 来 自 同 一 个 成 像 设 备 ( 如 手 术 前 和 手 术 后, (b) 中 少 的 那 部 分 代 表 病 变 组 织 ) (c) 和 (d) 给 出 了 两 个 图 像 空 间 中 点 对 点 的 映 射 过 程 (a) 中 的 每 一 个 点 x, ) 都 被 映 射 到 (b) 中 唯 一 的 一 个 点 ( y 1 1 ( x 2, y2 ),(c) 表 示 前 向 过 程,(d) 表 示 逆 向 过 程 如 果 这 种 映 射 是 一 对 一 的, 即 一 个 图 像 空 间 中 的 每 一 个 点 在 另 外 一 个 图 像 空 间 中 都 有 对 应 点 ( 如 (a) 和 (b) 中 的 两 个 黑 点 ), 或 者 至 少 在 医 疗 诊 断 上 感 兴 趣 的 那 些 点 能 够 准 确 或 近 似 准 确 的 对 应 起 来, 我 们 就 称 为 配 准 (a) (b) (c) (d) 图 7-1 图 像 配 准 概 念 的 图 示 表 示 210
218 7 配 准 算 法 的 设 计 与 实 现 下 面 我 们 首 先 介 绍 MITK 中 采 用 的 配 准 算 法 框 架, 然 后 具 体 讨 论 配 准 的 分 模 块 实 现, 最 后 是 应 用 实 例 说 明 7.2 MITK 中 的 配 准 算 法 框 架 很 多 实 际 应 用 中 一 个 刚 性 变 换 就 可 以 描 述 图 像 间 的 空 间 对 应 关 系, 例 如 同 一 主 体 (Intrasubject) 的 脑 部 图 像 配 准 目 前, 刚 性 配 准 技 术 已 经 发 展 得 比 较 成 熟, 并 且 进 入 临 床 应 用 因 此 MITK 目 前 版 本 中 主 要 是 考 虑 这 些 技 术 的 实 现 当 然 也 有 很 多 情 况 下 需 要 用 到 各 种 比 较 复 杂 的 非 刚 性 变 换 相 比 较 而 言, 非 刚 性 配 准 还 处 于 很 活 跃 的 研 究 阶 段 [3] 实 际 配 准 过 程 中, 可 以 根 据 不 同 的 特 点 和 要 求 采 用 简 单 的 刚 性 变 换 (Regid Transform) 仿 射 变 换 (Affine Transform), 或 较 复 杂 的 弹 性 形 变 (Elastic Deform) 等 由 于 医 学 图 像 配 准 的 算 法 比 较 复 杂, 而 且 方 法 种 类 很 多, 新 的 方 法 也 层 出 不 穷, 为 了 给 使 用 者 提 供 一 个 开 放 的 易 于 扩 充 的 架 构, 便 于 以 后 添 加 新 的 配 准 算 法, 我 们 在 MITK 中 根 据 配 准 算 法 的 一 般 步 骤, 分 几 何 变 换 (Transform) 图 像 插 值 (Image Interpolator) 相 似 性 测 度 (Similarity Metric) 优 化 (Optimizer) 四 个 相 对 独 立 的 模 块 来 实 现 整 个 配 准 算 法, 各 模 块 间 的 相 互 关 系 如 图 7-2 所 示 对 于 两 幅 图 像 F 和 M, 分 别 用 函 数 f ( X ) 和 my ( ) 表 示, 其 中 X Y 为 各 自 图 像 的 定 义 域 ( 解 剖 结 构 空 间 ); 这 里 图 像 配 准 定 义 为 寻 找 一 种 几 何 变 换 T t( t 为 该 变 换 的 控 制 参 数 ), 使 S( f( X), m( Tt ( X )) 取 得 最 大 值 : T = arg max S( f( X), m( T( X))) (7-1) * t Tt t 其 中 S 为 对 任 意 两 幅 图 像 定 义 的 一 目 标 函 数, 用 来 衡 量 两 图 像 的 匹 配 效 果, 一 般 用 相 似 性 测 度 表 示 211
219 7 配 准 算 法 的 设 计 与 实 现 f ( X ) Image Similarity Metric St () Optimizer Registration * T t my ( ) mt ( t( X)) Image Interpolator Tt ( X) T t Transform Resample r( X) 图 7-2 配 准 算 法 框 架 图 从 算 法 框 图 中 可 以 看 出 整 个 配 准 算 法 的 流 程 : 1. 输 入 待 配 准 的 两 幅 图 像, 分 别 记 为 参 考 图 f ( X )(Fixed Volume) 和 浮 动 图 my ( )(Moving Volume); 2. 对 参 考 图 指 定 区 域 X 进 行 几 何 坐 标 变 换 (Transform) 得 到 新 的 区 域 Tt ( X) 坐 标, 其 中 t 表 示 变 换 参 数 ; 3. 通 过 一 定 的 插 值 方 法 (Image Interpolator) 得 到 浮 动 图 在 区 域 Tt ( X ) 的 取 值 mt ( t( X )); 4. 在 相 似 性 测 度 模 块 计 算 参 考 图 f ( X ) 和 插 值 图 mt ( ( X )) 的 相 似 度, 它 是 一 个 关 于 几 何 变 换 参 数 的 函 数 St; () 5. 相 似 度 函 数 St () 输 入 优 化 模 块 中 进 行 最 优 化 计 算 得 到 最 终 变 换 参 数, 这 个 过 程 在 计 算 中 一 般 通 过 迭 代 来 实 现, 即 重 复 步 骤 2~4 直 到 取 得 最 大 相 似 度 时 终 止 迭 代 循 环 6. 整 个 配 准 算 法 模 块 输 出 配 准 时 所 采 用 几 何 变 换 的 最 优 变 换 参 数 以 及 浮 动 图 在 最 优 变 换 下 的 插 值 图 像 重 采 样 模 块 Resample 由 Transform 和 Image Interpolator 组 成 可 以 看 出 在 此 过 程 中, 配 准 实 际 上 作 为 一 种 优 化 问 题 来 考 虑 : 寻 找 一 种 变 换 T t, 使 相 似 度 函 数 St () 取 得 最 大 值 下 面 分 别 对 以 上 各 算 法 模 块 作 简 要 介 绍 t 212
220 7 配 准 算 法 的 设 计 与 实 现 几 何 变 换 (Transform) 将 参 考 图 空 间 X (Fixed Volume Space) 中 像 素 点 映 射 到 浮 动 图 空 间 Y (Moving Volume Space) 中 去 这 和 配 准 中 图 像 变 换 的 直 观 理 解 有 点 不 一 样, 刚 好 是 它 的 逆 过 程 因 为 正 变 换 中 从 浮 动 图 空 间 变 换 到 参 考 图 空 间 后 可 能 会 产 生 空 洞, 不 利 于 后 续 的 插 值 处 理, 逆 变 换 则 可 以 避 免 这 一 点 几 何 变 换 的 类 型 一 般 有 刚 性 变 换 仿 射 变 换 投 影 变 换 曲 线 变 换 等 各 种 变 换 都 是 通 过 一 组 参 数 t 来 表 示, 例 如 刚 性 变 换 可 用 3 个 方 向 上 的 平 移 参 数 和 旋 转 角 度 共 6 个 参 数 来 表 示 几 何 变 换 模 块 在 MITK 中 用 Transform 抽 象 类 表 示, 具 体 的 变 换 算 法 由 Transform 派 生 类 实 现 图 像 插 值 (Image Interpolator) 是 为 了 估 计 浮 动 图 中 非 网 格 点 处 的 像 素 值 由 于 数 字 图 像 是 对 模 拟 图 像 的 网 格 采 样, 只 有 格 点 处 有 像 素 值, 而 参 考 图 空 间 中 的 格 点 映 射 到 浮 动 图 空 间 中 去 后 可 能 不 在 格 点 上, 为 了 得 到 这 些 浮 动 图 中 非 格 点 处 的 像 素 值 就 有 必 要 进 行 图 像 插 值 一 般 常 采 用 的 插 值 方 法 有 最 近 邻 插 值 线 性 插 值 B 样 条 插 值 等 图 像 插 值 模 块 有 两 个 输 入 一 个 输 出 : 浮 动 图 像 my ( ) (Moving Volume) 和 几 何 变 换 结 果 Tt ( X )(Transform 的 输 出 ) 作 为 输 入 ; 插 值 结 果 I( X) = m( Tt ( X)) 作 为 输 出 插 值 结 果 也 是 一 个 Volume, 因 此 MITK 中 将 该 抽 象 类 (Interpolator) 作 为 VolumeToVolumeFilter 的 派 生 类 相 似 性 测 度 (Similarity Metric Measure) 作 为 一 种 准 则 用 来 评 价 参 考 图 和 插 值 后 得 到 的 图 像 匹 配 的 效 果, 可 以 说 这 是 整 个 配 准 框 架 中 最 关 键 的 部 分, 它 直 接 影 响 配 准 效 果 的 好 坏 [4] 相 似 性 测 度 是 一 个 以 几 何 变 换 参 数 为 自 变 量 的 单 值 函 数 : St ( ) = S( f( X), mt ( t( X)) 该 模 块 以 参 考 图 f ( X ) 和 浮 动 图 插 值 后 得 到 的 图 象 mt ( t( X ) 作 为 输 入, 输 出 一 个 表 示 两 图 像 相 似 ( 相 关 ) 性 的 标 量 值, 可 以 看 作 一 个 以 变 换 参 数 为 自 变 量 的 函 数, 该 函 数 输 入 到 优 化 模 块 中 作 为 代 价 函 数 (Cost Function) 求 取 最 优 变 换 一 些 非 刚 性 配 准 算 法 中, 除 相 似 性 测 度 外, 还 要 考 虑 形 变 约 束, 两 者 之 和 作 为 优 化 的 代 价 函 数, 即 C = C + λc (7-2) similarity deformation 函 数 优 化 (Cost Function Optimizer) 如 同 图 像 处 理 与 分 析 领 域 中 的 很 多 问 题 一 样, 图 像 配 准 也 被 归 结 成 一 个 多 参 数 优 化 问 题 如 前 所 述, 给 定 一 种 配 准 准 则, 就 可 以 定 义 一 个 以 几 何 变 换 参 数 为 自 变 量 的 多 元 目 标 函 数, 通 过 对 该 目 标 函 数 的 最 优 化 搜 索 得 到 配 准 时 的 几 何 变 换 参 数 由 于 优 化 问 题 是 一 个 比 较 经 典 的 数 学 问 题, 有 很 多 解 决 方 法, 这 里 关 键 的 就 是 根 据 目 标 函 数 的 特 点 选 择 合 适 213
221 7 配 准 算 法 的 设 计 与 实 现 的 优 化 算 法 和 策 略, 准 确 高 效 快 速 地 得 到 结 果 MITK 中 上 述 算 法 模 块 分 别 用 Transform InterpolateFilter Metric Optimizer 四 个 抽 象 类 表 示 根 据 图 7-2 算 法 框 图,Transform 得 到 几 何 变 换 参 数 后 对 参 考 图 定 义 域 进 行 几 何 变 换, 输 出 变 换 后 的 坐 标 ;InterpolateFilter 输 入 Transform 变 换 后 的 坐 标 及 浮 动 图 进 行 插 值 运 算, 输 出 新 的 图 像 ;Metric 输 入 参 考 图 和 插 值 图, 输 出 相 似 度 函 数 ;Optimizer 对 输 入 的 相 似 度 函 数 进 行 优 化, 输 出 最 优 变 换 参 数 这 些 模 块 在 配 准 类 RegistrationFilter 中 通 过 相 应 的 接 口 组 合 到 一 起, 完 成 整 个 配 准 算 法 流 程, 最 后 输 出 参 考 图 变 换 后 的 重 采 样 图 像 显 然 RegistrationFilter InterpolateFilter 输 入 输 出 均 为 三 维 体 数 据 Volume, 可 以 作 为 VolumeToVolumeFilter 的 派 生 类 继 承 其 数 据 成 员 和 成 员 函 数 Transform Metric Optimizer 作 为 ProcesssObject 的 派 生 类 各 算 法 模 块 类 的 继 承 结 构 及 相 互 组 合 关 系 如 图 7-3 图 7-4 所 示 ProcessObject VolumeToVolumeFilter Transform Metric Optimizer RegistrationFilter InterpolateFilter 图 7-3 配 准 算 法 各 模 块 类 继 承 结 构 RegistrationFilter +SetOptimizer(o:Optimizer) +GetLastTransformParameters():vector<double> +GetOutput():Volume +Run():bool m_optimizer Optimizer +SetMetric(mt:Metric) +GetLastParameters():vector<double> +Run():bool m_metric m_optimizer->get LastParameters() Metric +SetTransform(t:Transform) +SetInterpolator(i:InterpolateFilter) +GetSimilarity(tp:vector<double>):double +Run():bool m_transform m_interpolator Transform +SetParameters(tp:vector<double>) +GetOutput():Volume +Run():bool InterpolateFilter +SetInput(m:Volume) +GetOutput():Volume +Run():bool 图 7-4 MITK 中 配 准 算 法 各 模 块 关 系 图 214
222 7 配 准 算 法 的 设 计 与 实 现 以 上 Transform InterpolateFilter Metric Optimizer 四 模 块 是 从 高 层 概 念 上 实 现 了 相 应 的 算 法, 提 供 基 本 的 数 据 成 员 和 成 员 函 数 接 口 不 同 类 型 的 各 种 具 体 算 法 通 过 它 们 的 派 生 类 在 虚 函 数 中 实 现 用 户 通 过 设 置 具 体 的 几 何 变 换 类 型 插 值 方 法 相 似 性 度 量 准 则 优 化 策 略 就 能 实 现 不 同 的 配 准 算 法 下 面 几 节 详 细 介 绍 MITK 中 采 用 的 具 体 几 何 变 换 类 型 插 值 方 法 相 似 性 度 量 准 则 和 优 化 策 略 7.3 几 何 变 换 空 间 映 射 T 描 述 了 一 幅 图 像 中 的 位 置 与 另 一 幅 图 像 中 的 相 应 位 置 之 间 的 关 系 这 里 的 图 像 可 以 是 二 维 的 (2D), 也 可 以 是 三 维 的 (3D), 所 以 这 种 映 射 可 能 是 从 二 维 空 间 到 二 维 空 间 从 三 维 空 间 到 三 维 空 间 或 者 是 在 三 维 和 二 维 之 间 的 变 换 然 而 在 所 有 上 述 情 况 中, 象 源 体 人 体 的 部 分 或 全 部 都 是 三 维 的 所 以, 对 大 多 数 情 况 而 言, 二 维 空 间 内 部 的 变 换 无 法 满 足 配 准 的 要 求 目 前 最 广 泛 的 配 准 应 用 中 就 包 含 三 维 图 像 对 之 间 的 配 准 另 外, 一 个 更 加 重 要 的 应 用 就 是 二 维 图 像 与 三 维 图 像 之 间 的 配 准 (2D-3D 配 准 ) 在 2D-3D 的 配 准 过 程 中,T 包 含 了 从 3D 物 体 到 2D 平 面 的 投 影, 以 及 3D-3D 的 变 换 刚 性 变 换 算 法 如 果 用 以 配 准 的 图 像 包 含 相 同 的 内 容, 只 是 位 置 有 所 不 同, 那 么 就 可 以 用 旋 转 和 平 移 来 描 述 配 准 变 换 这 就 是 刚 性 变 换 在 三 维 情 况 下, 刚 性 变 换 包 含 6 个 自 由 度, 它 们 分 别 是 : 沿 着 三 个 坐 标 轴 的 平 移 x y z, 以 及 围 绕 三 个 坐 标 轴 的 旋 转 α β γ 通 过 这 些 未 知 量, 我 们 可 以 构 造 一 个 刚 性 变 换 矩 阵 T rigid 它 可 以 将 一 幅 图 像 中 的 任 意 点 映 射 到 另 一 幅 图 像 中, 成 为 与 之 对 应 的 变 换 点 这 种 变 换 可 以 通 过 旋 转 变 换 R 和 平 移 变 换 t=(t x,t y,t z ) T 来 表 示 : T rigid (x) = Rx + t (7-3) 其 中, 旋 转 矩 阵 R 的 构 造 如 下 : cos β cosγ cosαsinγ + sinαsin β cosγ sinαsinγ cosαsin β cosγ R = cos β sin γ cosαcosγ sinαsin βsinγ sinαcosγ + cosαsin βsinγ (7-4) sin β sinαcos β cosαcos β 对 于 2D-3D 刚 性 配 准, 我 们 需 要 同 时 考 虑 刚 性 变 换 和 3D 物 体 在 平 面 上 的 投 影 我 们 可 以 将 刚 性 变 换 平 移 变 换 和 投 影 变 换 在 各 向 同 性 坐 标 系 中 统 一 成 215
223 7 配 准 算 法 的 设 计 与 实 现 一 个 4x4 的 矩 阵 : T rigid cos β cosγ cosαsinγ + sinαsin β cosγ sinαsinγ cosαsin β cosγ tx cos βsinγ cosαcosγ sinαsin βsinγ sinαcosγ + cosαsin βsinγ ty = sin β sinαcos β cosαcos β tz (7-5) 通 常 认 为 投 影 是 (x y z) 沿 着 z 轴 方 向 投 影 到 u v 平 面 这 种 投 影 变 换 可 以 通 过 图 像 系 统 的 内 在 参 数 (u 0 v 0 k u k v ) 来 表 示 对 于 x 光 投 影 而 言, 我 们 如 下 解 释 这 些 参 数 :u 0 和 v 0 定 义 了 光 线 穿 透 点 ( 在 (u v) 平 面 上, 该 点 的 法 矢 指 向 x 光 光 源 ),k v 和 k u 分 别 表 示 象 素 点 在 水 平 方 向 (u) 和 垂 直 方 向 (v) 的 大 小 另 外, 它 们 也 可 以 作 为 未 知 量, 这 样 就 会 给 配 准 算 法 增 加 四 个 自 由 度 投 影 变 换 矩 阵 T projection 可 以 表 示 成 4x3 的 矩 阵, 它 将 沿 着 z 轴 方 向 投 影 : ku 0 u0 0 Tprojection = 0 kv v0 0 (7-6) 在 各 向 同 性 坐 标 系 (x y z 1) T 中 的 3D 坐 标 同 这 个 矩 阵 相 乘, 得 到 向 量 (λ u, λv,λ) T 其 中 λ 代 表 缩 放 因 子, 投 影 到 2D 平 面 的 结 果 可 以 由 该 向 量 中 的 前 两 项 分 别 除 以 缩 放 因 子 λ 得 到 2D-3D 刚 性 配 准 所 需 要 的 变 换 T 2D-3D 可 以 由 投 影 变 换 和 刚 体 变 换 组 合 而 成 : T 2D-3D =T projection T rigid (7-7) 当 我 们 考 虑 骨 质 或 者 与 之 接 近 的 结 构 时, 这 种 刚 性 变 换 可 以 正 确 的 将 反 映 相 同 内 容 的 图 像 对 应 起 来 这 种 假 设 对 在 处 理 脑 部 图 像 的 时 候 非 常 适 用, 因 为 脑 颅 骨 限 制 了 大 脑 的 形 变 然 而 对 身 体 内 大 多 数 组 织 而 言, 刚 性 变 换 远 远 满 足 不 了 配 准 的 需 要, 我 们 需 要 更 多 的 自 由 度 来 足 够 精 确 地 描 述 组 织 的 形 变 线 性 变 换 与 一 对 一 变 换 线 性 变 换 : 许 多 作 者 认 为 仿 射 (affine) 变 换 是 线 性 的 从 严 格 意 义 上 讲, 这 是 不 正 确 的 线 性 变 换 是 一 种 满 足 以 下 条 件 的 特 殊 变 换 : L(αx A +βx A )=αl(x A )+βl(x A ) x x R (7-8) A, ' A 216
224 7 配 准 算 法 的 设 计 与 实 现 而 仿 射 变 换 的 平 移 部 分 不 符 合 这 一 条 件 确 切 的 说, 仿 射 变 化 是 一 种 线 性 变 换 与 平 移 变 换 的 复 合 变 换 更 进 一 步, 反 射 (reflection) 变 换 也 是 一 种 线 性 变 换, 但 是 它 们 在 图 像 配 准 中 很 少 用 到 举 例 而 言, 如 果 在 图 像 指 导 的 神 经 外 科 中 所 使 用 的 配 准 算 法 包 含 反 射 变 换, 那 么 就 有 可 能 导 致 在 穿 颅 手 术 中 定 位 到 错 误 的 一 边 如 果 怀 疑 到 算 法 中 可 能 包 括 反 射 变 换, 那 么 就 一 定 要 在 应 用 之 前 得 出 确 证, 以 免 造 成 事 故 一 对 一 变 换 : 在 对 同 一 目 标 的 配 准 中, 病 人 通 过 不 同 的 设 备 进 行 成 像 这 种 配 准 中 所 要 求 的 变 换 T 似 乎 是 一 对 一 的 这 意 味 着 图 像 A 中 的 点 经 过 变 换 后 与 图 像 B 中 唯 一 确 定 的 点 对 应, 反 之 亦 然 在 有 些 情 况 下, 这 种 规 则 并 不 适 用 首 先, 如 果 配 准 图 像 的 维 数 并 不 相 同, 例 如 x 光 照 片 和 CT 成 像 之 间 的 配 准, 一 对 一 变 换 就 是 不 可 能 的 其 次, 在 一 幅 图 像 中 的 采 样 数 据 并 没 有 反 映 在 另 一 幅 图 像 的 采 样 数 据 中 对 于 各 种 非 仿 射 变 换 配 准, 一 对 一 的 变 换 都 是 不 适 用 的 举 例 而 言, 在 对 不 同 目 标 的 配 准 中, 或 者 在 对 同 一 目 标 手 术 前 和 手 术 后 的 配 准 中,A 图 像 中 就 可 能 存 在 B 图 像 中 所 不 包 含 的 结 构, 反 之 亦 然 变 换 算 法 在 MITK 中 的 实 现 从 图 7-2 配 准 算 法 框 架 图 中 可 以 看 出, 变 换 模 块 Transform 接 受 优 化 模 块 Optimizer 的 输 出 作 为 输 入, 即 输 入 是 一 个 变 换 参 数 集 合 而 Transform 的 输 出 是 一 个 数 据 集, 它 记 载 着 变 换 后 点 的 坐 标 位 置, 其 组 织 形 式 与 volume 相 同 这 个 输 出 进 一 步 作 为 插 值 模 块 Interpolator 的 输 入 参 数 之 一 抽 象 类 Transform 的 框 架 图 如 图 7-5 示 : 217
225 7 配 准 算 法 的 设 计 与 实 现 Transform m_parameters:vector<double>* =NULL m_dimensions[3]:int = [1,1,1] m_fixedspacings[3]:float = [1.0,1.0,1.0] m_movingspacings[3]:float = [1.0,1.0,1.0] m_outdata:volume* = NULL +SetParameters(tp:<vector<double>) +SetDimensions(d[3]:int) +SetFixedSpacings(fs[3]:float) +SetMovingSpacings(ms[3]:float) +GetOutput():Volume +Run():bool RigidTransform +Run():bool AffineTransform +Run():bool?Transform +Run():bool 图 7-5 几 何 变 换 类 图 对 于 各 种 变 换 算 法, 其 用 户 接 口 都 是 一 致 的 通 过 函 数 void SetTransParameter(float * p, int n) 设 置 变 换 的 参 数 通 过 void SetVolumeSize(int w, int h, int n=1) 设 置 变 换 图 像 数 据 的 大 小 具 体 的 变 换 算 法 实 现 将 在 子 类 中, 由 virtual bool Excute() 的 重 载 函 数 实 现 在 变 换 参 数 和 图 像 数 据 大 小 设 置 完 成 之 后, 就 可 以 通 过 Run() 函 数 调 用 Exctute(), 从 而 完 成 变 换 工 作 最 终, 变 换 的 结 果 可 以 通 过 函 数 :Point * GetTransRslt( ) 得 到 其 它 函 数 介 绍 : int GetNumOfPoint( ) 获 取 参 与 变 换 的 点 的 个 数 int GetNumberOfParameters( ) 获 取 变 换 参 数 的 个 数 7.4 图 像 插 值 在 配 准 过 程 中, 相 似 性 测 度 (Metric) 通 常 是 比 较 固 定 图 像 (fixed image) 和 变 换 图 像 (moving image) 之 间 对 应 点 的 灰 度 值 当 一 个 点 通 过 某 种 变 换, 从 218
226 7 配 准 算 法 的 设 计 与 实 现 一 个 空 间 映 射 到 另 一 个 空 间 时, 目 标 点 的 坐 标 通 常 不 在 网 格 点 上 在 这 种 情 况 下, 插 值 算 法 就 需 要 用 来 估 计 目 标 点 的 灰 度 值 变 换 示 意 图 见 图 7-6: 图 7-6 固 定 图 像 网 格 点 与 变 换 后 的 浮 动 图 像 非 网 格 点 插 值 方 法 影 像 着 图 像 的 平 滑 性, 优 化 的 搜 索 空 间, 以 及 总 体 的 计 算 时 间 因 为 在 一 个 优 化 周 期 之 中, 插 值 算 法 将 被 执 行 成 千 上 万 次 所 以, 在 指 定 插 值 方 案 时, 我 们 需 要 在 计 算 复 杂 性 和 图 像 平 滑 性 之 间 做 一 个 权 衡 最 近 邻 插 值 图 7-7 最 近 邻 域 法 插 值 最 近 邻 域 法 是 指 把 距 离 非 相 网 点 (u,v) 最 近 的 u-- v 坐 标 系 中 的 格 网 点 的 灰 度 值 设 为 (u,v) 点 灰 度 值 的 算 法 如 图 7-7 所 示, 其 不 足 是 会 使 细 线 状 目 标 边 界 产 生 锯 齿 219
227 7 配 准 算 法 的 设 计 与 实 现 线 性 插 值 图 7-8 线 性 内 插 法 线 性 内 插 法 是 指 如 图 7-8 所 示, 采 用 在 (u,v) 周 围 四 个 格 网 点 的 灰 度 值 进 行 内 插, 其 关 系 式 为 : f( u, v) = (1 α ')(1 β') f( x, y) + β '(1 α') f( x, y+ 1) + α'(1 β') f( x+ 1, y) + α' β ' f( x+ 1, y+ 1) PV 插 值 图 7-9 PV 插 值 PV(Partial Volume) 插 值 是 指 如 图 7-9 所 示, 通 过 周 围 4 个 点 距 对 应 点 的 距 离 分 配 分 数 权 值, 以 使 它 们 贡 献 于 联 合 灰 度 分 布 统 计 假 设 两 幅 待 配 准 的 图 像 为 F( 浮 动 图 像 ) 和 R( 参 考 图 像 ) 令 T α 是 由 参 数 α 确 定 的 变 换 矩 阵, 它 将 被 作 用 于 F 假 设 T α 将 F 中 的 点 (a,b) 映 射 到 R 中 的 坐 标 (i+ i,j+ j ), 其 中 (i,j) 是 R 中 的 网 格 点, 并 且 0 i, j <1 如 果 ƒ 是 实 函 数, 满 足 : 220
228 7 配 准 算 法 的 设 计 与 实 现 1. ƒ(x) 0,x 是 实 数 2. f( n+ ) = 1, n是 整 数, 0 1 n= 那 么, 对 于 F 中 的 任 何 点 (a,b), 联 合 灰 度 分 布 为 : h(f(a,b),r(i+p,j+q))=h(f(a,b),r(i+p,j+q)) +ƒ(p- i )* ƒ(q- j )(7-9) 其 中 p,q,r 是 整 数,ƒ 是 核 函 数 插 值 算 法 在 MITK 中 的 实 现 从 图 7-2 中 可 以 看 出, 插 值 模 块 Interpolator 接 受 变 换 模 块 Transform 的 输 出 作 为 其 输 入 另 外, 它 还 有 一 个 mitkvolume 的 输 入 参 数 经 过 插 值 运 算 后, Interpolator 输 出 mitkvolume 类 型 的 参 数, 并 将 其 作 为 metric 模 块 的 输 入 InterpolateFilter m_indata:volume m_coordinates:volume m_outdata:volume +SetInput(i:Volume) +SetCoordinates(c:Volume) +GetOutput():Volume +Run():bool NearestNeighborInterpolateFilter +Run():bool LinearInterpolateFilter +Run():bool BSplineInterpolateFilter +Run():bool 图 7-10 图 像 插 值 类 图 Interoplator 模 块 函 数 介 绍 : void SetInput(mitkVolume *indata); 设 置 图 像 数 据 void SetTransRslt(Point * p, int w, int h, int n); 设 置 变 换 结 果 221
229 7 配 准 算 法 的 设 计 与 实 现 在 Interoplator 中 设 置 完 图 像 数 据 和 变 换 结 果 之 后, 就 可 以 通 过 Run() 调 用 Excute() 执 行 具 体 的 插 值 操 作 7.5 相 似 性 测 度 相 似 性 测 度 (Similarity Measure) 定 量 化 地 衡 量 了 两 幅 图 像 匹 配 的 效 果, 它 是 图 像 配 准 过 程 中 十 分 重 要 的 一 部 分 [4] 一 般 情 况 下 待 配 准 的 图 像 是 在 不 同 时 间 不 同 条 件 甚 至 不 同 成 像 技 术 下 获 取 的, 图 像 描 述 的 信 息 可 能 存 在 本 质 的 差 别, 这 种 情 况 下 就 没 有 绝 对 的 配 准 问 题, 那 么 我 们 的 任 务 就 是 寻 找 一 种 准 则, 使 两 幅 图 像 在 这 种 准 则 下 达 到 最 佳 的 匹 配 效 果 这 里 的 准 则 称 之 为 相 似 性 测 度, 在 一 些 非 刚 性 配 准 中 还 加 上 形 变 约 束 准 则 的 选 择 和 配 准 目 的 具 体 的 图 像 形 态 几 何 变 换 类 型 等 有 关 例 如, 有 些 准 则 允 许 很 大 的 几 何 变 换 搜 索 范 围, 而 有 些 准 则 要 求 初 始 位 置 和 最 优 的 配 准 结 果 比 较 接 近 才 能 得 到 正 确 结 果 ; 有 些 准 则 仅 仅 适 用 于 同 一 模 态 图 像 间 的 配 准, 而 有 些 准 则 能 处 理 不 同 模 态 的 图 像 配 准 遗 憾 的 是 现 在 还 没 有 一 个 明 确 的 准 则 来 指 导 在 各 种 情 况 下 如 何 选 择 配 准 的 相 似 度 量 准 则, 更 不 存 在 各 种 情 况 下 都 通 用 的 相 似 度 准 则 既 然 配 准 只 是 在 某 种 准 则 下 取 得 相 对 最 优, 准 则 的 选 择 直 接 影 响 着 配 准 的 效 果 因 此, 如 何 选 择 合 适 的 相 似 性 度 量 准 则 就 成 为 图 像 配 准 中 一 个 十 分 关 键 的 研 究 问 题, 大 量 的 研 究 论 文 也 表 明 了 这 一 点 从 发 表 的 论 文 来 看, 主 要 有 两 种 相 似 性 度 量 准 则 : 基 于 特 征 (feature-based) 和 基 于 体 素 (voxel-based) 基 于 特 征 的 准 则 一 般 是 最 小 化 两 图 像 相 应 特 征 间 的 距 离, 常 用 的 特 征 有 对 应 解 剖 结 构 中 的 控 制 点 二 维 边 缘 线 三 维 表 面 等 这 种 准 则 下, 通 常 先 要 提 取 特 征, 利 用 这 些 局 部 的 特 征 信 息 进 行 配 准 特 征 提 取 的 准 确 性 直 接 影 响 着 配 准 的 精 度 基 于 体 素 的 方 法 是 目 前 的 研 究 热 点 从 理 论 上 来 讲, 这 种 方 法 应 该 是 最 灵 活 的, 因 为 它 利 用 了 图 像 中 的 所 有 信 息 从 总 体 上 来 看, 这 类 准 则 中 较 常 见 的 有 :1 相 关 性 测 度, 包 括 相 关 系 数 傅 立 叶 域 的 互 相 关 和 相 位 相 关 等 ;2 总 体 平 均 差 最 小 化 ;3 灰 度 比 的 方 差 最 小 化 ;4 互 信 息 最 大 化 其 中 基 于 互 信 息 最 大 化 的 方 法 获 得 了 很 大 的 成 功 [5], 大 量 文 献 表 明, 该 方 法 不 仅 适 用 于 单 模 态 图 像 配 准, 对 多 模 态 图 像 配 准 问 题 也 能 取 得 不 错 的 结 果 MITK 中 目 前 实 现 了 下 列 基 于 体 素 的 相 似 性 度 量 准 则 : 灰 度 平 均 差 (Mean Squares Metric) 222
230 7 配 准 算 法 的 设 计 与 实 现 归 一 化 相 关 系 数 (Normalized Correlation Metric) Pattern Intensity 互 信 息 (Mutual Information Metric) 下 面 我 们 先 简 要 介 绍 这 几 种 准 则, 然 后 看 看 其 在 MITK 中 的 实 现 为 了 书 写 方 便, 我 们 记 参 考 图 f ( X ) 和 变 换 插 值 后 的 浮 动 图 mt ( t( X ) 为 A 和 B A i, B i 分 别 是 图 像 AB, 第 i 个 像 素 的 灰 度 值, N 是 计 算 区 域 的 像 素 个 数 灰 度 平 均 差 测 度 MitkMeanSquaresMetric 类 计 算 图 像 AB, 在 给 定 区 域 的 灰 度 平 均 差 : N 1 2 MS( A, B) = ( Ai Bi) (7-10) N i 理 想 情 况 下 的 最 优 测 度 是 0, 这 是 基 于 以 下 假 设 : 两 图 像 对 应 像 素 点 灰 度 值 相 同 因 此 该 准 则 只 适 用 于 同 模 态 图 像 配 准 该 准 则 计 算 简 单, 相 对 来 说 可 以 在 一 个 比 较 大 的 范 围 内 搜 索 匹 配 但 该 准 则 对 图 像 灰 度 值 的 线 性 变 化 比 较 敏 感 归 一 化 相 关 系 数 MitkNormalizedCorrelationMetric 计 算 图 像 AB, 的 归 一 化 互 相 关 系 数 N ( AB i ) i i NC( A, B) = (7-11) N 2 N 2 A i i B i i 理 想 情 况 下 该 准 则 的 最 优 值 是 1 该 准 则 也 仅 限 于 单 模 态 图 像 配 准, 并 且 产 生 尖 峰 状 极 值, 搜 索 范 围 较 小 Pattern Intensity 1 MitkPatternIntensityMetric 计 算 灰 度 差, 并 代 入 钟 形 函 数 求 和 2 1+ x N 1 PI( A, B) = (7-12) λ( Ai Bi) 其 中 λ 是 比 例 系 数, 控 制 搜 索 范 围 该 准 则 的 具 体 性 质 可 参 考 Penney[6] 和 Holden[4] 的 论 文 该 准 则 也 仅 限 于 单 模 态 图 像 配 准, 对 图 像 灰 度 值 的 线 性 变 化 比 较 敏 感 互 信 息 互 信 息 (Mutual Information) 是 信 息 论 中 的 一 个 概 念, 通 常 用 于 描 述 两 个 系 统 223
231 7 配 准 算 法 的 设 计 与 实 现 间 的 统 计 相 关 性, 或 一 个 系 统 中 包 含 的 另 一 个 系 统 中 信 息 的 多 少, 一 般 用 熵 (Entropy) 来 表 示 随 机 变 量 A 的 熵 定 义 为 H( A) = p ( a)log p ( a) da (7-13) A 两 个 随 机 变 量 A B 的 联 合 熵 定 义 为 A H ( A, B) = p ( a, b)log p ( a, b) dadb (7-14) AB 如 果 A B 互 相 独 立, 则 A, B p ( a, b) = p ( a) p ( b) (7-15) AB A B H( A, B) = H( A) + H( B) (7-16) 如 果 A B 不 互 相 独 立, 则 H( A, B) < H( A) + H( B) (7-17) 其 差 值 称 为 A B 的 互 信 息 I( AB, ) I( AB, ) = H( A) + H( B) H( AB, ) (7-18) 在 医 学 图 像 配 准 中, 待 配 准 的 两 幅 图 像 可 能 来 自 于 不 同 的 时 间 或 不 同 的 成 像 设 备, 但 它 们 都 基 于 共 同 的 人 体 解 剖 信 息, 它 们 被 看 作 两 个 随 机 变 量 时, 二 者 显 然 不 会 相 互 独 立, 并 且 我 们 假 设 当 它 们 的 空 间 位 置 达 到 一 致 时, 其 互 信 息 应 为 最 大 这 就 是 用 互 信 息 最 大 化 作 为 相 似 性 测 度 的 原 理 所 在 将 待 配 准 的 两 幅 图 像 看 作 二 维 随 机 变 量 ( A, B ) 计 算 其 互 信 息 之 前, 首 先 需 要 估 计 该 二 维 随 机 变 量 的 边 缘 概 率 分 布 密 度 函 数 pa( a ) pb( b ) 和 联 合 概 率 分 布 密 度 函 数 pab( ab, ) 一 个 最 直 观 的 想 法 就 是 用 图 像 的 灰 度 级 ( 联 合 ) 直 方 图 来 估 计, 即 各 灰 度 级 出 现 的 相 对 频 率 作 为 该 灰 度 级 像 素 的 概 率 ; 常 用 的 另 一 种 估 计 方 法 是 采 用 著 名 的 Parzen 窗 概 率 密 度 估 计 (Parzen Window density estimate) P * ( z ): p( z) P ( z) = R( z zi ), (7-19) N A zi A 其 中, N A 是 图 像 A 的 像 素 个 数 ( 或 者 说 随 机 变 量 A 的 样 本 数 ), R 是 窗 函 数, 其 定 义 域 内 积 分 等 于 1 窗 函 数 常 采 用 高 斯 函 数 GΨ ( z) : n/2 1/2 1 T 1 GΨ( z) (2 π ) Ψ exp( z Ψ z) (7-20) 2 * 1 224
232 7 配 准 算 法 的 设 计 与 实 现 估 计 出 边 缘 概 率 密 度 和 联 合 概 率 密 度 函 数 后, 可 以 很 容 易 求 出 两 图 像 的 互 信 息 有 文 献 指 出 待 配 准 两 图 像 重 叠 区 的 大 小 会 影 响 互 信 息 [5], 用 规 整 化 互 信 息 NMI(normalized mesure of mutual information) 或 熵 相 关 系 数 ECC 作 为 相 似 性 测 度 函 数 能 克 服 重 叠 区 的 影 响, 其 中 NMI 和 ECC 计 算 公 式 如 下 : H( A) + H( B) NMI ( A, B) =, (7-21) H( A, B) ECC( A, B) = 2 I( A, B), (7-22) H( A) + H( B) 显 然 NMI 和 ECC 有 如 下 关 系 : ECC( A, B) = 2 2/ NMI( A, B) (7-23) 采 用 基 于 体 素 方 法 计 算 相 似 度 时, 对 于 高 分 辨 率 的 三 维 体 数 据 集, 其 包 含 的 数 据 量 极 大, 为 减 少 计 算 量, 常 只 采 用 部 分 而 不 是 全 部 数 据 点, 计 算 前 先 对 原 图 像 进 行 重 采 样 相 似 性 测 度 在 MITK 中 的 实 现 从 图 7-2 可 以 看 出, 算 法 上 相 似 性 测 度 模 块 Metric 接 受 Fixed Volume 和 Image Interpolator 的 输 出 作 为 输 入, 即 输 入 两 个 Volume 体 数 据, 输 出 的 是 计 算 得 到 的 相 似 度 量 值 在 MITK 具 体 实 现 中, 我 们 用 Metic 这 一 抽 象 基 类 来 实 现 相 似 性 测 度, 其 框 架 如 图 7-11 所 示 225
233 7 配 准 算 法 的 设 计 与 实 现 Metric +SetFixedVolume(f:Volume) +SetMovingVolume(m:Volume) +SetInterpolator(i:Interpolator) +SetTransform(t:Transform) +SetTransformParameters(tp:vector<double>) +GetSimilarity (tp:vector<double>):double #Excute():bool m_fixedvolume m_movingvolume Volume Volume m_transform m_interpolator Transform +SetParameters(tp:vector<double>) +GetOutput(m:Volume) +Run():bool #Excute():bool Interpolator +SetInput(m:Volume) +GetOutput():Volume +Run():bool #Excute():bool 图 7-11 相 似 性 测 度 模 块 实 现 Metric MeanSquaresMetric NormalizedCorrelationMetric MutualInformationMetric PatternIntensityMetric 图 7-12 相 似 性 测 度 Metric 层 次 结 构 我 们 将 Transform 和 Interpolator 作 为 Metric 的 成 员 变 量 封 装 起 来, 在 bool Execute() 函 数 中 完 成 一 次 性 完 成 几 何 变 换 图 像 插 值 以 及 求 相 似 度 的 工 作 各 种 不 同 的 相 似 性 测 度 都 通 过 Metric 派 生 出 来, 并 在 虚 函 数 Execute() 中 具 体 实 现 如 图 7-12 所 示 对 于 各 种 相 似 性 测 度, 用 户 接 口 都 是 一 致 的, 即 通 过 成 员 函 数 void SetFixedVolume(mitkVolume *fixedvolume) 输 入 参 考 图 数 据 信 息, 通 过 函 数 void SetMovingVolume(mitkVolume *movingvolume) 输 入 浮 动 图 数 据 信 息, 通 过 void SetTransform(mitkTransform *transform) 和 void SetTransformParameters(vector<double> *parameters) 两 函 数 指 定 几 何 变 换 的 类 型 226
234 7 配 准 算 法 的 设 计 与 实 现 及 其 相 应 的 参 数, 通 过 函 数 void SetInterpolator(mitkInterpolator *interpolator) 指 定 图 像 插 值 的 类 型 算 法 计 算 结 果 通 过 double GetSimilarity(const vector<double> *parameters) 得 到 7.6 函 数 优 化 根 据 相 似 性 测 度 选 择 的 不 同, 配 准 变 换 的 参 数 求 解 方 式 可 分 成 两 类, 一 是 从 获 得 的 数 据 用 联 立 方 程 组 直 接 计 算 得 到 的, 二 是 以 对 定 义 在 参 数 空 间 的 能 量 函 数 最 优 化 搜 索 得 到 的 前 者 完 全 限 制 在 基 于 特 征 (feature-based) 的 配 准 应 用 中 在 后 者 中, 所 有 的 配 准 问 题 都 变 成 一 个 能 量 函 数 的 极 植 求 解 问 题, 能 量 函 数 是 由 需 要 被 优 化 的 变 换 参 数 表 示 的, 一 般 是 拟 凸 的, 能 用 标 准 的 优 化 算 法 求 解 极 值 图 像 配 准 问 题 本 质 上 是 多 参 数 优 化 问 题, 所 以 优 化 算 法 的 选 择 至 关 重 要 常 用 的 优 化 算 法 有 :Powell 法 下 山 单 纯 形 法 Arent 法 Levenberg-Marquadrt 法 Newton-Raphson 迭 代 法 随 机 搜 索 法 梯 度 下 降 法 遗 传 算 法 模 拟 退 火 法 几 何 hash 法 半 穷 尽 搜 索 法 在 实 际 应 用 中, 经 常 使 用 附 加 的 多 分 辨 率 和 多 尺 度 方 法 加 速 收 敛 降 低 需 要 求 解 的 变 换 参 数 数 目 避 免 局 部 最 小 值, 并 且 多 种 优 化 算 法 混 合 使 用, 即 开 始 时 使 用 粗 略 的 快 速 算 法, 然 后 使 用 精 确 的 慢 速 算 法 图 像 配 准 中 的 优 化 函 数 就 是 相 似 性 测 度 函 数 St, () 其 中 t 是 n 维 向 量, 表 示 几 何 变 换 中 的 n 个 参 数 这 里 的 优 化 函 数 St () 有 几 个 显 著 特 点 : 1. 一 般 来 说, St () 不 够 平 滑, 存 在 很 多 局 部 极 值 点 这 给 优 化 算 法 的 选 择 提 出 了 很 高 的 要 求, 由 于 这 些 局 部 极 值 点 的 存 在, 优 化 结 果 会 对 初 始 值 ( 初 始 变 换 ) 比 较 敏 感, 导 致 配 准 结 果 不 够 鲁 棒 2. St () 中 各 参 数 的 变 化 对 函 数 结 果 的 影 响 因 子 差 别 较 大 比 如 在 刚 性 变 换 下, 旋 转 比 平 移 引 起 的 图 像 变 化 要 大 因 此, 最 好 给 每 个 参 数 分 配 一 个 缩 放 因 子, 使 各 参 数 以 合 适 的 步 长 进 行 迭 代 搜 索, 减 少 搜 索 时 间 提 高 计 算 精 度 另 外 还 要 优 化 各 参 数 的 搜 索 顺 序, 比 如 考 虑 到 成 像 过 程 中, 病 人 在 xy 平 面 的 旋 转 和 平 移 比 其 它 方 向 的 转 转 和 平 移 都 要 大, 故 可 以 先 搜 索 xy 平 面 的 旋 转 和 平 移 参 数 227
235 7 配 准 算 法 的 设 计 与 实 现 3. 在 有 些 情 况 下, St () 的 全 局 最 优 并 不 带 来 最 好 的 配 准 效 果, 由 于 相 似 度 函 数 并 不 包 括 配 准 图 像 的 所 有 有 效 信 息, 两 幅 图 像 大 面 积 不 匹 配 时 反 而 比 正 确 匹 配 时 具 有 更 大 的 相 似 度 因 此 要 合 理 选 择 优 化 参 数 的 取 值 范 围 从 已 有 的 文 献 来 看, 比 较 多 的 采 用 Powell 程 序 进 行 优 化,MITK 中 PowellOptimizer 实 现 了 这 种 算 法 MITK 中 函 数 优 化 模 块 用 Optimizer 表 示, 继 承 自 ObiectProcess 类, 其 最 关 键 的 输 入 就 是 优 化 函 数 ( 相 似 性 测 度 ), 这 通 过 函 数 void SetMetric(mitkMetric *metric) 设 定, 在 Metric 类 的 成 员 函 数 double GetSimilarity(const vector<double> *parameters) 中 得 到 具 体 体 现 除 此 之 外 比 较 重 要 的 接 口 函 数 有 : void SetScales(const vector<double> *scales) 设 定 各 几 何 变 换 参 数 的 尺 度 因 子 ; void SetInitialPosition( const vector<double> *param ) 设 置 初 始 变 换 参 数 ; 各 种 具 体 的 优 化 算 法 也 同 Metric 模 块 类 似, 通 过 Optimizer 的 派 生 类 在 bool Execute() 函 数 中 实 现 如 图 7-13 所 示 Optimizer RegularStepGradientDescentOptimizer PowellOptimizer OnePlusOneEvolutionaryOptimizer 图 7-13 优 化 模 块 Optimizer 层 次 结 构 7.7 配 准 算 法 实 现 以 上 Transform InterpolateFilter Metric Optimizer 四 模 块 从 高 层 概 念 上 实 228
236 7 配 准 算 法 的 设 计 与 实 现 现 了 相 应 的 算 法, 提 供 基 本 的 数 据 成 员 和 成 员 函 数 接 口 不 同 类 型 的 各 种 具 体 算 法 通 过 它 们 的 派 生 类 在 虚 函 数 Run() 中 实 现, 配 准 类 RegistrationFilter 的 Run() 函 数 则 组 合 这 四 个 模 块, 形 成 一 个 完 整 的 算 法 流 程, 配 准 结 果 通 过 相 应 的 Get 函 数 得 到 为 了 提 高 算 法 的 易 用 性 和 灵 活 性, 我 们 在 RegistrtionFileter 中 也 提 供 了 各 常 用 数 据 和 参 数 的 输 入 输 出 接 口, 如 图 7-14 所 示 用 户 通 过 设 置 具 体 的 几 何 变 换 类 型 插 值 方 法 相 似 性 度 量 准 则 优 化 策 略 就 能 完 成 各 种 不 同 的 配 准 算 法 RegistrationFilter m_indata:volume m_movingvolume:volume m_outdata:volume m_transform:transform m_interpolator:interpolatefilter m_metric:metric m_optimizer:optimizer m_parameters:vetor<double> +SetInput(f:Volume) +SetMovingVolume(m:Volume) +SetTransform(t:Transform) +SetInitialTransformParameters(tp:vector<double>) +SetInterpolator(i:InterpolateFilter) +SetMetric(m:Metric) +SetOptimizer(o:Optimizer) +GetLastTransformParameters():vector<double> +GetOutput():Volume +Run():bool call functions: m_metric->set? m_optimizer->set? 图 7-14 配 准 类 图 7.8 应 用 实 例 下 面 用 一 个 简 单 的 例 子 来 说 明 如 何 使 用 MITK 中 的 配 准 算 法 来 实 现 两 幅 图 像 的 配 准 初 始 化 参 数 均 采 用 默 认 值 // 需 包 含 以 下 头 文 件 #include "mitkregistrationfilter.h" #include "mitkrigidtransform.h" #include "mitkmeansquaremetric.h" #include "mitknearestneighborinterpolatefilter.h" #include "mitkpowelloptimizer.h" #include "mitkvolume.h" // 初 始 化 配 准 算 法 229
237 7 配 准 算 法 的 设 计 与 实 现 mitkvolume* fixedvolume = new mitkvolume; mitkvolume* movingvolume = new mitkvolume; mitkregistrationfilter* registration = new mitkregistrationfilter; mitkrigidtransform* transform = new mitkrigidtransform; mitkmeansquaremetric* metric = new mitkmeansquaremetric; mitknearestneighborinterpolatefilter* interpolator = new mitknearestneighborinterpolatefilter; mitkpowelloptimizer* optimizer = new mitkpowelloptimizer; registration->setinput(fixedvolume); registration->setmovingvolume(movingvolume); registration->settransform(transform); registration->setinterpolator(interpolator); registration->setmetric(metric); registration->setoptimizer(optimizer); // 开 始 配 准 registration->run(); // 输 出 结 果 vector<double>* parameters; mitkvolume* outvolume; parameters = registration->getlasttransformparameters(); outvolume = registration->getoutput(); 7.9 小 结 本 章 介 绍 了 不 同 的 图 像 配 准 算 法 在 MITK 中 的 实 现 策 略, 通 过 算 法 分 析 与 抽 象, 在 高 层 概 念 上 整 个 配 准 算 法 被 分 成 四 个 相 互 独 立 的 模 块 :Transform Interpolate Metric Optimizer, 每 个 模 块 下 再 派 生 出 各 种 不 同 类 型 的 具 体 实 现, 最 后 将 这 四 个 模 块 按 照 固 定 的 流 程 搭 配 在 一 起 就 可 以 组 合 出 多 种 多 样 的 配 准 算 法 这 样 一 种 框 架 结 构 保 证 了 配 准 算 法 的 扩 充 性, 新 的 算 法 可 以 通 过 相 应 模 块 下 的 派 生 类 实 现, 而 且 各 种 算 法 的 性 能 也 能 够 在 这 一 致 的 框 架 结 构 中 方 便 地 进 行 比 较 MITK 中 目 前 的 配 准 算 法 还 主 要 是 考 虑 刚 性 配 准 算 法 的 实 现, 并 且 还 在 不 断 改 善 的 过 程 中 随 着 各 种 多 模 态 非 刚 性 配 准 算 法 的 出 现 和 完 善 [7], 比 如 基 于 流 体 模 型 基 于 有 限 元 的 方 法 等, 这 些 新 的 算 法 也 将 逐 渐 加 入 MITK 中 参 考 文 献 230
238 7 配 准 算 法 的 设 计 与 实 现 1. Brown L, A Survey of Image Registration Techniques, ACM Computing Surveys (CSUR), 1992,24(4): 田 捷, 包 尚 联, 周 明 全. 医 学 影 像 处 理 与 分 析. 北 京 : 电 子 工 业 出 版 社, J. Schnabel, et al. A Generic Framework for Non-rigid Registration Based on Non-uniform Multi-level Free-form Deformations, Medical Image Computing and Computer-Assisted Intervention (MICCAI 2001), Utrecht, NL, G. P. Penney, J. Weese, J. A. Little, P. Desmedt, D. L. G. Hill, and D. J. Hawkes. A comparision of similarity measures for use in 2d-3d medical image registration. IEEE Transactions on Medical Imaging, 17(4): , August J. Pluim, J. Maintz, M. Viergever, Mutual-Information-Based Registration of Medical Images: A Survey, IEEE Transaction on Medical Image, 22(8): , M. Holden, D. L. G. Hill, E. R. E. Denton, J. M. Jarosz, T. C. S. Cox, and D. J. Hawkes. Voxel similarity measures for 3d serial mr brain image registration. In A. Kuba, M. Samal, and A. Todd-Pkropek, editors, Information Processing in Medical Imaging 1999 (IPMI 99), pages Springer, H. Lester, S. Arridge, A Survey of Hierarchical Non-linear Medical Image Registration, Pattern Recognition, 32: ,
239 8 DICOM 标 准 的 实 现 8 DICOM 标 准 的 实 现 8.1 DICOM 标 准 简 介 计 算 机 技 术 的 发 展, 大 容 量 存 储 介 质 和 图 像 压 缩 技 术 的 应 用, 使 医 学 图 像 可 以 大 量 存 储 ; 计 算 机 运 行 速 度 的 提 高, 使 得 对 图 像 的 实 时 分 析 成 为 可 能 ; 计 算 机 显 示 技 术 和 虚 拟 现 实 技 术 的 发 展, 使 得 医 生 不 用 开 刀 就 可 以 看 到 病 人 体 内 逼 真 的 三 维 图 像 在 这 些 医 学 图 像 诊 断 的 技 术 当 中, 现 在 的 热 点 和 关 键 问 题 之 一 是 医 学 图 像 及 诊 断 数 据 的 存 储 和 传 输 问 题 八 十 年 代 以 来, 为 了 利 用 网 络 在 不 同 的 设 备 和 医 疗 诊 断 系 统 之 间 交 换 图 像 数 据 和 诊 断 信 息, 国 外 已 经 开 始 着 手 制 订 专 门 针 对 医 学 信 息 通 信 的 协 议 1983 年, 美 国 放 射 学 会 (American College of Radiology ACR) 和 美 国 电 器 制 造 商 协 会 (National Electrical Manufacturers Association NEMA) 成 立 了 一 个 联 合 委 员 会 开 发 相 关 标 准, 并 于 1985 年 发 布 了 ACR-NEMA 标 准 经 过 多 年 的 增 补 和 修 改,ACR-NEMA 标 准 最 终 演 变 成 为 新 一 代 的 DICOM3.0(Digital Imaging and Communications in Medicine) 标 准 [1] 在 这 个 标 准 中, 增 强 了 对 网 络 的 支 持, 成 为 医 学 影 像 设 备 的 国 际 标 准 通 信 协 议 现 在, 各 个 厂 家 的 医 疗 仪 器 和 医 学 诊 断 系 统 都 已 开 始 使 用 国 际 化 的 通 信 和 数 据 格 式 标 准 DICOM DICOM 标 准 的 产 生 和 演 化 DICOM 标 准 的 演 变 历 史 大 致 可 以 划 分 为 如 下 三 个 阶 段 : (1) ACR-NEMA 标 准 1.0 版 (1985 年 ) 美 国 放 射 学 会 (ACR) 和 美 国 电 器 制 造 商 协 会 (NEMA) 组 成 的 联 合 委 员 会 经 过 两 年 开 发, 于 1985 年 发 布 了 ACR-NEMA 年 10 月 和 1988 年 1 月 又 分 别 颁 布 了 两 个 修 改 版 本 在 这 些 标 准 当 中, 解 决 了 以 下 问 题 : 统 一 数 据 格 式 和 传 输 标 准, 实 现 了 不 同 厂 家 不 同 设 备 间 数 字 化 医 疗 数 据 的 通 信 问 题 ; 提 供 了 PACS(Picture Archiving and Communication System) 系 统 与 其 它 医 学 信 息 系 统 (Hospital Information System HIS) 的 接 口 [4]; 通 过 将 医 生 诊 断 信 息 数 据 随 同 病 人 图 像 数 据 按 统 一 格 式 提 供 给 不 同 的 232
240 8 DICOM 标 准 的 实 现 医 疗 设 备 和 医 疗 工 作 站, 并 建 立 诊 疗 数 据 库, 使 得 不 同 科 室 的 医 生 可 以 方 便 地 查 询 相 关 病 人 的 完 整 信 息 (2) ACR-NEMA 标 准 2.0 版 (1988 年 ) 1988 年 ACR-NEMA 标 准 颁 布 了 2.0 版 在 这 个 版 本 中, 除 包 含 ACR-NEMA1.0 的 内 容 之 外, 还 包 括 了 以 下 的 修 改 和 补 充 : 提 供 了 对 显 示 设 备 的 命 令 支 持 ; 引 入 新 的 层 次 结 构 模 型, 用 以 更 清 晰 地 标 识 医 疗 图 像 ; 增 加 新 的 数 据 元 素 以 描 述 医 学 图 像 的 相 关 信 息 ; 指 定 硬 件 接 口 标 准, 以 及 相 关 的 基 本 命 令 集 和 数 据 编 码 格 式 集 (3) DICOM 3.0 标 准 (1995 年 ) 为 了 更 有 效 地 支 持 医 学 信 息 系 统 HIS/RIS ( Hospital Information System/Radiology Information System) 对 网 络 通 信 的 要 求, 从 1989 年 开 始, ACR-NEMA 着 手 制 订 新 一 代 的 医 疗 数 据 通 信 标 准 为 了 有 别 于 早 期 的 ACR-NEMA 标 准, 将 其 命 名 为 医 学 数 字 图 像 数 据 通 信 标 准 DICOM 3.0(Digital Imaging and Communications in Medicine) DICOM 3.0 标 准 的 制 订 工 作 直 到 1995 年 才 基 本 完 成 在 此 期 间, 主 要 经 历 了 如 下 过 程 : 1991 年, 发 布 DICOM 标 准 的 1 8 章, 规 定 了 DICOM 通 信 和 数 据 编 码 的 主 要 内 容 ; 1992 年, 北 美 放 射 年 会 (Radiology Sociaty of North America RSNA) 发 布 对 标 准 1 8 章 的 测 试 结 果 ; 1993 年, 完 成 对 DICOM 标 准 的 1 8 章 的 修 订, 增 加 了 第 9 章 ( 点 到 点 通 信 支 持 ); 1994 年, 增 加 第 10 章 ( 媒 体 存 储 和 文 件 格 式 ); 1995 年, 增 加 第 11,12,13 章, 分 别 规 定 了 有 关 建 立 媒 体 存 储 方 式 存 档, 物 理 媒 体 数 据 格 式 以 及 点 到 点 打 印 管 理 的 有 关 内 容 ; 与 早 期 的 ACR-NEMA 标 准 相 比,DICOM 3.0 体 现 了 许 多 方 面 的 改 进 和 增 强 表 8-1 是 对 ACR-NEMA 2.0 标 准 和 DICOM 3.0 标 准 主 要 特 点 的 233
241 8 DICOM 标 准 的 实 现 对 比 表 8-1 ACR-NEMA 2.0 和 DICOM 3.0 的 主 要 特 点 对 比 ACR-NEMA 2.0 标 准 DICOM 3.0 基 于 直 观 的 信 息 模 型 提 供 的 服 务 在 命 令 中 组 成, 只 能 以 命 令 的 形 式 完 成 数 据 的 交 换 定 义 了 符 合 标 准 的 最 低 要 求, 但 没 有 描 述 标 准 符 合 声 明 的 规 定 只 支 持 点 对 点 通 信, 在 网 络 环 境 中 需 要 特 定 的 网 络 接 口 设 备 才 能 支 持 网 络 协 议 一 个 消 息 中 最 多 包 含 一 幅 图 像 定 义 了 ACR-NEMA 独 有 的 命 令 基 于 明 确 的 信 息 模 型, 使 用 E-R 模 型 描 述 信 息 对 象 之 间 的 关 系 支 持 的 服 务 根 据 其 用 途 描 述, 引 入 服 务 类 的 概 念 描 述 命 令 及 与 其 相 联 系 数 据 的 语 义 包 含 描 述 标 准 符 合 声 明 的 规 定, 允 许 存 在 不 同 级 别 的 标 准 符 合 声 明 以 供 用 户 选 择 支 持 网 络 通 信, 提 供 对 OSI 七 层 协 议 和 TCP/IP 协 议 等 工 业 标 准 的 全 面 支 持 支 持 文 件 夹 功 能, 一 个 消 息 中 可 以 包 含 多 个 图 像 使 用 现 存 的 其 他 标 准 定 义 命 令, 规 定 了 符 合 本 标 准 的 设 备 对 命 令 的 反 馈 和 数 据 交 换 方 式 医 学 信 息 数 据 通 信 领 域 正 在 经 历 一 个 飞 速 发 展 的 阶 段, 大 量 新 的 医 疗 设 备 计 算 机 诊 断 系 统 和 诊 疗 理 念 不 断 丰 富 着 这 一 领 域 因 此,DICOM 标 准 从 诞 生 之 日 起 就 是 一 个 开 放 的 标 准, 它 不 断 地 进 行 着 自 我 完 善 扩 充 和 演 化 的 过 程 为 了 适 应 这 种 内 容 不 断 扩 充 和 更 新 的 需 要,DICOM 标 准 采 用 了 广 义 的 信 息 对 象 定 义 IOD(Information Object Definition) 的 概 念, 不 仅 包 括 医 学 图 形 和 图 像, 也 包 括 大 量 相 关 的 检 查 报 告 等 广 义 的 信 息 对 象, 并 且 通 过 唯 一 标 识 UID(Unique Identifier) 的 方 式 在 网 络 环 境 下 唯 一 地 确 定 这 些 信 息 对 象 在 此 基 础 上,DICOM 234
242 8 DICOM 标 准 的 实 现 标 准 定 义 了 大 量 不 同 的 服 务 类 (Service Class), 用 以 完 成 不 同 的 服 务 功 能 因 此, 对 于 不 断 更 新 的 医 疗 设 备 及 其 各 种 类 型 的 数 据, 可 以 通 过 修 改 或 定 义 新 的 IOD 使 得 标 准 与 之 相 适 应 并 得 以 扩 充 ; 而 对 于 新 增 的 功 能, 则 可 以 通 过 定 义 新 的 服 务 类 来 完 成 这 样 就 达 到 了 不 需 对 DICOM 标 准 的 整 体 架 构 进 行 修 改 的 情 况 下, 完 成 标 准 本 身 不 断 扩 充 和 更 新 的 目 的 也 就 是 说,DICOM 标 准 可 以 在 不 断 吸 收 新 特 性 的 同 时, 仍 能 很 好 地 保 持 与 原 先 版 本 的 兼 容 性 [1] 实 际 上,ACR-NEMA 每 年 都 会 公 布 当 年 新 修 订 的 DICOM 版 本 MITK 中 部 分 DICOM 标 准 的 实 现 正 是 基 于 DICOM 3.0 的 2003 年 版 DICOM 标 准 的 主 要 特 点 作 为 医 学 信 息 通 信 领 域 的 国 际 标 准,DICOM 具 有 以 下 一 些 突 出 特 点 : (1) DICOM 协 议 是 一 种 上 层 网 络 协 议 [1] DICOM 协 议 处 于 OSI 开 放 系 统 互 联 七 层 协 议 的 上 三 层, 即 会 话 层 表 示 层 和 应 用 层 的 位 置, 而 在 七 层 协 议 的 下 层 主 要 使 用 TCP/IP 协 议 所 提 供 的 服 务 DICOM 协 议 要 求 在 数 据 的 编 码 传 输 之 前, 必 须 先 进 行 连 接 协 商 以 确 认 双 方 同 意 某 些 特 定 的 条 件, 可 以 完 成 特 定 的 通 信 功 能 DICOM 称 连 接 协 商 的 成 功 为 建 立 了 一 个 关 联 (association) 只 有 在 建 立 关 联 之 后, 才 能 进 行 DICOM 命 令 和 数 据 的 发 送 和 接 收 (2) DICOM 数 据 编 码 的 特 点 标 准 定 义 了 26 种 内 部 数 据 类 型 ; 像 素 数 据 的 编 码 支 持 JPEG 图 像 压 缩 ; 图 像 可 以 包 含 缩 略 图 和 正 常 图 像, 也 可 以 有 多 帧 格 式 ; DICOM 标 准 支 持 多 个 字 符 集 ; DICOM 具 有 自 己 独 特 的 数 据 模 型 ; DICOM 通 过 信 息 对 象 定 义 IOD(Information Object Definition) 的 形 式 来 完 整 地 建 立 和 定 义 医 院 环 境 下 的 数 据 模 型 ; DICOM 使 用 全 局 唯 一 标 识 UID(Unique Identifier) 在 网 络 环 境 235
243 8 DICOM 标 准 的 实 现 下 唯 一 地 标 识 各 种 IOD 信 息 对 象, 使 之 不 致 混 淆 (3) 拥 有 完 整 庞 大 的 数 据 字 典 DICOM 标 准 拥 有 一 个 庞 大 的 数 据 字 典, 其 内 容 包 含 了 几 乎 所 有 医 疗 环 境 下 的 常 用 数 据, 可 以 完 整 地 描 述 各 种 医 学 设 备 图 像 格 式 数 据 以 及 病 人 相 关 信 息 数 据 字 典 的 条 目 以 数 据 元 素 (Data Element) 为 单 位, 每 个 数 据 元 素 描 述 一 项 数 据 内 容, 如 病 人 姓 名 检 查 日 期 一 幅 图 像 的 像 素 数 据 等 都 可 以 是 一 个 数 据 元 素 数 据 字 典 具 有 可 扩 充 性 DICOM 标 准 预 留 出 数 据 字 典 的 一 部 分, 允 许 各 厂 家 按 照 标 准 的 格 式 自 定 义 新 的 数 据 元 素 (4) 通 过 服 务 类 (Service Class) 概 念 实 现 应 用 层 功 能 为 了 完 成 某 个 特 定 的 应 用 功 能 ( 如 图 像 管 理 打 印 管 理 等 ),DICOM 定 义 了 服 务 类 (Service Class) 的 概 念 服 务 类 描 述 了 可 以 对 信 息 对 象 IOD 所 做 的 操 作 服 务 类 和 信 息 对 象 结 合 起 来 构 成 了 DICOM 的 基 本 单 元, 称 为 服 务 - 对 象 对 (Service-Object Pair SOP)[1] 表 8-2 列 举 了 一 些 典 型 的 服 务 类 及 其 描 述 : 表 8-3 DICOM 服 务 类 举 例 服 务 类 描 述 Image storage Image query Image retrieval Image print Examination Storage resource 提 供 数 据 集 的 存 储 服 务 支 持 数 据 集 的 查 询 支 持 从 存 储 设 备 检 索 图 像 提 供 硬 拷 贝 图 像 生 成 服 务 支 持 检 查 管 理 支 持 网 络 数 据 存 储 资 源 管 理 (5) 离 线 媒 体 支 持 236
244 8 DICOM 标 准 的 实 现 DICOM 定 义 了 自 己 的 文 件 夹 结 构, 用 以 形 成 文 件 集 合 (File-set) 此 外, 允 许 以 媒 体 存 储 特 征 (media profiles) 的 形 式 定 义 对 数 据 的 不 同 媒 体 存 储 策 略 (6) 不 同 级 别 的 一 致 性 声 明 (Conformance Statement) DICOM 标 准 要 求 一 个 实 际 的 通 信 系 统 应 该 有 一 个 一 致 性 声 明 (Conformance Statement), 用 以 说 明 此 系 统 对 DICOM 协 议 的 支 持 程 度, 以 及 支 持 哪 些 类 型 的 数 据 和 服 务 DICOM 标 准 的 总 体 结 构 和 主 要 内 容 2003 版 的 DICOM 标 准 分 为 以 下 16 个 相 关 而 又 相 对 独 立 的 部 分 [1] : (1) DICOM 标 准 概 要 (Introduction and Overview); (2) 一 致 性 声 明 (Conformance); (3) 信 息 对 象 定 义 (Information Object Definitions); (4) 服 务 类 规 范 (Service Class Specifications); (5) 数 据 结 构 和 语 义 (Data Structure and Semantics); (6) 数 据 字 典 (Data Dictionary); (7) 消 息 交 换 (Message Exchange); (8) 消 息 交 换 的 网 络 通 信 支 持 (Network Communication Support for Message Exchange); (9) 消 息 交 换 的 点 到 点 通 信 模 式 (Point-to-Point Communication Support for Message Exchange); (10) 媒 体 存 储 和 文 件 格 式 (Media Storage and File Format); (11) 媒 体 存 储 策 略 (Media Storage Application Profiles); (12) 数 据 交 换 的 存 储 功 能 和 媒 体 格 式 (Storage Functions and Media Formats for Data Interchange); 237
245 8 DICOM 标 准 的 实 现 (13) 点 到 点 打 印 管 理 (Print Management Point-to-Point Communication Support); (14) 标 准 灰 度 显 示 功 能 (Grayscale Standard Display Function); (15) 安 全 策 略 (Security Profiles); (16) 内 容 映 射 资 源 (Content Mapping Resource) 由 于 点 到 点 通 信 已 逐 渐 被 网 络 通 信 所 代 替, 因 此 其 中 与 点 到 点 通 信 有 关 的 第 (9) 和 (13) 部 分 已 被 标 记 为 RETIRED, 不 再 更 新 上 述 16 个 部 分 大 致 可 以 归 结 为 以 下 几 个 主 要 的 方 面 : (1) 一 致 性 声 明 (Conformance) 这 部 分 提 出 了 医 疗 设 备 或 诊 断 系 统 为 满 足 DICOM 标 准 的 要 求, 所 必 须 遵 循 的 规 范 它 详 细 地 规 定 了 规 范 声 明 的 层 次 结 构, 以 及 声 明 中 各 部 分 所 必 须 包 含 的 内 容 实 际 上,DICOM 标 准 的 各 个 部 分 都 有 自 己 的 规 范 声 明 此 处 的 一 致 性 声 明 是 各 部 分 规 范 声 明 的 汇 总 一 致 性 声 明 可 以 分 为 几 个 主 要 部 分 : 本 医 疗 设 备 或 诊 断 系 统 ( 即 应 用 实 体 ) 所 支 持 的 DICOM 信 息 对 象 ; 应 用 实 体 系 统 支 持 的 服 务 类 ; 应 用 实 体 系 统 支 持 的 通 信 协 议, 如 TCP/IP 协 议 等 ; 所 支 持 的 表 示 上 下 文 信 息 ; 系 统 配 置 信 息 一 致 性 声 明 的 意 义 在 于 : 由 于 DICOM 协 议 十 分 庞 大, 以 至 于 至 今 仍 没 有 一 个 公 司 或 团 体 已 经 将 其 完 全 实 现 事 实 上, 往 往 只 需 要 实 现 其 中 一 部 分 功 能 即 可 满 足 实 际 需 要 因 此, 允 许 实 现 者 根 据 需 求 选 择 支 持 哪 些 DICOM 组 件, 也 允 许 进 行 扩 展 不 同 的 系 统 会 有 不 同 的 支 持 部 分, 所 以 也 会 有 不 同 的 一 致 性 声 明 [1]; 用 户 或 系 统 设 计 人 员 通 过 对 比 两 种 不 同 实 现 的 一 致 性 声 明, 就 能 够 判 断 出 两 个 系 统 是 否 可 以 进 行 互 操 作 和 通 信 238
246 8 DICOM 标 准 的 实 现 (2) 信 息 对 象 定 义 (Information Object Definitions) 这 一 部 分 具 体 介 绍 了 DICOM 标 准 面 向 对 象 的 信 息 结 构 模 型, 即 用 信 息 对 象 定 义 IOD 来 描 述 现 实 世 界 中 的 各 种 医 疗 信 息 实 体 这 里 详 细 定 义 了 多 种 IOD, 并 规 定 了 它 们 的 内 部 结 构 DICOM 用 IOD 定 义 服 务 类 所 作 用 的 对 象 的 数 据 结 构 和 属 性,IOD 是 对 现 实 世 界 中 具 有 共 同 属 性 实 体 的 面 向 对 象 的 抽 象 为 了 方 便 标 准 的 扩 展 并 保 持 与 以 前 版 本 的 兼 容 性,DICOM 定 义 了 两 种 IOD, 即 正 规 IOD(Normalized IOD) 和 复 合 IOD(Composite IOD) 每 个 IOD 由 用 途 说 明 和 大 量 相 关 的 属 性 组 成, 但 IOD 本 身 并 不 包 括 各 个 属 性 的 值, 而 是 只 包 含 其 定 义 这 些 属 性 描 述 的 内 容 虽 然 千 差 万 别, 却 拥 有 几 乎 相 同 的 结 构 属 性 又 按 照 一 定 规 则 分 成 组, 以 利 于 被 不 同 的 IOD 复 用 当 需 要 表 示 现 实 世 界 的 某 个 实 体 时, 就 要 将 相 应 的 IOD 实 例 化, 这 时 IOD 属 性 的 值 被 填 充 进 来 这 些 属 性 值 在 下 文 介 绍 的 服 务 类 作 用 下 可 以 不 断 发 生 变 化 (3) 服 务 类 规 范 (Service Class Specifications) 标 准 的 这 一 部 分 指 定 作 用 于 信 息 对 象 实 例 上 的 操 作, 即 所 提 供 的 服 务, 如 图 像 的 存 储 查 询 检 索 打 印 等, 并 称 之 为 服 务 类 一 个 特 定 的 服 务 类 可 通 过 一 至 多 个 命 令 作 用 于 一 至 多 个 IOD 实 例 这 里 具 体 阐 明 了 每 个 服 务 类 对 其 命 令 元 素 的 规 范 要 求, 以 及 通 信 服 务 的 提 供 者 和 使 用 者 应 完 成 的 功 能 这 一 部 分 还 提 供 了 以 下 一 些 服 务 类 的 实 例 : 存 储 服 务 类 (Storage Service Class) 查 询 服 务 类 (Query Service Class) 检 索 服 务 类 (Retrieval Service Class) 检 查 管 理 服 务 类 (Study Management Service Class) 数 据 结 构 和 编 码 (Data Structure and Encoding) 这 一 部 分 规 定 了 服 务 类 为 完 成 特 定 操 作 而 交 换 的 数 据 的 构 造 过 程 和 编 码 结 构 服 务 类 的 命 令 和 IOD 数 据 都 要 经 过 编 码 成 指 定 结 构 的 数 据 流, 才 能 形 成 消 息 并 完 成 发 送 过 程 ; 同 样, 要 接 收 网 络 上 的 命 令 和 数 据, 必 须 以 逆 过 程 进 行 239
247 8 DICOM 标 准 的 实 现 解 码 数 据 可 以 分 为 命 令 集 (Command-set) 和 数 据 集 (Data-set), 数 据 集 允 许 嵌 套 (4) 数 据 字 典 (Data Dictionary) 数 据 字 典 标 明 了 已 注 册 的 IOD 属 性 数 据 的 类 型 标 识 和 含 义 一 般 来 说, 应 包 含 每 个 属 性 的 如 下 特 征 : 属 性 的 唯 一 标 签 (tag): 包 括 一 个 组 号 和 一 个 元 素 号, 用 户 可 用 这 两 个 号 码 检 索 这 个 属 性 ; 属 性 的 名 称 : 用 于 了 解 其 含 义 ; 属 性 的 数 据 类 型 ( 如 character string, integer 等 ) 数 据 字 典 被 用 于 在 通 信 过 程 中 辅 助 建 造 数 据 集 (5) 消 息 交 换 (Message Exchange) DICOM 标 准 的 这 一 部 分 指 定 了 为 达 到 特 定 医 疗 信 息 交 换 的 目 的 而 引 入 的 操 作 和 协 议 这 些 操 作 用 来 完 成 服 务 类 所 定 义 的 相 应 服 务 一 个 典 型 的 DICOM 消 息 由 一 个 命 令 流 和 紧 随 其 后 的 数 据 流 ( 可 选 ) 组 成 这 里 定 义 了 各 服 务 类 所 发 送 和 接 收 的 消 息, 并 阐 述 了 以 下 规 则 : 建 立 和 终 止 关 联 (association) 的 规 则 ; 控 制 交 换 网 络 命 令 请 求 和 响 应 的 规 则 ; 用 于 建 造 命 令 流 数 据 和 消 息 的 编 码 规 则 ; 消 息 交 换 的 网 络 通 信 模 式 (Network Communication Support for Message Exchange) 这 部 分 是 DICOM 通 信 协 议 的 核 心, 它 详 细 制 订 了 医 学 图 像 和 相 关 信 息 在 网 络 上 通 信 所 需 使 用 的 服 务 和 协 议 的 具 体 内 容 这 些 服 务 和 协 议 内 容 要 协 调 并 保 证 网 络 上 的 DICOM 应 用 实 体 间 通 信 的 效 率 DICOM 通 信 协 议 是 OSI 七 层 协 议 的 一 个 子 集, 它 主 要 包 括 一 些 OSI 上 层 服 务, 包 括 表 示 层 服 务 (ISO8822) 和 OSI 规 定 的 连 接 控 制 服 务 单 元 (Association Control Service Element ACSE,ISO8649) 的 协 议 服 务 内 容 在 此 基 础 上, 对 等 应 用 实 体 间 能 够 建 立 关 联, 传 送 消 息 和 终 止 关 联 [1] 240
248 8 DICOM 标 准 的 实 现 DICOM 协 议 广 泛 地 支 持 现 有 的 网 络 环 境 和 技 术, 如 ISO CSMA/CD (Ethernet),FDDI,ISDN,X.25 等 ;DICOM 协 议 与 TCP/IP 协 议 相 结 合 可 以 很 好 地 实 现 其 功 能 从 TCP/IP 网 络 环 境 向 其 它 OSI 环 境 移 植 也 很 方 便 8.2 MITK 中 DICOM 标 准 的 实 现 MITK 中 DICOM 标 准 的 实 现 基 于 NEMA 发 布 的 2003 年 版 DICOM 3.0 标 准 但 是 由 于 DICOM 标 准 十 分 庞 大,MITK 不 可 能 也 没 有 必 要 将 其 完 全 实 现 MITK 所 需 要 的 只 是 从 存 储 设 备 上 读 取 DICOM 格 式 的 文 件 以 及 将 处 理 过 的 图 像 数 据 存 储 为 简 单 格 式 的 DICOM 文 件, 所 以 MITK 中 所 实 现 的 部 分 实 际 上 是 DICOM 标 准 中 关 于 文 件 读 写 的 一 个 子 集, 主 要 涉 及 的 部 分 有 : 信 息 对 象 定 义 数 据 结 构 和 语 义 数 据 字 典 以 及 媒 体 存 储 和 文 件 格 式 等 MITK 中 完 成 DICOM 文 件 读 写 功 能 的 类 是 mitkdicomreader 和 mitkdicomwriter 考 虑 到 DICOM 标 准 本 身 的 规 模, 我 们 将 实 现 DICOM 文 件 读 写 功 能 的 基 本 数 据 结 构 及 算 法 提 取 出 来 成 为 一 个 相 对 独 立 的 部 分 作 为 MITK 的 一 个 Utility, 而 mitkdicomreader/writer 只 是 对 这 个 Utility 的 一 个 封 装, 面 向 MITK 用 户 提 供 DICOM 文 件 的 读 写 功 能, 如 图 8-1 所 示 241
249 8 DICOM 标 准 的 实 现 Some Filters MITK Volume DICOMReader DICOMWriter File to image Image to file MITK Utilities DICOM Utility DICOM Files 图 8-1 MITK 中 DICOM 文 件 读 写 部 分 的 基 本 框 架 DICOM 数 据 编 码 方 式 和 文 件 结 构 [1] [1] (1) DICOM 数 据 编 码 规 则 1. DICOM 数 据 流 的 结 构 在 DICOM 标 准 中, 真 实 世 界 中 的 某 个 信 息 对 象 (Information Object) 被 编 码 为 一 个 数 据 集 (Data Set), 从 而 对 其 进 行 存 储 或 传 输 数 据 集 以 数 据 元 素 (Data Element) 为 编 码 单 元, 每 一 个 数 据 元 素 存 储 了 该 数 据 集 某 一 个 属 性 的 值, 其 编 码 格 式 如 图 8-2 所 示 Data Set order of transmission Data Element Data Element Data Element... Data Element Data Element Tag VR Value Length Value Field 图 8-2 Data Set 编 码 格 式 其 中, 每 个 数 据 元 素 包 括 一 下 一 些 字 段 的 内 容 : 242
250 8 DICOM 标 准 的 实 现 (1) 标 签 (Tag) 标 签 是 一 对 16 位 无 符 号 整 数 ( 占 4 字 节 ), 其 格 式 用 16 进 制 表 示 为 (gggg, eeee), 其 中,gggg 是 组 号 (Group Number),eeee 是 元 素 号 (Element Number) 组 号 表 示 该 数 据 元 素 属 于 哪 一 组 属 性 ( 性 质 相 近 或 相 关 的 属 性 被 编 入 同 一 组 中 ), 比 如 有 关 病 人 的 信 息 ( 姓 名 性 别 之 类 ) 都 被 编 入 0010 组 ; 而 元 素 号 则 用 于 区 分 同 一 组 中 的 不 同 属 性 的 数 据 元 素 由 组 号 和 元 素 号 组 成 的 标 签 唯 一 标 识 了 某 个 数 据 元 素 的 属 性, 数 据 字 典 即 通 过 标 签 值 来 检 索 特 定 的 数 据 元 素, 比 如 标 签 值 为 (0010,0010) 的 数 据 元 素 所 存 储 的 数 据 为 病 人 姓 名 DICOM 标 准 定 义 了 两 大 类 的 数 据 元 素 : 标 准 数 据 元 素 : 除 (0000,eeee) (0002,eeee) (0004,eeee) 和 (0006,eeee) 之 外 组 号 为 偶 数 的 数 据 元 素 这 些 都 是 已 经 由 DICOM 标 准 规 定 了 具 体 属 性 的 数 据 元 素, 其 含 义 可 以 通 过 数 据 字 典 检 索 得 到 组 号 为 和 0006 的 数 据 元 素 为 DICOM 标 准 所 保 留, 主 要 用 于 DIMSE(DICOM Message Service Element) 命 令 以 及 DICOM 文 件 格 式 ; 私 有 数 据 元 素 : 除 (0001,eeee) (0003,eeee) (0005,eeee) (0007, eeee) 和 (FFFF,eeee) 之 外 组 号 为 奇 数 的 数 据 元 素 这 类 数 据 元 素 可 以 由 用 户 定 义 其 属 性 含 义, 属 于 扩 展 部 分, 不 编 入 数 据 字 典 通 用 的 DICOM 数 据 解 析 程 序 一 般 不 处 理 这 一 部 分 数 据 元 素 组 号 为 和 FFFF 的 数 据 元 素 同 样 为 DICOM 标 准 所 保 留 另 外, 没 一 组 的 第 一 个 元 素, 即 标 签 为 (gggg,0000) 的 数 据 元 素 是 一 个 特 殊 的 数 据 元 素, 它 记 录 了 某 个 数 据 集 中 所 有 属 于 该 组 的 数 据 元 素 的 总 长 度 (2) 数 据 类 型 (Value Representation,VR) 数 据 类 型 是 一 个 2 字 节 的 字 符 串, 表 示 该 数 据 元 素 的 数 据 域 (Value Field) 所 存 储 数 据 的 类 型 该 字 段 是 可 选 字 段 某 个 数 据 集 中 的 数 据 元 素 是 否 包 含 这 个 字 段, 由 该 数 据 集 的 传 输 语 法 (Transfer Syntax) 所 决 定, 即 若 传 输 语 法 规 定 为 隐 式 数 据 类 型 243
251 8 DICOM 标 准 的 实 现 (Implicit VR), 则 该 字 段 被 省 略, 在 解 析 过 程 中 可 以 根 据 数 据 元 素 的 标 签 值, 通 过 检 索 数 据 字 典 找 到 该 数 据 元 素 的 数 据 类 型 ; 而 当 传 输 语 法 规 定 为 显 式 数 据 类 型 (Explicit VR) 时, 每 个 数 据 元 素 则 必 须 包 含 该 字 段 DICOM 标 准 所 规 定 的 数 据 类 型 如 表 8-3 所 示 表 8-3 数 据 类 型 描 述 VR 名 称 对 应 数 据 域 的 格 式 描 述 值 长 度 ( 字 节 ) 对 应 C++ 类 型 AE Application Entity, 字 符 串 16 char* AS Age String, 格 式 为 nnnd nnnw nnnm 或 nnny 的 字 符 串,nnn 是 一 个 3 位 十 进 制 数 表 示 天 数 (D) 周 数 (W) 月 数 (M) 或 年 数 (Y) 4 char* AT Attribute Tag, 一 对 16 位 无 符 号 整 型 数 4 unsigned short[2] CS Code String, 字 符 串 16 char* DA Date, 格 式 为 yyyymmdd 的 日 期 字 符 串 8 char[8] DS Decimal String, 用 字 符 串 表 示 的 实 数, 如 E5 等 16 char* (to double) DT Date Time, 格 式 为 yyyymmdd hhmmss.ffffff 26 char* (to time_t) &zzzz 的 日 期 时 间 字 符 串, 其 中 从 左 到 右 依 次 为 :yyyy=year,mm=month,dd=day, hh=hour, mm=minute, ss=second, ffffff=fractional Second, &= + / -, zzzz=hours 和 Minutes 的 偏 移 量 FL Floating Point Single,32 位 单 精 度 浮 点 数 4 float FD Floating Point Double,64 位 双 精 度 浮 点 数 8 double IS Integer String, 以 字 符 串 表 示 的 十 进 制 整 数, 12 char* (to int) 244
252 8 DICOM 标 准 的 实 现 如 等, 其 取 值 范 围 为 [-2 31, ] LO Long String, 长 字 符 串 64 char* LT Long Text, 长 文 本 数 据 char* OB Other Byte String, 编 码 方 式 由 传 输 语 法 所 规 自 定 义 char* 定 的 字 节 流 OF Other Float String,32 位 浮 点 数 组 float* OW Other Word String, 编 码 方 式 由 传 输 语 法 所 自 定 义 short* 规 定 的 双 字 节 (16 位 ) 流 PN Person Name, 由 5 个 部 分 组 成 的 人 名 字 符 每 个 部 char* 串, 依 次 是 :Family Name Complex Given 分 64 Name Complex Middle Name Name Prefix Name Suffix, 任 一 部 分 都 可 为 空, 每 部 分 间 用 ^ 隔 开 SH Short String, 短 字 符 串 16 char* SL Singed Long, 带 符 号 32 位 整 型 数 4 int (long) SQ Sequence of Items, 包 含 0 个 或 多 个 Item, 用 N/A N/A 于 存 储 嵌 套 的 数 据 集 SS Signed Short, 带 符 号 16 位 整 型 数 2 short ST Short Text, 短 文 本 数 据 1024 char* TM Time, 格 式 为 hhmmss.frac 的 时 间 字 符 串, hh 范 围 为 00-23, mm 和 ss 范 围 均 为 00-59,frac 范 围 为 UI Unique Identifier, 唯 一 标 识 符, 数 字 字 符 串, 中 间 以. 分 隔, 如 char* (to time_t) char* UL Unsigned Long, 无 符 号 32 位 整 型 数 4 unsigned int 245
253 8 DICOM 标 准 的 实 现 (unsigned long) UN Unknown, 未 知 类 型 任 意 N/A US Unsigned Short, 无 符 号 16 位 整 型 数 2 unsigned short UT Unlimited Text, 无 限 制 文 本 char* (3) 数 据 长 度 (Value Length) 一 个 16 位 或 32 位 整 型 数 ( 根 据 VR 种 类 和 VR 是 显 式 还 是 隐 式 决 定 ), 指 定 数 据 域 (Value Field) 的 长 度 ( 以 字 节 为 单 位 ) DICOM 标 准 规 定 数 据 域 长 度 必 须 是 偶 数, 不 足 时 要 用 填 充 字 节 补 齐 若 该 字 段 编 码 为 FFFFFFFFH, 则 表 示 长 度 不 定, 适 用 于 VR 为 SQ 和 UN 的 数 据 元 素 对 于 VR 为 OB 和 OW 的 数 据 元 素, 在 特 定 的 传 输 语 法 下 也 可 能 将 此 字 段 编 码 为 FFFFFFFFH (4) 数 据 域 (Value Field) 存 放 真 正 的 数 据, 必 须 为 偶 数 个 字 节, 不 足 时 由 填 充 字 节 补 齐 其 数 据 类 型 由 该 数 据 元 素 的 VR 指 出, 可 以 存 放 多 个 值, 由 \ 分 隔 根 据 不 同 的 传 输 语 法 ( 在 文 件 头 中 或 通 信 协 商 阶 段 确 定 ), 数 据 元 素 的 格 式 可 为 表 8-4 表 8-5 或 表 8-6 所 示 的 三 者 之 一 表 8-4 显 式 VR 为 OB OW OF SQ UT 或 UN 时 的 数 据 元 素 格 式 标 签 数 据 类 型 数 据 长 度 数 据 域 组 号 元 素 号 VR 2 字 节 保 32 位 无 符 号 偶 数 字 节 的 数 据, 其 编 (16 位 ( 16 位 ( OB 留, 置 为 整 数 码 格 式 由 VR 和 传 输 语 无 符 号 无 符 号 OW 0000H 法 确 定 整 数 ) 整 数 ) OF SQ UT 或 UN ) 246
254 8 DICOM 标 准 的 实 现 2 字 节 2 字 节 2 字 节 2 字 节 4 字 节 由 数 据 长 度 确 定 或 不 定 表 8-5 显 式 VR 除 OB OW OF SQ UT 和 UN 以 外 的 数 据 元 素 格 式 标 签 数 据 类 型 数 据 长 度 数 据 域 组 号 (16 位 元 素 号 (16 位 无 符 号 整 数 ) VR,2 字 节 字 符 串 16 位 无 符 号 整 数 偶 数 字 节 的 数 据, 其 编 码 格 式 由 VR 和 传 输 语 法 确 定 无 符 号 整 数 ) 2 字 节 2 字 节 2 字 节 2 字 节 由 数 据 长 度 确 定 表 8-6 隐 式 VR 数 据 元 素 格 式 标 签 数 据 长 度 数 据 域 组 号 (16 位 无 符 号 整 数 ) 元 素 号 (16 位 无 符 号 整 数 ) 32 位 无 符 号 整 数 偶 数 字 节 的 数 据, 其 编 码 格 式 由 VR 和 传 输 语 法 确 定 2 字 节 2 字 节 4 字 节 由 数 据 长 度 确 定 或 不 定 2. 字 节 序 (Byte Ordering) 任 何 存 储 单 位 大 于 1 字 节 的 数 据 ( 比 如 32 位 整 型 数 以 4 字 节 为 一 个 存 储 单 位 ) 都 存 在 字 节 排 序 的 问 题, 即 每 一 个 数 据 单 元 从 起 始 地 址 开 始 所 有 字 节 按 从 低 到 高 排 序 还 是 从 高 到 低 排 序, 前 者 称 为 小 端 序 (Little Endian), 后 者 称 为 大 端 序 (Big Endian) 比 如 一 个 32 位 整 数 , 用 16 进 制 表 示 是 3ADE68B1, 则 在 小 端 序 的 方 式 下 存 储 为 B1 68 DE 3A( 存 储 单 元 地 址 从 左 往 右 递 增 ), 在 大 端 序 下 存 储 为 3A DE 68 B1 不 同 体 系 结 构 的 计 算 机 可 能 采 用 不 同 的 字 节 序 来 存 储 数 据, 比 如 IBM PC 系 列 兼 容 计 算 机 采 用 小 端 序, 而 Apple 的 Power PC 系 列 则 采 用 大 端 序 DICOM 标 准 同 时 支 持 两 种 字 节 序, 具 体 采 用 哪 种 字 节 序 由 传 输 语 法 确 定, 缺 省 采 用 小 端 247
255 8 DICOM 标 准 的 实 现 序 因 此, 当 读 取 与 本 地 计 算 机 硬 件 要 求 的 字 节 序 不 同 的 数 据 时, 对 于 那 些 多 字 节 的 数 据 单 元 必 须 先 交 换 前 后 字 节 (Byte Swapping) 才 能 进 行 进 一 步 的 处 理 3. 数 据 集 的 嵌 套 (Nesting of Data Sets) 当 数 据 元 素 的 类 型 (VR) 为 SQ 时, 其 数 据 域 包 含 了 一 系 列 的 项 (Item), 每 一 项 又 是 一 个 数 据 集 (Data Set), 这 就 构 成 了 数 据 集 的 嵌 套 与 SQ 有 关 的 数 据 元 素 有 : 项 元 素 (Item), 其 标 签 为 (FFFE,E000); 项 定 界 符 (Item Delimitation), 标 签 为 (FFFE,E00D); 序 列 定 界 符 (Sequence Delimitation), 标 签 为 (FFFE,E0DD) 这 三 个 数 据 元 素 比 较 特 殊, 它 们 的 编 码 格 式 并 不 受 传 输 语 法 的 限 制, 并 且 统 一 采 用 隐 式 VR 的 编 码 方 式 但 是, 它 们 的 数 据 域 所 嵌 套 包 含 的 数 据 集 则 还 是 由 传 输 语 法 规 定 其 编 码 格 式 VR 为 SQ 的 数 据 元 素 所 嵌 套 的 每 一 个 数 据 集 被 封 装 进 一 个 项 元 素 中, 由 于 采 用 隐 式 VR 编 码, 其 格 式 如 表 8-6 所 示 而 根 据 数 据 长 度 字 段 的 不 同, 项 元 素 的 编 码 方 式 有 两 种 : 显 式 长 度 (Explicit Length): 项 元 素 的 数 据 长 度 字 段 包 含 数 据 域 的 长 度 ( 以 字 节 为 单 位 ); 未 定 义 长 度 (Undefined Length): 项 元 素 的 数 据 长 度 字 段 编 码 为 FFFFFFFFH, 因 为 未 指 明 后 面 数 据 域 的 长 度, 所 以 必 须 紧 跟 一 个 标 签 为 (FFFE,E00D) 的 项 定 界 符 标 志 该 项 的 结 束, 项 定 界 符 不 包 含 任 何 数 据, 其 数 据 长 度 字 段 必 须 为 H 整 个 VR 为 SQ 的 数 据 元 素 编 码 根 据 数 据 长 度 字 段 的 不 同 也 有 相 应 的 两 种 编 码 方 式 : 显 式 长 度 (Explicit Length): 项 元 素 的 数 据 长 度 字 段 包 含 数 据 域 的 长 度 ( 以 字 节 为 单 位 ), 该 长 度 为 数 据 域 中 所 有 项 的 总 长 度 ; 未 定 义 长 度 (Undefined Length): 项 元 素 的 数 据 长 度 字 段 编 码 为 FFFFFFFFH, 因 为 未 指 明 后 面 数 据 域 的 长 度, 所 以 必 须 紧 跟 一 个 标 签 为 (FFFE,E0DD) 的 序 列 定 界 符 标 志 该 序 列 的 结 束, 序 列 定 界 符 不 包 含 任 何 数 据, 其 数 据 长 度 字 段 必 须 为 H 248
256 8 DICOM 标 准 的 实 现 上 述 情 况 下 的 实 例 如 表 8-7 表 8-8 和 表 8-9 所 示 表 8-7 带 三 个 显 式 长 度 项 的 隐 式 VR 为 SQ 的 数 据 元 素 例 子 标 签 数 据 长 度 数 据 域 (gg 0000 第 一 项 第 二 项 第 三 项 gg,e eee) VR 为 SQ 0F00 H 项 标 签 (FFF E,E0 00) 项 长 度 F8 H 项 值 ( 数 据 集 ) 项 标 签 (FFF E,E0 00) 项 长 度 F8 H 项 值 ( 数 据 集 ) 项 标 签 (FFF E,E0 00) 项 长 度 F8 H 项 值 ( 数 据 集 ) 4 字 4 字 4 字 4 字 04F8H 4 字 4 字 04F8H 4 字 4 字 04F8H 节 节 节 节 字 节 节 节 字 节 节 节 字 节 表 8-8 带 两 个 显 式 长 度 项 的 显 式 VR 为 SQ 未 定 义 长 度 的 数 据 元 素 例 子 标 签 数 据 类 型 数 据 长 度 数 据 域 (gg gg,e SQ FFF FFF 第 一 项 第 二 项 序 列 定 界 符 eee) VR 为 SQ H 保 留 FFH 未 定 义 长 度 (FFF E,E0 00) 项 长 度 98A 52C 项 值 ( 数 据 集 ) (FFF E,E0 00) 项 长 度 B 项 值 ( 数 据 集 ) (FFF E,E0 DD) H 68H CH 4 字 字 4 字 4 字 98A52 4 字 4 字 B 字 4 字 节 字 字 节 节 节 C68H 节 节 62CH 节 节 节 节 字 节 字 节 249
257 8 DICOM 标 准 的 实 现 表 8-4 带 未 定 义 长 度 项 的 隐 式 VR 为 SQ 且 未 定 义 长 度 的 数 据 元 素 例 子 标 签 数 据 长 度 数 据 域 (gg gg,e FFF FFF 第 一 项 第 二 项 序 列 定 界 符 eee) VR 为 SQ FFH 未 定 义 长 度 (FFF E,E0 00) 项 长 度 B 项 值 ( 数 据 集 ) (FFF E,E0 00) FFF FFF FFH 未 指 项 值 ( 数 据 集 ) 项 定 界 符 (FFF E,E H (FFF E,E0 DD) H 6H 定 长 0D) 度 4 字 4 字 4 字 4 字 17B 4 字 4 字 未 定 4 字 4 字 4 字 4 字 节 节 节 节 6 字 节 节 义 长 节 节 节 节 节 度 4. 像 素 数 据 的 编 码 和 压 缩 像 素 数 据 往 往 是 DICOM 数 据 流 中 数 据 量 最 大, 同 时 也 是 最 重 要 的 数 据 记 录 像 素 数 据 的 数 据 元 素 标 签 为 (7FE0,0010), 其 所 含 的 像 素 数 据 由 大 量 的 像 素 单 元 (Pixel Cell) 构 成, 每 个 像 素 单 元 的 编 码 方 式 由 下 列 三 个 数 据 元 素 的 值 所 决 定 : 分 配 位 数 (Bits Allocated), 标 签 为 (0028,0100), 表 示 每 个 像 素 单 元 占 用 多 少 位 (Bits); 存 储 位 数 (Bits Stored), 标 签 为 (0028,0101), 表 示 在 为 每 个 像 素 单 元 分 配 的 所 有 存 储 位 中, 有 多 少 位 存 储 了 像 素 采 样 值 (Pixel Sample Value); 高 位 (High Bit), 标 签 为 (0028,0102), 表 示 存 储 像 素 采 样 值 的 最 高 位 在 分 配 的 存 储 位 中 在 第 几 位 ( 从 0 开 始 ) 250
258 8 DICOM 标 准 的 实 现 例 如, 分 配 位 数 为 24, 存 储 位 数 为 18, 高 位 为 19, 表 示 每 个 像 素 单 元 占 用 24 位 存 储 空 间, 其 中 的 18 位 存 储 了 实 际 的 像 素 采 样 值, 而 这 18 位 的 最 高 位 对 应 于 24 位 的 第 19 位, 如 8-3 所 示 除 被 像 素 采 样 值 占 用 的 存 储 位 以 外 未 使 用 的 存 储 位 可 以 另 作 它 用 High Bit = Pixel Sample 0 Bits Stored = 18 Bits Allocated = 24 图 8-4 像 素 单 元 编 码 实 例 像 素 数 据 可 以 采 用 自 然 格 式 (Native Format) 或 者 封 装 格 式 (Encapsulated Format) 进 行 存 储 或 传 输 在 自 然 格 式 下, 所 有 像 素 单 元 不 经 任 何 形 式 的 转 换 编 码 ( 如 压 缩 等 ) 而 直 接 按 序 存 储 在 (7FE0,0010) 数 据 元 素 的 数 据 域, 应 用 程 序 通 过 给 定 的 分 配 位 数 存 储 位 数 及 高 位 值 对 其 进 行 解 读, 通 常 VR 为 OW, 如 果 分 配 位 数 小 于 等 于 8, 也 可 采 用 OB 在 封 装 格 式 下, 像 素 数 据 的 所 有 像 素 单 元 采 用 DICOM 标 准 以 外 的 某 种 编 码 方 式 进 行 编 码, 编 码 方 式 由 传 输 语 法 确 定, 通 常 是 某 种 压 缩 格 式, 比 如 JPEG 压 缩 格 式 在 此 方 式 下,(7FE0,0010) 数 据 元 素 的 VR 必 须 为 OB, 其 数 据 域 存 储 的 是 编 码 后 的 字 节 流, 应 用 程 序 必 须 对 此 字 节 流 用 相 同 的 编 码 方 式 进 行 解 码, 然 后 才 能 得 到 有 意 义 的 像 素 单 元 数 据 封 装 格 式 采 用 分 段 的 方 式 存 储 编 码 后 的 字 节 流, 每 一 段 (Fragment) 被 编 码 进 一 个 显 式 长 度 的 项 (Item) 中, 整 个 项 的 序 列 以 一 个 基 本 偏 移 表 项 (Basic Offset Table Item) 开 始, 对 于 多 帧 (Multi-frame) 的 图 像, 其 中 记 录 了 每 一 帧 的 起 始 段 ( 项 ) 相 对 于 第 一 项 开 头 的 位 移 量, 以 此 来 区 分 多 帧 图 像 的 多 个 帧, 而 对 于 单 帧 或 只 有 一 帧 的 多 帧 图 像, 该 偏 移 表 的 数 据 域 可 为 空, 数 据 长 度 字 段 相 应 编 码 为 H 而 且 在 封 装 格 式 下,(7FE0,0010) 数 据 元 素 的 数 据 长 度 必 须 是 未 定 义 的, 因 此 整 个 项 的 序 列 以 一 个 序 列 定 界 符 作 为 结 尾 ( 参 考 表 8-8 的 例 子 ) 251
259 8 DICOM 标 准 的 实 现 此 外, 大 多 数 的 编 码 方 式 会 将 图 像 或 像 素 格 式 的 相 关 信 息 编 码 进 最 终 的 字 节 流 中, 比 如 图 像 的 宽 高, 上 面 讲 到 的 每 个 像 素 单 元 的 分 配 位 数 存 储 位 数 等, 这 时 候 DICOM 标 准 要 求 相 关 数 据 元 素 的 值 必 须 与 编 码 进 像 素 数 据 字 节 流 的 相 关 信 息 保 持 一 致 DICOM 标 准 接 受 JPEG JPEG 2000 JPEG-LS 及 RLE 压 缩 格 式 5. 唯 一 标 识 符 (Unique Identifier,UID) 为 了 在 网 络 环 境 下 唯 一 地 标 识 各 种 信 息,DICOM 采 用 了 UID 的 方 式 UID 的 定 义 基 于 ISO8824 标 准, 并 使 用 ISO 中 所 注 册 的 值 来 保 证 全 局 唯 一 性 一 个 UID 唯 一 标 识 符 可 以 用 公 式 表 示 为 : UID=<org root>.<suffix> 其 中 org root 代 表 组 织 编 号 ( 如 制 造 商 研 究 单 位 等 ), 而 suffix 部 分 则 是 在 此 组 织 范 围 内 的 唯 一 编 号 这 两 部 分 均 由 一 串 点 号 隔 开 的 数 字 组 成, 如 <org root>= 代 表 美 国 电 器 制 造 商 协 会 6. 传 输 语 法 (Transfer Syntax) 前 面 已 经 多 次 提 到 传 输 语 法, 所 谓 传 输 语 法 就 是 一 组 编 码 规 则, 用 于 无 歧 义 地 解 读 传 输 中 的 或 存 储 于 存 储 设 备 上 的 DICOM 数 据 不 同 的 传 输 语 法 用 一 组 UID 来 区 分, 常 用 的 传 输 语 法 如 表 8-10 所 示 表 8-10 常 用 传 输 语 法 UID 值 说 明 隐 式 VR, 小 端 序 ( 缺 省 传 输 语 法 ) 显 式 VR, 小 端 序 显 式 VR, 小 端 序, Deflate 压 缩 显 式 VR, 大 端 序 JPEG Baseline 压 缩 ( 有 损 JPEG 8-bit 压 缩 的 缺 省 传 输 语 法 ) 252
260 8 DICOM 标 准 的 实 现 JPEG Extended 压 缩 ( 有 损 JPEG 12-bit 压 缩 的 缺 省 传 输 语 法 ) JPEG 无 损 压 缩 (Non-Hierarchical) JPEG 无 损 压 缩 (Non-Hierarchical, First-Order Prediction,JPEG 无 损 压 缩 的 缺 省 传 输 语 法 ) JPEG-LS 无 损 压 缩 JPEG-LS 有 损 压 缩 JPEG 2000 压 缩 ( 仅 有 损 方 式 ) JPEG 2000 压 缩 RLE 无 损 压 缩 其 中, 所 有 压 缩 编 码 方 式 均 采 用 显 式 VR 和 小 端 序 编 码 ~ ~ 均 为 JPEG 的 各 种 压 缩 格 式, 已 标 记 为 RETIRED, 故 不 再 详 细 说 明 (2) DICOM 文 件 结 构 典 型 的 DICOM 文 件 由 两 部 分 组 成 : 文 件 元 信 息 (File Meta Information): 包 含 此 文 件 所 存 储 的 数 据 集 的 识 别 信 息, 包 括 文 件 识 别 前 缀 数 据 集 标 识 及 存 储 格 式 等 信 息 ; DICOM 数 据 集 (DICOM Data Set): 文 件 所 存 储 的 DICOM 数 据, 是 文 件 的 主 题 其 中, 文 件 元 信 息 有 可 以 分 为 以 下 一 些 部 分 : 文 件 前 同 步 码 (File Preamble): 长 128 字 节, 可 以 存 放 一 些 生 成 该 DICOM 文 件 的 应 用 程 序 的 某 些 指 定 信 息, 如 不 使 用 则 全 部 置 为 00H; 253
261 8 DICOM 标 准 的 实 现 DICOM 前 缀 (DICOM Prefix):4 字 节 字 符 串, 规 定 为 DICM ; 文 件 元 数 据 元 素 (File Meta Elements): 一 组 组 号 为 0002H 的 DICOM 数 据 元 素, 包 含 文 件 数 据 集 的 标 识 格 式 等 信 息, 其 中 标 签 为 (0002, 0010) 的 数 据 元 素 即 存 储 了 传 输 语 法 UID 这 组 数 据 元 素 均 采 用 显 式 VR 和 小 端 序 格 式 存 储 DICOM 文 件 结 构 如 图 8-4 所 示 File Preamble (128 bytes) DICOM Prefix ("DICM") File Meta Information File Meta Elements DICOM Data Set 图 8-4 DICOM 文 件 结 构 DICOM 文 件 读 写 模 块 (DICOM Utility) 的 实 现 如 前 所 述,MITK 中 关 于 DICOM 文 件 的 读 写 功 能 是 作 为 一 个 独 立 的 Utility 模 块 实 现 的, 该 模 块 的 框 架 如 图 8-5 所 示 254
262 8 DICOM 标 准 的 实 现 m_dataset DcmFile +GetDataSet() +SetDataSet() +ReadFromFile() +WriteToFile() Load data from file Save data to file DcmDataProcess +FileToImage() +ImageToFile() # ParseDefaultPixelData() # ParseJpegLosslessNonhier14B() # ParseRleLossless() # ParseJpegLossy()... DcmDataSet +AddElement() +FineElement() +GetElementNum() +GetElement() m_elements Decode DICOM data set to image data Encode image data to DICOM data set DcmDataElement tag : unsigned long groupnum : unsigned short elementnum : unsigned short valuelength : unsigned long value : void*... +Init() DcmImage width : int height : int framenum : int channelnum : int cellbytes : int... +GetPixelData() 图 8-5 DICOM 文 件 读 写 模 块 框 架 从 图 中 可 以 看 出, 该 模 块 的 核 心 部 分 是 DcmDataProcess 类, 其 中 包 含 了 对 DICOM 数 据 流 进 行 解 码 的 全 部 过 程 但 是 该 类 提 供 给 外 部 使 用 的 接 口 却 相 对 简 单 而 且 直 观 :FileToImage() 对 从 DICOM 文 件 中 读 取 的 像 素 进 行 解 码 并 产 生 出 用 户 可 用 的 图 像 数 据, 封 装 在 DcmImage 对 象 中 ;ImageToFile() 则 将 用 户 给 定 的 图 像 数 据 编 码 为 DICOM 数 据 流 通 过 DcmFile 类 提 供 的 接 口 写 入 DICOM 文 件 中 即 DcmFile 只 负 责 数 据 结 构 的 组 织 和 DICOM 文 件 的 读 写 操 作, 而 具 体 的 编 码 和 解 码 工 作 则 由 DcmDataProcess 完 成 下 面 分 别 介 绍 其 中 所 用 到 的 基 本 数 据 结 构 及 编 解 码 方 法 (1) 基 本 数 据 结 构 DcmDataElement class DcmDataElement public: DcmDataElement(); void Init(unsigned short group = 0, unsigned short element = 0, 255
263 8 DICOM 标 准 的 实 现 unsigned short vr = VR_UN, unsigned long vl = 0, void *value = NULL, bool issquence = false); ~DcmDataElement(); ; unsigned long tag; // 标 签 unsigned short groupnum; // 组 号 unsigned short elementnum; // 元 素 号 unsigned short VR; // 数 据 类 型 unsigned long VL; // 数 据 长 度 bool issequence; // 是 否 为 嵌 套 序 列 void *value; // 指 向 该 元 素 数 据 的 指 针 可 以 看 出, 该 结 构 完 全 对 应 于 DICOM 标 准 中 对 数 据 元 素 的 编 码 格 式 其 中, tag 实 际 上 就 是 groupnum 和 elementnum 的 组 合, 之 所 以 重 复 存 放, 主 要 是 为 了 方 便 解 码 issequence 标 明 了 该 数 据 元 素 是 否 包 含 了 嵌 套 序 列, 如 果 值 为 true, 则 value 应 理 解 为 一 个 指 向 DcmDataSet 的 指 针, 而 指 向 的 这 个 DcmDataSet 所 包 含 的 均 为 Item 类 型 的 数 据 元 素, 每 个 Item 均 包 含 一 个 子 数 据 集, 即 其 value 均 为 指 向 一 个 DcmDataSet 的 指 针 ( 参 考 图 8-6) DcmDataSet class DcmDataSet private: // 一 个 DcmDataElement 的 列 表 vector<dcmdataelement *> m_elements; public: DcmDataSet(); ~DcmDataSet(); // 添 加 数 据 元 素. void AddElement(DcmDataElement *element); // 在 该 数 据 集 中 寻 找 标 签 值 为 tag 的 数 据 元 素. // 若 找 到, 则 返 回 true, 且 element 中 为 指 向 该 数 据 元 素 的 指 针 ; // 否 则 返 回 false. bool Find(const unsigned long tag, DcmDataElement * &element); 256
264 8 DICOM 标 准 的 实 现 // 该 数 据 集 是 否 为 空. bool IsEmpty(); // 返 回 该 数 据 集 中 所 包 含 数 据 元 素 的 个 数. int GetElementNum(); ; // 取 得 该 数 据 集 中 第 n 个 数 据 元 素. DcmDataElement* GetElement(int n); 该 结 构 完 全 对 应 于 DICOM 标 准 中 的 数 据 集 定 义 它 维 护 了 一 个 数 据 元 素 的 列 表, 并 提 供 一 些 接 口 对 其 中 的 数 据 元 素 进 行 方 便 的 存 取 对 于 嵌 套 结 构 的 数 据 集, 其 逻 辑 结 构 如 图 8-6 所 示 Data Set Element 0 issequence = false Element 1 issequence = false Element 2 issequence = true... Element n issequence = false Data Set of Items Item Element 0 issequence = true Item Element 1 issequence = true... Item Element m issequence = true Sub Data Set 0 Element 0 issequence = false... Element k 1 issequence = false Sub Data Set 1 Element 0 issequence = false... Element k 2 issequence = false :. Sub Data Set m Element 0 issequence = false... Element k m issequence = false DcmImage class DcmImage public: 图 8-6 嵌 套 数 据 集 的 结 构 示 意 图 257
265 8 DICOM 标 准 的 实 现 DcmImage() width = height = 0; framenum = channelnum = cellbytes =1; spacingx = spacingy = thickness = 1.0f; location = windowcenter = windowwidth = 0.0f; isunsigned = iscolorbypxl = true; pixeldata = NULL; ~DcmImage() delete []pixeldata; void Clear() width = height = 0; framenum = channelnum = cellbytes =1; spacingx = spacingy = 1.0f; location = windowcenter = windowwidth = 0.0f; isunsigned = iscolorbypxl = true; delete []pixeldata; pixeldata = NULL; // 取 得 指 向 像 素 数 据 的 指 针. // 注 意 : 调 用 此 函 数 后, 像 素 数 据 所 占 内 存 由 用 户 释 放. void* GetPixelData() void *data = pixeldata; pixeldata = NULL; return data; void SetPixelData(void *data) pixeldata = data; bool IsPixelDataOk() return (pixeldata!=null); int framenum; 258
266 8 DICOM 标 准 的 实 现 int width; int height; int channelnum; int cellbytes; float spacingx; float spacingy; float thickness; float location; float windowcenter; float windowwidth; bool isunsigned; bool iscolorbypxl; private: void *pixeldata; ; 这 是 一 个 辅 助 类, 用 于 存 放 解 码 后 的 图 像 数 据, 包 括 与 图 像 相 关 的 信 息 和 解 码 后 的 像 素 数 据 (Pixel Data) 为 尽 量 避 免 对 像 素 数 据 所 占 内 存 区 的 操 作 失 误, 对 指 向 像 素 数 据 的 指 针 采 取 了 适 当 的 保 护, 通 过 提 供 GetPixelData() SetPixelData() 以 及 IsPixelDataOk() 来 提 供 对 像 素 数 据 的 存 取 以 及 数 据 有 效 性 的 判 别 (2) DICOM 文 件 的 读 写 DcmFile DcmFile 类 封 装 了 对 DICOM 文 件 的 读 写 操 作 该 类 包 含 一 个 DcmDataSet, 通 过 ReadFromFile() 将 DICOM 文 件 中 的 数 据 元 素 提 取 出 来, 生 成 一 个 个 DcmDataElement 对 象, 加 入 到 DcmDataSet 对 象 中, 组 织 成 如 图 8-6 所 示 的 树 状 逻 辑 结 构, 而 DcmFile 本 身 并 不 对 解 读 数 据 集 的 含 意 做 任 何 尝 试, 这 是 后 面 要 讲 到 的 DcmDataProcess 类 所 完 成 的 工 作 DcmFile 首 先 解 读 DICOM 文 件 头 信 息, 即 本 章 第 二 节 中 所 提 到 的 File Meta Information, 得 到 存 储 该 文 件 所 使 用 的 传 输 语 法, 从 而 取 得 该 文 件 所 包 含 数 据 集 的 编 码 格 式, 为 读 取 DICOM 数 据 集 做 准 备 DICOM 数 据 集 的 嵌 套 结 构 给 文 件 的 读 取 带 来 了 一 定 的 困 难, 这 里 我 们 采 用 递 归 的 方 法 来 读 取 文 件 所 包 含 的 DICOM 数 据 集 并 构 造 出 树 状 结 构 的 259
267 8 DICOM 标 准 的 实 现 DcmDataSet 对 象, 该 方 法 通 过 下 面 5 个 子 过 程 的 相 互 协 作 而 实 现 : int ReadELDataSet(DcmDataSet *dataset, unsigned long datalength); int ReadULDataSet(DcmDataSet *dataset, unsigned long delimittag); int ReadELSQ(DcmDataSet *dataset, unsigned long datalength); int ReadULSQ(DcmDataSet *dataset, unsigned long delimittag); int ReadOBSQ(DcmDataSet *dataset, unsigned long delimittag); 其 中,ReadELDataSet() 用 于 读 取 显 式 长 度 的 DICOM 数 据 集 ; ReadULDataSet() 用 于 读 取 未 定 义 长 度 的 DICOM 数 据 集 ;ReadELSQ() 用 于 读 取 显 式 长 度 的 Item 序 列 ;ReadULSQ() 用 于 读 取 未 定 义 长 度 的 Item 序 列 ;ReadOBSQ 专 门 用 于 读 取 封 装 格 式 下 像 素 数 据 的 Item 序 列 这 些 子 过 程 在 将 文 件 中 的 数 据 元 素 读 入 内 存 时 统 一 将 其 格 式 转 换 为 小 端 序 及 显 式 VR, 对 于 隐 式 VR 的 数 据, 通 过 检 索 DICOM 数 据 字 典 得 到 其 VR 并 填 入 DcmDataElement 对 象 的 VR 域 中 其 伪 代 码 如 下 所 示 : int ReadELDataSet(DcmDataSet *dataset, unsigned long datalength) if datalength == 0 return 0; DcmDataElement *element; DcmDataSet *subdataset; int count = 0; do element = new DcmDataElement; 从 文 件 中 读 取 当 前 数 据 元 素 的 相 关 信 息 填 入 element(tag VR VL 等, 不 包 括 数 据 域 ); count += 读 入 的 字 节 数 ; dataset->addelement(element); if element->vl==0xffffffff subdataset = new DcmDataSet; if element->vr== SQ or element->vr== UN count += ReadULSQ(subDataSet, 0xFFFEE0DD); else if element->vr== OB count += ReadOBSQ(subDataSet, 0xFFFEE0DD); end if element->issequence = true; element->value = subdataset; else if element->vr== SQ subdataset = new DcmDataSet; count += ReadELSQ(subDataSet, element->vl); element->issequence = true; 260
268 8 DICOM 标 准 的 实 现 element->value = subdataset; else 从 文 件 读 出 element->vl 字 节 的 内 容 写 入 element->value 域 ( 必 要 时 做 Byte Swapping); count += 读 入 的 字 节 数 ; end if while count<datalength; return count. int ReadULDataSet(DcmDataSet *dataset, unsigned long delimittag) DcmDataElement *element; DcmDataSet *subdataset; int count = 0; do element = new DcmDataElement; 从 文 件 中 读 取 当 前 数 据 元 素 的 相 关 信 息 填 入 element(tag VR VL 等, 不 包 括 数 据 域 ); count += 读 入 的 字 节 数 ; if element->tag==delimittag break; // 出 口 dataset->addelement(element); if element->vl==0xffffffff subdataset = new DcmDataSet; if element->vr== SQ or element->vr== UN count += ReadULSQ(subDataSet, 0xFFFEE0DD); else if element->vr== OB count += ReadOBSQ(subDataSet, 0xFFFEE0DD); end if element->issequence = true; element->value = subdataset; else if element->vr== SQ subdataset = new DcmDataSet; count += ReadELSQ(subDataSet, element->vl); element->issequence = true; element->value = subdataset; else 从 文 件 读 出 element->vl 字 节 的 内 容 写 入 element->value 域 ( 必 要 时 做 Byte Swapping); end if while 文 件 未 结 束 ; 261
269 8 DICOM 标 准 的 实 现 return count. int ReadELSQ(DcmDataSet *dataset, unsigned long datalength) if datalength == 0 return 0; DcmDataElement *item; DcmDataSet *subdataset; int count = 0; do item = new DcmDataElement; 从 文 件 中 读 取 当 前 数 据 元 素 的 相 关 信 息 填 入 item(tag VR VL 等, 不 包 括 数 据 域 ); count += 读 入 的 字 节 数 ; dataset->addelement(item); if item->vl==0xffffffff subdataset = new DcmDataSet; count += ReadULDataSet(subDataSet, 0xFFFEE00D); else subdataset = new DcmDataSet; count += ReadELDataSet(subDataSet, item->vl); end if item->issequence = true; item->value = subdataset; while count<datalength; return count. int ReadULSQ(DcmDataSet *dataset, unsigned long delimittag) DcmDataElement *item; DcmDataSet *subdataset; int count = 0; do item = new DcmDataElement; 从 文 件 中 读 取 当 前 数 据 元 素 的 相 关 信 息 填 入 item(tag VR VL 等, 不 包 括 数 据 域 ); count += 读 入 的 字 节 数 ; if item->tag==delimittag break; // 出 口 dataset->addelement(item); if item->vl==0xffffffff subdataset = new DcmDataSet; 262
270 8 DICOM 标 准 的 实 现 count += ReadULDataSet(subDataSet, 0xFFFEE00D); else subdataset = new DcmDataSet; count += ReadELDataSet(subDataSet, item->vl); end if item->issequence = true; item->value = subdataset; while 文 件 未 结 束 ; return count. int ReadOBSQ(DcmDataSet *dataset, unsigned long delimittag) DcmDataElement *item; DcmDataSet *subdataset; int count = 0; do item = new DcmDataElement; 从 文 件 中 读 取 当 前 数 据 元 素 的 相 关 信 息 填 入 item(tag VR VL 等, 不 包 括 数 据 域 ); count += 读 入 的 字 节 数 ; if item->tag==delimittag break; // 出 口 dataset->addelement(item); if item->vl!=0 从 文 件 读 取 item->vl 个 字 节 的 数 据 放 入 item->value 域 ; count += item->vl; end if while 文 件 未 结 束 ; return count. 相 对 于 将 DICOM 数 据 集 从 文 件 中 读 入 DcmDataSet 对 象 来 说, 将 DcmDataSet 对 象 中 树 状 结 构 的 数 据 写 入 指 定 的 DICOM 文 件 要 简 单 一 些, 直 接 将 DcmDataSet 中 数 据 按 传 输 语 法 所 规 定 的 格 式 写 入 文 件, 对 于 嵌 套 的 数 据 结 构 也 采 用 递 归 的 方 式 处 理 这 一 功 能 由 WriteToFile() 接 口 实 现 在 WriteToFile() 中 首 先 调 用 WriteMetaInfo() 将 File Meta Information 写 入 文 件 头 部, 然 后 调 用 WriteDataSet() 函 数 将 DcmDataSet 对 象 中 的 数 据 写 入 文 件 WriteDataSet() 是 一 个 递 归 函 数, 用 伪 代 码 描 述 如 下 : void WriteDataSet(DcmDataSet *dataset, FILE *fp) 263
271 8 DICOM 标 准 的 实 现 DcmDataElement *element; int i; for i=0 to dataset->getelementnum()-1 do element = dataset->getelement(i); 将 element->tag element->vr element->vl 按 传 输 语 法 要 求 的 格 式 写 入 文 件 fp( 若 element->vl 为 奇 数, 则 相 应 写 入 数 值 应 为 element->vl+1); if element->issequence WriteDataSet((DcmDataSet *)element->value, fp); // 递 归 调 用 if element->vl==0xffffffff // 未 定 义 长 度, 需 补 上 项 或 序 列 的 结 束 标 志 if element->tag==0xfffee000 // 项 结 束 将 一 个 tag 为 0xFFFEE00D,VL 为 0 的 项 定 界 符 写 入 文 件 fp; else // 序 列 结 束 将 一 个 tag 为 0xFFFEE0DD,VL 为 0 的 序 列 定 界 符 写 入 文 件 fp; end if end if else // 一 般 数 据 元 素 将 element->value 中 的 element->vl 个 字 节 的 数 据 写 入 文 件 fp ( 若 element->vl 为 奇 数, 则 在 末 尾 加 上 一 个 填 充 字 节 ); end if end for. (3) DICOM 数 据 流 的 编 码 和 解 码 DcmDataProcess DcmDataProcess 类 封 装 了 DICOM 数 据 流 的 编 码 和 解 码 功 能 解 码 时, 它 直 接 从 一 个 DcmFile 对 象 得 到 其 中 包 含 的 DcmDataSet 对 象, 其 中 包 含 了 与 实 际 DICOM 文 件 完 全 对 应 的 但 经 过 结 构 化 之 后 的 数 据, 解 码 程 序 用 DICOM 数 据 元 素 的 标 签 作 为 关 键 字, 可 以 很 方 便 地 从 DcmDataSet 对 象 中 取 得 对 应 的 数 据 元 素 对 于 普 通 的 数 据 元 素, 直 接 可 从 其 value 域 得 到 所 需 数 据, 而 对 于 封 装 结 构 的 像 素 数 据, 必 须 提 供 相 应 的 解 码 过 程, 比 如 对 于 采 用 RLE 无 损 压 缩 的 像 素 数 据,DcmDataProcess 类 提 供 了 一 个 ParseRleLossless() 函 数 对 其 进 行 解 码 解 码 后 所 得 的 图 像 数 据 写 入 一 个 DcmImage 对 象 提 供 给 用 户 编 码 时, 它 从 用 户 得 到 一 个 待 编 码 的 DcmImage, 根 据 用 户 指 定 的 传 输 语 法 构 造 一 个 DcmDataSet 对 象, 然 后 交 给 DcmFile 将 其 写 入 文 件 264
272 8 DICOM 标 准 的 实 现 DICOM Utility 在 MITK 中 的 封 装 在 MITK 中, 通 过 mitkdicomreader 和 mitkdicomwriter 两 个 类 封 装 了 DICOM Utility 的 功 能, 提 供 给 用 户 使 用 mitkdicomreader 是 mitkvolumereader 的 一 种, 它 负 责 读 取 一 系 列 的 DICOM 文 件 中 所 包 含 的 图 像 数 据, 将 其 构 造 成 一 个 mitkvolume( 参 见 2.2.2) 提 供 给 后 续 处 理 的 算 法 作 为 输 入 数 据 为 了 提 供 一 个 比 较 规 整 的 Volume 数 据, mitkdicomreader 对 于 用 DcmDataProcess 读 入 的 一 系 列 切 片 还 要 作 一 些 处 理, 主 要 是 按 切 片 的 位 置 排 序, 切 片 的 位 置 信 息 在 DICOM 文 件 中 由 SLICE LOCATION 数 据 元 素 提 供 如 果 切 片 间 距 不 一 致, 则 以 最 小 间 距 为 标 准, 通 过 插 值 补 上 间 距 比 较 大 的 切 片 之 间 所 缺 的 切 片, 以 利 于 后 续 算 法 的 处 理 作 为 一 个 读 取 单 一 切 片 图 像 的 例 子, 下 面 的 代 码 演 示 了 在 mitkdicomreader 中 如 何 使 用 DcmDataProcess 提 供 的 功 能 读 取 DICOM 文 件 中 的 图 像 数 据 : int mitkdicomreader::_readsinglefile(volumeinfo &info) DcmDataProcess dcmp; //DcmDataProcess 对 象 DcmImage image; // 用 于 存 放 解 码 后 的 图 像 数 据 // 调 用 DcmDataProcess 类 的 FileToImage() 接 口 读 取 DICOM 文 件 中 的 图 像 数 据, // 并 对 出 现 的 错 误 进 行 相 应 的 处 理 switch (dcmp.filetoimage(image,this->_getfilename(0))) case DcmDataProcess::RT_ERROR: mitkerrormessage(this->_getfilename(0) << ": " << dcmp.geterrormessage()); return -1; case DcmDataProcess::RT_WARNING: mitkwarningmessage(this->_getfilename(0) << ": " << dcmp.getwarningmessage()); break; // 将 图 像 信 息 传 给 Volume info.width = image.width; info.height = image.height; 265
273 8 DICOM 标 准 的 实 现 info.channelnum = image.channelnum; info.cellbytes = image.cellbytes; info.isunsigned = image.isunsigned; info.spacingx = image.spacingx; info.spacingy = image.spacingy; info.spacingz = image.thickness; info.windowcenter = image.windowcenter; info.windowwidth = image.windowwidth; // 处 理 像 素 数 据 void *data = image.getpixeldata(); char *bufptr = (char *)data; unsigned long imagebytes = image.width * image.height * image.channelnum * image.cellbytes; // 处 理 多 帧 的 情 况 if (image.framenum > 1) m_ismultiframe = true; for (int i=0; i<image.framenum; i++) SLICE *slice = new SLICE; slice->iscolorbypxl = image.iscolorbypxl; slice->location = i; slice->value = bufptr; bufptr += imagebytes; m_slices->push_back(slice); info.imagenum = image.framenum; // 调 整 切 片 顺 序 和 间 距 if (this->_adjustslices(info.width,info.height,info.channelnum, info.cellbytes,info.isunsigned,false)!= 0) return -1; return 0; mitkdicomwriter 是 mitkvolumewriter 的 一 种, 负 责 将 一 个 mitkvolume 对 266
274 8 DICOM 标 准 的 实 现 象 中 的 所 有 切 片 的 图 像 数 据 分 别 存 储 到 一 个 个 DICOM 文 件 中 该 类 的 处 理 要 比 mitkdicomreader 简 单 的 多, 只 需 循 环 地 调 用 DcmDataProcess 的 ImageToFile() 就 可 以 输 出 所 有 切 片 图 像 到 文 件 中 目 前 MITK 中 的 mitkdicomwriter 采 用 显 式 VR 小 端 序 的 缺 省 DICOM 数 据 格 式 生 成 DICOM 文 件, 即 传 输 语 法 UID 为 下 面 的 代 码 演 示 了 在 mitkdicomwriter 中 如 何 使 用 DcmDataProcess 提 供 的 接 口 将 Volume 中 的 切 片 图 像 存 储 到 DICOM 文 件 中 : bool mitkdicomwriter::execute() // 要 写 的 文 件 个 数 int filenum = this->_getfilecount(); if (filenum <= 0) mitkerrormessage("please input file names first."); return false; // 要 写 入 DICOM 文 件 的 Volume mitkvolume *InputVolume = this->getinput(); // Volume 中 包 含 的 切 片 张 数 int imagenum = InputVolume->GetImageNum(); if (filenum > imagenum) mitkwarningmessage("file names are more than images, the last " << filenum imagenum << " file names will be ignored."); else if (filenum < imagenum) mitkerrormessage("images are more than file names, cannot save all images."); return false; // 填 充 相 关 图 像 信 息 DcmImage image; 267
275 8 DICOM 标 准 的 实 现 image.width = InputVolume->GetWidth(); image.height = InputVolume->GetHeight(); image.channelnum = InputVolume->GetNumberOfChannel(); image.cellbytes = InputVolume->GetDataTypeSize(); image.spacingx = InputVolume->GetSpacingX(); image.spacingy = InputVolume->GetSpacingY(); image.windowcenter = InputVolume->GetWindowCenter(); image.windowwidth = InputVolume->GetWindowWidth(); switch (InputVolume->GetDataType()) case MITK_CHAR: case MITK_SHORT: case MITK_INT: case MITK_LONG: image.isunsigned = false; break; default: image.isunsigned = true; // 设 定 文 件 头 信 息 DCMFILEMETAINFO metainfo; metainfo.mediastoragesopclassuid = ""; metainfo.mediastoragesopinstanceuid = ""; metainfo.transfersyntaxuid = UID_EXPLICIT_VR_LITTLE_ENDIAN; metainfo.implementationclassuid = ""; metainfo.implementationversionname = ""; metainfo.sourceapplicationentitytitle = ""; DcmDataProcess dcmp; //DcmDataProcess 对 象 void *dataptr = InputVolume->GetData(); // 指 向 像 素 数 据 的 指 针 unsigned long imagebytes = image.width * image.height * image.channelnum * image.cellbytes; string str; str.assign(m_studyuid); dcmp.setstudyuid(str); 268
276 8 DICOM 标 准 的 实 现 str.assign(m_seriesuid); dcmp.setseriesuid(str); for (int fileidx=0; fileidx<imagenum; fileidx++) image.location = InputVolume->GetSpacingZ() * fileidx; image.setpixeldata(dataptr); // 通 过 调 用 DcmDataProcess 类 的 ImageToFile 接 口 将 图 像 数 据 写 入 文 件, // 并 对 出 现 的 错 误 作 相 应 处 理 switch (dcmp.imagetofile(metainfo,image, this->_getfilename(fileidx), true,fileidx)) case DcmDataProcess::RT_ERROR: mitkerrormessage(dcmp.geterrormessage()); return false; case DcmDataProcess::RT_WARNING: mitkwarningmessage(dcmp.getwarningmessage()); continue; dataptr = (char *)dataptr + imagebytes; return true; 8.3 小 结 本 章 介 绍 了 DICOM 标 准 在 MITK 中 的 实 现 DICOM 标 准 本 身 所 包 含 的 内 容 庞 大, 涵 盖 面 十 分 广 泛, 但 MITK 作 为 专 注 于 医 学 影 像 处 理 与 分 析 的 算 法 开 发 平 台, 不 可 能 也 没 有 必 要 完 全 实 现 DICOM 标 准, 所 以 我 们 只 是 将 精 力 集 中 在 对 DICOM 文 件 读 写 功 能 的 实 现 上, 通 过 一 个 相 对 独 立 的 DICOM Utility 来 提 供 MITK 所 需 的 DICOM 文 件 读 写 功 能 269
277 8 DICOM 标 准 的 实 现 DICOM Utility 严 格 按 照 DICOM 标 准 中 关 于 数 据 结 构 和 编 码 规 范 的 规 定 进 行 设 计, 它 的 基 础 结 构, 包 括 DcmFile DcmDataSet 和 DcmDataElement, 其 适 应 性 是 相 当 好 的, 基 本 上 能 兼 容 DICOM 标 准 中 的 所 有 不 同 的 编 码 方 式, 这 为 以 后 的 扩 展 带 来 了 很 大 的 方 便 可 以 预 见, 随 着 MITK 的 不 断 发 展,DICOM Utility 的 功 能 也 将 不 断 得 到 完 善 和 增 强, 从 而 为 MITK 提 供 强 有 力 的 DICOM 数 据 解 析 支 持 参 考 文 献 1. National Electrical Manufacturers Association.Digital Imaging and Communications in Medicine (DICOM), 刘 景 春, 田 捷, 常 红 星, 曹 勇, 邱 峰.PACS 的 结 构 与 实 现. 中 国 医 学 影 像 技 术,2000, 第 16 卷 第 1 期 3. 田 捷, 包 尚 联, 周 明 全. 医 学 影 像 处 理 与 分 析. 北 京 : 电 子 工 业 出 版 社, 邱 峰, 田 捷, 曹 勇 等.PACS 系 统 综 述. 中 国 医 学 影 像 技 术,2000, 第 16 卷 第 1 期 5. Bidgood W.D., Horii S. Introduction to the ACR-NEMA DICOM Standard. RadioGraphics, 1992,12(2):
278 9 应 用 MITK 开 发 实 际 项 目 9 应 用 MITK 开 发 实 际 项 目 在 介 绍 了 MITK 的 设 计 与 具 体 实 现 之 后, 本 章 将 以 几 个 实 例 来 说 明 如 何 应 用 MITK 来 开 发 实 际 的 项 目 受 篇 幅 所 限, 不 宜 介 绍 过 于 复 杂 的 大 型 项 目 的 实 现 细 节, 只 能 以 几 个 短 小 精 悍 的 例 子 来 说 明 MITK 的 使 用 规 范 以 及 在 使 用 过 程 中 需 要 注 意 的 一 些 问 题 读 者 可 以 将 其 作 为 使 用 MITK 开 发 实 际 项 目 的 入 门 教 程 本 章 将 以 目 前 使 用 比 较 广 泛 的 VC++6.0 作 为 集 成 开 发 环 境 MITK 开 发 包 可 以 在 的 Download 页 面 下 载, 下 载 的 压 缩 包 mitk.zip 解 压 后 得 到 一 个 MITK 目 录, 其 中 包 含 4 个 子 目 录, 其 中 Include 子 目 录 下 的 所 有 头 文 件 和 Lib 子 目 录 下 的 Mitk_dll.lib 文 件 是 编 译 使 用 MITK 的 应 用 程 序 时 所 需 要 的, Lib 子 目 录 下 的 Mitk_dll.dll 是 编 译 生 成 的 应 用 程 序 在 执 行 时 所 要 用 到 的 动 态 链 接 库, 请 保 证 你 的 应 用 程 序 在 运 行 时 能 找 到 这 个 dll 文 件, 通 常 的 做 法 是 将 其 拷 贝 到 应 用 程 序 相 同 的 目 录 下, 更 省 事 的 做 法 是 将 其 拷 贝 到 \%Windir%\System32\ 目 录 下, 在 下 面 的 章 节 中 我 们 假 设 你 已 经 将 Mitk_dll.dll 放 置 到 合 适 的 位 置 而 不 再 做 额 外 的 说 明 9.1 开 发 环 境 的 设 置 首 先, 用 VC++6.0 新 建 一 个 MFC 工 程, 我 们 将 其 命 名 为 MITKTest, 如 图 9-1 所 示 选 择 Single document, 然 后 Finish, 这 样 就 建 立 了 一 个 单 文 档 结 构 的 MFC 工 程 接 下 来 要 对 这 个 工 程 做 一 些 额 外 的 设 置, 以 使 其 能 使 用 MITK 类 库 选 择 Project Settings 菜 单, 在 弹 出 的 对 话 框 中 选 择 C/C++ 属 性 页, 在 Category 中 选 择 Preprocessor 项, 在 Additional include directories 下 面 的 编 辑 框 中 输 入 你 放 置 MITK 头 文 件 的 位 置, 我 们 将 工 程 直 接 建 立 在 mitk.zip 解 压 后 得 到 的 MITK\Examples 目 录 下, 因 此 这 里 我 们 输 入../../Include, 如 图 9-2 所 示 仍 然 是 Project settings 对 话 框, 选 择 Link 属 性 页, 在 Category 中 选 择 Input 项, 在 Additional library path 下 面 的 编 辑 框 中 输 入 你 放 置 Mitk_dll.lib 的 位 置, 我 们 在 这 儿 填 入../../Lib, 在 Object/library modules 下 271
279 9 应 用 MITK 开 发 实 际 项 目 面 的 编 辑 框 中 输 入 Mitk_dll.lib, 如 图 9-3 所 示 图 9-1 新 建 MFC 工 程 272
280 9 应 用 MITK 开 发 实 际 项 目 图 9-2 设 置 MITK 头 文 件 路 径 图 9-3 设 置 MITK 库 文 件 路 径 273
281 9 应 用 MITK 开 发 实 际 项 目 以 上 设 置 请 读 者 根 据 自 己 的 实 际 情 况 自 行 调 整, 前 提 是 让 VC 在 编 译 时 能 够 找 到 MITK 的 头 文 件 及 库 文 件 接 下 来 就 要 写 一 些 代 码, 建 立 MITK 的 显 示 环 境, 这 在 下 面 各 节 的 例 子 中 都 是 要 用 到 的 首 先, 在 MITKTestView.h 中 给 CMITKTestView 类 添 加 一 个 mitkview * 类 型 的 成 员 变 量 提 供 显 示 环 境 : class mitkview; //mitkview 类 的 前 向 声 明 class CMITKTestView : public CView... protected: mitkview *m_view; // 添 加 一 个 指 向 mitkview 的 指 针... ; 接 着, 在 ClassWidzard 里 面 给 CMITKTestView 类 添 加 WM_CREATE WM_SIZE 和 WM_DESTROY 的 消 息 处 理 函 数, 如 图 9-4 和 图 9-5 和 图 9-6 所 示 : 274
282 9 应 用 MITK 开 发 实 际 项 目 图 9-4 添 加 WM_CREATE 的 消 息 处 理 函 数 图 9-5 添 加 WM_SIZE 的 消 息 处 理 函 数 275
283 9 应 用 MITK 开 发 实 际 项 目 图 9-6 添 加 WM_DESTROY 的 消 息 处 理 函 数 在 MITKTestView.cpp 开 头 添 加 #include mitkview.h : #include "stdafx.h" #include "MITKTest.h" #include "MITKTestDoc.h" #include "MITKTestView.h" #include "mitkview.h" // 添 加 mitkview 的 头 文 件 #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = FILE ; #endif... 在 OnCreate 函 数 中 添 加 代 码 生 成 并 初 始 化 一 个 mitkview: 276
284 9 应 用 MITK 开 发 实 际 项 目 int CMITKTestView::OnCreate(LPCREATESTRUCT lpcreatestruct) if (CView::OnCreate(lpCreateStruct) == -1) return -1; // TODO: Add your specialized creation code here // 得 到 当 前 客 户 区 大 小 RECT clientrect; this->getclientrect(&clientrect); int wwidth = clientrect.right - clientrect.left; int wheight = clientrect.bottom - clientrect.top; // 产 生 mitkview 对 象 m_view = new mitkview; // 设 置 父 窗 口 句 柄 m_view->setparent(getsafehwnd()); // 设 置 mitkview 在 父 窗 口 中 显 示 的 位 置 和 大 小 m_view->setleft(0); m_view->settop(0); m_view->setwidth(wwidth); m_view->setheight(wheight); // 设 置 mitkview 的 背 景 颜 色 ( 这 里 将 其 设 置 为 黑 色 ) m_view->setbackcolor(0, 0, 0); // 显 示 mitkview m_view->show(); return 0; 在 OnSize 函 数 中 添 加 代 码 以 便 在 父 窗 口 改 变 大 小 时,mitkView 也 能 相 应 地 调 整 自 身 大 小 : void CMITKTestView::OnSize(UINT ntype, int cx, int cy) CView::OnSize(nType, cx, cy); // TODO: Add your message handler code here 277
285 9 应 用 MITK 开 发 实 际 项 目 // 防 止 对 NULL 指 针 操 作 if (!m_view) return; // 更 新 mitkview 在 父 窗 口 中 的 位 置 和 尺 寸 m_view->setleft(0); m_view->settop(0); m_view->setwidth(cx); m_view->setheight(cy); // 刷 新 mitkview m_view->update(); 在 OnDestroy() 函 数 中 添 加 代 码 以 便 在 父 窗 口 销 毁 时 即 时 删 除 包 含 的 mitkview 注 意 : 不 要 将 这 步 工 作 放 到 CMITKTestView 的 析 构 函 数 中 去 做, 虽 然 在 这 里 这 么 做 不 会 出 现 任 何 问 题, 但 是 因 为 MITK 允 许 在 同 一 个 父 窗 口 中 容 纳 多 个 mitkview, 当 mitkview 的 个 数 多 于 一 个 时, 在 析 构 函 数 删 除 这 些 mitkview 会 导 致 程 序 异 常, 正 确 的 做 法 时 在 父 窗 口 响 应 WM_DESTROY 消 息 时 就 即 时 删 除 这 些 mitkview 代 码 如 下 : void CMITKTestView::OnDestroy() CView::OnDestroy(); // TODO: Add your message handler code here if (m_view) m_view->delete(); m_view = NULL; 最 后 别 忘 了 在 CMITKTestView 类 的 构 造 函 数 里 初 始 化 m_view 指 针 为 NULL 以 防 出 现 野 指 针 : CMITKTestView::CMITKTestView() // TODO: add construction code here m_view = NULL; // 初 始 化 指 针 变 量 278
286 9 应 用 MITK 开 发 实 际 项 目 开 发 环 境 的 设 置 到 此 基 本 完 成, 编 译 一 下 整 个 工 程, 如 果 没 有 错 误 的 话 程 序 运 行 结 果 是 下 面 这 个 样 子 : 9.2 一 个 简 单 的 图 像 浏 览 器 图 9-7 运 行 结 果 MITK 的 I/O 部 分 本 身 支 持 了 包 括 DICOM 在 内 的 多 种 格 式 医 学 图 像 文 件 的 读 写, 在 这 一 节 我 们 就 在 9.1 的 基 础 上 添 砖 加 瓦, 编 写 一 个 简 单 的 医 学 图 像 浏 览 工 具 在 开 始 之 前 有 一 点 需 要 说 明, 在 MITK 中 对 图 像 的 显 示 环 境 由 一 个 从 mitkview 派 生 而 来 的 类 mitkimageview 负 责, 所 以 对 9.1 完 成 的 程 序 必 须 做 一 定 的 修 改, 主 要 是 把 CMITKTestView 中 的 成 员 变 量 m_view 类 型 由 mitkview * 变 为 mitkimageview *, 在 OnCreate() 中 生 成 的 对 象 也 变 为 mitkimageview 在 MITKTestView.h 中 的 改 动 如 下 : class mitkimageview; //mitkimageview 类 的 前 向 声 明 class CMITKTestView : public CView 279
287 9 应 用 MITK 开 发 实 际 项 目... protected: mitkimageview *m_view;... ; // 添 加 一 个 指 向 mitkimageview 的 指 针 在 MITKTestView.cpp 中 的 改 动 如 下 :... #include "mitkimageview.h" // 添 加 mitkimageview 的 头 文 件... int CMITKTestView::OnCreate(LPCREATESTRUCT lpcreatestruct)... // 产 生 mitkimageview 对 象 m_view = new mitkimageview;... 编 译 后 运 行 结 果 应 该 与 图 9-7 没 什 么 区 别 接 下 来 就 进 入 本 节 的 正 题, 让 我 们 一 步 一 步 地 做 出 这 个 图 像 浏 览 器 第 一 步 : 添 加 打 开 文 件 的 菜 单 项 MITK 的 I/O 部 分 目 前 支 持 6 种 数 据 格 式 :DICOM JPEG BMP TIFF IM0 Raw, 第 一 种 是 医 学 影 像 的 专 用 格 式,MITK 的 实 现 基 于 DICOM 标 准 3.0 接 下 来 3 种 是 常 见 的 图 像 文 件 格 式, 最 后 两 种 是 体 数 据 文 件 格 式 IM0 后 面 还 会 遇 到 (MITK 网 站 上 提 供 的 测 试 文 件 就 是 这 种 格 式 ), Raw 是 生 数 据 格 式, 其 本 身 只 保 存 图 像 数 据, 不 包 含 任 何 与 图 像 相 关 的 信 息 ( 如 图 像 长 宽 等 ) 后 面 会 讲 怎 么 用 MITK 读 这 些 数 据 先 让 我 们 把 读 取 这 些 文 件 的 菜 单 项 加 上 首 先, 将 文 件 菜 单 下 的 打 开 项 改 为 Pop-up 方 式 的 菜 单 项, 如 图 9-8 所 示 280
288 9 应 用 MITK 开 发 实 际 项 目 图 9-8 修 改 打 开 菜 单 项 接 下 来 在 后 面 弹 出 的 菜 单 栏 中 依 次 添 加 相 应 的 打 开 文 件 菜 单 项, 如 图 9-9 所 示 是 添 加 打 开 DICOM 文 件 的 菜 单 项 各 菜 单 项 的 ID 依 次 取 : ID_FILE_OPEN_DICOM ID_FILE_OPEN_BMP ID_FILE_OPEN_JPEG ID_FILE_OPEN_TIFF ID_FILE_OPEN_IM0 ID_FILE_OPEN_RAW 最 后 完 成 的 菜 单 如 图 9-10 所 示 图 9-9 添 加 打 开 DICOM 文 件 的 菜 单 项 281
289 9 应 用 MITK 开 发 实 际 项 目 图 9-10 完 成 后 的 菜 单 第 二 步 : 在 CMITKTestDoc 类 里 面 添 加 一 个 mitkvolume * 成 员 变 量, 用 来 存 放 读 入 的 数 据 同 时 添 加 一 个 保 护 成 员 函 数 clearvolume() 在 读 入 新 数 据 时 清 理 原 先 的 数 据 ; 添 加 一 个 公 共 成 员 函 数 GetVolume() 以 使 CMITKTestView 中 能 得 到 指 向 mitkvolume 数 据 的 指 针 mitkvolume 是 MITK 中 最 基 础 的 数 据 对 象 之 一, 代 表 一 个 三 维 断 层 图 像 数 据 集, 简 单 的 说, 就 是 一 系 列 切 片 图 像 (slices) 堆 积 在 一 起 组 成 的, 所 有 的 输 入 图 像 数 据 都 要 表 达 成 Volume 才 能 进 行 后 续 处 理 在 MITKTestDoc.h 中 的 相 关 代 码 如 下 : class mitkvolume; //mitkvolume 类 的 前 向 声 明 class CMITKTestDoc : public CDocument... // Attributes public: // 提 供 取 得 指 向 mitkvolume 指 针 的 接 口 mitkvolume* GetVolume() return m_volume;... protected: // 清 理 Volume void clearvolume(); // 指 向 mitkvolume 的 指 针 mitkvolume *m_volume; 282
290 9 应 用 MITK 开 发 实 际 项 目... ; 在 大 多 数 情 况 下, 临 时 生 成 的 对 象 如 果 以 某 种 方 式 Add/Set 进 MITK 中 去 之 后, MITK 就 替 你 接 管 了 这 个 对 象, 什 么 时 候 该 从 内 存 中 删 除 它 将 由 MITK 自 行 决 定 比 如 将 一 个 mitkvolume 对 象 作 为 输 入 数 据 通 过 某 个 算 法 对 象 的 SetInput() 函 数 加 入 到 该 算 法 中, 则 当 算 法 对 象 被 删 除 时 其 中 的 输 入 数 据 也 一 并 被 删 除, 也 就 是 说 算 法 对 象 向 外 输 出 结 果 但 不 保 留 原 始 数 据 大 多 数 时 候 这 是 符 合 实 际 的, 但 是 也 有 特 殊 情 况, 比 如 此 处 的 mitkvolume 对 象, 我 们 希 望 能 够 控 制 它 的 生 命 周 期 而 不 让 MITK 过 早 地 删 除 这 个 对 象, 这 时 就 要 通 过 调 用 mitkvolume 对 象 的 AddReference() 函 数 向 MITK 表 明 我 也 在 引 用 这 个 对 象, 请 不 要 删 除 它 而 当 你 需 要 要 删 除 它 的 时 候, 调 用 RemoveReference() 函 数 解 除 引 用, 该 函 数 会 判 断 当 前 此 对 象 的 被 引 用 数, 如 果 值 为 0 则 删 除 这 个 对 象 ( 注 意, 不 要 再 调 用 Delete() 了, 在 RemoveReference() 之 前 调 用 Delete() 将 不 起 任 何 作 用, 而 在 RemoveReference() 之 后 调 用 Delete() 可 能 会 引 起 不 可 预 期 的 错 误 ) 经 过 这 些 解 释 再 看 CMITKTestDoc.cpp 里 面 的 相 关 代 码 应 该 不 难 理 解 了 : #include "stdafx.h" #include "MITKTest.h" #include "MITKTestDoc.h" #include "mitkvolume.h" //mitkvolume 的 头 文 件... //////////////////////////////////////////////////////////////////// // CMITKTestDoc construction/destruction CMITKTestDoc::CMITKTestDoc() // TODO: add one-time construction code here m_volume = NULL; // 初 始 化 指 针 变 量 CMITKTestDoc::~CMITKTestDoc() this->clearvolume(); // 清 理 Volume 数 据 283
291 9 应 用 MITK 开 发 实 际 项 目... //////////////////////////////////////////////////////////////////// // CMITKTestDoc commands void CMITKTestDoc::clearVolume() if (m_volume) // 解 除 引 用 m_volume->removereference(); // 这 时 候 的 m_volume 是 无 意 义 的 指 针, 应 将 其 置 为 NULL m_volume = NULL; 第 三 步 : 给 CMITKTestDoc 类 添 加 代 码 实 现 读 各 种 图 像 文 件 的 功 能 首 先, 在 ClassWizard 中 添 加 与 上 述 6 个 打 开 文 件 菜 单 项 ID 对 应 的 消 息 处 理 函 数, 图 9-11 为 ID_FILE_OPEN_IM0 添 加 了 消 息 处 理 函 数, 其 他 的 消 息 处 理 函 数 如 法 炮 制 284
292 9 应 用 MITK 开 发 实 际 项 目 图 9-11 为 ID_FILE_OPEN_IM0 添 加 消 息 处 理 函 数 在 继 续 下 去 之 前, 先 简 要 解 释 一 下 mitk*reader 的 用 法 本 节 所 用 到 的 Reader 均 属 于 mitkvolumereader, 它 是 mitksource 的 一 种, 是 一 种 特 殊 的 算 法, 它 没 有 输 入 数 据 只 有 输 出 数 据 mitkvolumereader 顾 名 思 义 输 出 的 就 是 一 个 mitkvolume 使 用 mitk*reader, 首 先 要 告 诉 它 要 读 取 的 文 件 名, 一 个 Reader 可 以 处 理 一 系 列 的 文 件, 所 以 可 以 通 过 AddFileName() 添 加 多 个 文 件 名 ; 然 后 要 设 置 一 些 读 取 参 数, 比 如 对 于 JPEG TIFF BMP, 还 需 要 设 定 像 素 间 距 和 切 片 间 距, 因 为 这 些 信 息 文 件 本 身 并 不 提 供, 但 它 们 对 以 后 的 处 理 是 很 重 要 的 对 于 Raw, 要 设 定 的 就 更 多 了, 还 包 括 图 像 长 宽 等 基 本 信 息, 而 对 于 IM0 和 DICOM 文 件, 由 于 其 文 件 本 身 包 含 了 所 有 与 图 像 相 关 的 信 息, 包 括 切 片 及 像 素 之 间 的 间 距 等 等, 所 以 不 用 什 么 额 外 的 设 定, 另 外 Raw 和 IM0 由 于 一 个 文 件 就 包 含 了 一 个 Volume, 所 以 只 用 设 一 个 文 件 名, 实 际 上, 你 加 入 的 所 有 文 件 名 对 于 这 两 个 Reader 来 说, 只 有 第 一 个 是 有 用 的, 其 他 的 都 被 忽 略 了 ; 接 着, 就 可 以 调 用 Run() 来 运 行 这 个 Reader, 读 取 文 件 里 面 的 数 据, 注 意, 该 函 数 返 回 一 个 bool 型 的 变 量, 可 以 根 据 返 回 值 来 判 断 读 文 件 过 程 中 是 否 发 生 错 误 如 果 没 有 错 误 发 生, 285
293 9 应 用 MITK 开 发 实 际 项 目 通 过 GetOutput() 得 到 输 出 的 mitkvolume 由 上 面 的 解 释 可 知, 对 于 BMP JPEG TIFF 这 样 的 通 用 图 像 存 储 格 式, 由 于 其 文 件 本 身 并 不 能 存 储 医 学 图 像 处 理 所 需 的 一 些 信 息, 所 以 需 要 添 加 额 外 的 对 话 框 在 打 开 文 件 时 手 动 输 入 这 些 信 息, 对 于 Raw 文 件 更 是 如 此 图 9-12 是 输 入 三 个 方 向 像 素 间 距 的 对 话 框, 在 读 取 BMP JPEG TIFF 格 式 的 文 件 时 需 要 用 到 图 9-13 是 为 输 入 读 取 Raw 文 件 时 所 需 信 息 而 设 计 的 对 话 框, 其 中 标 题 字 节 数 指 的 是 Raw 文 件 开 头 一 段 自 定 义 信 息 的 长 度 ( 以 字 节 为 单 位 ),MITK 在 读 取 Raw 文 件 时 会 跳 过 这 一 段 ; 大 端 序 是 为 兼 容 Mac 系 列 的 计 算 机 存 储 方 式 准 备 的 ; 非 交 错 存 储 方 式 是 针 对 多 通 道 图 像 而 言, 比 如 RGB 三 通 道 彩 色 图 像, 一 般 是 采 用 RGBRGB... 这 种 各 通 道 像 素 值 交 错 存 储 的 方 式, 也 有 的 是 采 用 RRR...GGG...BBB... 这 种 各 通 道 分 开 存 储 的 方 式 ( 按 颜 色 平 面 存 储 ) MITK 在 读 入 图 像 数 据 时 会 根 据 这 些 信 息 对 数 据 进 行 一 些 必 要 的 预 处 理, 以 便 统 一 格 式 由 于 界 面 设 计 并 非 本 书 的 主 题, 所 以 关 于 这 两 个 对 话 框 的 设 计 请 读 者 自 行 参 考 VC 界 面 编 程 的 相 关 书 籍, 这 里 就 不 再 多 说 了 图 9-12 设 置 像 素 间 距 的 对 话 框 286
294 9 应 用 MITK 开 发 实 际 项 目 图 9-13 读 取 Raw 文 件 时 设 置 参 数 的 对 话 框 下 面 是 MITKTestDoc.cpp 中 的 代 码 : // MITKTestDoc.cpp : implementation of the CMITKTestDoc class // #include "stdafx.h" #include "MITKTest.h" #include "MITKTestDoc.h" // 两 个 参 数 设 置 对 话 框 的 头 文 件 #include "SpacingSetDlg.h" #include "RawSetDlg.h" // 用 到 的 MITK 相 关 类 的 头 文 件 #include "mitkvolume.h" #include "mitkim0reader.h" #include "mitkjpegreader.h" #include "mitkdicomreader.h" #include "mitktiffreader.h" #include "mitkbmpreader.h" #include "mitkrawreader.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = FILE ; #endif 287
295 9 应 用 MITK 开 发 实 际 项 目 //////////////////////////////////////////////////////////////////// ///////// // CMITKTestDoc IMPLEMENT_DYNCREATE(CMITKTestDoc, CDocument) BEGIN_MESSAGE_MAP(CMITKTestDoc, CDocument) //AFX_MSG_MAP(CMITKTestDoc) ON_COMMAND(ID_FILE_OPEN_BMP, OnFileOpenBmp) ON_COMMAND(ID_FILE_OPEN_DICOM, OnFileOpenDicom) ON_COMMAND(ID_FILE_OPEN_IM0, OnFileOpenIm0) ON_COMMAND(ID_FILE_OPEN_JPEG, OnFileOpenJpeg) ON_COMMAND(ID_FILE_OPEN_RAW, OnFileOpenRaw) ON_COMMAND(ID_FILE_OPEN_TIFF, OnFileOpenTiff) //AFX_MSG_MAP END_MESSAGE_MAP() //////////////////////////////////////////////////////////////////// ///////// // CMITKTestDoc construction/destruction CMITKTestDoc::CMITKTestDoc() // TODO: add one-time construction code here m_volume = NULL; // 初 始 化 指 针 变 量 CMITKTestDoc::~CMITKTestDoc() this->clearvolume(); // 清 理 Volume 数 据 BOOL CMITKTestDoc::OnNewDocument() if (!CDocument::OnNewDocument()) return FALSE; // TODO: add reinitialization code here 288
296 9 应 用 MITK 开 发 实 际 项 目 // (SDI documents will reuse this document) return TRUE; //////////////////////////////////////////////////////////////////// // CMITKTestDoc serialization void CMITKTestDoc::Serialize(CArchive& ar) if (ar.isstoring()) // TODO: add storing code here else // TODO: add loading code here //////////////////////////////////////////////////////////////////// // CMITKTestDoc diagnostics #ifdef _DEBUG void CMITKTestDoc::AssertValid() const CDocument::AssertValid(); void CMITKTestDoc::Dump(CDumpContext& dc) const CDocument::Dump(dc); #endif //_DEBUG //////////////////////////////////////////////////////////////////// // CMITKTestDoc commands void CMITKTestDoc::clearVolume() 289
297 9 应 用 MITK 开 发 实 际 项 目 if (m_volume) // 解 除 引 用 m_volume->removereference(); // 这 时 候 的 m_volume 是 无 意 义 的 指 针, 应 将 其 置 为 NULL m_volume = NULL; void CMITKTestDoc::OnFileOpenBmp() // TODO: Add your command handler code here // 生 成 打 开 文 件 对 话 框 ( 可 以 选 择 多 个 文 件 ) CFileDialog dlg(true, ".bmp", NULL, OFN_HIDEREADONLY OFN_OVERWRITEPROMPT OFN_ALLOWMULTISELECT, "BMP 文 件 (*.bmp) *.bmp ", NULL); if (dlg.domodal() == IDOK) // 弹 出 设 置 像 素 间 距 的 对 话 框 CSpacingSetDlg setdlg; if (setdlg.domodal() == IDCANCEL) return; // 生 成 一 个 mitkbmpreader mitkbmpreader *reader = new mitkbmpreader; // 根 据 对 话 框 中 的 数 据 设 置 像 素 间 距 reader->setspacingx(setdlg.m_spacingx); reader->setspacingy(setdlg.m_spacingy); reader->setspacingz(setdlg.m_spacingz); // 得 到 所 有 选 中 文 件 的 文 件 名, 加 入 到 reader 中 // 各 个 切 片 在 Volume 中 是 有 排 列 次 序 的, // 对 于 BMP 文 件 来 说, 次 序 信 息 只 能 从 文 件 名 得 到, // 但 为 简 便 起 见, 这 儿 省 去 了 对 文 件 名 进 行 排 序 的 过 程 290
298 9 应 用 MITK 开 发 实 际 项 目 POSITION pos = dlg.getstartposition(); while (pos) reader->addfilename(dlg.getnextpathname(pos)); // 运 行 reader if (reader->run()) // 若 无 错 误 发 生, 则 清 除 旧 的 数 据, 加 载 新 的 数 据 this->clearvolume(); m_volume = reader->getoutput(); m_volume->addreference(); // 重 要! // 更 新 View 中 的 显 示 this->updateallviews(null); // 删 除 reader reader->delete(); void CMITKTestDoc::OnFileOpenDicom() // TODO: Add your command handler code here // 生 成 打 开 文 件 对 话 框 ( 可 以 选 择 多 个 文 件 ) CFileDialog dlg(true, NULL, NULL, OFN_HIDEREADONLY OFN_OVERWRITEPROMPT OFN_ALLOWMULTISELECT, "DICOM 文 件 (*.*) *.* ", NULL); if (dlg.domodal() == IDOK) // 生 成 一 个 mitkdicomreader mitkdicomreader *reader = new mitkdicomreader; // 得 到 所 有 选 中 文 件 的 文 件 名, 加 入 到 reader 中 // DICOM 文 件 本 身 包 含 了 切 片 的 位 置 信 息,mitkDICOMReader 会 根 据 相 关 信 息 // 对 切 片 进 行 重 排, 所 以 这 里 只 需 要 把 文 件 名 直 接 加 入 进 去 就 可 以 了 POSITION pos = dlg.getstartposition(); 291
299 9 应 用 MITK 开 发 实 际 项 目 while (pos) reader->addfilename(dlg.getnextpathname(pos)); // 运 行 reader if (reader->run()) // 若 无 错 误 发 生, 则 清 除 旧 的 数 据, 加 载 新 的 数 据 this->clearvolume(); m_volume = reader->getoutput(); m_volume->addreference(); // 重 要! // 更 新 View 中 的 显 示 this->updateallviews(null); // 删 除 reader reader->delete(); void CMITKTestDoc::OnFileOpenIm0() // TODO: Add your command handler code here // 生 成 打 开 文 件 对 话 框 CFileDialog dlg(true, ".im0", NULL, OFN_HIDEREADONLY OFN_OVERWRITEPROMPT, "IM0 文 件 (*.im0) *.im0 ", NULL); if (dlg.domodal() == IDOK) // 生 成 一 个 mitkim0reader mitkim0reader *reader = new mitkim0reader; // 一 个 IM0 文 件 就 包 含 一 个 Volume, 只 需 加 一 个 文 件 名 reader->addfilename(dlg.getpathname()); // 运 行 reader if (reader->run()) 292
300 9 应 用 MITK 开 发 实 际 项 目 // 若 无 错 误 发 生, 则 清 除 旧 的 数 据, 加 载 新 的 数 据 this->clearvolume(); m_volume = reader->getoutput(); m_volume->addreference(); // 重 要! // 更 新 View 中 的 显 示 this->updateallviews(null); // 删 除 reader reader->delete(); void CMITKTestDoc::OnFileOpenJpeg() // TODO: Add your command handler code here // 生 成 打 开 文 件 对 话 框 ( 可 以 选 择 多 个 文 件 ) CFileDialog dlg(true, ".jpg", NULL, OFN_HIDEREADONLY OFN_OVERWRITEPROMPT OFN_ALLOWMULTISELECT, "JPEG 文 件 (*.jpeg;*.jpg) *.jpeg;*.jpg ", NULL); if (dlg.domodal() == IDOK) // 弹 出 设 置 像 素 间 距 的 对 话 框 CSpacingSetDlg setdlg; if (setdlg.domodal() == IDCANCEL) return; // 生 成 一 个 mitkjpegreader mitkjpegreader *reader = new mitkjpegreader; // 根 据 对 话 框 中 的 数 据 设 置 像 素 间 距 reader->setspacingx(setdlg.m_spacingx); reader->setspacingy(setdlg.m_spacingy); reader->setspacingz(setdlg.m_spacingz); // 得 到 所 有 选 中 文 件 的 文 件 名, 加 入 到 reader 中 293
301 9 应 用 MITK 开 发 实 际 项 目 // 各 个 切 片 在 Volume 中 是 有 排 列 次 序 的, // 对 于 JPEG 文 件 来 说, 次 序 信 息 只 能 从 文 件 名 得 到, // 但 为 简 便 起 见, 这 儿 省 去 了 对 文 件 名 进 行 排 序 的 过 程 POSITION pos = dlg.getstartposition(); while (pos) reader->addfilename(dlg.getnextpathname(pos)); // 运 行 reader if (reader->run()) // 若 无 错 误 发 生, 则 清 除 旧 的 数 据, 加 载 新 的 数 据 this->clearvolume(); m_volume = reader->getoutput(); m_volume->addreference(); // 重 要! // 更 新 View 中 的 显 示 this->updateallviews(null); // 删 除 reader reader->delete(); void CMITKTestDoc::OnFileOpenRaw() // TODO: Add your command handler code here // 生 成 打 开 文 件 对 话 框 CFileDialog dlg(true, ".raw", NULL, OFN_HIDEREADONLY OFN_OVERWRITEPROMPT, "Raw 文 件 (*.raw;*.*) *.raw;*.* ", NULL); if (dlg.domodal() == IDOK) // 弹 出 参 数 设 置 对 话 框 CRawSetDlg setdlg; if (setdlg.domodal() == IDCANCEL) return; // 生 成 一 个 mitkrawreader 294
302 9 应 用 MITK 开 发 实 际 项 目 mitkrawreader *reader = new mitkrawreader; // 根 据 对 话 框 中 的 数 据 设 置 读 取 图 像 的 参 数 // 图 像 的 尺 寸 以 及 切 片 数 reader->setwidth(setdlg.m_width); reader->setheight(setdlg.m_height); reader->setimagenum(setdlg.m_imagenum); // 三 个 方 向 的 像 素 间 距 reader->setspacingx(setdlg.m_spacingx); reader->setspacingy(setdlg.m_spacingy); reader->setspacingz(setdlg.m_spacingz); // 通 道 数 reader->setchannelnum(setdlg.m_channelnum); // 数 据 类 型 // 其 值 为 MITK_CHAR MITK_SHORT... 中 的 一 个, // 这 些 数 据 类 型 的 定 义 参 见 mitkglobal.h reader->setdatatype(setdlg.m_datatype); // 是 否 是 大 端 序 reader->setendian((bool)(setdlg.m_isbigendian!=0)); // 各 通 道 是 否 分 开 存 储 ( 按 颜 色 平 面 存 储 ) reader->setplanarcfg((bool)(setdlg.m_iscolorbyplane!=0)); reader->addfilename(dlg.getpathname()); if (reader->run()) // 若 无 错 误 发 生, 则 清 除 旧 的 数 据, 加 载 新 的 数 据 this->clearvolume(); m_volume = reader->getoutput(); m_volume->addreference(); // 重 要! // 更 新 View 中 的 显 示 this->updateallviews(null); 295
303 9 应 用 MITK 开 发 实 际 项 目 // 删 除 reader reader->delete(); void CMITKTestDoc::OnFileOpenTiff() // TODO: Add your command handler code here // 生 成 打 开 文 件 对 话 框 ( 可 以 选 择 多 个 文 件 ) CFileDialog dlg(true, ".tif", NULL, OFN_HIDEREADONLY OFN_OVERWRITEPROMPT OFN_ALLOWMULTISELECT, "TIFF 文 件 (*.tif;*.tiff) *.tif;*.tiff ", NULL); if (dlg.domodal() == IDOK) // 弹 出 设 置 像 素 间 距 的 对 话 框 CSpacingSetDlg setdlg; if (setdlg.domodal() == IDCANCEL) return; // 生 成 一 个 mitktiffreader mitktiffreader *reader = new mitktiffreader; // 根 据 对 话 框 中 的 数 据 设 置 像 素 间 距 reader->setspacingx(setdlg.m_spacingx); reader->setspacingy(setdlg.m_spacingy); reader->setspacingz(setdlg.m_spacingz); // 得 到 所 有 选 中 文 件 的 文 件 名, 加 入 到 reader 中 // 各 个 切 片 在 Volume 中 是 有 排 列 次 序 的, // 对 于 TIFF 文 件 来 说, 次 序 信 息 只 能 从 文 件 名 得 到, // 但 为 简 便 起 见, 这 儿 省 去 了 对 文 件 名 进 行 排 序 的 过 程 POSITION pos = dlg.getstartposition(); while (pos) reader->addfilename(dlg.getnextpathname(pos)); if (reader->run()) // 若 无 错 误 发 生, 则 清 除 旧 的 数 据, 加 载 新 的 数 据 296
304 9 应 用 MITK 开 发 实 际 项 目 this->clearvolume(); m_volume = reader->getoutput(); m_volume->addreference(); // 重 要! // 更 新 View 中 的 显 示 this->updateallviews(null); // 删 除 reader reader->delete(); 第 四 步 : 在 CMITKTestView 里 面 添 加 一 个 mitkimagemodel* 成 员, 用 来 显 示 Volume 里 面 的 图 像 MITK 的 View 并 不 直 接 绘 制 场 景 中 的 对 象, 它 只 是 提 供 一 个 显 示 环 境, 同 时 对 场 景 中 的 对 象 进 行 管 理 场 景 中 可 显 示 的 对 象 被 抽 象 成 Model, 一 个 View 可 以 带 多 个 Model, 通 过 AddModel() 添 加 新 的 Model,RemoveModel() 移 除 场 景 中 已 有 的 Model View 遍 历 它 所 包 含 的 所 有 Model, 调 用 它 的 Render() 函 数 来 绘 制 Model, 所 以 实 际 对 象 的 绘 制 是 由 其 对 应 的 Model 来 完 成 的 在 本 节 中 用 到 了 mitkimagemodel, 它 是 专 门 用 来 显 示 Volume 中 断 层 图 像 的 Model, 它 有 三 种 显 示 方 式 : 显 示 X-Y Y-Z 或 Z-X 平 面 的 断 层 图 像, 我 们 现 在 只 用 了 缺 省 的 方 式 来 显 示 X-Y 平 面 的 断 层 图 像, 其 实 就 是 读 入 的 图 像 在 CMITKTestView 里 添 加 一 个 mitkimagemodel* 类 型 的 成 员 变 量 : class mitkimageview; //mitkimageview 类 的 前 向 声 明 class mitkimagemodel; //mitkimagemodel 类 的 前 向 声 明 class CMITKTestView : public CView... protected: mitkimageview *m_view; // 添 加 一 个 指 向 mitkimageview 的 指 针 mitkimagemodel *m_imagemodel; // 添 加 一 个 指 向 mitkimagemodel 的 指 针... ; 在 MITKTestView.cpp 中 添 加 代 码, 显 示 读 入 的 图 像 : 297
305 9 应 用 MITK 开 发 实 际 项 目 // MITKTestView.cpp : implementation of the CMITKTestView class // #include "stdafx.h" #include "MITKTest.h" #include "MITKTestDoc.h" #include "MITKTestView.h" // 相 关 头 文 件 #include "mitkimageview.h" #include "mitkimagemodel.h"... //////////////////////////////////////////////////////////////////// // CMITKTestView construction/destruction CMITKTestView::CMITKTestView() // TODO: add construction code here // 初 始 化 指 针 变 量 为 NULL m_view = NULL; m_imagemodel = NULL;... //////////////////////////////////////////////////////////////////// // CMITKTestView drawing void CMITKTestView::OnDraw(CDC* pdc) CMITKTestDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here // Volume 数 据 更 新,m_ImageModel 也 相 应 更 新 if (m_imagemodel->getdata()!= pdoc->getvolume()) m_imagemodel->setdata(pdoc->getvolume()); 298
306 9 应 用 MITK 开 发 实 际 项 目... //////////////////////////////////////////////////////////////////// // CMITKTestView message handlers int CMITKTestView::OnCreate(LPCREATESTRUCT lpcreatestruct) if (CView::OnCreate(lpCreateStruct) == -1) return -1; // TODO: Add your specialized creation code here // 得 到 当 前 客 户 区 大 小 RECT clientrect; this->getclientrect(&clientrect); int wwidth = clientrect.right - clientrect.left; int wheight = clientrect.bottom - clientrect.top; // 产 生 mitkview 对 象 m_view = new mitkimageview; // 设 置 父 窗 口 句 柄 m_view->setparent(getsafehwnd()); // 设 置 mitkview 在 父 窗 口 中 显 示 的 位 置 和 大 小 m_view->setleft(0); m_view->settop(0); m_view->setwidth(wwidth); m_view->setheight(wheight); // 设 置 mitkview 的 背 景 颜 色 ( 这 里 将 其 设 置 为 黑 色 ) m_view->setbackcolor(0, 0, 0); // 生 成 一 个 mitkimagemodel 并 将 其 加 入 到 View 中 m_imagemodel = new mitkimagemodel; m_view->addmodel(m_imagemodel); // 显 示 mitkview m_view->show(); 299
307 9 应 用 MITK 开 发 实 际 项 目 return 0;... void CMITKTestView::OnDestroy() CView::OnDestroy(); // TODO: Add your message handler code here // 删 除 m_view // 注 意 : 不 需 要 删 除 m_imagemodel, // 自 m_imagemodel 通 过 AddModel() 加 入 到 m_view 中 起, // 其 生 命 周 期 由 m_view 来 管 理, // 当 删 除 m_view 时,m_View 会 负 责 删 除 它 所 带 的 所 有 Model if (m_view) m_view->delete(); m_view = NULL;... 至 此, 编 译 运 行, 应 该 可 以 打 开 图 像 文 件 了 mitkimageview 在 缺 省 情 况 下 会 在 图 像 上 加 上 一 个 十 字 箭 头, 如 图 9-14 所 示 可 以 在 CMITKTestView 中 通 过 调 用 m_view->setcrossarrow(false) 去 掉 这 个 箭 头, 我 们 将 这 句 加 在 OnCreate() 函 数 中 : int CMITKTestView::OnCreate(LPCREATESTRUCT lpcreatestruct)... // 设 置 mitkview 的 背 景 颜 色 ( 这 里 将 其 设 置 为 黑 色 ) m_view->setbackcolor(0, 0, 0); // 生 成 一 个 mitkimagemodel 并 将 其 加 入 到 View 中 m_imagemodel = new mitkimagemodel; m_view->addmodel(m_imagemodel); 300
308 9 应 用 MITK 开 发 实 际 项 目 // 将 缺 省 显 示 的 十 字 箭 头 去 掉 m_view->setcrossarrow(false); // 显 示 mitkview m_view->show(); return 0; 图 9-14 读 入 并 显 示 一 个 DICOM 文 件 到 这 里 为 止, 基 本 功 能 就 都 已 经 完 成 了 但 是 一 般 情 况 下, 一 个 Volume 总 是 由 多 张 切 片 组 成 的, 现 在 的 程 序 还 没 有 在 同 一 个 Volume 中 浏 览 各 张 切 片 图 像 的 功 能, 下 面 就 加 上 这 一 功 能 在 工 具 栏 添 加 一 组 按 钮, 如 图 9-15 所 示 ID 分 别 取 为 ID_SLICE_FIRST ID_SLICE_PREV ID_SLICE_NEXT ID_SLICE_LAST 301
309 9 应 用 MITK 开 发 实 际 项 目 图 9-15 在 工 具 栏 添 加 浏 览 切 片 的 按 钮 在 ClassWizard 里 给 上 述 ID 添 加 相 应 的 消 息 处 理 函 数, 如 图 9-16 所 示 图 9-16 给 按 钮 ID 添 加 消 息 处 理 函 数 然 后, 在 MITKTestView.cpp 中 添 加 相 应 代 码 实 现 浏 览 功 能 : void CMITKTestView::OnSliceFirst() 302
310 9 应 用 MITK 开 发 实 际 项 目 // TODO: Add your command handler code here if (m_imagemodel) // 定 位 在 第 1 张 切 片 m_imagemodel->setcurrentslicenumber(0); // 更 新 View 中 的 显 示 m_view->update(); void CMITKTestView::OnSliceLast() // TODO: Add your command handler code here if (m_imagemodel) // 定 位 到 最 后 一 张 切 片 m_imagemodel->setcurrentslicenumber( m_imagemodel->gettotalslicenumber()-1); // 更 新 View 中 的 显 示 m_view->update(); void CMITKTestView::OnSliceNext() // TODO: Add your command handler code here if (m_imagemodel) // 定 位 到 下 一 张 切 片 m_imagemodel->nextslice(); // 更 新 View 中 的 显 示 m_view->update(); void CMITKTestView::OnSlicePrev() 303
311 9 应 用 MITK 开 发 实 际 项 目 // TODO: Add your command handler code here if (m_imagemodel) // 定 位 到 前 一 张 切 片 m_imagemodel->prevslice(); // 更 新 View 中 的 显 示 m_view->update(); 好 了, 编 译 运 行, 最 后 结 果 如 图 9-17 所 示 图 9-17 运 行 结 果 mitkimageview 所 带 的 缺 省 Manipulator 已 经 实 现 了 常 用 的 鼠 标 操 作, 所 以 不 用 写 一 行 代 码, 你 的 程 序 就 拥 有 了 对 图 像 进 行 平 移 缩 放 和 窗 宽 / 窗 位 调 整 的 功 能, 其 中, 按 住 鼠 标 左 键 拖 动 鼠 标 是 平 移 图 像 ; 按 住 鼠 标 右 键 拖 动 鼠 标 是 对 图 像 进 行 缩 放 ; 按 住 鼠 标 中 键 拖 动 鼠 标 是 调 整 图 像 的 窗 宽 / 窗 位, 水 平 移 动 调 整 的 是 窗 宽, 垂 直 移 动 调 整 的 是 窗 位 304
312 9 应 用 MITK 开 发 实 际 项 目 9.3 用 MITK 进 行 表 面 重 建 利 用 MITK 提 供 的 等 值 面 提 取 算 法 可 以 对 Volume 数 据 进 行 表 面 重 建, 重 建 的 结 果 可 以 通 过 Surface Model 显 示, 下 面 的 例 子 就 展 示 了 如 何 用 MITK 来 实 现 这 些 功 能 回 到 9.1 建 立 的 工 程, 按 9.2 的 方 法 加 入 读 取 Volume 数 据 的 部 分 然 后 给 CMITKTestDoc 类 添 加 一 个 mitkmesh* 类 型 的 成 员 变 量,mitkMesh 在 MITK 中 是 除 mitkvolume 之 外 的 另 一 大 数 据 类 型, 代 表 一 个 三 维 的 几 何 数 据 对 象, 具 体 的 说 就 是 用 三 角 片 来 表 示 的 物 体 表 面 模 型, 表 面 重 建 输 出 的 就 是 这 种 类 型 的 数 据 MITKTestDoc.h 及 MITKTestDoc.cpp 中 添 加 的 代 码 如 下, 基 本 上 和 mitkvolume 成 员 相 关 代 码 是 对 称 的 : // MITKTestDoc.h : interface of the CMITKTestDoc class // ////////////////////////////////////////////////////////////////////... class mitkvolume; //mitkvolume 类 的 前 向 声 明 class mitkmesh; //mitkmesh 类 的 前 向 声 明 class CMITKTestDoc : public CDocument protected: // create from serialization only CMITKTestDoc(); DECLARE_DYNCREATE(CMITKTestDoc) // Attributes public: // 提 供 取 得 指 向 mitkvolume 指 针 的 接 口 mitkvolume* GetVolume() return m_volume; // 提 供 取 得 指 向 mitkmesh 指 针 的 接 口 mitkmesh* GetMesh() return m_mesh;... protected: 305
313 9 应 用 MITK 开 发 实 际 项 目 void clearvolume(); void clearmesh(); // 清 理 Volume // 清 理 Mesh mitkvolume *m_volume; // 指 向 mitkvolume 的 指 针 mitkmesh *m_mesh; // 指 向 mitkmesh 的 指 针... ;... // MITKTestDoc.cpp : implementation of the CMITKTestDoc class // #include "stdafx.h" #include "MITKTest.h" #include "MITKTestDoc.h" #include "SpacingSetDlg.h" #include "RawSetDlg.h" #include "ThresholdDlg.h" #include "mitkvolume.h" #include "mitkmesh.h" // 添 加 mitkmesh 类 的 头 文 件 #include "mitkim0reader.h" #include "mitkjpegreader.h" #include "mitkdicomreader.h" #include "mitktiffreader.h" #include "mitkbmpreader.h" #include "mitkrawreader.h"... //////////////////////////////////////////////////////////////////// // CMITKTestDoc construction/destruction CMITKTestDoc::CMITKTestDoc() // TODO: add one-time construction code here // 初 始 化 指 针 变 量 306
314 9 应 用 MITK 开 发 实 际 项 目 m_volume = NULL; m_mesh = NULL; CMITKTestDoc::~CMITKTestDoc() this->clearvolume(); // 清 理 Volume 数 据 this->clearmesh(); // 清 理 Mesh 数 据... //////////////////////////////////////////////////////////////////// // CMITKTestDoc commands void CMITKTestDoc::clearVolume() if (m_volume) // 解 除 引 用 m_volume->removereference(); // 这 时 候 的 m_volume 是 无 意 义 的 指 针, 应 将 其 置 为 NULL m_volume = NULL; void CMITKTestDoc::clearMesh() if (m_mesh) // 解 除 引 用 m_mesh->removereference(); // 这 时 候 的 m_mesh 是 无 意 义 的 指 针, 应 将 其 置 为 NULL m_mesh = NULL;
315 9 应 用 MITK 开 发 实 际 项 目 接 着 在 工 具 栏 添 加 一 个 按 钮, 其 功 能 就 是 对 读 入 的 Volume 数 据 进 行 表 面 重 建, 如 图 9-18 所 示 图 9-18 添 加 表 面 重 建 按 钮 为 改 按 钮 ID 添 加 消 息 处 理 函 数, 注 意, 要 添 加 在 CMITKTestDoc 类 中, 如 图 9-19 所 示 308
316 9 应 用 MITK 开 发 实 际 项 目 图 9-19 给 ID_MC 添 加 消 息 处 理 函 数 另 外, 还 需 加 一 个 对 话 框, 接 受 用 户 输 入 的 阈 值 作 为 重 建 算 法 的 输 入 参 数, 如 图 9-20 所 示 图 9-20 设 置 阈 值 的 对 话 框 现 在 可 以 来 写 表 面 重 建 部 分 的 代 码 了 这 里, 我 们 使 用 标 准 的 Marching Cubes 算 法 来 进 行 表 面 重 建, 在 MITK 中, 该 算 法 被 封 装 在 mitkmarchingcubes 类 中, 该 类 是 一 个 Filter, 代 表 Marching Cubes 算 法 对 象, 它 接 收 一 个 mitkvolume 输 入, 经 过 处 理, 输 出 一 个 mitkmesh 对 象, 具 体 代 码 如 下 : // MITKTestDoc.cpp : implementation of the CMITKTestDoc class // 309
317 9 应 用 MITK 开 发 实 际 项 目... #include "mitkmarchingcubes.h" // 包 含 mitkmarchingcubes 头 文 件... void CMITKTestDoc::OnMc() // TODO: Add your command handler code here // 产 生 阈 值 设 置 对 话 框 CThresholdDlg dlg; // 显 示 阈 值 设 置 对 话 框 if (dlg.domodal() == IDOK) // 生 成 一 个 mitkmarchingcubes 对 象 mitkmarchingcubes *mc = new mitkmarchingcubes; // 将 从 对 话 框 中 得 到 的 阈 值 设 置 给 Marching Cubes 算 法 mc->setthreshold(dlg.m_lowvalue, dlg.m_highvalue); // 设 置 输 入 数 据 mc->setinput(m_volume); // 运 行 该 算 法, 如 果 算 法 正 常 结 束 则 更 新 Mesh 数 据 并 显 示 if (mc->run()) // 清 除 旧 的 Mesh 数 据 this->clearmesh(); // 从 mitkmarchingcubes 算 法 得 到 输 出 结 果 m_mesh = mc->getoutput(); m_mesh->addreference(); // 重 要 // 更 新 View UpdateAllViews(NULL); // 删 除 mitkmarchingcubes 对 象 mc->delete(); 310
318 9 应 用 MITK 开 发 实 际 项 目 接 下 来 在 CMITKTestView 类 中 添 加 代 码 将 生 成 的 mitkmesh 对 象 显 示 出 来 首 先 给 CMITKTestView 类 添 加 一 个 mitksurfacemodel* 类 型 的 成 员 变 量 在 MITK 中,mitkSurfaceModel 属 于 一 种 Data Model, 表 示 某 数 据 对 象 在 三 维 场 景 中 的 显 示 模 型,mitkSurfaceModel 即 是 对 应 于 mitkmesh 对 象 的 显 示 模 型, 通 过 三 维 重 建 得 到 的 mitkmesh 对 象 将 由 mitksurfacemodel 来 负 责 绘 制 代 码 如 下 : // MITKTestView.h : interface of the CMITKTestView class // ////////////////////////////////////////////////////////////////////... class mitkview; //mitkview 类 的 前 向 声 明 class mitksurfacemodel; //mitksurfacemodel 类 的 前 向 声 明 class CMITKTestView : public CView... protected: // 添 加 一 个 指 向 mitkview 的 指 针 mitkview *m_view; // 添 加 一 个 指 向 mitksurfacemodel 的 指 针 mitksurfacemodel *m_surfacemodel;... ;... 在 MITKTestView.cpp 开 头 添 加 #include mitksurfacemodel.h, 修 改 OnCreate() 函 数, 添 加 mitksurfacemodel 的 初 始 化 代 码 : int CMITKTestView::OnCreate(LPCREATESTRUCT lpcreatestruct) if (CView::OnCreate(lpCreateStruct) == -1) 311
319 9 应 用 MITK 开 发 实 际 项 目 return -1; // TODO: Add your specialized creation code here // 得 到 当 前 客 户 区 大 小 RECT clientrect; this->getclientrect(&clientrect); int wwidth = clientrect.right - clientrect.left; int wheight = clientrect.bottom - clientrect.top; // 产 生 mitkview 对 象 m_view = new mitkview; // 设 置 父 窗 口 句 柄 m_view->setparent(getsafehwnd()); // 设 置 mitkview 在 父 窗 口 中 显 示 的 位 置 和 大 小 m_view->setleft(0); m_view->settop(0); m_view->setwidth(wwidth); m_view->setheight(wheight); // 设 置 mitkview 的 背 景 颜 色 ( 这 里 将 其 设 置 为 黑 色 ) m_view->setbackcolor(0, 0, 0); // 显 示 mitkview m_view->show(); // 生 成 一 个 mitksurfacemodel m_surfacemodel = new mitksurfacemodel; // 设 置 表 面 材 质 属 性 ( 这 些 属 性 可 以 随 时 调 整 ) m_surfacemodel->getproperty()->setambientcolor(0.75f, 0.75f, 0.75f, 1.0f); m_surfacemodel->getproperty()->setdiffusecolor(1.0f, 0.57f, 0.04f, 1.0f); m_surfacemodel->getproperty()->setspecularcolor(1.0f, 1.0f, 1.0f, 1.0f); m_surfacemodel->getproperty()->setspecularpower(100.0f); m_surfacemodel->getproperty()->setemissioncolor(0.0f, 0.0f, 0.0f, 0.0f); 312
320 9 应 用 MITK 开 发 实 际 项 目 // 将 Model 加 入 到 View 中 m_view->addmodel(m_surfacemodel); return 0; 修 改 OnDraw() 函 数 以 便 在 绘 制 之 前 保 证 mitkmesh 数 据 的 有 效 性 : void CMITKTestView::OnDraw(CDC* pdc) CMITKTestDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here // Mesh 数 据 更 新,Surface Model 也 随 之 更 新 if (m_surfacemodel->getdata()!= pdoc->getmesh()) m_surfacemodel->setdata(pdoc->getmesh()); 完 成 之 后 编 译 运 行, 在 打 开 菜 单 的 子 菜 单 中 选 择 一 种 格 式 的 文 件 打 开, 读 入 一 个 Volume, 然 后 按 下, 在 弹 出 的 设 置 阈 值 对 话 框 中 输 入 阈 值 进 行 重 建 阈 值 的 选 择 取 决 于 要 重 建 表 面 的 物 质 的 性 质, 需 要 一 些 先 验 知 识, 主 要 是 该 物 质 的 密 度 ( 在 图 像 中 表 现 为 灰 度 值 ) 在 哪 个 范 围 之 内 当 输 入 合 适 的 阈 值 后 点 确 定 按 钮 即 开 始 表 面 重 建, 稍 候 片 刻 其 结 果 就 会 显 示 出 来, 在 你 可 以 用 鼠 标 对 结 果 进 行 一 些 简 单 的 操 作 : 按 住 左 键 拖 动 鼠 标 是 旋 转, 中 键 是 平 移, 邮 件 是 缩 放 图 9-21 是 一 个 重 建 结 果 的 示 例 313
321 9 应 用 MITK 开 发 实 际 项 目 图 9-21 重 建 结 果 示 例 9.4 一 个 比 较 完 善 的 例 子 9.2 和 9.3 都 是 比 较 简 单 的 例 子, 功 能 比 较 单 一 在 这 一 节, 我 们 将 综 合 上 面 两 节 所 完 成 的 功 能, 作 出 一 个 比 较 完 善 的 应 用 程 序 先 来 看 一 下 完 成 后 的 整 个 应 用 程 序 的 运 行 状 况 所 图 9-22 示 是 正 在 进 行 表 面 重 建 时 的 显 示 界 面, 在 这 个 例 子 中 增 加 了 重 建 进 度 的 显 示, 如 所 示 图 9-23 所 示 是 重 建 结 束 后 的 程 序 界 面 主 窗 口 的 整 个 客 户 区 被 划 分 为 左 右 两 部 分, 右 边 比 较 大 的 显 示 区 用 来 显 示 经 过 表 面 重 建 生 成 的 三 维 模 型, 左 边 又 被 划 分 为 三 个 小 的 显 示 区, 分 别 显 示 读 入 的 Volume 数 据 X-Y Y-Z 及 Z-X 平 面 的 断 层 图 像 314
322 9 应 用 MITK 开 发 实 际 项 目 图 9-22 表 面 重 建 时 显 示 进 度 315
323 9 应 用 MITK 开 发 实 际 项 目 图 9-23 重 建 完 成 后 的 界 面 工 具 栏 中 的 按 钮 大 部 分 都 在 9.2 和 9.3 中 介 绍 过 了, 其 功 能 大 致 相 同 新 增 按 钮 的 功 能 如 下 : : 显 示 读 入 的 Volume 的 相 关 信 息 ; : 显 示 生 成 的 Mesh 的 相 关 信 息 ; : 显 示 生 成 的 Mesh 的 所 有 顶 点, 如 图 9-24 所 示 ; : 显 示 Mesh 的 线 框 模 型, 如 图 9-25 所 示 ; : 显 示 Mesh 的 表 面 模 型, 如 图 9-23 界 面 中 所 示 316
324 9 应 用 MITK 开 发 实 际 项 目 图 9-24 点 显 示 的 三 维 模 型 317
325 9 应 用 MITK 开 发 实 际 项 目 图 9-25 线 框 显 示 的 三 维 模 型 有 了 上 面 两 节 的 基 础, 本 节 将 不 再 拘 泥 于 编 程 细 节 的 介 绍, 而 把 主 要 精 力 放 在 如 何 实 现 新 增 的 功 能 上 首 先 是 主 窗 口 的 划 分 在 一 个 父 窗 口 下, 可 以 同 时 安 放 多 个 mitkview, 通 过 mitkview 的 SetLeft() SetTop() SetWidth() 和 SetHeight() 确 定 每 一 个 mitkview 在 父 窗 口 中 的 位 置 ( 以 父 窗 口 左 上 角 为 坐 标 原 点 ) 和 尺 寸 在 本 节 的 例 子 中, 一 共 在 主 窗 口 的 客 户 区 放 了 4 个 mitkview, 左 边 三 个 是 mitkimageview(mitkview 的 子 类 ), 用 来 显 示 三 个 方 向 的 断 层 图 像, 右 边 一 个 mitkview 用 来 显 示 表 面 重 建 生 成 的 三 维 模 型 完 成 这 部 分 功 能 的 代 码 主 要 集 中 在 MITKTestView.h 和 MITKTestView.cpp 中, 如 下 所 示 : 318
326 9 应 用 MITK 开 发 实 际 项 目 // MITKTestView.h : interface of the CMITKTestView class // ////////////////////////////////////////////////////////////////////... class CMITKTestDoc; class mitkimageview; //mitkimageview 类 的 前 向 声 明 class mitkimagemodel; //mitkimagemodel 类 的 前 向 声 明 class mitkview; //mitkview 类 的 前 向 声 明 class mitksurfacemodel; //mitksurfacemodel 类 的 前 向 声 明 class CMITKTestView : public CView... public: // 改 变 选 中 的 mitkimageview void ChangeSelectedImageView(mitkImageView *selview);... protected: 针 // 以 下 为 显 示 切 片 图 像 而 设 mitkimageview *m_imageview[3]; mitkimagemodel *m_imagemodel[3]; mitkimageview *m_curimageview; mitkimagemodel *m_curimagemodel; // 三 个 指 向 mitkimageview 的 指 针 // 三 个 指 向 mitkimagemodel 的 指 针 // 指 向 当 前 选 中 mitkimageview 的 指 针 // 指 向 当 前 选 中 mitkimagemodel 的 指 // 以 下 为 显 示 重 建 的 表 面 模 型 而 设 mitkview *m_sceneview; // 一 个 指 向 mitkview 的 指 针 mitksurfacemodel *m_surfacemodel; // 一 个 指 向 mitksurfacemodel 的 指 针 // Generated message map functions protected: //AFX_MSG(CMITKTestView) afx_msg int OnCreate(LPCREATESTRUCT lpcreatestruct); afx_msg void OnSize(UINT ntype, int cx, int cy); 319
327 9 应 用 MITK 开 发 实 际 项 目 afx_msg void OnSliceFirst(); afx_msg void OnSliceLast(); afx_msg void OnSliceNext(); afx_msg void OnSlicePrev(); afx_msg void OnDestroy(); afx_msg void OnPoints(); afx_msg void OnWireframe(); afx_msg void OnSurface(); //AFX_MSG DECLARE_MESSAGE_MAP() ;... // MITKTestView.cpp : implementation of the CMITKTestView class // #include "stdafx.h" #include "MITKTest.h" #include "MITKTestDoc.h" #include "MITKTestView.h" // 需 要 的 头 文 件 #include "mitkimageview.h" #include "mitkimagemodel.h" #include "mitksurfacemodel.h" #include "ChooseViewManipulator.h" // 各 个 View 之 间 的 空 隙 宽 度 const int VIEW_MARGIN = 1;... //////////////////////////////////////////////////////////////////// // CMITKTestView construction/destruction CMITKTestView::CMITKTestView() // TODO: add construction code here 320
328 9 应 用 MITK 开 发 实 际 项 目 // 初 始 化 指 针 变 量 为 NULL for (int i=0; i<3; ++i) m_imageview[i] = NULL; m_imagemodel[i] = NULL; m_curimageview = NULL; m_curimagemodel = NULL; m_sceneview = NULL; m_surfacemodel = NULL; CMITKTestView::~CMITKTestView()... //////////////////////////////////////////////////////////////////// // CMITKTestView drawing void CMITKTestView::OnDraw(CDC* pdc) CMITKTestDoc* pdoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here // Volume 数 据 更 新,m_ImageModel 也 相 应 更 新, 且 将 显 示 的 切 片 定 位 在 中 间 if (m_imagemodel[0]->getdata()!= pdoc->getvolume()) m_imagemodel[0]->setdata(pdoc->getvolume()); m_imagemodel[0]->setcurrentslicenumber(m_imagemodel[0]->gettotals licenumber()/2); m_imagemodel[1]->setdata(pdoc->getvolume()); m_imagemodel[1]->setcurrentslicenumber(m_imagemodel[1]->gettotals licenumber()/2); 321
329 9 应 用 MITK 开 发 实 际 项 目 m_imagemodel[2]->setdata(pdoc->getvolume()); m_imagemodel[2]->setcurrentslicenumber(m_imagemodel[2]->gettotals licenumber()/2); // Mesh 数 据 更 新,m_SurfaceModel 也 相 应 更 新 if (m_surfacemodel->getdata()!= pdoc->getmesh()) m_surfacemodel->setdata(pdoc->getmesh());... //////////////////////////////////////////////////////////////////// // CMITKTestView message handlers int CMITKTestView::OnCreate(LPCREATESTRUCT lpcreatestruct) if (CView::OnCreate(lpCreateStruct) == -1) return -1; // TODO: Add your specialized creation code here // 得 到 当 前 客 户 区 大 小 RECT clientrect; this->getclientrect(&clientrect); int wwidth = clientrect.right - clientrect.left + 1; int wheight = clientrect.bottom - clientrect.top + 1; // 三 个 mitkimageview 的 尺 寸 int imagewidth = (int)((wwidth - VIEW_MARGIN * 3) / 3.0); int imageheight = (int)((wheight - VIEW_MARGIN * 4) / 3.0); int imageheight1 = wheight - imageheight * 2 - VIEW_MARGIN * 4; // 下 面 产 生 三 个 mitkimageview 对 象 安 排 在 父 窗 口 中 // 并 且 给 每 个 mitkimageview 生 成 一 个 可 显 示 的 mitkimagemodel // mitkimagemodel 的 SetViewMode() 函 数 就 是 设 定 显 示 断 面 的 方 向 的 mitkimageview *tempview; 322
330 9 应 用 MITK 开 发 实 际 项 目 mitkimagemodel *tempmodel; tempview = new mitkimageview; tempmodel = new mitkimagemodel; tempmodel->setviewmode(mitkimagemodel::view_xy); tempview->setparent(getsafehwnd()); tempview->setleft(view_margin); tempview->settop(view_margin); tempview->setwidth(imagewidth); tempview->setheight(imageheight1); tempview->setbackcolor(0,0,0); tempview->setcrossarrow(false); tempview->setmanipulator(new ChooseViewManipulator(this)); tempview->addmodel(tempmodel); tempview->show(); m_imageview[0] = tempview; m_imagemodel[0] = tempmodel; tempview = new mitkimageview; tempmodel = new mitkimagemodel; tempmodel->setviewmode(mitkimagemodel::view_yz); tempview->setparent(getsafehwnd()); tempview->setleft(view_margin); tempview->settop(imageheight1 + VIEW_MARGIN * 2); tempview->setwidth(imagewidth); tempview->setheight(imageheight); tempview->setbackcolor(0,0,0); tempview->setcrossarrow(false); tempview->setmanipulator(new ChooseViewManipulator(this)); tempview->addmodel(tempmodel); tempview->show(); m_imageview[1] = tempview; m_imagemodel[1] = tempmodel; tempview = new mitkimageview; tempmodel = new mitkimagemodel; tempmodel->setviewmode(mitkimagemodel::view_zx); tempview->setparent(getsafehwnd()); tempview->setleft(view_margin); tempview->settop(imageheight1 + imageheight + VIEW_MARGIN * 3); 323
331 9 应 用 MITK 开 发 实 际 项 目 tempview->setwidth(imagewidth); tempview->setheight(imageheight); tempview->setbackcolor(0,0,0); tempview->setcrossarrow(false); tempview->setmanipulator(new ChooseViewManipulator(this)); tempview->addmodel(tempmodel); tempview->show(); m_imageview[2] = tempview; m_imagemodel[2] = tempmodel; // 初 始 情 况 第 一 个 mitkimageview 为 选 中 状 态 this->changeselectedimageview(m_imageview[0]); // 下 面 生 成 左 边 显 示 三 维 模 型 的 mitkview m_sceneview = new mitkview; m_sceneview->setparent(getsafehwnd()); m_sceneview->setleft(imagewidth + VIEW_MARGIN * 2); m_sceneview->settop(view_margin); m_sceneview->setwidth(wwidth - imagewidth - VIEW_MARGIN * 3); m_sceneview->setheight(wheight - VIEW_MARGIN * 2); m_sceneview->setbackcolor(0, 0, 0); m_sceneview->show(); // 生 成 一 个 mitksurfacemodel m_surfacemodel = new mitksurfacemodel; // 设 置 表 面 材 质 属 性 ( 这 些 属 性 可 以 随 时 调 整 ) m_surfacemodel->getproperty()->setambientcolor(0.75f, 0.75f, 0.75f, 1.0f); m_surfacemodel->getproperty()->setdiffusecolor(1.0f, 0.57f, 0.04f, 1.0f); m_surfacemodel->getproperty()->setspecularcolor(1.0f, 1.0f, 1.0f, 1.0f); m_surfacemodel->getproperty()->setspecularpower(100.0f); m_surfacemodel->getproperty()->setemissioncolor(0.0f, 0.0f, 0.0f, 0.0f); 324
332 9 应 用 MITK 开 发 实 际 项 目 // 将 Model 加 入 到 View 中 m_sceneview->addmodel(m_surfacemodel); return 0; void CMITKTestView::OnSize(UINT ntype, int cx, int cy) CView::OnSize(nType, cx, cy); // TODO: Add your message handler code here // 更 新 mitkview 在 父 窗 口 中 的 位 置 和 尺 寸 int imagewidth = (int)((cx - VIEW_MARGIN * 3) / 3.0); int imageheight = (int)((cy - VIEW_MARGIN * 4) / 3.0); int imageheight1 = cy - imageheight * 2 - VIEW_MARGIN * 4; // 调 整 三 个 mitkimageview 的 位 置 和 尺 寸 m_imageview[0]->setleft(view_margin); m_imageview[0]->settop(view_margin); m_imageview[0]->setwidth(imagewidth); m_imageview[0]->setheight(imageheight1); m_imageview[1]->setleft(view_margin); m_imageview[1]->settop(imageheight1 + VIEW_MARGIN * 2); m_imageview[1]->setwidth(imagewidth); m_imageview[1]->setheight(imageheight); m_imageview[2]->setleft(view_margin); m_imageview[2]->settop(imageheight1 + imageheight + VIEW_MARGIN * 3); m_imageview[2]->setwidth(imagewidth); m_imageview[2]->setheight(imageheight); // 调 整 左 边 的 mitkview 的 位 置 和 尺 寸 m_sceneview->setleft(imagewidth + VIEW_MARGIN * 2); m_sceneview->settop(view_margin); m_sceneview->setwidth(cx - imagewidth - VIEW_MARGIN * 3); m_sceneview->setheight(cy - VIEW_MARGIN * 2); 325
333 9 应 用 MITK 开 发 实 际 项 目 void CMITKTestView::OnSliceFirst() // TODO: Add your command handler code here if (m_curimagemodel) // 定 位 在 第 1 张 切 片 m_curimagemodel->setcurrentslicenumber(0); // 更 新 View 中 的 显 示 m_curimageview->update(); void CMITKTestView::OnSliceLast() // TODO: Add your command handler code here if (m_curimagemodel) // 定 位 到 最 后 一 张 切 片 m_curimagemodel->setcurrentslicenumber(m_imagemodel[0]->gettotals licenumber()-1); // 更 新 View 中 的 显 示 m_curimageview->update(); void CMITKTestView::OnSliceNext() // TODO: Add your command handler code here if (m_curimagemodel) // 定 位 到 下 一 张 切 片 m_curimagemodel->nextslice(); // 更 新 View 中 的 显 示 m_curimageview->update(); 326
334 9 应 用 MITK 开 发 实 际 项 目 void CMITKTestView::OnSlicePrev() // TODO: Add your command handler code here if (m_curimagemodel) // 定 位 到 前 一 张 切 片 m_curimagemodel->prevslice(); // 更 新 View 中 的 显 示 m_curimageview->update(); void CMITKTestView::OnDestroy() CView::OnDestroy(); // TODO: Add your message handler code here for (int i=0; i<3; ++i) if (m_imageview[i]) m_imageview[i]->delete(); m_imageview[i] = NULL; if (m_sceneview) m_sceneview->delete(); m_sceneview = NULL; void CMITKTestView::OnPoints() // TODO: Add your command handler code here m_surfacemodel->getproperty()->setrepresentationtypetopoints(); 327
335 9 应 用 MITK 开 发 实 际 项 目 m_sceneview->update(); void CMITKTestView::OnWireframe() // TODO: Add your command handler code here m_surfacemodel->getproperty()->setrepresentationtypetowireframe() ; m_sceneview->update(); void CMITKTestView::OnSurface() // TODO: Add your command handler code here m_surfacemodel->getproperty()->setrepresentationtypetosurface(); m_sceneview->update(); void CMITKTestView::ChangeSelectedImageView(mitkImageView *selview) if (m_curimageview == selview) return; if (m_curimageview) m_curimageview->setselected(false); m_curimageview->update(); m_curimageview = selview; if (m_curimageview) // 设 置 m_curimageview 为 选 中 状 态 并 更 新 View m_curimageview->setselected(true); m_curimageview->update(); // 从 当 前 选 中 的 mitkimageview 中 得 到 当 前 的 mitkimagemodel m_curimagemodel = mitkimagemodel::safedowncast(m_curimageview->getmodel(0)); 328
336 9 应 用 MITK 开 发 实 际 项 目 else m_curimagemodel = NULL; 注 意 到 在 上 面 的 代 码 中 还 包 含 了 其 他 一 些 功 能, 首 先 是 以 不 同 的 方 式 显 示 ( 点 显 示 线 框 显 示 和 面 显 示 ) 三 维 模 型 的 功 能 在 OnPoints() OnWireframe() 和 OnSurface() 三 个 函 数 中, 都 是 通 过 直 接 得 到 Surface Model 的 Property 然 后 将 其 设 置 成 所 需 要 的 显 示 方 式 而 实 现 的, 三 维 模 型 的 表 面 材 质 也 是 通 过 这 种 方 式 设 置 其 次 是 对 三 个 mitkimageview 的 选 择 功 能, 在 上 面 的 代 码 中 切 片 的 浏 览 都 是 针 对 m_curimageview 和 m_curimagemodel 即 当 前 选 中 的 mitkimageview 进 行 的, 从 前 面 的 图 中 也 可 以 看 到 当 前 处 于 选 中 状 态 下 的 mitkimageview 是 有 一 个 红 色 的 方 框 包 围 的 要 实 现 用 鼠 标 对 这 三 个 mitkimageview 进 行 选 择 的 功 能, 还 牵 扯 到 另 外 一 个 类 :ChooseViewManipulator, 默 认 情 况 下,mitkImageView 包 含 的 Manipulator 是 mitkimageviewmanipulatorstandard, 它 实 现 了 三 种 基 本 的 鼠 标 操 作, 这 个 在 9.2 中 已 经 有 所 介 绍, 在 这 个 例 子 中, 我 们 不 仅 想 保 留 这 些 基 本 功 能, 还 要 再 提 供 鼠 标 任 意 键 按 下 是 对 所 在 View 的 选 择, 所 以 ChooseViewManipulator 从 mitkimageviewmanipulatorstandard 继 承, 并 重 载 它 的 OnMouseDown 函 数, 加 入 新 的 功 能, 主 要 是 通 过 调 用 CMITKTestView 的 ChangeSelectedImageView() 函 数 进 行 具 体 的 切 换 操 作 ChooseViewManipulator 的 具 体 代 码 如 下 : // ChooseViewManipulator.h : interface of the ChooseViewManipulator class // //////////////////////////////////////////////////////////////////// #ifndef ChooseViewManipulator_h #define ChooseViewManipulator_h #include "mitkimageviewmanipulatorstandard.h" class CMITKTestView; class ChooseViewManipulator : public mitkimageviewmanipulatorstandard public: // 如 果 不 需 要 RTTI, 这 句 可 以 省 去 MITK_TYPE(ChooseViewManipulator, mitkimageviewmanipulatorstandard) 329
337 9 应 用 MITK 开 发 实 际 项 目 ChooseViewManipulator(CMITKTestView *parentview); // 重 载 父 类 的 OnMouseDown() 函 数, 加 入 选 择 所 在 mitkimageview // 为 当 前 选 中 mitkimageview 的 功 能 virtual void OnMouseDown(int mousebutton, bool ctrldown, bool shiftdown, int xpos, int ypos); protected: virtual ~ChooseViewManipulator(); // 保 存 指 向 父 窗 口 View 的 指 针, 以 便 在 OnMouseDown() 触 发 时 // 调 用 其 ChangeSelectedImageView() 函 数 改 变 当 前 选 中 的 // mitkimageview CMITKTestView *m_pview; private: ; #endif // ChooseViewManipulator.cpp : implementation of the // ChooseViewManipulator class // #include "stdafx.h" #include "MITKTest.h" #include "MITKTestView.h" #include "ChooseViewManipulator.h" #include "mitkimageview.h" ChooseViewManipulator::ChooseViewManipulator(CMITKTestView *parentview) : m_pview(parentview) ChooseViewManipulator::~ChooseViewManipulator() 330
338 9 应 用 MITK 开 发 实 际 项 目 void ChooseViewManipulator::OnMouseDown(int mousebutton, bool ctrldown, bool shiftdown, int xpos, int ypos) // 调 用 父 类 的 函 数 保 留 原 始 功 能 mitkimageviewmanipulatorstandard::onmousedown(mousebutton, ctrldown, shiftdown, xpos, ypos); // 添 加 选 择 所 在 mitkimageview 为 当 前 选 定 mitkimageview 的 功 能 // m_view 是 其 父 类 中 的 成 员, 保 存 了 指 向 该 Manipulator 所 在 的 View 的 指 针 if (m_pview) m_pview->changeselectedimageview(mitkimageview::safedowncast(m_vi ew)); 有 了 这 个 类, 在 生 成 每 一 个 mitkimageview 的 时 候 通 过 如 上 面 的 代 码 中 那 样 调 用 tempview->setmanipulator(new ChooseViewManipulator(this)) 将 缺 省 的 Manipulator 换 掉, 这 样 就 在 保 留 标 准 Manipulator 功 能 的 基 础 上 加 入 了 自 定 义 的 功 能 接 下 来, 我 们 再 来 看 一 下 如 何 在 表 面 重 建 时 显 示 其 进 度 这 一 功 能 的 实 现 主 要 是 依 靠 MITK 提 供 的 Observer mitkoberver 类 是 一 个 高 度 抽 象 的 类, 它 只 提 供 了 一 个 Update() 接 口,MITK 中 所 有 自 mitkobject 基 础 下 来 的 类 均 有 添 加 一 组 mitkobserver 的 功 能, 通 过 AddObserver() RemoveObserver() 管 理 其 所 包 含 的 mitkobserver 队 列 在 程 序 运 行 过 程 中, 通 过 调 用 mitkobserver 提 供 的 Update() 接 口 通 知 具 体 的 Observer 更 新 状 态 我 们 所 要 做 的 就 是 从 mitkobserver 派 生 出 具 体 的 Observer, 通 过 实 现 Update() 接 口, 在 界 面 上 更 新 所 观 察 对 象 的 状 态 在 本 节 的 例 子 中, 我 们 为 MITK 中 的 mitkfilter 做 了 一 个 Observer,mitkFilter 继 承 自 mitkprocessobject, 它 已 经 实 现 了 一 些 记 录 当 前 程 序 的 运 行 进 度 的 基 本 功 能, 在 我 们 的 Observer 中 通 过 调 用 GetProgressRate() 就 可 以 得 到 当 前 程 序 的 运 行 进 度 需 要 注 意 的 是 在 开 始 之 前, 必 须 通 过 SetProgressRateMax() 设 定 用 来 度 量 进 度 的 整 数 的 最 大 值, 在 程 序 运 行 的 整 个 过 程 中, 进 度 值 将 从 0 增 大 到 这 个 设 定 的 最 大 值 以 下 是 FilterObserver 的 具 体 实 331
339 9 应 用 MITK 开 发 实 际 项 目 现 : // FilterObserver.h : interface of the FilterObserver class // //////////////////////////////////////////////////////////////////// #ifndef FilterObserver_h #define FilterObserver_h #include "mitkobserver.h" class mitkfilter; class CProgressDlg; class FilterObserver : public mitkobserver public: FilterObserver(mitkFilter *obj); // 从 父 类 继 承 的 必 须 实 现 的 虚 函 数 // MITK 中 的 对 象 就 通 过 这 个 接 口 通 知 Observer 更 新 状 态 virtual void Update(); // 在 这 个 函 数 中 完 成 一 些 必 要 的 初 始 化 工 作 void StartShowProgress(const char *title); // 结 束 进 度 显 示, 隐 藏 对 话 框 void FinishShowProgress(); protected: virtual ~FilterObserver(); mitkfilter *m_object; // 指 向 观 察 对 象 的 指 针 CProgressDlg *m_progressdlg; // 指 向 显 示 进 度 的 对 话 框 的 指 针 private: ; #endif 332
340 9 应 用 MITK 开 发 实 际 项 目 // FilterObserver.cpp : implementation of the FilterObserver class // #include "stdafx.h" #include "MITKTest.h" #include "FilterObserver.h" #include "mitkfilter.h" #include "ProgressDlg.h" FilterObserver::FilterObserver(mitkFilter *obj) : m_object(obj) m_progressdlg = NULL; FilterObserver::~FilterObserver() if (m_progressdlg) delete m_progressdlg; void FilterObserver::StartShowProgress(const char *title) if (m_object == NULL) return; m_object->setprogressratemax(1000); // 产 生 显 示 进 度 的 对 话 框 if (m_progressdlg == NULL) m_progressdlg = new CProgressDlg; m_progressdlg->create(cprogressdlg::idd); // 以 下 代 码 将 显 示 进 度 的 对 话 框 定 位 在 主 窗 口 的 中 心 CRect mfrect; CRect dlgrect; ::AfxGetApp()->GetMainWnd()->GetWindowRect(&mfRect); 333
341 9 应 用 MITK 开 发 实 际 项 目 m_progressdlg->getwindowrect(dlgrect); 2; 2; int newleft = (mfrect.left + mfrect.right) / 2 - dlgrect.width() / int newtop = (mfrect.top + mfrect.bottom) / 2 - dlgrect.height() / m_progressdlg->movewindow(newleft, newtop, dlgrect.width(), dlgrect.height()); m_progressdlg->showwindow(sw_show); // 一 些 相 关 参 数 的 初 始 化 // 注 意 : 进 度 条 的 最 大 值 跟 m_object->setprogressratemax() 的 设 置 需 保 持 一 致 m_progressdlg->setmaxprogress(1000); m_progressdlg->setprogressstep(1); m_progressdlg->updateprogress(0); m_progressdlg->setlabeltext(title); void FilterObserver::FinishShowProgress() if (m_progressdlg) m_progressdlg->showwindow(sw_hide); void FilterObserver::Update() if (m_object && m_progressdlg) // 更 新 进 度 条 的 显 示 m_progressdlg->updateprogress(m_object->getprogressrate()); FilterObserver 将 显 示 进 度 的 任 务 交 给 对 话 框 CProgressDlg 去 做, 该 对 话 框 的 样 子 如 所 示 具 体 怎 么 制 作 这 个 对 话 框 就 不 细 讲 了, 上 面 用 到 的 几 个 函 数 如 334
342 9 应 用 MITK 开 发 实 际 项 目 下 ( 均 为 inline 函 数 ): #if!defined(afx_progressdlg_h _7541_402A_9E3D_F62AF713A692 INCLUDED_) #define AFX_PROGRESSDLG_H _7541_402A_9E3D_F62AF713A692 INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 // ProgressDlg.h : header file // //////////////////////////////////////////////////////////////////// // CProgressDlg dialog class CProgressDlg : public CDialog // Construction public: CProgressDlg(CWnd* pparent = NULL); // standard constructor // 更 新 标 签 中 显 示 的 文 本 void SetLabelText(LPCSTR labeltext) m_infolabel.setwindowtext(labeltext); // 设 置 进 度 条 的 最 大 值 void SetMaxProgress(int maxprogress) m_progressctrl.setrange32(0, maxprogress); // 设 置 进 度 条 的 步 进 值 void SetProgressStep(int step) m_progressctrl.setstep(step); // 更 新 进 度 条 位 置 void UpdateProgress(int pos) m_progressctrl.setpos(pos); // Dialog Data //AFX_DATA(CProgressDlg) 335
343 9 应 用 MITK 开 发 实 际 项 目 enum IDD = IDD_PROGRESS_DIALOG ; CProgressCtrl m_progressctrl; CStatic m_infolabel; //AFX_DATA // Overrides // ClassWizard generated virtual function overrides //AFX_VIRTUAL(CProgressDlg) protected: virtual void DoDataExchange(CDataExchange* pdx); // DDX/DDV support //AFX_VIRTUAL // Implementation protected: ; // Generated message map functions //AFX_MSG(CProgressDlg) //AFX_MSG DECLARE_MESSAGE_MAP() //AFX_INSERT_LOCATION // Microsoft Visual C++ will insert additional declarations immediately before the previous line. #endif //!defined(afx_progressdlg_h _7541_402A_9E3D_F62AF713A692 INCLUDED_) 在 MITKTestDoc.cpp 中 添 加 代 码, 为 重 建 算 法 附 加 一 个 显 示 进 度 的 Observer: void CMITKTestDoc::OnMc() // TODO: Add your command handler code here // 产 生 阈 值 设 置 对 话 框 CThresholdDlg dlg; 336
344 9 应 用 MITK 开 发 实 际 项 目 // 显 示 阈 值 设 置 对 话 框 if (dlg.domodal() == IDOK) // 生 成 一 个 mitkmarchingcubes 对 象 mitkmarchingcubes *mc = new mitkmarchingcubes; // 生 成 一 个 以 mc 为 观 察 对 象 的 FilterObserver FilterObserver *observer = new FilterObserver(mc); // 将 生 成 的 FilterObserver 加 入 到 mc 中 mc->addobserver(observer); // 将 从 对 话 框 中 得 到 的 阈 值 设 置 给 Marching Cubes 算 法 mc->setthreshold(dlg.m_lowvalue, dlg.m_highvalue); // 设 置 输 入 数 据 mc->setinput(m_volume); // 初 始 化 Observer observer->startshowprogress(" 表 面 重 建..."); // 运 行 算 法 bool r = mc->run(); // 结 束 进 度 的 显 示 observer->finishshowprogress(); // 如 果 算 法 正 常 结 束 则 更 新 Mesh 数 据 并 显 示 if (r) this->clearmesh(); m_mesh = mc->getoutput(); m_mesh->addreference(); UpdateAllViews(NULL); // 删 除 mitkmarchingcubes 对 象 // 注 意 : 不 用 删 除 observer, 它 将 由 mc 来 删 除 337
345 9 应 用 MITK 开 发 实 际 项 目 mc->delete(); 显 示 Volume 和 Mesh 相 关 信 息 的 代 码 也 在 MITKTestDoc.cpp 中 : void CMITKTestDoc::OnVolumeinfo() // TODO: Add your command handler code here if (!m_volume) return; CString msg; msg.format("width:\t%d\nheight:\t%d\nslices:\t%d\n\nchannel Number: %d\nbits per Channel: %d\n\nx Spacing: %.3fmm\nY Spacing: %.3fmm\nZ Spacing: %.3fmm\t", m_volume->getwidth(), m_volume->getheight(), m_volume->getimagenum(), m_volume->getnumberofchannel(), m_volume->getdatatypesize()*8, m_volume->getspacingx(), m_volume->getspacingy(), m_volume->getspacingz()); ::AfxMessageBox(msg, MB_OK MB_ICONINFORMATION); void CMITKTestDoc::OnSurfaceinfo() // TODO: Add your command handler code here if (!m_mesh) return; CString msg; msg.format("vertices:\t%d\ntriangles:\t%d\t", m_mesh->getvertexnumber(), m_mesh->getfacenumber()); ::AfxMessageBox(msg, MB_OK MB_ICONINFORMATION); 至 此, 几 个 主 要 的 功 能 均 已 介 绍 完, 实 际 上, 本 节 中 的 例 子 还 有 很 多 地 方 可 338
346 9 应 用 MITK 开 发 实 际 项 目 以 改 进, 比 如 可 以 增 加 几 个 对 话 框 来 调 节 三 维 模 型 的 表 面 材 质 在 View 中 添 加 一 些 MITK 提 供 的 Widgets 来 丰 富 交 互 功 能 等 等, 甚 至 可 以 像 上 面 提 到 的 ChooseViewManipulator 和 FilterObserver 那 样 扩 展 MITK 实 现 自 定 义 的 功 能, 限 于 篇 幅, 这 些 内 容 不 能 一 一 介 绍 了 读 者 可 以 自 己 动 手 实 践 一 下, 用 MITK 来 开 发 自 己 的 软 件 项 目 另 外, 第 十 一 章 所 介 绍 的 3DMed 就 是 基 于 MITK 开 发 的 一 套 规 模 比 较 大 的 软 件, 读 者 可 以 以 此 作 为 参 考 9.5 小 结 本 章 介 绍 了 MITK 开 发 实 际 项 目 的 例 子, 通 过 开 发 环 境 的 设 置, 二 维 功 能 的 图 像 浏 览 器, 再 到 用 MITK 进 行 表 面 重 建, 最 后 给 出 了 一 个 完 善 的 例 子, 其 目 的 是 让 读 者 能 够 按 照 本 章 的 引 导, 一 步 步 熟 悉 MITK, 使 得 MITK 能 够 帮 助 读 者 更 好 的 理 解 MITK, 更 好 的 使 用 MITK 开 发 自 己 的 应 用 程 序 339
347 10 扩 充 MITK 功 能 10 扩 充 MITK 功 能 MITK 本 身 是 一 个 开 放 的 架 构, 允 许 用 户 自 己 扩 充 其 功 能, 以 满 足 项 目 开 发 中 的 需 求 在 MITK 中, 可 以 扩 充 的 部 分 包 括 Filter Reader 和 Writer, 通 过 扩 充 Filter, 可 以 添 加 自 己 的 处 理 算 法 ; 通 过 扩 充 Reader, 可 以 添 加 自 己 的 文 件 格 式 的 导 入 功 能 ; 通 过 扩 充 Writer, 可 以 添 加 自 己 的 文 件 格 式 的 保 存 功 能 要 扩 充 这 几 个 部 分, 必 须 在 实 现 自 己 的 算 法 的 时 候 遵 循 一 些 MITK 规 定 的 接 口 本 章 首 先 介 绍 这 些 接 口, 然 后 以 两 个 实 际 的 例 子 来 讲 述 如 何 扩 充 MITK 的 功 能, 这 两 个 例 子 一 个 是 如 何 扩 充 Reader 的 功 能, 另 外 一 个 是 如 何 扩 充 Filter 的 功 能, 读 者 在 阅 读 这 两 个 例 子 之 后, 可 以 对 扩 充 MITK 功 能 有 个 充 分 的 了 解, 并 可 以 在 自 己 的 项 目 中 来 使 用 和 上 一 章 的 风 格 一 样, 这 两 个 例 子 也 使 用 Microsoft Visual C 来 作 为 编 程 环 境, 并 且 一 步 一 步 地 加 以 说 明 10.1 扩 充 MITK 功 能 的 预 备 知 识 在 MITK 中, 是 通 过 类 的 继 承 层 次 来 实 现 不 同 的 功 能 的, 其 中 在 一 些 上 层 的 父 类 里 面 规 定 了 一 些 子 类 必 须 实 现 的 接 口, 子 类 来 实 现 这 些 接 口 从 而 完 成 具 体 的 功 能, 这 是 整 个 的 大 的 原 则 在 第 二 章 中 介 绍 了 MITK 的 整 体 框 架, 整 个 MITK 是 由 数 据 和 算 法 来 构 成 的, 而 算 法 又 是 由 三 个 不 同 的 种 类 :Source Filter 和 Target 来 构 成 的, 其 在 MITK 的 整 个 类 层 次 结 构 中 的 位 置 如 图 10-1 所 示 图 10-1 MITK 中 算 法 类 的 上 层 结 构 其 中, 在 它 们 共 同 的 父 类 ProcessObject 中, 规 定 了 一 个 公 有 的 接 口 bool Run 340
348 10 扩 充 MITK 功 能 (), 这 就 意 味 着 所 有 的 MITK 的 算 法 类 都 因 之 而 继 承 了 这 一 接 口, 所 以 在 最 终 用 户 使 用 某 个 算 法 时, 可 以 直 接 调 用 其 Run 成 员 函 数 来 进 行 算 法 的 计 算 ProcessObject 为 了 能 使 其 子 类 实 现 不 同 的 Run 的 行 为, 从 而 实 现 不 同 的 算 法, 在 Run 函 数 里 面 采 用 了 Template Method 的 设 计 模 式, 调 用 一 个 保 护 的 虚 函 数 Execute, 使 其 子 类 来 重 载 Execute 函 数 来 完 成 实 际 的 工 作,Run 函 数 的 一 部 分 代 码 如 下 所 示 : bool mitkprocessobject::run() ; return (this->execute()); 对 于 Reader 来 说, 它 属 于 Source 的 一 种, 主 要 任 务 是 完 成 各 种 格 式 文 件 的 读 入, 并 转 换 为 MITK 的 内 部 的 统 一 的 格 式 它 是 一 个 比 较 上 层 的 抽 象 基 类, 其 在 整 个 MITK 的 类 层 次 中 的 位 置 如 图 10-2 所 示 Source Reader +AddFileName(in const char *filename) VolumeReader +GetOutput() : Volume MeshReader +GetOutput() : Mesh 图 10-2 Reader 的 类 层 次 结 构 Reader 内 部 维 护 着 一 个 文 件 名 的 列 表 ( 因 为 有 的 格 式 的 文 件 只 需 要 一 个, 而 有 的 格 式 的 文 件 需 要 多 个 ), 可 以 使 用 其 AddFilename 成 员 函 数 来 将 要 读 入 的 文 件 的 文 件 名 传 进 来 考 虑 到 MITK 的 数 据 分 为 两 种 :Volume 和 Mesh, 所 以 Reader 也 分 为 两 种 :VolumeReader 和 MeshReader VolumeReader 主 要 读 入 一 个 341
349 10 扩 充 MITK 功 能 三 维 的 数 据 集, 不 同 格 式 的 支 持 由 其 子 类 来 完 成, 目 前 MITK 里 面 提 供 了 对 DICOM IM0 RAW BMP JPEG TIFF 文 件 格 式 的 支 持, 分 别 由 mitkdicomreader mitkim0reader mitkrawreader mitkbmpreader mitkjpegreader 和 mitktiffreader 来 完 成 ; 而 MeshReader 主 要 读 入 三 维 网 格 数 据, 不 同 格 式 的 支 持 由 其 子 类 来 完 成, 目 前 MITK 里 面 提 供 了 对 Wavefront OBJ 快 速 成 型 机 STL 文 件 格 式 的 支 持, 分 别 由 mitkobjreader mitkstlreader 来 完 成 对 于 Writer 来 说, 它 属 于 Target 的 一 种, 主 要 任 务 是 完 成 各 种 格 式 文 件 的 磁 盘 写 入, 它 是 一 个 比 较 上 层 的 抽 象 基 类, 其 在 整 个 MITK 的 类 层 次 中 的 位 置 如 图 10-3 所 示 : 图 10-3 Writer 的 类 层 次 结 构 如 Reader 一 样,Writer 内 部 也 维 护 着 一 个 文 件 名 的 列 表, 可 以 使 用 其 AddFilename 成 员 函 数 来 将 要 读 入 的 文 件 的 文 件 名 传 进 来 考 虑 到 MITK 的 数 据 分 为 两 种 :Volume 和 Mesh, 所 以 Writer 也 分 为 两 种 :VolumeWriter 和 MeshWriter VolumeWriter 主 要 将 内 存 中 的 一 个 Volume 写 入 磁 盘, 不 同 格 式 的 支 持 由 其 子 类 来 完 成, 目 前 MITK 里 面 提 供 了 对 DICOM IM0 RAW BMP JPEG TIFF 文 件 格 式 的 支 持, 分 别 由 mitkdicomwriter mitkim0writer mitkrawwriter mitkbmpwriter mitkjpegwriter 和 mitktiffwriter 来 完 成 ; 而 MeshWriter 主 要 是 将 内 存 中 的 一 个 Mesh 写 入 磁 盘, 不 同 格 式 的 支 持 由 其 子 类 来 完 成, 目 前 MITK 里 面 提 供 了 对 Wavefront OBJ 快 速 成 型 机 STL 文 件 格 式 的 支 持, 分 别 由 342
350 10 扩 充 MITK 功 能 mitkobjwriter mitkstlwriter 来 完 成 至 于 Filter, 在 节 已 经 有 比 较 详 细 的 介 绍, 这 里 就 不 再 赘 述 了 10.2 实 例 之 一 : 扩 充 Reader 功 能 好 了, 了 解 了 上 面 的 预 备 知 识 之 后, 下 面 本 节 以 一 个 实 际 的 例 子 来 演 示 如 何 在 自 己 的 项 目 里 面 来 扩 充 MITK 的 Reader 所 支 持 的 格 式, 风 格 上 和 第 九 章 的 例 子 一 样, 通 过 一 步 一 步 的 深 入 浅 出 的 讲 解, 加 上 程 序 代 码 的 辅 助, 来 力 图 把 概 念 讲 解 清 楚 扩 充 Reader 功 能 的 一 般 步 骤 要 读 取 一 种 MITK 不 支 持 的 文 件 格 式, 可 以 通 过 扩 充 Reader 的 类 层 次 结 构 来 实 现 扩 充 Reader 功 能 的 一 般 步 骤 如 下 : 第 一 步, 先 判 断 自 己 要 读 取 的 文 件 的 内 容 是 一 个 三 维 的 数 据 集 还 是 三 维 的 面 片 网 格, 比 如 一 系 列 的 BMP 文 件 就 是 一 个 三 维 的 数 据 集, 而 OBJ 和 STL 等 文 件 就 是 三 维 的 面 片 网 格 ; 第 二 步, 写 自 己 的 文 件 格 式 的 Reader, 如 果 要 读 取 的 文 件 是 三 维 数 据 集, 那 么 选 择 从 VolumeReader 继 承, 如 果 要 读 取 的 文 件 是 三 维 网 格, 那 么 选 择 从 MeshReader 继 承 ; 第 三 步, 实 现 虚 函 数 bool Execute(), 在 这 个 函 数 里 面 完 成 文 件 的 实 际 读 入 工 作, 并 将 数 据 填 入 Volume 或 者 Mesh 中 实 例 程 序 的 功 能 下 面 我 们 要 完 成 的 实 例 程 序, 将 要 读 入 一 种 我 们 自 己 定 义 的 格 式 的 文 件, 其 数 据 内 容 为 三 维 数 据 集, 这 种 文 件 有 一 个 文 件 头, 记 录 了 一 些 文 件 大 小 像 素 尺 寸 等 信 息, 然 后 是 实 际 数 据 其 具 体 文 件 格 式 如 图 10-4 所 示 343
351 10 扩 充 MITK 功 能 图 10-4 文 件 格 式 例 子 程 序 的 功 能 很 简 单, 就 是 打 开 上 面 格 式 的 文 件 并 将 其 读 入 内 存 中, 其 文 件 菜 单 里 面 有 一 项 打 开 自 定 义 文 件, 点 击 此 菜 单 项 后 将 会 弹 出 一 个 打 开 自 定 义 格 式 文 件 对 话 框, 如 图 10-5 所 示 选 中 要 打 开 的 文 件 以 后, 例 子 程 序 会 调 用 我 们 自 己 扩 充 的 Reader, 解 析 文 件 并 将 其 读 入 内 存, 为 了 简 化 清 晰 起 见, 这 个 例 子 并 没 有 显 示 读 进 来 的 数 据, 侧 重 点 在 如 何 扩 充 Reader 的 功 能 上, 请 参 考 8 的 例 子 去 实 现 数 据 的 显 示 等 功 能 344
352 10 扩 充 MITK 功 能 图 10-5 打 开 文 件 对 话 框 实 例 程 序 的 制 作 还 是 和 第 九 章 一 样, 我 们 通 过 具 体 的 例 子 一 步 一 步 地 展 现 如 何 扩 充 MITK 中 Reader 的 功 能 第 一 步, 是 在 Microsoft Visual C 的 IDE 开 发 环 境 中 新 建 一 个 MFC 工 程, 单 文 档 界 面 的, 工 程 名 字 叫 做 MitkEnhancement, 这 一 步 的 操 作 过 程 这 里 就 不 再 赘 述 了 第 二 步, 修 改 菜 单, 删 除 不 必 要 的 菜 单 项, 只 留 下 文 件 查 看 帮 助 三 个 菜 单 项, 在 文 件 菜 单 里 面 删 除 原 先 的 子 菜 单, 加 入 打 开 自 定 义 文 件 子 菜 单, 修 改 后 的 菜 单 如 图 10-6 所 示 345
353 10 扩 充 MITK 功 能 图 10-6 修 改 后 的 菜 单 第 三 步, 到 了 添 加 自 己 的 Reader 的 时 候 了, 用 New Class 生 成 一 个 我 们 自 己 的 类, 名 字 叫 做 CMyMitkReader, 如 图 10-7 所 示 然 后 遵 循 节 中 所 述 的 一 般 规 则, 因 为 我 们 要 打 开 的 文 件 是 一 个 三 维 数 据 文 件, 所 以 应 该 从 VolumeReader 继 承, 之 后 实 现 Execute 接 口 CMyMitkReader 的 声 明 在 MyMitkReader.h 文 件 中, 其 代 码 如 下 所 示 : class CMyMitkReader : public mitkvolumereader public: CMyMitkReader(); virtual ~CMyMitkReader(); protected: virtual bool Execute(); ; 可 以 从 代 码 中 看 出,CMyMitkReader 公 有 继 承 了 mitkvolumereader, 并 重 载 了 其 Protected 的 虚 函 数 Execute, 当 然 在 前 面 应 该 加 上 包 含 mitkvolumereader 的 预 处 理 指 令 : #include "mitkvolumereader.h" 346
354 10 扩 充 MITK 功 能 图 10-7 新 建 CMyMitkReader 类 CMyMitkReader 的 实 现 代 码 在 MyMitkReader.cpp 文 件 中, 对 于 它 来 说, 最 重 要 的 就 是 函 数 Execute 的 实 现, 在 这 个 函 数 内 必 须 实 现 解 析 文 件 格 式 读 数 据 至 Volume 中 等 任 务 为 了 解 析 文 件 格 式, 首 先 我 们 定 义 一 个 结 构 来 容 纳 文 件 头, 其 代 码 如 下 所 示 : struct FILEHEADER int m_imgwidth; int m_imgheight; int m_imgnum; float m_spacingx; float m_spacingy; float m_spacingz; ; 347
355 10 扩 充 MITK 功 能 这 个 结 构 对 应 着 图 10-4 文 件 格 式 中 的 文 件 头 信 息, 有 了 它 以 后, 就 可 以 实 现 Execute 函 数 了, 其 代 码 如 下 所 示 : bool CMyMitkReader::Execute() int filecount = this->_getfilecount(); if (filecount <= 0) AfxMessageBox(" 尚 没 有 添 加 文 件 名 "); return false; // 得 到 Reader 的 输 出 Volume 的 指 针, 注 意 在 GetOutput // 里 面 已 经 创 建 了 Volume. mitkvolume *outputvolume = this->getoutput(); // 得 到 文 件 名, 因 为 要 读 入 的 是 三 维 文 件, 故 只 需 一 个 文 件 名 即 可. const char *filename = this->_getfilename(0); // 打 开 文 件, 并 读 取 文 件 头 信 息. FILE *inputfile = fopen(filename, "rb"); if(inputfile == NULL) AfxMessageBox(" 不 能 打 开 文 件 "); return false; FILEHEADER fileheader; fread(&fileheader, sizeof(fileheader), 1, inputfile); // 写 输 出 Volume 的 必 要 信 息. outputvolume->setwidth(fileheader.m_imgwidth); outputvolume->setheight(fileheader.m_imgheight); outputvolume->setimagenum(fileheader.m_imgnum); outputvolume->setspacingx(fileheader.m_spacingx); outputvolume->setspacingy(fileheader.m_spacingy); outputvolume->setspacingz(fileheader.m_spacingz); outputvolume->setdatatypetofloat(); outputvolume->setnumberofchannel(1); 348
356 10 扩 充 MITK 功 能 // 分 配 必 要 的 内 存. void *volmemory = outputvolume->allocate(); if(volmemory == NULL) AfxMessageBox(" 内 存 不 足 "); return false; // 得 到 Volume 所 占 用 的 内 存 的 大 小 int volsize = outputvolume->getactualmemorysize(); // 将 实 际 数 据 读 入 Volume 中. fread(volmemory, 1, volsize, inputfile); // 关 闭 文 件 并 返 回. fclose(inputfile); return true; 上 面 的 代 码 首 先 得 到 此 Reader 的 文 件 名, 然 后 打 开 文 件 并 读 入 文 件 头 信 息, 之 后 设 置 Volume 的 各 项 属 性, 分 配 Volume 的 内 部 数 据 缓 冲, 最 后 将 数 据 读 入 Volume 大 部 分 代 码 都 加 了 注 释, 整 个 过 程 也 并 不 是 很 复 杂, 这 里 要 解 释 的 就 是 Reader 本 身 的 一 些 API, 它 提 供 了 两 个 保 护 的 函 数 _getfilecount 和 _getfilename 给 派 生 类 使 用, 来 访 问 Reader 内 部 的 文 件 名 列 表 对 于 从 VolumeReader 派 生 下 来 的 子 类, 还 继 承 了 GetOutput 函 数, 其 内 部 在 第 一 次 被 访 问 时 生 成 一 个 Volume 的 指 针, 并 将 Volume 的 各 项 属 性 设 置 为 初 始 值, 实 际 数 据 缓 冲 也 没 有 分 配, 在 其 后 的 调 用 中 将 直 接 返 回 这 个 Volume 的 指 针 因 此 对 于 我 们 的 CMyMitkReader 来 说, 可 以 直 接 通 过 GetOutput 函 数 拿 到 输 出 的 Volume, 然 后 填 充 其 各 项 属 性, 分 配 实 际 数 据 内 存 并 填 充 数 据, 这 样 就 完 成 了 一 个 Reader 的 功 能 第 四 步, 完 成 了 CMyMitkReader 以 后, 剩 下 的 工 作 就 很 容 易 了, 在 文 档 类 CMitkEnhancementDoc 里 面 添 加 成 员 变 量 mitkvolume *m_volume; 349
357 10 扩 充 MITK 功 能 以 记 录 实 际 数 据, 并 添 加 处 理 函 数 OnFileOpenCustom, 响 应 菜 单 打 开 自 定 义 文 件 的 单 击 事 件, 其 代 码 如 下 所 示 : void CMitkEnhancementDoc::OnFileOpenCustom() // 显 示 打 开 文 件 对 话 框. COpenFileDialog filedialog; filedialog.settitle(" 打 开 自 定 义 格 式 文 件 "); filedialog.setfilter(" 自 定 义 格 式 (*.*)\0*.*\0\0"); // 如 果 用 户 选 择 了 一 个 文 件 并 点 确 定 按 钮. if(filedialog.run()) int nfilterindex = filedialog.getfilterindex(); CString szfilename = filedialog.getpathname(); // 清 除 掉 旧 的 Volume. this->clearvolume(); // 生 成 一 个 我 们 自 己 的 Reader 对 象. CMyMitkReader *myreader = new CMyMitkReader; // 设 置 文 件 名. myreader->addfilename(filedialog.getfilename()); // 运 行 我 们 自 己 的 Reader, 完 成 读 入 过 程. myreader->run(); // 得 到 Reader 读 出 来 的 Volume. m_volume = myreader->getoutput(); m_volume->addreference(); // 删 除 Reader. myreader->delete(); 350
358 10 扩 充 MITK 功 能 在 这 一 部 分 代 码 里 面, 我 们 首 先 弹 出 一 个 打 开 文 件 对 话 框, 让 用 户 选 择 要 打 开 的 文 件 名, 然 后 生 成 我 们 扩 充 的 CMyMitkReader 的 对 象, 设 置 文 件 名, 并 调 用 其 Run 函 数 以 实 际 执 行 我 们 的 Reader 的 功 能, 最 后 删 除 掉 此 Reader 在 这 里 需 要 说 明 的 是 clearvolume 函 数, 它 的 功 能 是 将 上 一 次 读 入 的 Volume 释 放 掉, 其 代 码 如 下 所 示 : void CMitkEnhancementDoc::clearVolume() if (m_volume) m_volume->removereference(); m_volume = NULL; 剩 下 的 事 情 就 是 修 改 CMitkEnhancementDoc 的 构 造 和 析 构 函 数, 完 成 必 要 的 初 始 化 和 善 后 工 作 了, 其 代 码 如 下 所 示 : CMitkEnhancementDoc::CMitkEnhancementDoc() m_volume = NULL; CMitkEnhancementDoc::~CMitkEnhancementDoc() this->clearvolume(); 至 此, 扩 充 Reader 功 能 的 实 例 程 序 已 经 基 本 完 成, 你 可 以 结 合 第 九 章 的 例 子 来 增 添 这 个 实 例 程 序 的 功 能, 对 读 入 的 数 据 进 行 显 示 处 理 等 操 作 对 于 Writer 的 扩 充, 在 概 念 上 和 步 骤 上 与 扩 充 Reader 是 相 同 的, 这 里 不 再 赘 述, 读 者 可 以 根 据 本 节 的 内 容, 自 己 进 行 实 验 351
359 10 扩 充 MITK 功 能 10.3 实 例 之 二 : 扩 充 Filter 功 能 本 节 接 着 上 一 节 的 例 子, 在 里 面 增 加 格 式 转 换 的 功 能, 来 演 示 如 何 在 自 己 的 项 目 里 面 来 扩 充 MITK 的 Filter 的 功 能, 也 就 是 如 何 扩 充 新 的 算 法 掌 握 了 这 一 节 的 内 容 以 后, 你 就 可 以 将 自 己 的 算 法 集 成 到 MITK 中 去, 并 且 可 以 和 MITK 中 已 有 的 同 类 算 法 作 比 较, 甚 至 可 以 进 行 算 法 的 性 能 评 价 等 工 作 扩 充 Filter 功 能 的 一 般 步 骤 扩 充 Filter 功 能 的 步 骤 和 扩 充 Reader 的 步 骤 相 似, 也 是 通 过 扩 充 Filter 的 类 层 次 结 构 来 实 现 的 扩 充 Filter 功 能 的 一 般 步 骤 如 下 : 第 一 步, 先 判 断 自 己 要 实 现 的 算 法 的 种 类, 这 个 可 以 从 算 法 的 输 入 与 输 出 数 据 类 型 来 判 断 : 如 果 输 入 和 输 出 都 是 Volume 类 型 的, 那 么 此 算 法 属 于 VolumeToVolumeFilter, 如 果 输 入 是 Volume 类 型 的, 而 输 出 是 Mesh 类 型 的, 那 么 此 算 法 属 于 VolumeToMeshFilter, 其 余 的 依 此 类 推 ; 第 二 步, 写 自 己 的 算 法 的 Filter, 如 果 自 己 的 算 法 是 属 于 VolumeToVolumeFilter, 则 从 VolumeToVolumeFilter 公 有 继 承, 如 果 自 己 的 算 法 是 属 于 VolumeToMeshFilter, 则 从 VolumeToMeshFilter 公 有 继 承 ; 第 三 步, 实 现 虚 函 数 bool Execute(), 在 这 个 函 数 里 面 完 成 算 法 的 实 际 过 程, 也 就 是 对 输 入 数 据 的 处 理, 生 成 输 出 数 据 实 例 程 序 的 功 能 下 面 我 们 接 着 10.2 节 所 完 成 的 实 例 程 序, 继 续 在 其 上 增 加 功 能, 可 以 把 读 进 来 的 数 据 的 格 式 进 行 转 换 当 运 行 例 子 程 序 时, 将 会 发 现 在 菜 单 栏 上 多 了 格 式 转 换 菜 单 及 子 菜 单, 首 先 通 过 文 件 菜 单 项 下 面 的 打 开 自 定 义 文 件 打 开 一 个 文 件 后, 我 们 可 以 点 击 格 式 转 换 下 面 的 转 换 成 Double 将 读 入 的 数 据 的 格 式 转 换 成 双 精 度 浮 点 (Double) 型, 而 点 击 转 换 成 Short 将 会 把 读 入 的 数 据 的 格 式 转 换 成 短 整 型 (Short) 同 样, 为 了 简 化 清 晰 起 见, 这 个 例 子 并 没 有 显 示 格 式 转 换 后 的 数 据, 也 没 有 对 其 进 行 进 一 步 的 处 理, 这 里 的 侧 重 点 放 在 如 何 扩 充 Filter 的 功 能 上, 请 参 考 第 九 章 的 例 子 去 实 现 数 据 的 显 示 处 理 等 功 能 352
360 10 扩 充 MITK 功 能 实 例 程 序 的 制 作 和 上 一 个 例 子 一 样, 我 们 通 过 具 体 的 例 子 一 步 一 步 地 展 现 如 何 扩 充 MITK 中 Filter 的 功 能 第 一 步, 利 用 上 一 个 例 子 程 序 的 工 程 文 件 MitkEnhancement, 直 接 在 Microsoft Visual C++ 集 成 环 境 中 打 开 它 第 二 步, 修 改 菜 单, 在 菜 单 栏 上 增 加 格 式 转 换 菜 单 项, 并 且 在 其 下 增 加 两 个 子 菜 单 : 转 换 成 Double 和 转 换 成 Short, 修 改 后 的 菜 单 如 图 10-8 所 示 图 10-8 格 式 转 换 菜 单 第 三 步, 到 了 添 加 自 己 的 Filter 的 时 候 了, 用 New Class 生 成 一 个 我 们 自 己 的 类, 名 字 叫 做 CMyMitkFilter, 如 图 10-9 所 示 然 后 遵 循 节 中 所 述 的 一 般 规 则, 因 为 我 们 要 实 现 的 算 法 输 入 的 数 据 是 一 个 Volume, 输 出 的 数 据 是 另 外 一 个 数 据 类 型 不 同 的 Volume, 所 以 应 该 从 VolumeToVolumeFilter 继 承, 之 后 实 现 Execute 接 口 CMyMitkFilter 的 声 明 在 MyMitkFilter.h 文 件 中, 其 代 码 如 下 所 示 : class CMyMitkFilter : public mitkvolumetovolumefilter public: CMyMitkFilter(); virtual ~CMyMitkFilter(); 353
361 10 扩 充 MITK 功 能 void ConvertToUnsignedChar(); void ConvertToChar(); void ConvertToToUnsignedShort(); void ConvertToShort(); void ConvertToUnsignedInt(); void ConvertToInt(); void ConvertToUnsignedLong(); void ConvertToLong(); void ConvertToFloat(); void ConvertToDouble(); protected: virtual bool Execute(); private: int m_datatype; ; 图 10-9 新 建 CMyMitkFilter 类 354
362 10 扩 充 MITK 功 能 可 以 从 代 码 中 看 出,CMyMitkFilter 公 有 继 承 了 mitkvolumetovolumefilter, 并 重 载 了 其 Protected 的 虚 函 数 Execute, 当 然 在 前 面 应 该 加 上 包 含 mitkvolumetovolumefilter 的 预 处 理 指 令 : #include "mitkvolumetovolumefilter.h" 另 外, 在 CMyMitkFilter 中 定 义 的 一 系 列 的 ConvertTo*** 函 数, 是 用 来 设 置 转 换 后 的 Volume 的 数 据 类 型 的 ; 并 且 CMyMitkFilter 类 还 有 一 个 私 有 的 数 据 成 员 m_datatype, 用 来 记 录 转 换 后 的 Volume 的 数 据 类 型, 实 际 上 ConvertTo*** 函 数 就 是 操 纵 的 m_datatype, 用 其 来 反 应 当 前 的 状 态 CMyMitkFilter 的 实 现 代 码 在 MyMitkFilter.cpp 文 件 中, 让 我 们 先 来 看 一 下 最 简 单 的 ConvertTo*** 函 数 的 实 现, 它 们 只 是 简 单 地 将 m_datatype 变 量 设 置 成 目 标 格 式, 其 代 码 如 下 所 示 : void CMyMitkFilter::ConvertToUnsignedChar() m_datatype = MITK_UNSIGNED_CHAR; void CMyMitkFilter::ConvertToChar() m_datatype = MITK_CHAR; void CMyMitkFilter::ConvertToToUnsignedShort() m_datatype = MITK_UNSIGNED_SHORT; void CMyMitkFilter::ConvertToShort() m_datatype = MITK_SHORT; void CMyMitkFilter::ConvertToUnsignedInt() 355
363 10 扩 充 MITK 功 能 m_datatype = MITK_UNSIGNED_INT; void CMyMitkFilter::ConvertToInt() m_datatype = MITK_INT; void CMyMitkFilter::ConvertToUnsignedLong() m_datatype = MITK_UNSIGNED_LONG; void CMyMitkFilter::ConvertToLong() m_datatype = MITK_LONG; void CMyMitkFilter::ConvertToFloat() m_datatype = MITK_FLOAT; void CMyMitkFilter::ConvertToDouble() m_datatype = MITK_DOUBLE; 在 这 些 代 码 中, MITK_*** 这 些 常 量 是 在 MITK 里 面 预 先 定 义 好 的, 代 表 不 同 的 数 据 类 型, 它 们 直 接 对 应 着 C 或 者 C++ 语 言 里 面 的 标 准 数 据 类 型 对 CMyMitkFilter 来 说, 最 重 要 的 就 是 函 数 Execute 的 实 现, 在 这 个 函 数 内 必 须 实 现 处 理 输 入 Volume, 将 其 格 式 转 换 为 m_datatype 所 指 示 的 目 标 格 式, 最 后 写 至 输 出 Volume 中 等 一 系 列 任 务 为 了 完 成 例 子 程 序 的 功 能, 我 们 本 来 只 需 限 制 m_datatype 为 MITK_DOUBLE 或 者 MITK_SHORT 两 者 之 一, 但 是 为 了 演 示 MITK 的 功 能, 这 里 允 许 m_datatype 可 以 为 任 何 数 据 类 型, 这 样 就 356
364 10 扩 充 MITK 功 能 导 致 了 比 较 复 杂 的 程 序 实 现, 不 过 从 这 个 例 子 中 可 以 学 到 很 多 MITK 内 部 的 细 节, 也 可 以 加 深 对 MITK 的 了 解 Execute 函 数 的 整 个 代 码 如 下 所 示 : bool CMyMitkFilter::Execute() mitkvolume *involume = this->getinput(); // 判 断 输 入 数 据 是 否 为 空. if(involume == NULL) AfxMessageBox(" 没 有 设 置 输 入 数 据 "); return false; unsigned char *indata = (unsigned char *)involume->getdata(); if(indata == NULL) AfxMessageBox(" 输 入 Volume 数 据 为 空 "); return false; // 得 到 输 出 Volume 的 指 针. mitkvolume *outvolume = this->getoutput(); // 设 置 输 出 Volume 的 必 要 的 属 性, // 大 部 分 与 输 入 Volume 的 相 同. outvolume->setwidth(involume->getwidth()); outvolume->setheight(involume->getheight()); outvolume->setimagenum(involume->getimagenum()); outvolume->setnumberofchannel(involume->getnumberofchannel()); outvolume->setspacingx(involume->getspacingx()); outvolume->setspacingy(involume->getspacingy()); outvolume->setspacingz(involume->getspacingz()); // 设 置 输 出 Volume 的 数 据 类 型. outvolume->setdatatype(m_datatype); // 为 实 际 的 数 据 分 配 内 存. unsigned char *outdata = NULL; 357
365 10 扩 充 MITK 功 能 if((outdata = (unsigned char *)outvolume->allocate()) == NULL) AfxMessageBox(" 内 存 不 足 "); return false; // 根 据 输 入 Volume 的 数 据 类 型, // 调 用 不 同 的 模 板 函 数 来 完 成 格 式 转 换. switch(involume->getdatatype()) case MITK_FLOAT: t_executeconvertion(involume, (float *)indata, outdata, m_datatype); break; case MITK_DOUBLE: t_executeconvertion(involume, (double *)indata, outdata, m_datatype); break; case MITK_INT: t_executeconvertion(involume, (int *)indata, outdata, m_datatype); break; case MITK_UNSIGNED_INT: t_executeconvertion(involume, (unsigned int *)indata, outdata, m_datatype); break; case MITK_LONG: t_executeconvertion(involume, (long *)indata, outdata, m_datatype); break; case MITK_UNSIGNED_LONG: t_executeconvertion(involume, (unsigned long *)indata, outdata, m_datatype); break; 358
366 10 扩 充 MITK 功 能 case MITK_SHORT: t_executeconvertion(involume, (short *)indata, outdata, m_datatype); break; case MITK_UNSIGNED_SHORT: t_executeconvertion(involume, (unsigned short *)indata, outdata, m_datatype); break; case MITK_UNSIGNED_CHAR: t_executeconvertion(involume, (unsigned char *)indata, outdata, m_datatype); break; case MITK_CHAR: t_executeconvertion(involume, (char *)indata, outdata, m_datatype); break; default: AfxMessageBox(" 未 知 数 据 类 型 "); return false; return true; 在 这 段 代 码 中, 最 核 心 的 地 方 在 于 那 个 大 的 Switch 语 句, 它 判 断 输 入 Volume 的 数 据 类 型, 然 后 将 实 际 数 据 指 针 indata 强 制 类 型 转 换 成 此 种 类 型 的 指 针 为 了 减 少 重 复 的 代 码, 这 里 还 使 用 了 模 板 函 数 t_executeconvertion 来 处 理 不 同 的 数 据 类 型, 将 输 入 的 数 据 指 针 类 型 参 数 化, 实 际 的 格 式 转 换 就 是 在 此 函 数 里 面 完 成 的, 其 代 码 如 下 所 示 : template <typename T> void t_executeconvertion(mitkvolume *involume, T *indata, unsigned char *outdata, int datatype) 359
367 10 扩 充 MITK 功 能 int i, j, k, l; int imagewidth = involume->getwidth(); int imageheight = involume->getheight(); int imagenum = involume->getimagenum(); int channelnum = involume->getnumberofchannel(); float *foutdata = (float *) outdata; double *doutdata = (double *) outdata; int *ioutdata = (int *) outdata; unsigned int *uioutdata = (unsigned int *) outdata; long *loutdata = (long *) outdata; unsigned long *uloutdata = (unsigned long *) outdata; short *soutdata = (short *) outdata; unsigned short *usoutdata = (unsigned short *) outdata; char *coutdata = (char *) outdata; unsigned char *ucoutdata = (unsigned char *) outdata; for(k = 0; k < imagenum; k++) for(j = 0; j < imageheight; j++) for(i = 0; i < imagewidth; i++) for(l = 0; l < channelnum; l++) switch(datatype) case MITK_FLOAT: foutdata[0] = (float) indata[0]; foutdata++; break; case MITK_DOUBLE: doutdata[0] = (double) indata[0]; doutdata++; break; case MITK_INT: ioutdata[0] = (int) indata[0]; ioutdata++; 360
368 10 扩 充 MITK 功 能 break; case MITK_UNSIGNED_INT: uioutdata[0] = (unsigned int) indata[0]; uioutdata++; break; case MITK_LONG: loutdata[0] = (long) indata[0]; loutdata++; break; case MITK_UNSIGNED_LONG: uloutdata[0] = (unsigned long) indata[0]; uloutdata++; break; case MITK_SHORT: soutdata[0] = (short) indata[0]; soutdata++; break; case MITK_UNSIGNED_SHORT: usoutdata[0] = (unsigned short) indata[0]; usoutdata++; break; case MITK_UNSIGNED_CHAR: ucoutdata[0] = (unsigned char) indata[0]; ucoutdata++; break; case MITK_CHAR: coutdata[0] = (char) indata[0]; coutdata++; break; indata++; 361
369 10 扩 充 MITK 功 能 在 模 板 函 数 t_executeconvertion 中, 需 要 四 个 参 数, 第 一 个 involume 是 CMyMitkFilter 的 输 入 Volume, 用 来 从 中 得 到 图 像 的 宽 高 切 片 张 数 等 信 息 ; 第 二 个 indata 是 被 参 数 化 的 数 据 指 针, 它 可 以 指 向 任 意 类 型 的 数 据 ; 第 三 个 参 数 outdata 是 CMyMitkFilter 的 输 出 Volume 的 实 际 数 据 指 针 ; 第 四 个 参 数 datatype 用 来 指 示 outdata 的 实 际 数 据 类 型 由 于 datatype 可 以 取 任 意 的 数 据 类 型, 所 以 这 个 函 数 里 面 最 复 杂 的 地 方 还 是 在 于 那 个 大 的 Switch 语 句, 它 判 断 outdata 的 数 据 类 型, 并 用 合 适 的 指 针 (foutdata doutdata 等 ) 来 进 行 运 算, 将 输 入 数 据 indata 里 面 的 数 据 强 制 类 型 转 换 成 所 需 要 的 目 标 格 式, 并 写 进 outdata 中 整 个 程 序 并 不 复 杂, 只 是 由 于 要 处 理 的 数 据 类 型 很 多, 所 以 比 较 烦 琐 考 虑 到 输 入 数 据 的 类 型 可 以 在 10 种 不 同 的 类 型 中 选 择, 而 输 出 的 数 据 类 型 也 可 以 在 10 种 不 同 的 类 型 中 选 择, 所 以 整 个 可 能 的 组 合 有 10 10=100 种, 而 程 序 必 须 处 理 这 100 种 不 同 的 组 合, 因 此 代 码 逻 辑 就 显 得 比 较 重 要 了 第 四 步, 完 成 了 CMyMitkFilter 以 后, 剩 下 的 工 作 就 很 容 易 了, 在 文 档 类 CMitkEnhancementDoc 里 面 添 加 处 理 函 数 OnConvertToDouble 和 OnConvertToShort, 分 别 响 应 菜 单 转 换 成 Double 和 转 换 成 Short 的 单 击 事 件, 其 代 码 如 下 所 示 : void CMitkEnhancementDoc::OnConvertToDouble() if(m_volume == NULL) return; CMyMitkFilter *afilter = new CMyMitkFilter; // 设 置 输 入 数 据. afilter->setinput(m_volume); 362
370 10 扩 充 MITK 功 能 // 设 置 转 换 后 的 数 据 格 式. afilter->converttodouble(); // 运 行 此 算 法. afilter->run(); // 得 到 输 出 数 据. mitkvolume *outvolume = afilter->getoutput(); // 使 用 outvolume. // // 删 除 算 法 对 象. afilter->delete(); void CMitkEnhancementDoc::OnConvertToShort() if(m_volume == NULL) return; CMyMitkFilter *afilter = new CMyMitkFilter; // 设 置 输 入 数 据. afilter->setinput(m_volume); // 设 置 转 换 后 的 数 据 格 式. afilter->converttoshort(); // 运 行 此 算 法. afilter->run(); // 得 到 输 出 数 据. mitkvolume *outvolume = afilter->getoutput(); // 使 用 outvolume. // // 删 除 算 法 对 象. afilter->delete(); 363
371 10 扩 充 MITK 功 能 这 两 个 函 数 的 实 现 大 致 相 同, 都 是 先 生 成 一 个 CMyMitkFilter 的 对 象, 然 后 设 置 输 入 数 据, 设 置 转 换 后 的 数 据 类 型, 运 行 算 法, 得 到 输 出 数 据 并 使 用, 最 后 删 除 自 己 的 Filter 对 象 这 里 为 了 简 化 起 见, 并 没 有 详 细 给 出 对 输 出 Volume 的 显 示 和 处 理, 读 者 有 兴 趣 可 以 自 行 加 上 至 此, 扩 充 Filter 功 能 的 实 例 程 序 已 经 基 本 完 成, 以 这 个 程 序 为 蓝 本, 读 者 可 以 开 发 自 己 的 算 法, 将 其 集 成 到 MITK 层 次 结 构 中 10.4 小 结 本 章 以 实 例 来 描 述 如 何 扩 充 MITK 的 功 能, 正 如 我 们 一 开 始 就 追 求 的 目 标, MITK 的 架 构 是 开 放 式 的, 用 户 可 以 方 便 地 往 里 面 扩 充 自 己 的 算 法, 从 而 不 断 地 增 强 MITK 的 功 能 本 章 首 先 以 一 个 读 入 自 定 义 格 式 的 文 件 为 例 子, 来 讲 解 如 何 扩 充 MITK 中 的 Reader 功 能, 从 而 支 持 更 多 的 数 据 格 式, 并 且 这 个 例 子 的 概 念 可 以 完 全 应 用 到 对 Writer 的 扩 充 中 去 本 章 的 第 二 个 例 子 以 对 数 据 进 行 格 式 转 换 为 例, 来 讲 解 如 何 扩 充 MITK 中 的 Filter 功 能, 也 是 最 核 心 的 功 能, 因 为 所 有 的 算 法 都 要 通 过 Filter 来 实 现 扩 充 Filter 功 能 也 可 以 允 许 不 同 的 算 法 在 同 一 个 框 架 下 实 现, 从 而 进 行 算 法 的 评 价 工 作 364
372 11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 11.1 背 景 介 绍 前 面 的 章 节 介 绍 了 关 于 MITK 的 设 计 框 架 以 及 各 个 子 模 块 的 功 能 以 及 实 现 细 节 我 们 已 经 知 道,MITK 是 一 个 软 件 开 发 包, 它 的 功 能 类 似 于 VTK 和 ITK, 可 以 用 来 进 行 二 次 开 发 而 本 章 要 介 绍 的 则 是 一 个 医 学 影 像 处 理 与 分 析 系 统, 主 要 面 对 医 生, 为 其 提 供 直 观 易 用 的 辅 助 工 具 来 进 行 更 准 确 的 医 疗 诊 断, 同 时 也 可 以 应 用 于 远 程 医 疗 以 及 医 疗 教 学 中 本 章 主 要 介 绍 我 们 自 主 开 发 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed(3D Medical Image Processing and Analyzing System), 它 的 设 计 思 想 主 要 功 能 以 及 一 些 实 现 细 节 11.2 相 关 工 作 在 国 际 上 有 很 多 医 学 影 像 处 理 与 分 析 的 应 用 系 统, 包 括 商 业 软 件 和 科 研 软 件, 这 里 的 科 研 软 件 指 的 是 软 件 的 目 的 是 为 了 提 供 给 科 研 人 员 或 者 医 疗 人 员 进 行 研 究 和 开 发 使 用, 而 不 是 为 了 纯 粹 的 商 业 目 的, 但 它 不 一 定 是 免 费 软 件, 这 将 在 下 面 介 绍 由 于 3DMed 的 目 的 是 形 成 一 个 科 研 软 件, 所 以 这 里 只 介 绍 相 关 的 国 际 上 的 工 作, 对 于 纯 粹 的 商 业 软 件 并 不 涉 及 DVIEWNIX 系 统 简 介 3DVIEWNIX 系 统 是 由 美 国 宾 州 大 学 放 射 系 医 学 影 像 处 理 小 组 开 发 的, 提 供 了 医 学 影 像 预 处 理 二 维 和 三 维 可 视 化 图 像 分 析 等 功 能, 它 是 使 用 C 语 言 在 Unix 下 开 发 的, 利 用 X-Window 提 供 用 户 界 面 [5] 3DVIEWNIX 系 统 的 特 色 之 处 就 是 提 供 了 很 多 图 像 分 割 工 具, 包 括 域 值 分 割 基 于 模 糊 连 接 度 的 分 割 Livewire 分 割 等 等, 这 些 工 具 简 化 了 用 户 对 图 像 分 割 的 工 作 量, 非 常 有 价 值 由 于 3DVIEWNIX 开 发 的 比 较 早, 在 上 世 纪 80 年 代 就 已 经 推 出, 所 以 是 国 际 上 相 当 知 名 的 一 个 系 统 但 是 该 软 件 系 统 并 不 是 一 个 免 费 软 件, 甚 至 对 科 研 目 的 和 教 学 目 的 也 不 免 费 提 供, 另 外 加 上 其 只 能 在 Unix 环 境 下 运 行, 用 户 界 面 比 较 复 杂, 所 以 应 用 范 围 收 到 限 制 另 外, 它 现 在 公 开 发 行 的 最 新 版 本 是 1.4, 更 新 比 较 缓 慢, 有 很 多 最 近 国 际 上 同 类 系 统 比 较 流 行 的 一 些 新 的 特 性 都 没 有 吸 收 进 去, 365
373 11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 显 得 稍 微 有 一 点 过 时 VolView 系 统 简 介 VolView 系 统 是 由 美 国 Kitware 公 司 开 发 的, 其 主 要 目 的 是 提 供 一 个 易 于 使 用 的 交 互 式 的 可 视 化 工 具 VolView 虽 然 是 一 个 商 业 软 件, 但 是 Kitware 公 司 负 责 开 发 并 维 护 着 两 个 非 常 著 名 的 开 放 源 码 的 医 学 影 像 处 理 开 发 包 VTK 和 ITK, 其 科 研 背 景 非 常 浓 厚 VolView 也 是 基 于 VTK 和 ITK 开 发 出 来 的, 并 且 提 供 免 费 的 试 用 下 载 正 如 其 名 字 所 示,VolView 主 要 的 强 项 在 于 体 绘 制 (Volume Rendering), 其 提 供 了 非 常 好 的 界 面 来 辅 助 用 户 完 成 复 杂 的 调 节 参 数 的 过 程 在 其 2.0 版 本 之 前,VolView 并 不 提 供 分 割 功 能, 其 面 绘 制 (Surface Rendering) 功 能 也 是 通 过 脚 本 语 言 来 支 持 的, 不 是 非 常 完 善 但 是 在 其 最 新 推 出 的 2.0 版 本 以 后, 通 过 ITK 这 一 强 大 的 分 割 与 配 准 开 发 包, 提 供 了 比 较 多 的 分 割 算 法, 也 提 供 了 一 些 等 值 面 生 成 (Isosurface Generation) 的 算 法 来 支 持 面 绘 制 VolView 系 统 现 在 主 要 是 针 对 Windows 操 作 系 统 提 供, 在 Linux 系 统 下 也 有 提 供, 不 过 版 本 比 较 低 另 外, 其 新 提 供 的 分 割 功 能 集 中 在 一 个 相 对 比 较 小 的 面 板 上, 需 要 调 节 的 参 数 又 比 较 多, 界 面 不 够 直 观 11.33DMed 的 整 体 设 计 此 处 所 讲 的 3DMed 是 我 们 在 自 己 以 前 老 版 本 的 3DMed[6] [7] 的 基 础 之 上, 基 于 我 们 新 开 发 的 一 套 集 成 化 的 医 学 影 像 处 理 与 分 析 开 发 包 MITK[8] 而 完 全 重 新 编 制 的 新 版 本 的 3DMed 其 提 供 了 更 灵 活 的 框 架 更 强 大 的 易 扩 充 性 以 及 更 友 好 的 界 面, 最 重 要 的 是, 现 在 3DMed 更 改 了 发 行 方 式, 已 经 成 为 免 费 软 件 (Freeware) DMed 的 设 计 目 标 对 于 软 件 设 计, 尤 其 是 医 学 影 像 这 一 特 定 领 域 内 的 复 杂 软 件 设 计, 必 须 事 先 有 一 个 非 常 明 确 的 设 计 目 标 3DMed 从 一 开 始 设 计, 就 始 终 追 求 以 下 几 个 高 层 的 设 计 目 标 : (1) 支 持 跨 平 台 这 一 目 标 是 考 虑 到 3DMed 的 潜 在 用 户 分 为 两 类, 一 类 是 研 究 和 开 发 人 员, 另 外 一 类 是 普 通 用 户 他 们 在 对 操 作 系 统 的 选 择 上 有 着 不 同 的 倾 向, 为 了 使 366
374 11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 3DMed 能 够 得 到 最 广 泛 的 应 用, 支 持 跨 平 台 是 非 常 重 要 的 一 个 环 节 为 了 实 现 这 一 目 标,3DMed 的 设 计 是 完 全 分 成 模 块 来 进 行 的, 它 的 整 个 的 结 构 如 图 11-1 所 示 其 中 3DMed 是 建 立 在 两 个 开 发 包 的 基 础 之 上, 一 个 是 我 们 自 己 开 发 的 医 学 影 像 处 理 与 分 析 开 发 包 MITK, 它 负 责 提 供 所 有 的 图 像 处 理 可 视 化 分 割 配 准 等 核 心 算 法, 并 且 我 们 在 设 计 MITK 的 时 候, 目 标 之 一 就 是 实 现 跨 平 台 的 支 持 ; 另 外 一 个 是 用 户 界 面 开 发 包, 为 了 能 够 让 3DMed 跨 平 台 运 行, 这 个 用 户 界 面 开 发 包 也 必 须 能 够 跨 平 台, 幸 运 的 是, 现 在 有 很 多 这 样 的 支 持 Windows Linux 等 多 个 操 作 系 统 的 用 户 界 面 开 发 包 可 以 免 费 得 到 建 立 在 这 两 个 跨 平 台 的 开 发 包 的 基 础 之 上, 整 个 3DMed 的 代 码 也 是 全 部 使 用 ANSI C++ 编 写, 没 有 使 用 任 何 编 译 器 提 供 的 特 殊 关 键 字 或 者 特 殊 函 数, 因 此 3DMed 可 以 很 自 然 地 在 多 个 操 作 系 统 下 运 行 图 DMed 的 结 构 图 (2) 强 大 的 可 扩 充 性 前 面 已 经 提 到 过,3DMed 是 属 于 科 研 软 件 性 质 的, 所 以 其 一 个 最 重 要 的 设 计 目 标 就 是 可 扩 充 性, 允 许 第 三 方 的 科 研 机 构 或 者 开 发 人 员 将 自 己 的 功 能 集 成 到 3DMed 中 去 为 此 3DMed 里 面 提 供 了 一 个 灵 活 的 基 于 Plugin 的 框 架, 所 有 3DMed 的 主 要 功 能, 包 括 分 割 可 视 化 等, 都 是 通 过 一 个 一 个 的 Plugin 来 实 现 的, 这 些 Plugin 在 运 行 时 被 动 态 加 载, 因 此 用 户 可 以 按 照 3DMed 事 先 定 义 好 的 Plguin 的 规 范, 来 开 发 自 己 的 Plugin, 并 放 入 特 定 的 目 录, 这 样 3DMed 就 能 367
375 11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 动 态 地 将 其 加 载 进 来 (3) 易 于 获 取 根 据 我 们 对 先 前 版 本 的 3DMed 的 一 定 范 围 的 免 费 发 行 所 取 得 的 经 验, 为 了 使 3DMed 能 够 得 到 最 广 泛 的 应 用, 必 须 使 其 能 够 很 容 易 地 被 用 户 获 取 现 在 我 们 已 经 将 3DMed 作 为 免 费 软 件 (Freeware) 发 行, 并 且 直 接 放 在 Internet 网 络 上 供 国 内 外 相 关 人 员 免 费 下 载, 除 了 不 能 用 于 商 业 目 的 和 必 须 保 留 版 权 信 息 等 一 些 条 件 外, 用 户 可 以 完 全 免 费 地 在 自 己 的 科 研 工 作 中 使 用 3DMed DMed 提 供 的 功 能 简 介 图 11-2 给 出 了 3DMed 所 提 供 的 基 本 的 功 能, 其 中 二 维 操 作 虚 拟 切 割 和 三 维 测 量 等 功 能 是 在 3DMed 的 核 心 中 实 现 的, 相 对 固 定, 同 时 为 了 形 成 一 个 相 对 完 整 的 版 本, 核 心 里 面 也 实 现 了 基 本 的 表 面 绘 制 和 体 绘 制 功 能 ; 而 医 学 影 像 数 据 I/O 医 学 影 像 分 割 医 学 影 像 配 准 表 面 绘 制 和 体 绘 制 等 功 能 是 由 Plugins 动 态 加 载 进 来 的, 也 就 意 味 着 这 一 部 分 的 功 能 是 可 以 根 据 需 要 来 添 加 的, 是 动 态 部 分 下 面 分 别 对 每 一 个 功 能 作 简 单 的 介 绍 图 DMed 提 供 的 功 能 列 表 (1) 医 学 影 像 数 据 I/O 这 一 部 分 的 功 能 主 要 是 提 供 对 多 种 医 学 影 像 数 据 格 式 的 支 持, 为 了 能 够 处 理 368
376 11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 新 的 格 式 以 及 方 便 地 加 入 其 它 格 式 的 支 持, 这 一 部 分 采 用 了 Plugin 机 制, 每 一 种 格 式 的 读 和 写 都 分 别 是 一 个 独 立 的 Plugin, 可 以 随 时 被 加 载 现 在 3DMed 中 提 供 了 对 DICOM 3.0 标 准 ( 部 分 支 持 ) 规 定 的 数 据 的 读 取 和 存 储 序 列 BMP 图 像 的 读 取 和 存 储 序 列 JPEG 图 像 的 读 取 和 存 储 序 列 TIFF 图 像 的 读 取 和 存 储 ACR-NEMA 标 准 规 定 的 数 据 的 读 取 和 存 储 生 数 据 的 读 取 和 存 储 另 外, 这 一 部 分 的 功 能 可 以 当 做 一 个 格 式 转 换 器, 在 3DMed 所 支 持 的 这 些 数 据 格 式 中 进 行 转 换 (2) 二 维 操 作 这 一 部 分 的 功 能 主 要 是 提 供 二 维 的 阅 片 功 能, 主 要 包 括 : 三 个 断 面 上 的 图 像 的 同 时 显 示 图 像 浏 览 动 画 播 放 窗 宽 窗 位 调 整 几 何 变 换 伪 彩 显 示 测 量 和 标 注 等 等 使 用 者 可 以 选 择 各 种 方 式 阅 片, 并 能 够 通 过 滤 波 处 理 来 消 除 噪 声, 提 高 图 像 质 量 还 能 方 便 的 得 到 CT 值, 对 图 像 的 像 素 点 进 行 分 析 计 算 处 理, 得 出 相 关 的 完 整 数 据, 为 医 学 诊 断 提 供 从 定 性 到 定 量 更 客 观 的 信 息 (3) 医 学 影 像 分 割 分 割 是 整 个 医 学 影 像 处 理 与 分 析 的 核 心 所 在, 因 为 它 的 结 果 直 接 影 响 着 后 续 的 三 维 显 示 或 者 配 准 等 操 作 的 质 量 因 为 医 学 影 像 的 模 态 多 种 多 样, 也 各 有 各 的 特 点, 所 以 存 在 多 种 分 割 算 法 为 了 能 够 随 时 加 载 新 的 分 割 算 法, 在 3DMed 里 面, 这 一 部 分 也 采 用 了 Plugin 机 制, 每 一 种 分 割 算 法 都 是 一 个 独 立 的 Plugin, 可 以 在 运 行 时 被 加 载 当 前 3DMed 里 面 已 经 提 供 了 手 工 交 互 分 割 阈 值 分 割 种 子 生 长 分 割 Fast Marching 分 割 Live Wire 分 割 Level Set 分 割 等 算 法, 并 且 以 后 新 的 算 法 会 不 断 地 以 Plugin 的 形 式 加 进 去 (4) 表 面 绘 制 (Surface Rendering) 表 面 绘 制 提 供 了 一 种 手 段 来 以 真 实 感 的 三 维 图 形 来 显 示 人 体 内 部 器 官, 它 首 先 要 经 过 分 割 这 个 步 骤, 将 感 兴 趣 的 器 官 提 取 出 来, 然 后 用 三 维 重 建 算 法 生 成 其 表 面 (IsoSurface), 再 使 用 图 形 学 的 方 法 将 其 绘 制 出 来 3DMed 的 核 心 层 提 供 了 一 种 基 于 分 割 的 增 强 的 Marching Cubes 算 法 [9] 来 实 现 表 面 绘 制 功 能, 同 时 为 了 易 于 扩 充, 同 样 也 提 供 了 对 表 面 绘 制 Plugin 机 制 的 支 持 (5) 体 绘 制 (Volume Rendering) 体 绘 制 也 是 一 种 三 维 显 示 的 手 段, 但 是 与 表 面 绘 制 不 同, 它 不 需 要 经 过 分 割 369
377 11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 这 一 步 骤, 直 接 将 数 据 集 里 面 所 有 的 器 官 同 时 显 示 出 来, 用 传 递 函 数 (Transfer Function) 来 控 制 显 示 的 效 果, 它 可 以 很 直 观 地 反 映 出 整 个 数 据 场 的 全 貌 3DMed 的 核 心 层 提 供 了 基 于 Ray Casting 的 体 绘 制 算 法, 也 提 供 了 很 多 易 于 使 用 的 界 面 来 调 节 传 递 函 数, 不 过 考 虑 到 体 绘 制 算 法 的 多 样 性, 同 时 为 了 易 于 扩 充, 这 里 同 样 也 提 供 了 对 体 绘 制 Plugin 机 制 的 支 持 (6) 虚 拟 切 割 虚 拟 切 割 可 以 使 用 户 看 到 被 外 部 器 官 遮 挡 住 的 组 织 和 器 官, 它 通 过 将 数 据 场 的 一 部 分 切 割 掉, 从 而 更 清 晰 地 反 映 内 部 的 信 息 在 3DMed 里 面, 支 持 对 表 面 绘 制 和 体 绘 制 的 结 果 实 行 虚 拟 切 割, 其 中 对 于 表 面 绘 制, 支 持 任 意 平 面 的 切 割 ; 而 对 于 体 绘 制, 除 了 平 面 切 割 以 外, 还 支 持 用 立 方 体 进 行 切 割 (7) 三 维 测 量 三 维 测 量 是 对 二 维 图 像 的 测 量 与 标 注 功 能 的 扩 充, 它 可 以 允 许 使 用 者 进 行 空 间 任 意 两 个 点 之 间 的 距 离 测 量 空 间 角 度 的 测 量 等 等 为 了 在 两 维 的 输 入 设 备 和 显 示 设 备 之 下 能 够 提 供 直 观 的 直 接 对 三 维 物 体 的 操 作,3DMed 里 面 使 用 了 3D Widgets 来 实 现 三 维 的 人 机 交 互 界 面 (8) 医 学 影 像 配 准 配 准 和 分 割 可 视 化 算 法 一 起, 组 成 了 医 学 影 像 处 理 与 分 析 的 理 论 基 础 有 了 配 准, 就 可 以 实 现 多 个 不 同 模 态 的 医 学 影 像 的 信 息 融 合 和 可 视 化, 可 以 为 临 床 诊 断 或 者 科 学 研 究 提 供 更 多 的 有 用 信 息 考 虑 到 配 准 算 法 的 多 样 性, 在 3DMed 里 面, 这 一 部 分 也 采 用 了 Plugin 机 制, 每 一 种 配 准 算 法 都 是 一 个 独 立 的 Plugin, 可 以 随 时 被 加 载 当 前 3DMed 里 面 只 提 供 了 少 量 的 刚 性 配 准 算 法, 随 着 3DMed 功 能 的 不 断 完 善, 多 种 刚 性 和 非 刚 性 的 配 准 算 法 将 会 很 快 以 Plugin 的 形 式 加 进 去 11.43DMed 的 Plugin 整 体 框 架 的 实 现 3DMed 是 医 学 影 像 这 一 特 定 领 域 内 的 一 个 复 杂 的 软 件 系 统, 它 的 核 心 算 法 由 MITK 来 提 供, 而 它 自 己 是 处 在 应 用 层 上 面, 正 如 图 1 所 示 下 面 从 软 件 设 计 的 角 度 比 较 宏 观 地 介 绍 3DMed 的 整 体 框 架, 更 偏 重 于 算 法 角 度 的 文 献 请 参 考 [8] 370
378 11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 因 为 3DMed 是 一 个 大 而 复 杂 的 系 统, 牵 涉 到 医 学 影 像 分 割 配 准 和 可 视 化 等 多 个 方 面, 而 这 三 个 方 面 中 的 每 一 个 小 的 方 面, 本 身 都 是 一 个 非 常 复 杂 的 系 统, 有 很 多 种 不 同 的 算 法, 有 很 多 不 同 的 参 数 需 要 调 节 为 了 降 低 3DMed 各 个 模 块 之 间 的 藕 合 性, 同 时 更 重 要 的 目 的 是 为 了 给 使 用 者 提 供 一 个 开 放 的 易 扩 充 的 架 构, 整 个 3DMed 使 用 了 Plugin 机 制 所 谓 Plugin, 就 是 一 个 动 态 链 接 库 [10], 在 Windows 操 作 系 统 下 的 表 现 形 式 就 是 一 个 DLL 文 件, 它 按 照 一 定 的 规 范 来 编 写, 所 以 可 以 被 3DMed 的 核 心 所 动 态 加 载 并 调 用 要 实 现 一 个 Plugin 机 制, 必 须 有 三 个 部 分 相 互 协 作 来 共 同 完 成, 如 图 3 所 示 第 一 个 部 分 3DMed Kernel 是 系 统 核 心, 它 负 责 加 载 管 理 并 调 用 各 个 Plugin; 第 二 个 部 分 Plugin SDK 是 编 写 Plugin 的 规 范, 所 有 Plugin 的 编 写 者 都 必 须 遵 循 这 个 规 范, 生 成 的 Plugin 才 能 被 系 统 核 心 所 识 别 并 加 载 ; 第 三 个 部 分 Plugins 就 是 各 个 具 体 的 Plugin 实 现 了, 它 们 一 方 面 遵 循 Plugin SDK 所 制 定 的 接 口, 另 外 一 方 面 要 负 责 实 现 具 体 的 功 能 下 面 将 分 别 讲 述 这 三 个 部 分 在 3DMed 里 面 的 实 现 图 11-3 Plugin 框 架 的 三 个 组 成 部 分 Plugin SDK 的 实 现 Plugin SDK 在 整 个 Plugin 框 架 中 起 着 至 关 重 要 的 作 用, 它 不 仅 为 系 统 核 心 提 供 Plugin 的 调 用 接 口, 同 时 也 为 Plugin 的 开 发 者 ( 可 以 是 使 用 者 ) 规 定 了 撰 写 Plugin 所 必 须 遵 循 的 接 口 要 想 使 第 三 方 开 发 者 能 够 写 出 自 己 的 Plugin, 那 么 必 须 把 3DMed 的 内 部 数 据 通 过 一 个 接 口 暴 露 给 外 部 在 3DMed 中, 使 用 的 数 371
379 11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 据 有 两 个 大 类 :medvolume 和 medmesh, 它 们 分 别 代 表 由 医 学 影 像 设 备 采 集 得 到 的 三 维 数 据 集 和 表 面 重 建 算 法 生 成 的 几 何 面 片 网 格 它 们 分 别 通 过 一 些 Get 函 数 将 自 己 的 属 性 以 及 实 际 数 据 暴 露 给 Plugin 的 开 发 者, 如 图 11-4 所 示 另 外, 整 个 3DMed 的 Plugins 被 分 为 五 个 大 类, 分 别 是 I/O Plugins Filter Plugins Registration Plugins Segmentation Plugins 和 Visualization Plugins, 请 参 看 图 11-4 它 们 都 从 一 个 共 同 的 抽 象 基 类 medplugin 继 承 下 来, 并 且 都 继 承 了 纯 虚 函 数 Show, 其 子 类 必 须 实 现 这 一 纯 虚 函 数 以 完 成 具 体 功 能 I/O Plugins 提 供 对 不 同 数 据 格 式 的 读 入 和 保 存, 由 于 数 据 对 象 分 为 两 种, 所 以 这 一 部 分 总 共 有 四 种 Plugins, 分 别 是 对 Volume 的 读 和 写 对 Mesh 的 读 和 写 对 于 读 数 据 的 Plugin 来 说, 它 只 需 通 过 其 GetOutput 函 数 将 读 进 来 的 数 据 以 Volume 或 者 Mesh 的 形 式 返 回 就 行 了 ; 对 于 写 数 据 的 Plugin 来 说, 它 需 要 用 SetInput 函 数 从 系 统 核 心 那 里 得 到 一 个 Volume 或 者 Mesh, 然 后 将 其 写 成 自 己 的 格 式 对 于 Filter Plugins 来 说, 也 因 为 两 种 不 同 的 数 据 而 分 为 两 种 Plugin:medVolumeFilterPlugin 以 SetInput 函 数 从 系 统 核 心 那 里 得 到 一 个 Volume, 然 后 经 过 自 己 在 Show 函 数 里 面 的 处 理, 生 成 一 个 滤 波 后 的 Volume, 通 过 GetOutput 函 数 返 回 给 系 统 核 心, 大 部 分 的 图 像 处 理 算 法 都 可 以 通 过 这 种 Plugin 加 载 进 3DMed;medMeshFilterPlugin 以 SetInput 函 数 从 系 统 核 心 那 里 得 到 一 个 Mesh, 然 后 经 过 自 己 在 Show 函 数 里 面 的 处 理, 生 成 一 个 滤 波 后 的 Mesh, 通 过 GetOutput 函 数 返 回 给 系 统 核 心, 大 部 分 的 数 字 几 何 处 理 算 法, 包 括 面 片 化 简 面 片 平 滑 面 片 细 分 等 都 可 以 通 过 这 种 Plugin 加 载 进 3DMed Registration Plugins 是 直 接 对 应 各 种 配 准 算 法 的, 它 需 要 两 个 Volume, 生 成 一 个 配 准 后 的 Volume Segmentation Plugins 是 直 接 对 应 分 割 算 法 的, 它 实 际 上 可 以 归 为 medvolumefilter 的 一 种, 但 是 考 虑 到 分 割 算 法 的 重 要 性, 这 里 还 是 将 其 单 独 作 为 一 个 种 类, 它 接 收 一 个 原 始 的 未 分 割 的 Volume, 生 成 一 个 分 割 后 的 Volume Visualization Plugins 是 直 接 对 应 各 种 可 视 化 算 法 的, 除 了 系 统 核 心 里 面 提 供 的 标 准 的 算 法 以 外, 其 它 新 的 算 法 可 以 通 过 这 种 手 段 加 载 进 来 这 种 Plugin 只 需 要 接 收 一 个 Volume, 然 后 直 接 生 成 显 示 后 的 结 果 图 像, 可 以 让 使 用 者 交 互 操 作 372
380 11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 图 11-4 Plugin SDK 的 类 层 次 结 构 图 Plugins 的 实 现 前 面 已 经 提 到 过, 一 个 Plugin 实 际 上 就 是 一 个 动 态 链 接 库, 在 功 能 上 对 应 着 某 一 种 具 体 的 算 法 要 实 现 一 个 具 体 的 Plugin, 首 先 要 确 定 其 属 于 哪 一 类 的 Plugin, 比 如 是 要 实 现 读 入 一 系 列 BMP 这 一 功 能 的 Plugin, 那 么 它 应 该 是 属 于 I/O Plugins 下 面 的 VolumeImport 这 一 种 类, 所 以 其 必 须 从 medvolumeimportplugin 继 承, 并 实 现 父 类 的 纯 虚 函 数 Show, 在 其 中 完 成 BMP 文 件 的 读 取 工 作 另 外, 实 现 一 个 Plugin 时 还 有 一 些 技 术 难 题 需 要 解 决, 因 为 动 态 加 载 一 个 动 态 链 接 库 时, 只 能 从 中 得 到 C 函 数 的 指 针, 而 C++ 由 于 命 名 转 换 的 问 题, 不 能 直 接 拿 到 C++ 成 员 函 数 的 指 针 为 了 解 决 这 一 难 题,3DMed 中 使 用 了 设 计 模 式 中 的 Abstract Factory[11], 它 要 求 每 个 Plugin 必 须 提 供 一 个 C 函 数 MakePlugin, 动 态 生 成 一 个 自 己 的 实 例, 但 是 将 其 转 换 为 父 类 类 型 的 指 针 并 返 回, 下 面 依 旧 以 BMP 的 读 入 这 一 Plugin 为 例, 它 的 MakePlugin 函 数 的 声 明 和 实 现 如 下 : medvolumeimportplugin* MakePlugin() return new medbmpimportplugin; DMed Kernel 的 实 现 系 统 核 心 的 主 要 作 用 是 在 运 行 时 动 态 加 载 各 个 类 型 的 Plugins, 并 根 据 类 型, 动 态 生 成 菜 单 项, 在 使 用 者 点 击 菜 单 项 时 调 用 对 应 的 Plugin 功 能, 并 且 在 系 统 373
381 11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 退 出 时 清 除 掉 加 载 进 来 的 Plugins 要 动 态 加 载 Plugins, 那 么 每 个 Plugin 必 须 提 供 一 些 必 要 的 信 息, 以 便 系 统 核 心 能 够 识 别 它 们 为 其 分 类 并 且 生 成 对 应 的 菜 单 项 在 3DMed 的 实 现 里, 要 求 每 个 Plugin 必 须 提 供 如 下 的 几 个 C 函 数, 以 便 系 统 核 心 能 够 得 到 它 们 的 指 针 并 调 用 它 们 : const char* GetTypeName(); const char* GetClassname(); const char* GetMenuDescription(); 其 中,GetTypeName 给 出 这 个 Plugin 的 类 型, 就 是 图 11-4 所 示 的 九 种 类 型 中 之 一 ;GetClassname 给 出 这 个 Plugin 的 名 字 ;GetMenuDescription 给 出 这 个 Plugin 在 界 面 菜 单 上 显 示 的 字 符 串 为 了 简 化 Plugin 开 发 者 的 工 作,Plugin SDK 使 用 C/C++ 语 言 里 面 的 宏 作 为 代 码 生 成 器, 自 动 生 成 包 括 MakePlugin 在 内 的 这 四 个 必 须 的 函 数 下 面 给 出 medvolumeimportplugin 这 种 类 型 的 Plugin 所 对 应 的 宏 代 码 : #define IMPLEMENT_VOLUME_IMPORT(ClassName, MenuDescription) \ extern "C" \ MEDEXPORT const char* GetTypeName() return "VolumeImportPlugin"; \ MEDEXPORT const char* GetClassname() return #ClassName; \ MEDEXPORT const char* GetMenuDescription() return MenuDescription; \ MEDEXPORT medvolumeimportplugin* MakePlugin() return new ClassName; \ 加 载 完 Plugins 以 后, 还 必 须 对 其 进 行 管 理 3DMed 里 面 仍 然 使 用 了 Abstract Factory 这 一 设 计 模 式 来 进 行 Plugins 的 管 理, 从 而 保 证 框 架 的 灵 活 性 和 优 雅 性 如 图 11-5 所 示,medFactory 是 一 个 模 板 类,T 是 待 参 数 化 的 类 型, 它 可 以 是 九 种 Plugin 类 型 中 的 一 种 medfactory 内 部 使 用 一 个 map 来 管 理 Plugins, 并 且 使 用 Plugin 的 名 字 作 为 键 值 来 索 引 Plugin 的 指 针 Add 函 数 将 一 个 新 的 Plugin 登 374
382 11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 记 到 Factory 里 面, 而 Create 函 数 从 Plugin 的 名 字 得 到 其 实 际 指 针,Clear 函 数 销 毁 掉 所 有 的 Plugins 图 11-5 Plugins Factory 架 构 下 面 的 伪 代 码 给 出 了 3DMed 核 心 在 运 行 时 加 载 并 管 理 各 种 Plugins 的 流 程 图 : 对 于 Plugin 目 录 下 面 的 每 个 文 件 : 判 断 是 否 有 GetTypeName 等 四 个 必 须 的 函 数 ; 如 果 有, 则 表 明 是 一 个 有 效 的 Plugin; typenamestr = GetTypeName(); classnamestr = GetClassName(); menudescriptionstr = GetMenuDescription(); 由 typenamestr 来 判 断 此 Plugin 的 类 型 ; typenamestr newplugin = MakePlugin(); medfactory<typenamestr> :: Add(classNameStr, newplugin); 生 成 动 态 菜 单, 菜 单 标 题 是 menudescriptionstr; 设 置 菜 单 的 消 息 响 应 事 件 为 此 Plugin 的 Show 函 数 ; 375
383 11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 11.5 应 用 实 例 图 11-6 给 出 了 3DMed 的 主 界 面, 左 边 是 参 数 调 节 区 域, 右 边 上 半 部 分 是 三 维 显 示 区 域, 下 半 部 分 是 二 维 显 示 区 域 三 维 显 示 给 出 了 人 体 大 脑 的 表 面 绘 制, 二 维 显 示 给 出 了 三 个 断 面 上 的 图 像, 同 时 一 部 分 给 以 伪 彩 显 示 图 DMed 的 主 界 面 376
384 11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 图 11-7 肿 瘤 分 割 的 应 用 实 例 图 11-8 配 准 的 应 用 实 例 图 11-9 可 视 化 的 应 用 实 例 图 11-7 给 出 了 一 个 肿 瘤 的 分 割 实 例, 左 边 的 图 像 是 原 始 的 切 片, 中 间 的 图 像 是 分 割 后 的 结 果, 肿 瘤 以 红 色 部 分 显 示, 右 边 的 图 像 是 三 维 重 建 后 的 显 示 结 果 377
385 11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 图 11-8 给 出 了 CT 图 像 和 MR 图 像 配 准 的 实 例, 左 边 的 图 像 是 原 始 的 CT 图 像, 中 间 的 图 像 是 原 始 的 MR 图 像, 右 边 的 图 像 是 配 准 并 叠 加 在 一 起 的 图 像 图 11-9 给 出 了 三 个 可 视 化 的 例 子, 左 边 的 图 像 是 对 骨 骼 进 行 面 绘 制 得 到 的 结 果, 中 间 的 图 像 是 对 骨 骼 和 皮 肤 进 行 多 层 面 绘 制 得 出 的 结 果, 右 边 的 图 像 是 体 绘 制 并 经 过 切 割 后 得 到 的 结 果 11.6 小 结 本 书 介 绍 了 我 们 在 前 期 工 作 积 累 的 基 础 上 开 发 的 新 版 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed, 目 前 我 们 已 经 完 成 一 个 初 步 的 版 本, 为 了 更 大 限 度 地 普 及 3DMed 的 用 户, 现 在 3DMed 已 经 作 为 免 费 软 件 发 行 任 何 人 都 可 以 在 免 费 下 载 并 使 用 3DMed, 同 时 在 这 个 网 址 还 可 以 免 费 下 载 我 们 开 发 的 底 层 算 法 包 MITK 在 将 来 的 工 作 中, 我 们 将 不 断 地 收 集 用 户 的 反 馈 信 息, 持 续 地 改 善 3DMed 另 外, 我 们 还 将 不 断 地 往 3DMed 里 面 加 入 更 多 的 Plugins, 如 果 第 三 方 开 发 的 Plugins 达 到 一 定 的 标 准 以 后, 也 可 以 加 进 3DMed 的 发 行 版 本 当 中, 通 过 这 一 手 段 来 不 断 地 补 充 和 丰 富 它 的 功 能 378
386 12 开 发 3DMed 的 Plugin 12 开 发 3DMed 的 Plugin 上 一 章 介 绍 了 3DMed 的 整 体 功 能 和 Plugin 框 架, 从 中 我 们 可 以 看 到 3DMed 也 是 一 个 开 放 的 架 构, 允 许 用 户 自 己 通 过 撰 写 Plugin 来 扩 充 其 功 能, 以 满 足 实 际 使 用 中 的 需 求 本 章 介 绍 如 何 开 发 3DMed 的 Plugin, 首 先 给 出 总 体 介 绍, 然 后 通 过 两 个 实 际 的 例 子, 以 Microsoft Visual C 来 作 为 编 程 环 境, 一 步 一 步 地 演 示 如 何 撰 写 自 己 的 Plugin 12.1 总 体 介 绍 在 3DMed 中, 所 有 的 Plugins 分 为 五 个 大 类 (I/O Plugin Filter Plugin Registration Plugin Segmentation Plugin 和 Visualization Plugin), 其 中 I/O Plugin 又 分 为 medvolumeimportplugin medvolumeexportplugin medmeshimportplugin 和 medmeshexportplugin 四 个 小 类,Filter Plugin 又 分 为 medvolumefilterplugin 和 medmeshfilterplugin 两 个 小 类 这 五 大 类 九 小 类 Plugin 定 义 了 编 写 3DMed Plugin 时 必 须 遵 循 的 接 口, 是 整 个 Plugin 机 制 的 基 础 要 编 写 一 个 3DMed 的 Plugin, 一 般 的 步 骤 如 下 所 示 : 第 一 步, 先 判 断 自 己 要 完 成 的 Plugin 在 功 能 上 属 于 上 面 所 讲 的 五 大 类 中 哪 一 类 的, 然 后 再 找 出 从 属 的 具 体 的 某 个 小 类 ; 第 二 步, 写 自 己 的 Plugin, 从 第 一 步 中 选 择 的 那 个 基 类 中 公 有 继 承 ; 第 三 步, 实 现 虚 函 数 bool Show (), 在 这 个 函 数 里 面 完 成 Plugin 的 具 体 功 能 注 意 的 是, 在 这 个 函 数 里 面 你 可 以 作 出 完 整 的 GUI 图 形 界 面 来 完 成 某 项 功 能, 也 可 以 不 要 图 形 界 面, 只 是 在 后 台 完 成 功 能 这 个 接 口 规 定 的 相 当 宽 松, 所 以 实 现 GUI 图 形 界 面 时 所 用 的 编 程 工 具 也 并 没 有 限 制, 本 章 中 的 例 子 使 用 Microsoft 的 MFC 来 作 为 GUI 开 发 工 具, 而 3DMed 里 面 自 带 的 所 有 的 Plugin 都 是 使 用 跨 平 台 的 GUI 类 库 Qt 来 开 发 的, 你 甚 至 可 以 使 用 Windows API, 或 者 Borland 的 C++ Builder, 所 有 这 些 工 具 作 出 来 的 Plugin 都 可 以 很 好 地 被 3DMed 的 主 程 序 所 管 理 和 调 用 另 外,3DMed 通 过 两 个 数 据 类 :medvolume 和 medmesh 把 自 己 内 部 的 数 据 暴 露 给 Plugin 的 开 发 者, 所 以 要 开 发 一 个 3DMed 的 Plugin, 还 必 须 对 这 两 个 数 据 类 所 提 供 的 API 函 数 有 所 了 解,medVolume 所 提 供 的 主 要 的 API 函 数 如 下 : 379
387 12 开 发 3DMed 的 Plugin void SetWidth(int w); // 设 置 Volume 的 宽 度 (Pixel 为 单 位 ) int GetWidth(); // 得 到 Volume 的 宽 度 (Pixel 为 单 位 ) void SetHeight(int h); // 设 置 Volume 的 高 度 (Pixel 为 单 位 ) int GetHeight(); // 得 到 Volume 的 高 度 (Pixel 为 单 位 ) void SetImageNum(int s); // 设 置 Volume 的 切 片 张 数 int GetImageNum(); // 得 到 Volume 的 切 片 张 数 void SetSpacingX(float px); // 设 置 像 素 间 的 水 平 间 距 (mm 为 单 位 ) float GetSpacingX(); // 得 到 像 素 间 的 水 平 间 距 (mm 为 单 位 ) void SetSpacingY(float py); // 设 置 像 素 间 的 竖 直 间 距 (mm 为 单 位 ) float GetSpacingY(); // 得 到 像 素 间 的 竖 直 间 距 (mm 为 单 位 ) void SetSpacingZ(float pz); // 设 置 切 片 间 的 间 距 (mm 为 单 位 ) float GetSpacingZ(); // 得 到 切 片 间 的 间 距 (mm 为 单 位 ) // 设 置 Volume 的 通 道 数 (1 为 灰 度 图 像,3 为 RGB 彩 色 图 像 ) void SetNumberOfChannel( int n ); // 得 到 Volume 的 通 道 数 (1 为 灰 度 图 像,3 为 RGB 彩 色 图 像 ) int GetNumberOfChannel(); // 得 到 实 际 的 数 据 指 针, 必 须 依 据 GetDataType 返 回 的 数 据 类 型, // 将 其 强 制 转 换 成 相 应 的 指 针 类 型 才 能 使 用 void* GetRawData(); // 得 到 某 一 张 切 片 的 数 据 指 针, 必 须 依 据 GetDataType 返 回 的 数 据 类 型, // 将 其 强 制 转 换 成 相 应 的 指 针 类 型 才 能 使 用 void* GetSliceData(int slicenum); // 得 到 此 Volume 的 数 据 类 型, 返 回 的 值 及 其 代 表 的 意 义 如 下 : // MED_CHAR c 语 言 中 char 类 型 // MED_UNSIGNED_CHAR c 语 言 中 unsigned char 类 型 // MED_SHORT c 语 言 中 short 类 型 // MED_UNSIGNED_SHOR c 语 言 中 unsigned short 类 型 // MED_INT c 语 言 中 int 类 型 // MED_UNSIGNED_INT c 语 言 中 unsigned int 类 型 // MED_LONG c 语 言 中 long 类 型 // MED_UNSIGNED_LONG c 语 言 中 unsigned long 类 型 // MED_FLOAT c 语 言 中 float 类 型 380
388 12 开 发 3DMed 的 Plugin // MED_DOUBLE c 语 言 中 double 类 型 int GetDataType(); // 设 置 Volume 的 数 据 类 型, 变 量 type 的 取 值 及 意 义 参 见 GetDataType void SetDataType(int type); // 设 置 Volume 的 数 据 类 型 为 c 语 言 中 的 float 类 型 void SetDataTypeToFloat(); // 设 置 Volume 的 数 据 类 型 为 c 语 言 中 的 double 类 型 void SetDataTypeToDouble(); // 设 置 Volume 的 数 据 类 型 为 c 语 言 中 的 int 类 型 void SetDataTypeToInt(); // 设 置 Volume 的 数 据 类 型 为 c 语 言 中 的 unsigned int 类 型 void SetDataTypeToUnsignedInt(); // 设 置 Volume 的 数 据 类 型 为 c 语 言 中 的 long 类 型 void SetDataTypeToLong(); // 设 置 Volume 的 数 据 类 型 为 c 语 言 中 的 unsigned long 类 型 void SetDataTypeToUnsignedLong(); // 设 置 Volume 的 数 据 类 型 为 c 语 言 中 的 short 类 型 void SetDataTypeToShort(); // 设 置 Volume 的 数 据 类 型 为 c 语 言 中 的 unsigned short 类 型 void SetDataTypeToUnsignedShort(); // 设 置 Volume 的 数 据 类 型 为 c 语 言 中 的 char 类 型 void SetDataTypeToUnsignedChar(); // 设 置 Volume 的 数 据 类 型 为 c 语 言 中 的 unsigned char 类 型 void SetDataTypeToChar(); // 根 据 预 先 设 置 好 的 Width Height SliceNumber Datatype 和 ChannelNumber // 来 计 算 实 际 数 据 所 需 的 内 存 大 小, 并 分 配 内 存, 最 后 返 回 首 地 址 需 要 注 意 的 是, 在 调 // 用 此 函 数 之 前, 必 须 确 保 已 经 调 用 了 SetWidth SetHeight SetImageNum //SetNumberOfChannel SetDataType 设 置 好 了 相 关 的 信 息 void* Allocate(); // 返 回 数 据 所 占 用 的 内 存 的 大 小, 单 位 是 字 节 unsigned long GetActualMemorySize(); medmesh 提 供 的 主 要 的 API 函 数 如 下 : // 设 置 此 Mesh 的 顶 点 个 数 void SetVertexNumber(int number); 381
389 12 开 发 3DMed 的 Plugin // 得 到 此 Mesh 的 顶 点 个 数 int GetVertexNumber(); // 设 置 此 Mesh 的 三 角 片 个 数 void SetFaceNumber(int number); // 得 到 此 Mesh 的 三 角 片 个 数 int GetFaceNumber(); // 得 到 此 Mesh 的 顶 点 数 据 指 针 float* GetVertexData(); // 得 到 此 Mesh 的 三 角 面 片 数 据 指 针 unsigned int* GetFaceData(); // 得 到 此 Mesh 的 包 围 盒, 返 回 一 个 指 向 六 个 浮 点 数 的 指 针. // 六 个 浮 点 数 的 顺 序 及 意 义 为 : 最 小 x, 最 小 y, 最 小 z, // 最 大 x, 最 大 y, 最 大 z float* GetBoundingBox(void); // 设 置 此 Mesh 的 包 围 盒, 参 数 意 义 见 GetBoundingBox void SetBoundingBox(float minx, float miny, float minz, float maxx, float maxy, float maxz); // 得 到 顶 点 数 据 和 三 角 面 片 数 据 所 占 的 内 存 大 小, 单 位 是 字 节 unsigned long GetActualMemorySize(); 有 了 medvolume 和 medmesh 提 供 的 API 函 数 以 后,Plugin 的 开 发 者 就 可 以 通 过 它 们 访 问 3DMed 的 内 部 数 据, 并 且 他 们 不 需 知 道 MITK 的 存 在, 只 需 要 和 medvolume 和 medmesh 打 交 道 就 行 了 这 也 是 3DMed 的 Plugin 框 架 的 灵 活 性 的 体 现 之 一, 可 以 允 许 3DMed 的 使 用 者 在 没 有 MITK 的 情 况 下, 照 样 可 以 扩 充 3DMed 的 功 能 上 面 给 出 了 一 些 比 较 抽 象 的 总 体 介 绍, 下 面 将 会 提 供 一 些 撰 写 Plugin 的 更 具 体 的 内 容 首 先 一 个 Plugin 是 一 个 动 态 链 接 库, 所 以 在 一 开 始 创 建 工 程 的 时 候 就 要 选 择 正 确 的 工 程 类 型 ; 其 次 所 有 Plugin 的 开 发 都 要 用 到 PluginsSDK, 它 里 面 提 供 了 medvolume medmesh 以 及 九 个 小 类 的 Plugin 的 定 义,PluginsSDK 是 随 着 3DMed 一 起 分 发 的, 提 供 了 必 要 的 头 文 件 和 库 文 件 ; 最 后,Plugin 的 开 发 可 以 分 为 两 种, 一 种 使 用 MITK 作 为 底 层 算 法 库, 一 种 可 以 完 全 不 使 用 MITK, 382
390 12 开 发 3DMed 的 Plugin 纯 粹 加 入 自 己 的 算 法, 如 果 使 用 MITK 的 话, 那 么 开 发 Plugin 时 还 需 要 MITK 的 头 文 件 以 及 相 应 的 库 文 件 下 面 将 用 两 个 具 体 的 实 例 来 一 步 一 步 地 演 示 3DMed Plugin 的 开 发, 其 中 第 一 个 实 例 使 用 MITK, 而 第 二 个 实 例 不 使 用 MITK, 力 图 将 不 同 的 情 况 以 及 一 些 基 本 的 概 念 阐 明 清 楚 12.2 Plugin 实 例 : 使 用 MITK 本 实 例 使 用 Microsoft Visual C 作 为 工 具, 从 如 何 建 立 工 程, 到 修 改 工 程 的 设 置, 以 及 具 体 的 编 程 实 现, 一 直 到 最 后 集 成 进 3DMed, 给 出 了 一 个 完 整 的 例 子, 演 示 如 何 在 使 用 MITK 的 情 况 下 开 发 3DMed 的 Plugin 为 了 清 晰 和 简 化 起 见, 这 个 Plugin 的 功 能 比 较 简 单, 只 是 读 入 一 系 列 BMP 格 式 的 文 件, 并 送 给 3DMed 处 理 工 程 的 建 立 及 设 置 在 Microsoft Visual C 的 IDE 环 境 下, 新 建 一 个 工 程 IOPlugin, 工 程 的 类 型 选 择 MFC AppWizard(dll) ( 如 图 12-1 所 示 ), 因 为 我 们 要 使 用 MFC 作 GUI 图 形 界 面, 并 且 目 标 是 生 成 一 个 动 态 链 接 库 在 接 下 来 的 一 步 中, 选 择 DLL 类 型 为 Regular DLL using shared MFC DLL, 如 图 12-2 所 示 此 时 可 以 选 择 Finish 按 钮 完 成 工 程 的 创 建 383
391 12 开 发 3DMed 的 Plugin 图 12-1 新 建 IOPlugin 工 程 图 12-2 Dll 类 型 选 择 384
392 12 开 发 3DMed 的 Plugin 创 建 完 工 程 以 后, 选 Projcet - Settings 菜 单, 在 弹 出 来 的 Project Settings 对 话 框 中, 选 择 C/C++ 标 签 页, 在 Category 列 表 框 里 面 选 择 Preprocessor, 然 后 在 Additional Include directories 编 辑 框 中 输 入 PluginsSDK 和 MITK 的 头 文 件 路 径, 如 图 12-3 所 示 需 要 注 意 的 是 这 里 实 际 的 路 径 依 赖 于 你 的 机 器 上 的 PluginsSDK 和 MITK 的 放 置 路 径, 请 根 据 实 际 情 况 设 置 图 12-3 设 置 必 要 的 头 文 件 路 径 设 置 完 头 文 件 路 径 以 后, 接 下 来 要 设 置 的 是 必 要 的 导 入 库 文 件 的 路 径 保 持 Projcet Settings 对 话 框 打 开, 选 择 Link 标 签 页, 在 Category 列 表 框 中 选 择 Input, 然 后 在 Object/library modules 编 辑 框 中 输 入 PluginsSDK.lib Mitk_dll.lib, 表 示 要 使 用 这 两 个 导 入 库, 并 且 还 需 要 在 Additional library path 编 辑 框 中 输 入 这 两 个 导 入 库 文 件 所 在 的 路 径, 如 图 12-4 所 示 需 要 注 意 的 是 这 里 实 际 的 路 径 依 赖 于 你 的 机 器 上 的 PluginsSDK 和 MITK 的 库 文 件 的 放 置 路 径, 请 根 据 实 际 情 况 设 置 385
393 12 开 发 3DMed 的 Plugin 图 12-4 设 置 必 要 的 库 文 件 路 径 实 例 制 作 好 了, 设 置 好 烦 琐 的 工 程 选 项 以 后, 就 可 以 进 入 实 质 部 分 了 下 面 创 建 我 们 的 Plugin 的 类, 用 New Class 创 建 一 个 新 类, 并 将 其 命 名 为 CMyIOPlugin, 如 图 12-5 所 示 因 为 我 们 的 Plugin 的 功 能 是 读 入 一 系 列 BMP 格 式 的 文 件, 所 以 很 显 然 是 属 于 I/O Plugin 中 的 VolumeImportPlugin, 故 CMyIOPlugin 应 该 从 VolumeImportPlugin 公 有 继 承, 并 应 该 重 载 Show 函 数, 其 类 声 明 如 下 所 示 : class CMyIOPlugin : public medvolumeimportplugin public: CMyIOPlugin(); virtual ~CMyIOPlugin(); virtual bool Show(void); 386
394 12 开 发 3DMed 的 Plugin ; 当 然, 在 其 前 面 应 该 包 含 PluginsSDK 中 的 medplugin.h 文 件, 里 面 定 义 了 medvolumeimportplugin, 包 含 语 句 如 下 : #include "medplugin.h" 图 12-5 创 建 CMyIOPlugin 类 整 个 Plugin 的 功 能 都 在 最 重 要 的 Show 函 数 里 面 完 成, 其 提 供 一 个 打 开 文 件 387
395 12 开 发 3DMed 的 Plugin 对 话 框, 供 用 户 选 择 多 个 BMP 文 件, 并 打 开 这 些 文 件, 形 成 一 个 Volume 传 给 3DMed 去 处 理 因 为 此 例 子 使 用 了 MITK, 所 以 这 里 的 代 码 很 简 单, 直 接 使 用 了 MITK 中 提 供 的 mitkbmpreader, 整 个 Show 函 数 的 代 码 如 下 所 示 : bool CMyIOPlugin::Show(void) //MFC 的 规 定, 必 须 先 调 用 这 个 宏. AFX_MANAGE_STATE(AfxGetStaticModuleState()); COpenFileDialog filedialog; filedialog.settitle(" 打 开 序 列 BMP 文 件 "); filedialog.setfilter("bmp 文 件 (*.bmp)\0*.bmp\0\0"); filedialog.enablemultiselect(); // 如 果 用 户 选 择 了 文 件 并 点 确 定 按 钮. if(filedialog.run()) mitkbmpreader *areader = new mitkbmpreader; // 循 环 得 到 用 户 选 中 的 每 个 文 件 名, // 并 将 其 加 入 到 Reader 中. POSITION pos = filedialog.getstartposition(); CString szfilename; while(pos!= NULL) szfilename = filedialog.getnextpathname(pos); areader->addfilename(szfilename); // 设 置 一 下 其 它 必 要 的 信 息. areader->setspacingx(1.0f); areader->setspacingy(1.0f); areader->setspacingz(1.0f); // 调 用 Reader 的 实 际 读 入 程 序. areader->run(); // 生 成 输 出 的 数 据. 388
396 12 开 发 3DMed 的 Plugin m_data = new medvolume; m_data->setdata(areader->getoutput()); m_data->setfilename(szfilename); m_data->setname("my BMP Files"); areader->delete(); return true; return false; 上 面 的 代 码 加 了 大 量 的 注 释, 这 里 就 不 再 赘 述 了, 重 载 完 了 Show 函 数 以 后, 还 剩 下 最 后 一 件 事 情, 就 是 加 入 一 些 导 出 函 数, 这 一 切 只 需 调 用 下 面 的 宏 即 可 实 现, 其 中 第 一 个 参 数 是 我 们 所 写 的 Plugin 的 类 名, 这 里 当 然 是 CMyIOPlugin 了, 第 二 个 参 数 是 我 们 所 希 望 在 3DMed 菜 单 里 面 所 看 到 的 此 Plugin 对 应 的 菜 单 文 字 IMPLEMENT_VOLUME_IMPORT(CMyIOPlugin, "My BMP Plugin") 最 后 不 要 忘 了 在 前 面 加 上 必 要 的 头 文 件, 如 下 所 示 : //PluginsSDK 头 文 件 #include "medvolume.h" //MITK 头 文 件 #include "mitkbmpreader.h" #include "mitkvolume.h" 插 入 到 3DMed 经 过 了 上 面 的 步 骤 以 后, 现 在 可 以 编 译 整 个 工 程, 得 到 IOPlugin.dll 文 件, 并 将 其 拷 贝 到 3DMed 的 安 装 目 录 下 的 Plugins 子 目 录 里 面, 这 时 运 行 3DMed 主 程 序, 3DMed 加 载 完 所 有 的 插 件 以 后, 我 们 点 击 文 件 菜 单 下 的 加 载 体 数 据 菜 389
397 12 开 发 3DMed 的 Plugin 单 时, 就 会 发 现 我 们 的 My BMP Plugin 也 在 其 中, 如 图 12-6 所 示 当 点 击 My BMP Plugin 后, 就 会 有 打 开 对 话 框 弹 出 来, 让 用 户 选 择 BMP 文 件, 如 所 示 选 择 完 以 后 这 些 数 据 就 会 在 3DMed 中 打 开 并 显 示, 一 切 都 如 我 们 想 像 的 一 样 图 12-6 成 功 加 载 的 IOPlugin 图 12-7 IOPlugin 运 行 界 面 390
398 12 开 发 3DMed 的 Plugin 12.3 Plugin 实 例 : 不 使 用 MITK 在 有 些 情 况 下, 用 户 只 对 3DMed 感 兴 趣, 而 并 不 想 学 习 并 使 用 MITK, 那 么 照 样 可 以 撰 写 3DMed 的 Plugin 本 节 的 实 例 就 是 演 示 如 何 在 没 有 MITK 的 情 况 下, 来 编 写 有 效 的 Plugin 并 集 成 到 3DMed 中 去 本 实 例 仍 然 使 用 Microsoft Visual C 作 为 工 具, 从 如 何 建 立 工 程, 到 修 改 工 程 的 设 置, 以 及 具 体 的 编 程 实 现, 一 直 到 最 后 集 成 进 3DMed, 给 出 了 完 整 的 过 程 为 了 清 晰 和 简 化 起 见, 这 个 Plugin 的 功 能 比 较 简 单, 实 现 了 对 任 意 数 据 类 型 的 Volume 数 据 的 域 值 分 割 算 法, 支 持 设 置 高 域 值 和 低 域 值, 提 供 了 一 个 简 单 的 对 话 框 来 设 置 参 数 工 程 的 建 立 及 设 置 在 Microsoft Visual C 的 IDE 环 境 下, 新 建 一 个 工 程 SegPlugin, 工 程 的 类 型 选 择 MFC AppWizard(dll) ( 这 里 不 再 贴 图, 请 参 看 节 中 的 相 关 截 图 ), 因 为 我 们 要 使 用 MFC 作 GUI 图 形 界 面, 并 且 目 标 是 生 成 一 个 动 态 链 接 库 在 接 下 来 的 一 步 中, 选 择 DLL 类 型 为 Regular DLL using shared MFC DLL, 此 时 可 以 选 择 Finish 按 钮 完 成 工 程 的 创 建 创 建 完 工 程 以 后, 选 Projcet - Settings 菜 单, 在 弹 出 来 的 Project Settings 对 话 框 中, 选 择 C/C++ 标 签 页, 在 Category 列 表 框 里 面 选 择 Preprocessor, 然 后 在 Additional Include directories 编 辑 框 中 输 入 PluginsSDK 头 文 件 路 径, 如 图 12-8 所 示 需 要 注 意 的 是 这 里 实 际 的 路 径 依 赖 于 你 的 机 器 上 的 PluginsSDK 的 放 置 路 径, 请 根 据 实 际 情 况 设 置 设 置 完 头 文 件 路 径 以 后, 接 下 来 要 设 置 的 是 必 要 的 导 入 库 文 件 的 路 径 保 持 Projcet Settings 对 话 框 打 开, 选 择 Link 标 签 页, 在 Category 列 表 框 中 选 择 Input, 然 后 在 Object/library modules 编 辑 框 中 输 入 PluginsSDK.lib, 表 示 要 使 用 这 个 导 入 库, 并 且 还 需 要 在 Additional library path 编 辑 框 中 输 入 PluginsSDK.lib 所 在 的 路 径, 如 图 12-9 所 示 需 要 注 意 的 是 这 里 实 际 的 路 径 依 赖 于 你 的 机 器 上 的 PluginsSDK.lib 的 放 置 路 径, 请 根 据 实 际 情 况 设 置 391
399 12 开 发 3DMed 的 Plugin 图 12-8 设 置 必 要 的 头 文 件 路 径 图 12-9 设 置 必 要 的 库 文 件 路 径 392
400 12 开 发 3DMed 的 Plugin 实 例 制 作 设 置 好 烦 琐 的 工 程 选 项 以 后, 就 可 以 进 入 实 质 部 分 了 下 面 创 建 我 们 的 Plugin 的 类, 用 New Class 创 建 一 个 新 类, 并 将 其 命 名 为 CMySegPlugin, 如 图 所 示 因 为 我 们 的 Plugin 的 功 能 是 进 行 域 值 分 割, 所 以 很 显 然 是 属 于 SegmentationPlugin, 故 CMySegPlugin 应 该 从 SegmentationPlugin 公 有 继 承, 并 应 该 重 载 Show 函 数, 其 类 声 明 如 下 所 示 : class CMySegPlugin : public medsegmentationplugin public: CMySegPlugin(); virtual ~CMySegPlugin(); virtual bool Show(void); private: bool dosegmentation(float lowthre, float highthre); ; 图 创 建 CMySegPlugin 类 393
401 12 开 发 3DMed 的 Plugin 其 中 的 私 有 成 员 函 数 dosegmentation 用 来 完 成 实 际 的 分 割 工 作, 其 在 Show 函 数 里 面 被 调 用 下 面 需 要 作 的 是 GUI 的 图 形 界 面 工 作 了, 我 们 需 要 制 作 一 个 对 话 框, 在 ResourceView 面 板 上 点 右 键, 插 入 一 个 新 的 对 话 框 资 源, 在 Dialog Properties 对 话 框 中 将 其 ID 设 置 为 IDD_DIALOG_PARAMETER,Caption 设 置 为 设 置 高 低 域 值, 如 图 所 示 然 后 在 对 话 框 上 放 置 两 个 静 态 文 本 控 件 两 个 编 辑 框 控 件, 和 确 定 取 消 按 钮, 其 界 面 如 图 所 示 图 对 话 框 属 性 设 置 图 对 话 框 界 面 394
402 12 开 发 3DMed 的 Plugin 界 面 创 建 完 以 后, 使 用 ClassWizard 为 这 个 对 话 框 创 建 一 个 新 类, 类 的 名 字 叫 CDialogParameter, 如 图 所 示 然 后 再 使 用 ClassWizard 对 话 框 中 的 Member Variables 标 签 页, 将 对 话 框 中 的 两 个 编 辑 框 设 置 成 CDialogPapameter 类 的 成 员 变 量, 类 型 均 为 float 型, 分 别 如 图 和 图 所 示 图 创 建 CDialogParameter 类 图 添 加 低 域 值 成 员 变 量 395
403 12 开 发 3DMed 的 Plugin 图 添 加 高 域 值 成 员 变 量 最 后 剩 下 的 是 添 加 成 员 函 数 来 读 取 和 设 置 高 低 域 值 这 两 个 成 员 变 量 了, 分 别 是 SetLowThre SetHighThre GetLowThre 和 GetHighThre 函 数, 整 个 CDialogParameter 的 程 序 代 码 如 下 所 示 : class CDialogParameter : public CDialog // Construction public: CDialogParameter(CWnd* pparent = NULL); // standard constructor void SetLowThre(float lowthre); void SetHighThre(float highthre); float GetLowThre(); float GetHighThre(); // Dialog Data //AFX_DATA(CDialogParameter) enum IDD = IDD_DIALOG_PARAMETER ; float m_lowthre; float m_highthre; //AFX_DATA 396
404 12 开 发 3DMed 的 Plugin // Overrides // ClassWizard generated virtual function overrides //AFX_VIRTUAL(CDialogParameter) protected: virtual void DoDataExchange(CDataExchange* pdx); // DDX/DDV support //AFX_VIRTUAL // Implementation protected: ; // Generated message map functions //AFX_MSG(CDialogParameter) // NOTE: the ClassWizard will add member functions here //AFX_MSG DECLARE_MESSAGE_MAP() 终 于 作 完 了 烦 琐 的 GUI 图 形 界 面 工 作, 也 有 了 CDialogParameter 的 辅 助, 下 一 步 就 是 实 现 最 重 要 的 CMySegPlugin 类 中 的 Show 函 数 了, 其 实 现 代 码 如 下 所 示 : bool CMySegPlugin::Show(void) //MFC 的 规 定, 必 须 先 调 用 这 个 宏. AFX_MANAGE_STATE(AfxGetStaticModuleState()); // 弹 出 域 值 选 择 对 话 框. CDialogParameter paradialog; paradialog.setlowthre(0.0f); paradialog.sethighthre(255.0f); if(paradialog.domodal() == IDOK) // 作 实 际 的 域 值 分 割 工 作. return this->dosegmentation(paradialog.getlowthre(), paradialog.gethighthre()); 397
405 12 开 发 3DMed 的 Plugin return false; 里 面 用 到 了 我 们 刚 完 成 的 CDialogParameter 类, 弹 出 一 个 域 值 设 置 对 话 框, 如 果 用 户 设 置 完 高 低 域 值 并 选 择 了 确 定 以 后,Show 函 数 内 部 调 用 自 己 的 私 有 成 员 函 数 dosegmentation, 把 用 户 设 置 的 高 低 域 值 传 进 来, 具 体 的 工 作 由 dosegmentation 函 数 来 完 成, 其 实 现 代 码 如 下 所 示 : bool CMySegPlugin::doSegmentation(float lowthre, float highthre) if(lowthre >= highthre) return false; medvolume *involume = this->getinput(); if(involume == NULL) AfxMessageBox(" 输 入 数 据 为 空 "); return false; if(involume->getnumberofchannel()!= 1) AfxMessageBox(" 对 不 起, 只 支 持 单 通 道 的 数 据 "); return false; // 生 成 输 出 数 据 ( 即 分 割 后 数 据 ). m_outdata = new medvolume; // 设 置 分 割 后 数 据 的 属 性, // 和 原 始 数 据 大 部 分 一 致. medvolume *outvolume = this->getoutput(); outvolume->setwidth(involume->getwidth()); 398
406 12 开 发 3DMed 的 Plugin outvolume->setheight(involume->getheight()); outvolume->setimagenum(involume->getimagenum()); outvolume->setspacingx(involume->getspacingx()); outvolume->setspacingy(involume->getspacingy()); outvolume->setspacingz(involume->getspacingz()); outvolume->setnumberofchannel(involume->getnumberofchannel()); // 分 割 后 的 数 据 为 二 值 数 据, // 因 此 这 里 将 数 据 类 型 设 置 为 unsigned char(8bit). outvolume->setdatatype(med_unsigned_char); // 为 分 割 后 的 数 据 分 配 实 际 内 存. unsigned char *outdata = (unsigned char*) outvolume->allocate(); // 得 到 输 入 数 据 的 内 存 指 针. void *indata = involume->getrawdata(); // 根 据 输 入 数 据 的 类 型, // 使 用 模 板 函 数 来 完 成 实 际 分 割 过 程. switch(involume->getdatatype()) case MED_CHAR: t_executesegmentation((char*) indata, outdata, involume, lowthre, highthre); break; case MED_UNSIGNED_CHAR: t_executesegmentation((unsigned char*) indata, outdata, involume, lowthre, highthre); break; case MED_SHORT: t_executesegmentation((short*) indata, outdata, involume, lowthre, highthre); break; case MED_UNSIGNED_SHORT: t_executesegmentation((unsigned short*) indata, outdata, involume, lowthre, highthre); break; 399
407 12 开 发 3DMed 的 Plugin case MED_INT: t_executesegmentation((int*) indata, outdata, involume, lowthre, highthre); break; case MED_UNSIGNED_INT: t_executesegmentation((unsigned int*) indata, outdata, involume, lowthre, highthre); break; case MED_LONG: t_executesegmentation((long*) indata, outdata, involume, lowthre, highthre); break; case MED_UNSIGNED_LONG: t_executesegmentation((unsigned long*) indata, outdata, involume, lowthre, highthre); break; case MED_FLOAT: t_executesegmentation((float*) indata, outdata, involume, lowthre, highthre); break; case MED_DOUBLE: t_executesegmentation((double*) indata, outdata, involume, lowthre, highthre); break; default: AfxMessageBox(" 不 支 持 的 数 据 类 型!"); return false; return true; 400
408 12 开 发 3DMed 的 Plugin 由 于 这 个 例 子 并 不 使 用 MITK, 因 此 许 多 底 层 细 节 必 须 处 理, 所 以 程 序 稍 微 烦 琐 为 简 化 起 见, 这 个 例 子 只 处 理 单 通 道 的 数 据, 如 果 是 多 通 道 的 数 据, 可 以 先 经 过 预 处 理, 转 换 成 单 通 道 的 数 据 另 外, 这 个 地 方 大 量 地 使 用 了 medvolume 所 提 供 的 API 函 数, 所 以 请 参 考 12.1 节 的 介 绍 在 整 个 代 码 中, 因 为 要 处 理 不 同 数 据 类 型 的 输 入 数 据, 所 以 最 烦 琐 的 地 方 在 那 个 大 的 Switch 语 句 中, 判 断 输 入 数 据 的 类 型, 并 据 此 将 输 入 数 据 的 内 存 指 针 强 制 转 换 成 对 应 的 指 针 类 型, 交 给 模 板 函 数 t_executesegmentation 来 处 理, 而 t_executesegmentation 的 第 一 个 参 数 是 模 板 参 数, 可 以 被 实 例 化 成 各 种 不 同 的 指 针 类 型, 这 也 大 大 简 化 了 编 程 的 工 作 量 注 意 的 是 t_executesegmentation 并 不 是 CMySegPlugin 的 成 员 函 数, 而 只 是 一 个 普 通 函 数, 所 以 其 必 须 在 dosegmentation 函 数 前 面 被 声 明, 其 整 个 实 现 代 码 如 下 所 示 : template <class T> void t_executesegmentation(t *indata, unsigned char *outdata, medvolume *involume, float lowthresh, float highthresh) // 得 到 图 像 的 一 些 属 性 信 息. int imagewidth = involume->getwidth(); int imageheight = involume->getheight(); int imagenum = involume->getimagenum(); int i, j, k; // 循 环 遍 历 整 个 数 据. for(k = 0; k < imagenum; k++) for(j = 0; j < imageheight; j++) for(i = 0; i < imagewidth; i++) // 如 果 数 据 值 在 指 定 的 域 值 范 围 内, // 则 输 出 的 数 据 二 值 化 为 255. if(indata[0] >= lowthresh && indata[0] <= highthresh) outdata[0] = 255; 401
409 12 开 发 3DMed 的 Plugin // 否 则 为 0. else outdata[0] = 0; // 输 入 数 据 和 输 出 数 据 指 针 前 移. indata++; outdata++; 至 此 为 止, 已 经 完 成 了 整 个 程 序 中 最 艰 苦 的 部 分, 下 面 就 是 一 些 例 行 工 作 了, 首 先 是 加 入 必 要 的 导 出 函 数, 这 一 切 只 需 调 用 下 面 的 宏 即 可 实 现, 其 中 第 一 个 参 数 是 我 们 所 写 的 Plugin 的 类 名, 这 里 当 然 是 CMySegPlugin 了, 第 二 个 参 数 是 我 们 所 希 望 在 3DMed 菜 单 里 面 所 看 到 的 此 Plugin 对 应 的 菜 单 文 字 IMPLEMENT_SEGMENTATION (CMySegPlugin, "My Segmentation Plugin") 接 着 是 在 前 面 加 上 必 要 的 头 文 件, 如 下 所 示 : //PluginsSDK 头 文 件 #include "medvolume.h" // 对 话 框 头 文 件 #include "DialogParameter.h" 插 入 到 3DMed 经 过 了 上 面 的 步 骤 以 后, 现 在 可 以 编 译 整 个 工 程, 得 到 SegPlugin.dll 文 件, 并 将 其 拷 贝 到 3DMed 的 安 装 目 录 下 的 Plugins 子 目 录 里 面, 这 时 运 行 3DMed 主 程 序,3DMed 加 载 完 所 有 的 插 件 以 后, 我 们 点 击 分 割 算 法 菜 单 时, 就 会 发 现 402
410 12 开 发 3DMed 的 Plugin 我 们 的 My Segmentation Plugin 也 在 其 中, 如 图 所 示 当 点 击 My Segmentation Plugin 后, 就 会 弹 出 设 置 高 低 域 值 对 话 框, 让 用 户 设 置 参 数, 如 图 所 示, 设 置 完 并 点 确 定 按 钮 以 后,3DMed 将 利 用 分 割 后 的 数 据 进 行 三 维 重 建 并 显 示 图 成 功 加 载 的 SegPlugin 图 SegPlugin 的 运 行 界 面 403
411 12 开 发 3DMed 的 Plugin 12.4 小 结 本 章 给 出 了 如 何 开 发 3DMed 的 Plugin, 从 而 扩 充 3DMed 的 功 能 以 满 足 自 己 的 实 际 使 用 需 求 开 发 3DMed Plugin 有 两 种 途 径, 一 种 是 使 用 MITK 作 为 底 层 算 法 库, 这 种 方 式 可 以 充 分 利 用 MITK 提 供 的 功 能 和 各 种 算 法, 比 较 简 单 ; 另 外 一 种 不 使 用 MITK, 直 接 从 底 层 开 始 写 自 己 的 算 法, 这 种 适 用 于 在 用 户 手 上 没 有 MITK 的 情 况 下 扩 充 3DMed 的 功 能, 这 种 方 式 要 处 理 一 些 底 层 细 节, 因 此 相 对 于 第 一 种 方 法 来 说, 稍 微 复 杂 一 点 本 章 的 两 个 例 子 分 别 演 示 了 在 这 两 种 情 况 下 如 何 撰 写 自 己 的 3DMed Plugin 并 将 其 集 成 到 3DMed 中, 这 两 个 例 子 也 很 好 地 说 明 了 3DMed 对 这 两 种 方 法 都 支 持 的 很 好 用 户 可 以 根 据 自 己 的 实 际 情 况, 根 据 本 章 提 供 的 例 子, 来 编 写 3DMed 的 Plugin, 从 而 不 断 地 提 升 3DMed 的 功 能 404
412 405
413 附 录 A 医 学 影 像 数 据 集 1. 提 供 很 多 断 层 成 像 数 据 集 2. 爱 荷 华 大 学 放 射 系 提 供 的 多 套 三 维 医 学 影 像 数 据 集 3. 斯 坦 福 大 学 图 形 学 实 验 室 提 供 的 一 些 三 维 数 据 集 4. 美 国 虚 拟 人 体 数 据 集 5. 头 部 CT 数 据 6. 软 件 自 带 的 标 准 的 脑 部 数 据 406
414 附 录 B MITK 网 站 介 绍 一 如 何 获 取 MITK 和 3DMed? 登 录 在 Download 页 面 ( 下, 点 击 你 所 需 下 载 的 项 目, 然 后 会 弹 出 一 个 页 面 ( 如 下 图 所 示 ), 要 求 输 入 一 些 必 要 的 用 户 信 息, 包 括 姓 名 (Name) 单 位 (Company/Organization) 使 用 目 的 (Purpose) 电 子 邮 件 地 址 ( address) 电 话 号 码 (Phone Number), 其 中 前 4 项 是 必 填 项 下 面 是 一 个 询 问 是 否 加 入 邮 件 通 讯 列 表 的 选 择 项, 如 果 选 择 加 入, 就 可 以 在 第 一 时 间 收 到 我 们 的 更 新 信 息 最 后 在 接 受 许 可 协 议 ( I will accept the license! ) 前 打 勾 并 按 Submit 按 钮 提 交 注 册 单 就 可 以 正 常 下 载 了 必 须 提 醒 一 句, 目 前 MITK 和 3DMed 只 是 对 教 育 和 研 究 目 的 免 费 发 放, 切 勿 在 未 经 许 可 的 情 况 下 用 于 商 业 目 的, 违 者 必 究! 二 如 何 得 到 技 术 支 持? 由 于 MITK 和 3DMed 均 为 免 费 软 件, 所 以 我 们 通 过 Web 方 式 提 供 技 术 支 持 我 们 设 置 了 一 个 MITK 论 坛, 对 用 户 的 疑 问 意 见 以 及 建 议 作 出 回 应, 并 不 定 期 地 发 布 一 些 教 程 和 更 新 信 息, 以 满 足 用 户 需 求 同 时 MITK 论 坛 还 设 置 了 许 多 技 术 交 流 的 版 块, 为 MITK 和 3DMed 的 用 户 以 及 医 学 影 像 处 理 领 域 的 研 究 人 员 提 供 一 个 理 论 和 技 术 交 流 的 场 所, 并 希 望 以 此 来 推 动 MITK 和 3DMed 的 进 一 步 发 展 目 前 MITK 论 坛 对 普 通 用 户 也 是 开 放 的, 不 用 注 册 即 可 在 论 坛 提 问 或 发 表 看 法, 但 是 注 册 会 员 比 普 通 用 户 享 有 更 多 的 权 利, 比 如 即 时 从 论 坛 得 到 一 些 重 要 信 息 的 邮 件 通 知 和 获 得 以 附 件 方 式 提 供 的 更 新 程 序 等 407
415 论 坛 的 网 址 是 三 如 何 成 为 MITK 论 坛 的 注 册 会 员? 登 录 进 入 注 册 页 面, 填 写 一 些 必 要 信 息 ( 如 下 图 所 示 ), 其 中, 必 填 项 中 会 员 名 最 长 为 25 个 字 符 ( 中 文 每 字 按 2 字 符 记 ), 密 码 最 长 为 32 个 字 符 填 完 后 按 注 册 信 息 表 下 方 的 提 交 按 钮, 若 会 员 名 和 电 子 邮 件 地 址 未 与 已 注 册 会 员 重 复, 即 可 完 成 注 册, 否 则, 请 更 换 会 员 名 或 电 子 邮 件 地 址 重 新 注 册 四 如 何 在 论 坛 上 获 得 帮 助? 在 使 用 MITK 和 3DMed 软 件 的 过 程 中 遇 到 任 何 问 题 都 可 以 到 MITK 论 坛 上 寻 求 帮 助, 如 何 快 速 而 有 效 的 获 得 答 案 或 帮 助, 与 提 问 的 方 式 有 很 大 关 系 首 先, 我 们 建 议 您 在 提 问 之 前 先 尝 试 在 我 们 提 供 的 手 册 和 帮 助 文 档 中 寻 找 答 案, 这 些 资 源 包 含 在 MITK 网 站 的 Documentation 页 面 ( 和 您 所 下 载 的 软 件 包 中 如 果 您 所 提 的 问 题 可 以 很 容 易 的 在 这 些 文 档 中 找 到 答 案, 您 多 半 也 只 会 得 到 参 见 手 册 ( 或 文 档 ) 之 类 的 回 答 ; 其 次, 在 所 提 的 问 题 中 应 尽 可 能 将 您 所 遇 到 的 问 题 描 述 清 楚, 比 如 当 您 运 行 程 序 时 遇 到 异 常 退 出 的 情 况 ( 通 常 由 程 序 的 bug 引 起 ) 而 自 己 又 无 法 解 决 时, 您 在 所 提 的 问 题 中 应 当 尽 量 包 括 如 下 一 些 内 容 : (1) 出 错 情 况 的 描 述, 包 括 出 错 的 提 示 信 息, 当 时 程 序 的 运 行 参 数 等 ; (2) 出 错 程 序 所 读 取 的 外 部 数 据 的 信 息, 包 括 数 据 来 源 格 式 基 本 参 数 等, 最 好 能 提 供 原 始 数 据 文 件 ; (3) 出 错 程 序 的 运 行 环 境, 主 要 包 括 操 作 系 统 环 境 及 计 算 机 的 硬 件 配 置 等 另 外, 如 果 是 3DMed 运 行 时 的 错 误, 提 供 一 张 出 错 界 面 的 截 图 也 是 一 个 不 错 的 选 择 ; 408
416 第 三, 寻 找 合 适 的 版 面 提 出 自 己 的 问 题, 比 如 将 上 述 关 于 程 序 出 错 的 问 题 发 到 Bug Report 版, 而 将 关 于 MITK 中 某 个 类 使 用 方 法 的 疑 问 发 到 MITK 版 我 们 在 论 坛 设 立 了 许 多 主 题 版 面, 包 括 一 些 技 术 交 流 版 面, 找 到 合 适 的 板 面 提 出 你 的 疑 问 可 以 确 保 您 在 尽 量 短 的 时 间 内 得 到 回 应 同 时, 我 们 也 欢 迎 您 在 MITK 论 坛 发 表 您 的 意 见 建 议 心 得 体 会 以 及 交 流 一 些 与 医 学 影 像 处 理 相 关 的 技 术 问 题 等, 希 望 这 个 论 坛 能 成 为 一 个 我 们 共 同 拥 有 的 技 术 交 流 园 地 409
说 明 为 了 反 映 教 运 行 的 基 本 状 态, 为 校 和 院 制 定 相 关 政 策 和 进 行 教 建 设 与 改 革 提 供 据 依 据, 校 从 程 资 源 ( 开 类 别 开 量 规 模 ) 教 师 结 构 程 考 核 等 维 度, 对 2015 年 春 季 期 教 运 行 基
内 部 资 料 东 北 师 范 大 教 运 行 基 本 状 态 据 报 告 2015 年 春 季 期 教 务 处 2015 年 10 月 27 日 说 明 为 了 反 映 教 运 行 的 基 本 状 态, 为 校 和 院 制 定 相 关 政 策 和 进 行 教 建 设 与 改 革 提 供 据 依 据, 校 从 程 资 源 ( 开 类 别 开 量 规 模 ) 教 师 结 构 程 考 核 等 维 度,
何 秋 琳 张 立 春 视 觉 学 习 研 究 进 展 视 觉 注 意 视 觉 感 知
第 卷 第 期 年 月 开 放 教 育 研 究 何 秋 琳 张 立 春 华 南 师 范 大 学 未 来 教 育 研 究 中 心 广 东 广 州 随 着 图 像 化 技 术 和 电 子 媒 体 的 发 展 视 觉 学 习 也 逐 步 发 展 为 学 习 科 学 的 一 个 研 究 分 支 得 到 研 究 人 员 和 教 育 工 作 者 的 广 泛 关 注 基 于 此 作 者 试 图 对 视 觉 学 习
,,,,, :,, (.,, );, (, : ), (.., ;. &., ;.. &.., ;, ;, ),,,,,,, ( ) ( ),,,,.,,,,,, : ;, ;,.,,,,, (., : - ),,,, ( ),,,, (, : ),, :,
: 周 晓 虹 : - -., - - - -. :( ), -,.( ),,, -. - ( ).( ) ', -,,,,, ( ).( ),,, -., '.,, :,,,, :,,,, ,,,,, :,, (.,, );, (, : ), (.., ;. &., ;.. &.., ;, ;, ),,,,,,, ( ) ( ),,,,.,,,,,, : ;, ;,.,,,,, (., : - ),,,,
0 年 上 半 年 评 价 与 考 核 细 则 序 号 部 门 要 素 值 考 核 内 容 考 核 方 式 考 核 标 准 考 核 ( 扣 原 因 ) 考 评 得 3 安 全 生 产 目 30 无 同 等 责 任 以 上 道 路 交 通 亡 人 事 故 无 轻 伤 责 任 事 故 无 重 大 质 量
0 年 上 半 年 评 价 与 考 核 细 则 序 号 部 门 要 素 值 考 核 内 容 考 核 方 式 考 核 标 准 无 同 等 责 任 以 上 道 路 交 通 亡 人 事 故 3 无 轻 伤 责 任 事 故 目 标 30 及 事 无 重 大 质 量 工 作 过 失 故 管 无 其 他 一 般 责 任 事 故 理 在 公 司 文 明 环 境 创 建 中, 无 工 作 过 失 及 被 追 究 的
<433A5C446F63756D656E747320616E642053657474696E67735C41646D696E6973747261746F725CD7C0C3E65CC2DBCEC4CFB5CDB3CAB9D3C3D6B8C4CFA3A8BCF2BBAFA3A95CCAB9D3C3D6B8C4CF31302D31392E646F63>
( 一 ) 系 统 整 体 操 作 流 程 简 述 3 ( 二 ) 系 统 中 各 角 色 操 作 功 能 说 明 5 1. 学 院 管 理 员 5 2. 教 学 院 长 8 3. 指 导 教 师 10 4. 答 辩 组 组 长 12 5. 学 生 12 6. 系 统 管 理 员 15 ( 一 ) 论 文 系 统 常 见 问 题 16 ( 二 ) 论 文 查 重 常 见 问 题 22 1 2 主
文 化 记 忆 传 统 创 新 与 节 日 遗 产 保 护 根 据 德 国 学 者 阿 斯 曼 的 文 化 记 忆 理 论 仪 式 与 文 本 是 承 载 文 化 记 忆 的 两 大 媒 体 在 各 种 仪 式 行 为 中 节 日 以 其 高 度 的 公 共 性 有 组 织 性 和 历 史 性 而 特 别 适 用 于 文 化 记 忆 的 储 存 和 交 流 节 日 的 文 化 功 能 不 仅 在 于
评 委 : 李 炎 斌 - 个 人 技 术 标 资 信 标 初 步 审 查 明 细 表 序 号 投 标 单 位 投 标 函 未 按 招 标 文 件 规 定 填 写 漏 填 或 内 容 填 写 错 误 的 ; 不 同 投 标 人 的 投 标 文 件 由 同 一 台 电 脑 或 同 一 家 投 标 单
评 委 : 李 炎 斌 - 个 人 清 标 评 审 明 细 表 评 审 因 素 序 号 投 标 单 位 清 标 评 审 1 深 圳 市 创 捷 科 技 有 限 合 格 2 四 川 川 大 智 胜 软 件 股 份 有 限 合 格 3 北 京 航 天 长 峰 科 技 工 业 集 团 有 限 公 司 合 格 4 深 圳 中 兴 力 维 技 术 有 限 合 格 5 深 圳 键 桥 通 讯 技 术 股 份 有
龚 亚 夫 在 重 新 思 考 基 础 教 育 英 语 教 学 的 理 念 一 文 中 援 引 的 观 点 认 为 当 跳 出 本 族 语 主 义 的 思 维 定 式 后 需 要 重 新 思 考 许 多 相 连 带 的 问 题 比 如 许 多 发 音 的 细 微 区 别 并 不 影 响 理 解 和
语 音 语 篇 语 感 语 域 林 大 津 毛 浩 然 改 革 开 放 以 来 的 英 语 热 引 发 了 大 中 小 学 英 语 教 育 整 体 规 划 问 题 在 充 分 考 虑 地 区 学 校 和 个 体 差 异 以 及 各 家 观 点 的 基 础 上 遵 循 实 事 求 是 逐 级 定 位 逐 层 分 流 因 材 施 教 的 原 则 本 研 究 所 倡 导 的 语 音 语 篇 语 感 语 域
一 公 共 卫 生 硕 士 专 业 学 位 论 文 的 概 述 学 位 论 文 是 对 研 究 生 进 行 科 学 研 究 或 承 担 专 门 技 术 工 作 的 全 面 训 练, 是 培 养 研 究 生 创 新 能 力, 综 合 运 用 所 学 知 识 发 现 问 题, 分 析 问 题 和 解 决
上 海 市 公 共 卫 生 硕 士 专 业 学 位 论 文 基 本 要 求 和 评 价 指 标 体 系 ( 试 行 ) 上 海 市 学 位 委 员 会 办 公 室 二 O 一 二 年 三 月 一 公 共 卫 生 硕 士 专 业 学 位 论 文 的 概 述 学 位 论 文 是 对 研 究 生 进 行 科 学 研 究 或 承 担 专 门 技 术 工 作 的 全 面 训 练, 是 培 养 研 究 生 创
<4D F736F F D D323630D6D0B9FAD3A6B6D4C6F8BAF2B1E4BBAFB5C4D5FEB2DFD3EBD0D0B6AF C4EAB6C8B1A8B8E6>
中 国 应 对 气 候 变 化 的 政 策 与 行 动 2013 年 度 报 告 国 家 发 展 和 改 革 委 员 会 二 〇 一 三 年 十 一 月 100% 再 生 纸 资 源 目 录 前 言... 1 一 应 对 气 候 变 化 面 临 的 形 势... 3 二 完 善 顶 层 设 计 和 体 制 机 制... 4 三 减 缓 气 候 变 化... 8 四 适 应 气 候 变 化... 20
《C语言基础入门》课程教学大纲
C 语 言 开 发 入 门 教 程 课 程 教 学 大 纲 课 程 编 号 :201409210011 学 分 :5 学 分 学 时 :58 学 时 ( 其 中 : 讲 课 学 时 :39 学 时 上 机 学 时 :19 学 时 ) 先 修 课 程 : 计 算 机 导 论 后 续 课 程 :C++ 程 序 设 计 适 用 专 业 : 信 息 及 其 计 算 机 相 关 专 业 开 课 部 门 : 计
张 荣 芳 中 山 大 学 历 史 系 广 东 广 州 张 荣 芳 男 广 东 廉 江 人 中 山 大 学 历 史 系 教 授 博 士 生 导 师 我 们 要 打 破 以 前 学 术 界 上 的 一 切 偶 像 以 前 学 术 界 的 一 切 成 见 屏 除 我 们 要 实 地 搜 罗 材 料 到 民 众 中 寻 方 言 到 古 文 化 的 遗 址 去 发 掘 到 各 种 的 人 间 社 会 去
评 委 : 徐 岩 宇 - 个 人 技 术 标 资 信 标 初 步 审 查 明 细 表 序 号 投 标 单 位 投 标 函 未 按 招 标 文 件 规 定 填 写 漏 填 或 内 容 填 写 错 误 的 ; 不 同 投 标 人 的 投 标 文 件 由 同 一 台 电 脑 或 同 一 家 投 标 单
评 委 : 徐 岩 宇 - 个 人 清 标 评 审 明 细 表 评 审 因 素 序 号 投 标 单 位 清 标 评 审 1 深 圳 市 创 捷 科 技 有 限 合 格 2 四 川 川 大 智 胜 软 件 股 份 有 限 合 格 3 北 京 航 天 长 峰 科 技 工 业 集 团 有 限 公 司 合 格 4 深 圳 中 兴 力 维 技 术 有 限 合 格 5 深 圳 键 桥 通 讯 技 术 股 份 有
Microsoft Word - 文件汇编.doc
北 京 市 中 医 管 理 局 二 一 五 年 四 月 ... 1... 18 2015... 30 京 中 医 政 字 [2014]160 号 1 2 一 充 分 认 识 中 医 健 康 乡 村 建 设 工 作 的 重 要 意 义 二 建 立 健 全 工 作 保 障 机 制 2014 12 15 三 做 好 工 作 启 动 的 准 备 事 宜 1 2014 12 15 5-10 2014 12 15
(2015-2016-2)-0004186-04205-1 140242 信 号 与 系 统 Ⅰ 学 科 基 础 必 修 课 37 37 1 教 203 17 周 2016 年 06 月 13 日 (08:00-09:35) (2015-2016-2)-0004186-04205-1 141011
关 于 2015-2016 学 年 第 二 学 期 期 末 周 内 考 试 时 间 地 点 安 排 选 课 课 号 班 级 名 称 课 程 名 称 课 程 性 质 合 考 人 数 实 际 人 数 考 试 教 室 考 试 段 考 试 时 间 (2015-2016-2)-0006178-04247-1 130101 测 试 技 术 基 础 学 科 基 础 必 修 课 35 35 1 教 401 17 周
科 学 出 版 社 科 学 出 版 社 前 言 本 书 是 针 对 普 通 高 等 院 校 经 济 类 和 工 商 管 理 类 本 科 专 业 财 务 管 理 学 的 教 学 需 求, 结 合 教 育 部 经 济 管 理 类 本 科 财 务 管 理 学 课 程 教 学 大 纲 编 写 而 成 的 本 书 执 笔 者 都 是 长 期 工 作 在 财 务 管 理 教 学 一 线 的 专 业 教 师,
I
机 电 一 级 注 册 建 造 师 继 续 教 育 培 训 广 东 培 训 点 网 上 报 名 操 作 使 用 手 册 (2013 年 1 月, 第 一 版 ) 第 一 章 个 人 注 册 与 个 人 信 息 管 理 1. 个 人 注 册 ( 请 每 人 只 申 请 一 个 注 册 号, 如 果 单 位 批 量 报 班 单 位 帮 申 请 注 册, 不 需 个 人 再 注 册 ) 首 次 报 班,
1600 1000 40 50 2030 2000 采 取 行 动 的 机 会 90% 开 拓 成 功 的 道 路 2
简 略 版 本 :2015 3 10 2016 2021 全 球 卫 生 部 门 病 毒 性 肝 炎 战 略 2016 2021 2015 3 12 2012 2010 2014 2015 2016 2021 140 55% 35% 5 15% 5 20% 2.4 1.3 1.5 1 1600 1000 40 50 2030 2000 采 取 行 动 的 机 会 90% 开 拓 成 功 的 道 路
修改版-操作手册.doc
职 称 信 息 系 统 升 级 指 南 须 使 用 IE9 及 其 以 上 版 本 浏 览 器 或 谷 歌 浏 览 器 登 录 www.njrs.gov.cn 南 京 市 职 称 ( 职 业 资 格 ) 工 作 领 导 小 组 办 公 室 2016 年 5 月 目 录 一 申 报 人 员 操 作 指 南...1 1.1 职 称 初 定 申 报...1 1.1.1 职 称 初 定 基 础 信 息 填
抗 战 时 期 国 民 政 府 的 银 行 监 理 体 制 探 析 % # % % % ) % % # # + #, ) +, % % % % % % % %
抗 战 时 期 国 民 政 府 的 银 行 监 理 体 制 探 析 王 红 曼 抗 战 时 期 国 民 政 府 为 适 应 战 时 经 济 金 融 的 需 要 实 行 由 财 政 部 四 联 总 处 中 央 银 行 等 多 家 机 构 先 后 共 同 参 与 的 多 元 化 银 行 监 理 体 制 对 战 时 状 态 下 的 银 行 发 展 与 经 营 安 全 进 行 了 大 规 模 的 设 计 与
Microsoft Word - 第7章 图表反转形态.doc
第 七 章 图 表 反 转 形 态 我 们 知 道 市 场 趋 势 共 有 三 种 : 上 升 趋 势 下 降 趋 势 和 横 向 整 理 市 场 的 价 格 波 动 都 是 运 行 在 这 三 种 趋 势 中, 所 有 的 走 势 都 是 这 三 种 趋 势 的 排 列 组 合 如 图 市 场 趋 势 结 构 示 意 图 7-1 所 示 市 场 趋 势 结 构 示 意 图 7-1 图 市 场 趋
深圳市新亚电子制程股份有限公司
证 券 代 码 :002388 证 券 简 称 : 新 亚 制 程 公 告 编 号 :2016-053 深 圳 市 新 亚 电 子 制 程 股 份 有 限 公 司 2016 年 第 二 次 临 时 股 东 大 会 决 议 公 告 本 公 司 及 董 事 会 全 体 成 员 保 证 公 告 内 容 真 实 准 确 和 完 整, 不 存 在 虚 假 记 载 误 导 性 陈 述 或 者 重 大 遗 漏 特
新, 各 地 各 部 门 ( 单 位 ) 各 文 化 事 业 单 位 要 高 度 重 视, 切 实 加 强 领 导, 精 心 组 织 实 施 要 根 据 事 业 单 位 岗 位 设 置 管 理 的 规 定 和 要 求, 在 深 入 调 查 研 究 广 泛 听 取 意 见 的 基 础 上, 研 究 提
广 西 壮 族 自 治 区 人 事 厅 广 西 壮 族 自 治 区 文 化 厅 文 件 桂 人 发 2009 42 号 关 于 印 发 广 西 壮 族 自 治 区 文 化 事 业 单 位 岗 位 设 置 结 构 比 例 指 导 标 准 的 通 知 各 市 人 事 局 文 化 局, 区 直 各 部 门 ( 单 位 ): 根 据 人 事 部 印 发 的 事 业 单 位 岗 位 设 置 管 理 试 行 办
untitled
( 一 ) 深 刻 认 识 学 习 教 育 的 重 大 意 义 : - 3 - ( 二 ) 明 确 学 习 教 育 的 任 务 目 标 ( 三 ) 把 握 特 点 方 法 - 4 - ( 四 ) 坚 持 六 项 原 则 在 - 5 - ( 五 ) 着 力 解 决 问 题 - 6 - - 7 - - 8 - ( 一 ) 学 党 章 党 规, 进 一 步 明 确 党 员 标 准 树 立 行 为 规 范
课程类 别
美 声 演 唱 方 向 培 养 方 案 一 培 养 目 标 本 方 向 要 求 学 生 德 智 体 美 全 面 发 展, 培 养 能 在 文 艺 团 体 从 事 声 乐 演 唱 及 能 在 艺 术 院 校 从 事 本 方 向 教 学 的 高 级 门 人 才 二 培 养 规 格 本 方 向 学 生 应 系 统 掌 握 声 乐 演 唱 方 面 的 理 论 和 技 能, 具 备 较 高 的 声 乐 演 唱
ETF、分级基金规模、份额变化统计20130816
ETF 分 级 基 金 规 模 份 额 变 化 统 计 截 至 上 周 末, 全 市 场 股 票 型 ETF 规 模 约 1451 亿, 份 额 约 1215 亿,ETF 总 份 额 及 规 模 的 周 变 动 值 分 别 为 -23-44 亿, 份 额 与 规 模 均 下 降 ; 分 级 基 金 规 模 约 438 亿, 份 额 572 亿, 总 份 额 及 规 模 的 周 变 动 值 分 别 为
<433A5C55736572735C6B73625C4465736B746F705CB9FABCCAD6D0D2BDD2A9D7A8D2B5B8DFBCB6BCBCCAF5D6B0B3C6C6C0C9F3C9EAC7EBD6B8C4CFA3A832303136CDA8D3C3B0E6A3A92E646F63>
附 件 1 国 际 中 药 专 业 高 级 技 术 职 称 评 审 条 件 及 报 名 材 料 一 系 列 ( 一 ) 中 1 高 级 专 科 ( 副 ) 高 级 专 科 ( 副 ) 1 取 得 中 专 科 职 称 后, 独 立 从 事 中 临 床 实 践 5 年 以 上 2 取 得 中 博 士 学 位 后, 临 床 实 践 2 年 以 上 3 取 得 中 硕 士 学 位 后, 临 床 实 践 7
一 从 分 封 制 到 郡 县 制 一 从 打 虎 亭 汉 墓 说 起
县 乡 两 级 的 政 治 体 制 改 革 如 何 建 立 民 主 的 合 作 新 体 制 县 乡 人 大 运 行 机 制 研 究 课 题 组 引 言 一 从 分 封 制 到 郡 县 制 一 从 打 虎 亭 汉 墓 说 起 二 密 县 在 周 初 是 两 个 小 国 密 国 和 郐 国 三 密 县 的 第 一 任 县 令 卓 茂 四 明 清 时 代 的 密 县 二 从 集 中 的 动 员 体
马 克 思 主 义 公 正 观 的 基 本 向 度 及 方 法 论 原 则!! # #
马 克 思 主 义 公 正 观 的 基 本 向 度 及 方 法 论 原 则 马 俊 峰 在 社 会 公 正 问 题 的 大 讨 论 中 罗 尔 斯 诺 齐 克 哈 耶 克 麦 金 泰 尔 等 当 代 西 方 思 想 家 的 论 述 被 反 复 引 用 和 申 说 而 将 马 克 思 恩 格 斯 等 经 典 作 家 的 观 点 置 于 一 种 被 忽 视 甚 至 被 忘 却 的 状 态 形 成 这 种
名 称 生 命 科 学 学 院 083001 环 境 科 学 1 生 物 学 仅 接 收 院 内 调 剂, 初 试 分 数 满 足 我 院 生 物 学 复 试 最 低 分 数 线 生 命 科 学 学 院 071300 生 态 学 5 生 态 学 或 生 物 学 生 命 科 学 学 院 040102
华 中 师 范 大 学 2016 年 接 收 校 内 外 优 秀 硕 士 研 究 生 调 剂 信 息 表 名 称 经 济 与 工 商 管 理 学 院 020101 政 治 经 济 学 1 经 济 学 类 毕 业 学 校 与 报 考 学 校 不 低 于 我 校 办 学 层 次 经 济 与 工 商 管 理 学 院 020105 世 界 经 济 学 1 经 济 学 类 毕 业 学 校 与 报 考 学 校
( 二 ) 现 行 统 一 高 考 制 度 不 利 于 培 养 人 的 创 新 精 神,,,,,,,,,,,,, [ ],,,,,,,,,,, :, ;,,,,,,? ( 三 ) 现 行 统 一 高 考 制 度 不 利 于 全 体 学 生 都 获 得 全 面 发 展,, [ ],,,,,,,,,,,
( ) ( )... 李 雪 岩, 龙 耀 (. 广 西 民 族 大 学 商 学 院, 广 西 南 宁 ;. 中 山 大 学 教 育 学 院, 广 东 广 州 ) : 高 等 教 育 是 专 业 教 育 高 考 是 为 高 等 教 育 服 务 的, 是 为 高 等 专 业 教 育 选 拔 有 专 业 培 养 潜 质 的 人 才 现 行 高 考 制 度 忽 略 专 业 潜 质 的 因 素, 过 份 强
18 上 报 该 学 期 新 生 数 据 至 阳 光 平 台 第 一 学 期 第 四 周 至 第 六 周 19 督 促 学 习 中 心 提 交 新 增 专 业 申 请 第 一 学 期 第 四 周 至 第 八 周 20 编 制 全 国 网 络 统 考 十 二 月 批 次 考 前 模 拟 题 第 一 学
1 安 排 组 织 全 国 网 络 统 考 九 月 批 次 网 上 考 前 辅 导 第 一 学 期 第 一 周 统 考 考 前 半 个 月 2 下 发 全 国 网 络 统 考 九 月 批 次 准 考 证 第 一 学 期 第 一 周 导 出 下 半 年 成 人 本 科 学 士 学 位 英 语 统 一 考 试 报 考 3 信 息 第 一 学 期 第 一 周 4 教 学 计 划 和 考 试 计 划 上 网,
随着执业中医师资格考试制度的不断完善,本着为我校中医学专业认证服务的目的,本文通过对我校中医类毕业生参加2012年和2013年的中医执业医师考试成绩及通过率、掌握率进行分析,并与全国的平均水平进行差异比较分析,以此了解我校执业中医师考试的现状,进而反映我校中医类课程总体教学水平,发现考核知识模块教学中存在的不足,反馈给相关学院和教学管理部门,以此提高教学和管理水平。
2012-2013 中 医 类 别 执 业 医 师 综 合 笔 试 成 绩 分 析 反 馈 报 告 教 务 处 二 零 一 三 年 三 月 1 目 录 1 前 言 3 2 2012-2013 中 医 类 别 执 业 医 师 综 合 笔 试 成 绩 分 析 反 馈 报 告 4 附 件 1:2012 年 中 医 类 别 医 师 资 格 综 合 笔 试 院 校 学 科 成 绩 分 析 报 告 附 件 2:2013
金 不 少 于 800 万 元, 净 资 产 不 少 于 960 万 元 ; (3) 近 五 年 独 立 承 担 过 单 项 合 同 额 不 少 于 1000 万 元 的 智 能 化 工 程 ( 设 计 或 施 工 或 设 计 施 工 一 体 ) 不 少 于 2 项 ; (4) 近 三 年 每 年
工 程 设 计 与 施 工 资 质 标 准 一 总 则 建 筑 智 能 化 工 程 设 计 与 施 工 资 质 标 准 ( 一 ) 为 了 加 强 对 从 事 建 筑 智 能 化 工 程 设 计 与 施 工 企 业 的 管 理, 维 护 建 筑 市 场 秩 序, 保 证 工 程 质 量 和 安 全, 促 进 行 业 健 康 发 展, 结 合 建 筑 智 能 化 工 程 的 特 点, 制 定 本 标
中 国 软 科 学 年 第 期!!!
山 寨 模 式 的 形 成 机 理 及 其 对 组 织 创 新 的 启 示 山 寨 模 式 的 形 成 机 理 及 其 对 组 织 创 新 的 启 示 陶 厚 永 李 燕 萍 骆 振 心 武 汉 大 学 经 济 与 管 理 学 院 武 汉 大 学 中 国 产 学 研 合 作 问 题 研 究 中 心 湖 北 武 汉 北 京 大 学 经 济 研 究 所 光 华 天 成 博 士 后 工 作 站 北 京 本
中 中 中 中 部 中 岗 位 条 件 历 其 它 历 史 师 地 理 师 生 物 师 体 与 健 康 师 04 05 06 07 从 事 中 历 史 工 从 事 中 地 理 工 从 事 中 生 物 工 从 事 中 体 与 健 康 工 2. 课 程 与 论 ( 历 史 ); 2. 科 ( 历 史 )
中 中 中 部 中 26 年 系 统 事 业 公 开 计 划 岗 位 条 件 历 其 它 数 师 英 语 师 物 理 师 02 0 从 事 中 数 工 从 事 中 英 语 工 从 事 中 物 理 工 2. 课 程 与 论 ( 数 ); 2. 科 ( 数 );. 数 ; 4. 基 础 数 ; 5. 计 算 数 ; 6. 概 率 论 与 数 理 统 计 ; 7. 应 用 数 ; 8. 数. 课 程 与
2006年顺德区高中阶段学校招生录取分数线
2014 年 顺 德 区 高 中 阶 段 学 校 考 试 提 前 批 第 一 批 第 二 批 学 校 录 取 根 据 佛 山 市 办 提 供 的 考 生 数 据, 现 将 我 区 2014 年 高 中 阶 段 学 校 考 试 提 前 批 第 一 批 第 二 批 学 校 的 录 取 公 布 如 下 : 一 顺 德 一 中 录 取 分 第 1 志 愿, 总 分 585, 综 合 表 现 评 价 A, 考
¹ º ¹ º 农 业 流 动 人 口 是 指 户 口 性 质 为 农 业 户 口 在 流 入 地 城 市 工 作 生 活 居 住 一 个 月 及 以 上 的 流 动 人 口 非 农 流 动 人 口 是 指 户 口 性 质 为 非 农 户 口 在 流 入 地 城 市 工 作 生 活 居 住 一 个
¹ 改 革 开 放 年 来 人 口 流 动 规 模 持 续 增 加 对 我 国 社 会 经 济 的 持 续 发 展 起 到 了 重 要 作 用 为 全 面 了 解 我 国 流 动 人 口 生 存 状 况 准 确 把 握 流 动 人 口 发 展 规 律 和 趋 势 不 断 加 强 流 动 人 口 服 务 管 理 引 导 人 口 有 序 流 动 合 理 分 布 国 家 人 口 计 生 委 于 年 月 启
工 程 勘 察 资 质 标 准 根 据 建 设 工 程 勘 察 设 计 管 理 条 例 和 建 设 工 程 勘 察 设 计 资 质 管 理 规 定, 制 定 本 标 准 一 总 则 ( 一 ) 本 标 准 包 括 工 程 勘 察 相 应 专 业 类 型 主 要 专 业 技 术 人 员 配 备 技 术
住 房 和 城 乡 建 设 部 关 于 印 发 工 程 勘 察 资 质 标 准 的 通 知 建 市 [2013]9 号 各 省 自 治 区 住 房 和 城 乡 建 设 厅, 北 京 市 规 划 委, 天 津 上 海 市 建 设 交 通 委, 重 庆 市 城 乡 建 设 委, 新 疆 生 产 建 设 兵 团 建 设 局, 总 后 基 建 营 房 部 工 程 局, 国 务 院 有 关 部 门 建 设 司,
反 学 校 文 化 与 阶 级 再 生 产 小 子 与 子 弟 之 比 较 周 潇 作 者 通 过 对 北 京 某 打 工 子 弟 学 校 的 田 野 调 查 后 发 现 在 农 民 工 子 弟 中 间 盛 行 着 类 似 学 做 工 中 所 描 述 的 工 人 阶 级 小 子 的 反 学 校 文 化 但 是 由 于 制 度 安 排 与 社 会 条 件 的 差 异 子 弟 与 小 子 的 反 学 校
编号:
编 号 : 企 业 内 高 技 能 人 才 培 养 评 价 实 施 方 案 ( 仅 适 用 于 企 业 特 有 行 业 特 有 工 种 ) 实 施 单 位 ( 公 章 ) 申 报 日 期 年 _ 月 日 1 企 业 内 高 技 能 人 才 培 养 评 价 项 目 实 施 方 案 申 报 表 项 目 名 称 等 级 项 目 性 质 课 时 申 报 单 位 联 系 人 通 讯 地 址 电 话 手 机 电
西 南 民 族 学 院 学 报 哲 学 社 会 科 学 版 第 卷 资 料 来 源 中 国 统 计 年 鉴 年 年 新 中 国 五 十 年 统 计 资 料 汇 编 中 国 人 口 统 计 年 鉴 年 数 据 资 料 来 源 中 国 统 计 年 鉴 中 国 统 计 出 版 社 年 版 资 料 来 源
郑 长 德 教 育 的 发 展 人 力 资 源 的 开 发 是 决 定 西 部 民 族 地 区 未 来 发 展 的 关 键 因 素 之 一 是 实 施 西 部 大 开 发 战 略 提 高 其 经 济 竞 争 力 和 综 合 实 力 的 重 要 保 障 本 文 从 西 部 民 族 地 区 教 育 发 展 的 现 状 入 手 指 出 中 华 人 民 共 和 国 成 立 多 年 来 西 部 民 族 地 区
西 南 大 学 硕 士 学 位 论 文 网 络 购 物 动 机 问 卷 的 编 制 及 实 测 姓 名 : 曹 建 英 申 请 学 位 级 别 : 硕 士 专 业 : 基 础 心 理 学 指 导 教 师 : 张 进 辅 20090401 网 络 购 物 动 机 问 卷 的
全国建筑市场注册执业人员不良行为记录认定标准(试行).doc
- 1 - - 2 - 附 件 全 国 建 筑 市 场 注 册 执 业 人 员 不 良 记 录 认 定 标 准 ( 试 行 ) 说 明 为 了 完 善 建 筑 市 场 注 册 执 业 人 员 诚 信 体 系 建 设, 规 范 执 业 和 市 场 秩 序, 依 据 相 关 法 律 法 规 和 部 门 规 章, 根 据 各 行 业 特 点, 我 部 制 订 了 全 国 建 筑 市 场 注 册 执 业 人
一 开 放 性 的 政 策 与 法 规 二 两 岸 共 同 的 文 化 传 承 三 两 岸 高 校 各 自 具 有 专 业 优 势 远 见 杂 志 年 月 日
河 北 师 范 大 学 学 报 新 时 期 海 峡 两 岸 高 校 开 放 招 生 问 题 探 讨 郑 若 玲 王 晓 勇 海 峡 两 岸 高 校 开 放 招 生 是 新 时 期 推 进 海 峡 两 岸 高 等 教 育 交 流 与 合 作 的 重 要 尝 试 系 统 梳 理 改 革 开 放 以 来 两 岸 招 生 政 策 与 就 学 人 数 发 展 变 化 的 历 史 进 程 可 发 现 促 进 两
3 月 30 日 在 中 国 证 券 报 上 海 证 券 报 证 券 时 报 证 券 日 报 和 上 海 证 券 交 易 所 网 站 上 发 出 召 开 本 次 股 东 大 会 公 告, 该 公 告 中 载 明 了 召 开 股 东 大 会 的 日 期 网 络 投 票 的 方 式 时 间 以 及 审
北 京 市 君 致 律 师 事 务 所 关 于 浪 潮 软 件 股 份 有 限 公 司 2015 年 度 股 东 大 会 的 法 律 意 见 书 致 : 浪 潮 软 件 股 份 有 限 公 司 北 京 市 君 致 律 师 事 务 所 ( 以 下 简 称 本 所 ) 受 浪 潮 软 件 股 份 有 限 公 司 ( 以 下 简 称 公 司 ) 的 委 托, 指 派 律 师 出 席 2016 年 4 月
导 数 和 微 分 的 概 念 导 数 的 几 何 意 义 和 物 理 意 义 函 数 的 可 导 性 与 连 续 性 之 间 的 关 系 平 面 曲 线 的 切 线 和 法 线 导 数 和 微 分 的 四 则 运 算 基 本 初 等 函 数 的 导 数 复 合 函 数 反 函 数 隐 函 数 以
2015 年 考 研 数 学 二 考 试 大 纲 考 试 科 目 : 高 等 数 学 线 性 代 数 考 试 形 式 和 试 卷 结 构 一 试 卷 满 分 及 考 试 时 间 试 卷 满 分 为 150 分, 考 试 时 间 为 180 分 钟. 二 答 题 方 式 答 题 方 式 为 闭 卷 笔 试. 三 试 卷 内 容 结 构 高 等 教 学 约 78% 线 性 代 数 约 22% 四 试 卷
<4D6963726F736F667420576F7264202D2032303133C4EAB9A4B3CCCBB6CABFCAFDD1A7D7A8D2B5BFCEBFBCCAD4B4F3B8D9D3EBD2AAC7F3>
工 程 硕 士 数 学 考 试 大 纲 与 要 求 ( 包 括 高 等 数 学 和 线 性 代 数 ) 一 函 数 极 限 与 连 续 第 一 部 分 : 高 等 数 学 考 试 内 容 函 数 的 概 念 及 表 示 法 函 数 的 有 界 性 单 调 性 周 期 性 和 奇 偶 性 复 合 函 数 反 函 数 分 段 函 数 和 隐 函 数 基 本 初 等 函 数 的 性 质 及 其 图 形 初
!!!!!
美 国 旧 金 山 湾 区 田 野 调 查 札 记 !!!!! ! 个 案 一 男 士 年 龄 岁 籍 贯 沈 阳! !! 个 案 二 女 士 年 龄 岁 籍 贯 沈 阳!! !!! 一 新 古 典 经 济 学 移 民 理 论 的 解 释!! 二 制 度 层 面 的 原 因! 三 社 会 资 本 理 论 与 东 北 人 移 民 网 络 !!!!!! 四 社 会 关 系 网 络 资 源 配 置 理 论
作 为 生 产 者 式 文 本 的 女 性 主 义 通 俗 小 说 梅 丽 本 文 借 鉴 文 化 研 究 理 论 家 约 翰 费 斯 克 的 生 产 者 式 文 本 这 一 概 念 考 察 女 性 主 义 通 俗 小 说 的 文 本 特 征 写 作 策 略 和 微 观 政 治 意 义 女 性 主 义 通 俗 小 说 通 过 对 传 统 通 俗 小 说 的 挪 用 和 戏 仿 传 播 女 性 主 义
2014年中央财经大学研究生招生录取工作简报
2015 年 中 央 财 经 大 学 研 究 生 招 生 录 取 工 作 简 报 一 硕 士 研 究 生 招 生 录 取 情 况 2015 年 共 有 8705 人 报 考 我 校 硕 士 研 究 生, 其 中 学 术 型 研 究 生 报 考 3657 人, 专 业 硕 士 研 究 生 报 考 5048 人 ; 总 报 考 人 数 较 2014 年 增 长 1.4%, 学 术 型 报 考 人 数 较
抗 日 战 争 研 究 年 第 期
田 子 渝 武 汉 抗 战 时 期 是 国 共 第 二 次 合 作 的 最 好 时 期 在 国 共 合 作 的 基 础 上 出 现 了 抗 日 救 亡 共 御 外 侮 的 局 面 这 个 大 好 局 面 的 出 现 与 中 共 长 江 局 的 丰 功 伟 绩 是 分 不 开 的 但 长 期 以 来 由 于 有 一 个 王 明 的 右 倾 错 误 直 接 影 响 了 对 它 的 全 面 科 学 准 确
HSK( 一 级 ) 考 查 考 生 的 日 常 汉 语 应 用 能 力, 它 对 应 于 国 际 汉 语 能 力 标 准 一 级 欧 洲 语 言 共 同 参 考 框 架 (CEF) A1 级 通 过 HSK( 一 级 ) 的 考 生 可 以 理 解 并 使 用 一 些 非 常 简 单 的 汉 语
新 汉 语 水 平 考 试 HSK 为 使 汉 语 水 平 考 试 (HSK) 更 好 地 服 务 于 汉 语 学 习 者, 中 国 国 家 汉 办 组 织 中 外 汉 语 教 学 语 言 学 心 理 学 和 教 育 测 量 学 等 领 域 的 专 家, 在 充 分 调 查 了 解 海 外 实 际 汉 语 教 学 情 况 的 基 础 上, 吸 收 原 有 HSK 的 优 点, 借 鉴 近 年 来 国
上证指数
上 证 与 修 正 方 法 一 ( 一 ) 计 算 公 式 1. 上 证 指 数 系 列 均 采 用 派 许 加 权 综 合 价 格 指 数 公 式 计 算 2. 上 证 180 指 数 上 证 50 指 数 等 以 成 份 股 的 调 整 股 本 数 为 权 数 进 行 加 权 计 算, 计 算 公 式 为 : 报 告 期 指 数 =( 报 告 期 样 本 股 的 调 整 市 值 / 基 期 )
<4D6963726F736F667420576F7264202D20B9D8D3DAB0BABBAAA3A8C9CFBAA3A3A9D7D4B6AFBBAFB9A4B3CCB9C9B7DDD3D0CFDEB9ABCBBE32303132C4EAC4EAB6C8B9C9B6ABB4F3BBE1B7A8C2C9D2E2BCFBCAE92E646F6378>
上 海 德 载 中 怡 律 师 事 务 所 关 于 昂 华 ( 上 海 ) 自 动 化 工 程 股 份 有 限 公 司 二 〇 一 二 年 年 度 股 东 大 会 法 律 意 见 书 上 海 德 载 中 怡 律 师 事 务 所 上 海 市 银 城 中 路 168 号 上 海 银 行 大 厦 1705 室 (200120) 电 话 :8621-5012 2258 传 真 :8621-5012 2257
第二讲 数列
Togisu XueD Persolized Eduio Developme Ceer 高 考 中 不 等 式 问 题 的 解 决 方 法 通 润 达 久 王 力 前 言 : 近 年 来 不 等 式 问 题 正 越 来 越 多 的 出 现 在 调 研 题 和 高 考 试 题 中 而 且 大 多 出 现 在 江 苏 高 考 的 填 空 压 轴 题 中 是 高 考 考 察 的 重 点 和 难 点 由 于
收 入 支 出 项 目 2016 年 预 算 项 目 2016 年 预 算 预 算 01 表 单 位 : 万 元 ( 保 留 两 位 小 数 ) 一 公 共 财 政 预 算 拨 款 50.06 一 人 员 经 费 23.59 1 一 般 财 力 50.06 1 人 员 支 出 21.95 2 成 品
100.12 2016 年 龙 岩 市 部 门 预 算 表 报 送 日 期 : 年 月 日 单 位 负 责 人 签 章 : 财 务 负 责 人 签 章 : 制 表 人 签 章 : 收 入 支 出 项 目 2016 年 预 算 项 目 2016 年 预 算 预 算 01 表 单 位 : 万 元 ( 保 留 两 位 小 数 ) 一 公 共 财 政 预 算 拨 款 50.06 一 人 员 经 费 23.59
第2章 数据类型、常量与变量
第 2 章 数 据 类 型 常 量 与 变 量 在 计 算 机 程 序 中 都 是 通 过 值 (value) 来 进 行 运 算 的, 能 够 表 示 并 操 作 值 的 类 型 为 数 据 类 型 在 本 章 里 将 会 介 绍 JavaScript 中 的 常 量 (literal) 变 量 (variable) 和 数 据 类 型 (data type) 2.1 基 本 数 据 类 型 JavaScript
!!!!!!!!!!
有 限 理 性 动 物 精 神 及 市 场 崩 溃 对 情 绪 波 动 与 交 易 行 为 的 实 验 研 究 林 树 俞 乔 资 本 市 场 的 经 验 表 明 市 场 参 与 主 体 投 资 者 的 情 绪 波 动 对 资 产 交 易 与 价 格 决 定 产 生 了 不 可 忽 视 的 影 响 但 是 现 有 文 献 尚 缺 乏 对 这 一 重 要 因 素 的 研 究 因 此 本 文 的 目 的
物 流 从 业 人 员 职 业 能 力 等 级 证 书 分 为 四 个 级 别, 分 别 为 初 级 助 理 级 中 级 和 高 级 ; 采 购 从 业 人 员 职 业 能 力 等 级 证 书 分 为 三 个 级 别, 分 别 为 中 级 高 级 和 注 册 级 请 各 有 关 单 位 按 照 通
物 联 培 字 2016 16 号 各 有 关 单 位 : 为 适 应 国 家 一 带 一 路 战 略 实 施 和 物 流 产 业 转 型 升 级 对 人 才 的 新 要 求, 确 保 物 流 采 购 人 才 培 养 工 作 有 序 衔 接 和 持 续 健 康 发 展, 参 照 国 际 惯 例, 中 国 物 流 与 采 购 联 合 会 ( 以 下 简 称 中 物 联 ) 经 研 究 决 定, 以 物
解 决 困 扰 事 业 单 位 高 效 运 行 的 人 员 编 制 难 题 应 摒 弃 既 有 经 验 化 判 断 的 思 维 限 囿 经 由 规 范 化 程 式 化 维 度 专 注 于 事 业 单 位 人 员 编 制 的 标 准 管 理 考 虑 到 事 业 单 位 人 员 编 制 的 复 杂 性 和 公 益 导 向 宜 在 编 制 标 准 定 位 上 确 定 整 体 性 发 展 性 公 益 性 取
数 学 标 准 不 练 习 1.1 理 解 问 题 并 坚 持 解 决 这 些 问 题 1.2 以 抽 象 和 定 量 方 式 推 理 1.3 建 构 可 行 参 数 和 评 判 他 人 的 推 理 1.4 使 用 数 学 方 法 建 模 1.5 策 略 性 地 使 用 合 适 的 工 具 1.6
课 程 表 格 科 学 框 架 不 练 习 1.1 提 问 1.2 开 发 和 使 用 模 型 1.3 规 划 和 开 展 调 查 1.4 分 析 和 解 释 数 据 1.5 使 用 数 学 运 算 信 息 和 计 算 机 技 术 以 及 计 算 思 维 1.6 构 造 解 释 和 设 计 解 决 方 案 1.7 通 过 证 据 进 行 论 证 1.8 获 取 评 估 和 交 流 信 息 跨 领 域
朱 丽 明 柯 美 云 周 丽 雅 袁 耀 宗 罗 金 燕 候 晓 华 陈 旻 湖 滥 用 安 非 他 命 会 增 加 得 心 脏 病 的 风 险 据 美 国 科 技 新 闻 网 报 道 根 据 纽 约 路 透 社 报 道 一 份 新 的 研 究 显 示 青 年 及 成 年 人 若 滥 用 安 非 他 命 会 增 加 得 心 脏 病 的 风 险 美 国 德 州 大 学 西 南 医 学 中 心
公 开 刊 物 须 有 国 内 统 一 刊 (CN), 发 表 文 章 的 刊 物 需 要 在 国 家 新 闻 出 版 广 电 总 局 (www.gapp.gov.cn 办 事 服 务 便 民 查 询 新 闻 出 版 机 构 查 询 ) 上 能 够 查 到 刊 凡 在 有 中 国 标 准 书 公 开
杭 教 人 2014 7 杭 州 市 教 育 局 关 于 中 小 学 教 师 系 列 ( 含 实 验 教 育 管 理 ) 晋 升 高 级 专 业 技 术 资 格 有 关 论 文 要 求 的 通 知 各 区 县 ( 市 ) 教 育 局 ( 社 发 局 ), 直 属 学 校 ( 单 位 ), 委 托 单 位 : 为 进 一 步 规 范 杭 州 市 中 小 学 教 师 系 列 ( 含 实 验 教 育 管
教师上报成绩流程图
教 务 管 理 系 统 使 用 说 明 学 生 端 用 户 1 在 校 内 任 何 一 台 连 接 校 园 网 的 计 算 机 上 登 录 教 务 处 主 页 教 务 处 主 页 地 址 : http://jw.stdu.edu.cn/homepage 随 后 点 击 按 钮 ( 见 下 图 所 示 ), 即 可 进 入 综 合 教 务 管 理 系 统 2 在 综 合 教 务 管 理 区 域 内 键
伊 犁 师 范 学 院 611 语 言 学 概 论 全 套 考 研 资 料 <2016 年 最 新 考 研 资 料 > 2-2 语 言 学 纲 要 笔 记, 由 考 取 本 校 本 专 业 高 分 研 究 生 总 结 而 来, 重 点 突 出, 借 助 此 笔 记 可 以 大 大 提 高 复 习 效
伊 犁 师 范 学 院 611 语 言 学 概 论 全 套 考 研 资 料 ......2 伊 犁 师 范 学 院 802 文 学 概 论 全 套 考 研 资 料 ......2 伊 犁 师 范 学 院 702 普 通 物 理 全 套 考 研 资 料 ......3 伊 犁
目 录 关 于 图 标... 3 登 陆 主 界 面... 3 工 单 管 理... 5 工 单 列 表... 5 搜 索 工 单... 5 工 单 详 情... 6 创 建 工 单... 9 设 备 管 理 巡 检 计 划 查 询 详 情 销 售 管
宝 汇 德 Turbocare 微 服 务 系 统 客 户 操 作 手 册 Version 2.0 北 京 宝 汇 德 技 术 服 务 器 有 限 公 司 技 术 研 发 部 目 录 关 于 图 标... 3 登 陆 主 界 面... 3 工 单 管 理... 5 工 单 列 表... 5 搜 索 工 单... 5 工 单 详 情... 6 创 建 工 单... 9 设 备 管 理... 10 巡
三武一宗灭佛研究
四 川 大 学 博 士 学 位 论 文 三 武 一 宗 灭 佛 研 究 姓 名 : 张 箭 申 请 学 位 级 别 : 博 士 专 业 : 中 国 古 代 史 指 导 教 师 : 杨 耀 坤 20020101 三
微 积 分 ( 二 ) 教 学 大 纲 2 (2010 版 ) 课 程 编 码 :110861 课 程 名 称 : 微 积 分 学 时 / 学 分 :36/2 先 修 课 程 : 初 等 数 学 立 体 几 何 平 面 解 析 几 何 微 积 分 ( 一 ) 适 用 专 业 : 人 力 资 源 管
微 积 分 ( 二 ) 教 学 大 纲 2 (2010 版 ) 课 程 编 码 :110861 课 程 名 称 : 微 积 分 学 时 / 学 分 :36/2 先 修 课 程 : 初 等 数 学 立 体 几 何 平 面 解 析 几 何 微 积 分 ( 一 ) 适 用 专 业 : 人 力 资 源 管 理 等 专 业 开 课 教 研 室 : 大 学 数 学 教 研 室 执 笔 : 庄 乐 森 审 定 :
!!
梁 运 文 霍 震 刘 凯 本 文 利 用 奥 尔 多 中 心 的 调 查 数 据 从 三 个 方 面 对 我 国 城 乡 居 民 财 产 分 布 状 况 进 行 了 详 细 的 实 证 分 析 首 先 刻 画 了 我 国 城 乡 居 民 财 产 分 布 的 总 体 统 计 特 征 然 后 从 财 产 构 成 出 发 对 我 国 城 乡 居 民 财 产 分 布 进 行 了 结 构 分 解 最 后 通
黄 金 原 油 总 持 仓 增 长, 同 比 增 幅 分 别 为 4.2% 和 4.1% 而 铜 白 银 以 及 玉 米 则 出 现 减 持, 减 持 同 比 减 少 分 别 为 9.4%,9.4% 以 及 6.5% 大 豆, 豆 粕 结 束 连 续 4 周 总 持 仓 量 增 长, 出 现 小 幅
小 麦 净 多 持 仓 增 加, 豆 油 豆 粕 净 多 持 仓 减 少 美 国 CFTC 持 仓 报 告 部 门 : 市 场 研 究 与 开 发 部 类 型 : 量 化 策 略 周 报 日 期 :212 年 5 月 7 日 电 话 :592-5678753 网 址 :www.jinyouqh.com 主 要 内 容 : 根 据 美 国 CFTC 公 布 的 数 据, 本 报 告 中 的 11 个
附 件 : 上 海 市 建 筑 施 工 企 业 施 工 现 场 项 目 管 理 机 构 关 键 岗 位 人 员 配 备 指 南 二 一 四 年 九 月 十 一 日 2
公 开 上 海 市 城 乡 建 设 和 管 理 委 员 会 文 件 沪 建 管 2014 758 号 上 海 市 城 乡 建 设 和 管 理 委 员 会 关 于 印 发 上 海 市 建 筑 施 工 企 业 施 工 现 场 项 目 管 理 机 构 关 键 岗 位 人 员 配 备 指 南 的 通 知 各 区 县 建 设 和 交 通 委 员 会 : 为 进 一 步 加 强 对 建 设 工 程 施 工 现
第1篇 道路桥梁工程技术核心专业课程标准及学习绩效考评体系
陕 西 铁 路 工 程 职 业 技 术 学 院 课 程 标 准 ( 适 用 建 筑 工 程 技 术 专 业 ) 课 程 名 称 : 单 位 工 程 施 工 组 织 设 计 执 笔 人 : 王 恒 博 审 定 人 : 编 制 时 间 : 年 月 日 陕 西 铁 路 工 程 职 业 技 术 学 院 制 表 二 〇 一 一 年 九 月 课 程 标 准 一 课 程 基 本 信 息 课 程 编 码 略 开 设
类 似 地, 又 可 定 义 变 下 限 的 定 积 分 : ( ). 与 ψ 统 称 为 变 限 积 分. f ( ) d f ( t) dt,, 注 在 变 限 积 分 (1) 与 () 中, 不 可 再 把 积 分 变 量 写 成 的 形 式 ( 例 如 ) 以 免 与 积 分 上 下 限 的
5 ( 一 ) 微 积 分 学 基 本 定 理 当 函 数 的 可 积 性 问 题 告 一 段 落, 并 对 定 积 分 的 性 质 有 了 足 够 的 认 识 之 后, 接 着 要 来 解 决 一 个 以 前 多 次 提 到 过 的 问 题 在 定 积 分 形 式 下 证 明 连 续 函 数 必 定 存 在 原 函 数. 一 变 限 积 分 与 原 函 数 的 存 在 性 设 f 在 [,] 上
四川省卫生厅关于开展医疗美容主诊医师资格考试及换证工作的通知
四 川 省 医 学 会 文 件 川 学 会 医 字 [2014]161 号 四 川 省 医 学 会 关 于 开 展 医 疗 美 容 主 诊 医 师 资 格 考 试 及 换 证 工 作 的 通 知 各 市 ( 州 ) 卫 生 局 医 学 会 省 卫 生 和 计 划 生 育 委 员 会 直 属 医 疗 机 构 国 家 卫 生 和 计 划 生 育 委 员 会 驻 川 医 疗 机 构 : 根 据 四 川 省
证券代码:000066 证券简称:长城电脑 公告编号:2014-000
证 券 代 码 :000066 证 券 简 称 : 长 城 电 脑 公 告 编 号 :2016-092 中 国 长 城 计 算 机 深 圳 股 份 有 限 公 司 2016 年 度 第 三 次 临 时 股 东 大 会 决 议 公 告 本 公 司 及 其 董 事 会 全 体 成 员 保 证 信 息 披 露 内 容 的 真 实 准 确 完 整, 没 有 虚 假 记 载 误 导 性 陈 述 或 重 大 遗
正 规 培 训 达 规 定 标 准 学 时 数, 并 取 得 结 业 证 书 二 级 可 编 程 师 ( 具 备 以 下 条 件 之 一 者 ) (1) 连 续 从 事 本 职 业 工 作 13 年 以 上 (2) 取 得 本 职 业 三 级 职 业 资 格 证 书 后, 连 续 从 事 本 职 业
1. 职 业 概 况 1.1 职 业 名 称 可 编 程 师 1.2 职 业 定 义 可 编 程 师 国 家 职 业 标 准 从 事 可 编 程 序 控 制 器 (PLC) 选 型 编 程, 并 对 应 用 进 行 集 成 和 运 行 管 理 的 人 员 1.3 职 业 等 级 本 职 业 共 设 四 个 等 级, 分 别 为 : 四 级 可 编 程 师 ( 国 家 职 业 资 格 四 级 ) 三
2015-2016 学 年 第 二 学 期 集 中 考 试 安 排 (18 周 ) 考 试 日 期 :6 月 27 日 星 期 一 8:10-9:50 第 二 公 共 教 学 楼 A 区 A303 10811046 高 等 数 学 ( 理 二 2) 复 材 1501-2 材 料 科 学 与 工 程
考 试 时 间 2015-2016 学 年 第 二 学 期 集 中 考 试 安 排 (18 周 ) 考 试 日 期 :6 月 27 日 星 期 一 考 场 所 在 教 学 楼 ( 教 学 区 ) 考 试 教 室 课 程 号 课 程 名 考 生 所 在 专 业 ( 班 级 ) 考 生 所 属 学 院 8:10-9:50 第 二 公 共 教 学 楼 A 区 A101 10811026 高 等 数 学 (
Template BR_Rec_2005.dot
ITU-R BT.1789 建 议 书 1 ITU-R BT.1789 建 议 书 在 分 组 视 频 传 输 中 利 用 传 输 误 码 信 息 重 建 接 收 视 频 的 方 法 (ITU-R 44/6 和 ITU-R 109/6 课 题 ) (2007 年 ) 范 围 本 建 议 书 对 业 务 提 供 商 重 建 接 收 视 频 的 方 法 做 了 详 细 介 绍, 以 便 利 用 传 输
附件1:
附 件 5 增 列 硕 士 专 业 学 位 授 权 点 申 请 表 硕 士 专 业 学 位 类 别 ( 工 程 领 域 ): 工 程 硕 士 ( 控 制 工 程 领 域 ) 申 报 单 位 名 称 : 上 海 工 程 技 术 大 学 一 申 请 增 列 硕 士 专 业 学 位 授 权 点 论 证 报 告 申 请 增 列 硕 士 专 业 学 位 授 权 点 论 证 报 告 一 专 业 人 才 需 求
<4D F736F F D20322EC9F3BACBC8CBD4B1D7CAB8F1D7A2B2E1B9DCC0EDB9E6B7B6B8BDB1ED2E646F63>
审 核 人 员 资 格 管 理 规 范 版 次 :F/0 附 表 1: QMS/EMS/OHSMS 管 理 体 系 审 核 员 通 用 要 求 申 请 条 件 初 次 综 合 素 质 考 核 越 级 晋 升 条 件 实 习 审 核 员 审 核 员 主 任 审 核 员 1. 高 等 教 育 : 大 学 本 科 以 上 学 历, 或 大 专 学 历 及 相 2. 工 作 1 : ; 3. 专 业 工 作
第 四 条 建 设 单 位 对 可 能 产 生 职 业 病 危 害 的 建 设 项 目, 应 当 依 照 本 办 法 向 安 全 生 产 监 督 管 理 部 门 申 请 职 业 卫 生 三 同 时 的 备 案 审 核 审 查 和 竣 工 验 收 建 设 项 目 职 业 卫 生 三 同 时 工 作 可
第 51 号 建 设 项 目 职 业 卫 生 三 同 时 监 督 管 理 暂 行 办 法 已 经 2012 年 3 月 6 日 国 家 安 全 生 产 监 督 管 理 总 局 局 长 办 公 会 议 审 议 通 过, 现 予 公 布, 自 2012 年 6 月 1 日 起 施 行 国 家 安 全 生 产 监 督 管 理 总 局 骆 琳 二 一 二 年 四 月 二 十 七 日 建 设 项 目 职 业
保健食品功能目录管理办法
保 健 食 品 保 健 功 能 目 录 与 原 料 目 录 管 理 办 法 ( 征 求 意 见 稿 ) 第 一 章 总 则 第 一 条 [ 目 的 依 据 ] 为 规 范 保 健 食 品 保 健 功 能 目 录 和 保 健 食 品 原 料 目 录 的 管 理 工 作, 根 据 中 华 人 民 共 和 国 食 品 安 全 法, 制 定 本 办 法 第 二 条 [ 适 用 范 围 ] 中 华 人 民 共
抗 日 战 争 研 究! 年 第 期 # # # # #!!!!!!!! #!!
洪 小 夏 中 美 合 作 所 是 抗 战 时 期 中 美 两 国 在 反 法 西 斯 统 一 战 线 背 景 下 建 立 的 一 个 抗 日 军 事 合 作 机 构 但 过 去 由 文 学 影 视 作 品 给 人 造 成 的 印 象 似 乎 是 一 个 美 蒋 反 动 派 勾 结 的 集 中 营 中 共 十 一 届 三 中 全 会 以 后 逐 渐 有 人 为 其 正 名 但 长 期 宣 传 形 成
易 迪 拓 培 训 专 注 于 微 波 射 频 天 线 设 计 人 才 的 培 养 网 址 :http://www.edatop.com 射 频 和 天 线 设 计 培 训 课 程 推 荐 易 迪 拓 培 训 (www.edatop.com) 由 数 名 来 自 于 研 发 第 一 线 的 资 深 工 程 师 发 起 成 立, 致 力 并 专 注 于 微 波 射 频 天 线 设 计 研 发 人 才
目 录 一 系 统 访 问... 1 二 门 户 首 页 申 报 用 户 审 核 用 户... 2 三 系 统 登 录 用 户 名 密 码 登 录 新 用 户 注 册 用 户 登 录 已 注 册 用
水 路 运 输 建 设 综 合 管 理 信 息 系 统 - 门 户 系 统 用 户 手 册 二 零 一 五 年 十 一 月 目 录 一 系 统 访 问... 1 二 门 户 首 页... 1 1. 申 报 用 户... 1 2. 审 核 用 户... 2 三 系 统 登 录... 4 1. 用 户 名 密 码 登 录... 4 1.1 新 用 户 注 册... 4 1.2 用 户 登 录... 7
<4D6963726F736F667420576F7264202D20BFC9B1E0B3CCD0F2BFD8D6C6CFB5CDB3C9E8BCC6CAA6B9FABCD2D6B0D2B5B1EAD7BC2E646F63>
国 家 职 业 标 准 1 可 编 程 序 控 制 系 统 设 计 师 国 家 职 业 标 准 1. 职 业 概 况 1.1 职 业 名 称 可 编 程 序 控 制 系 统 设 计 师 1.2 职 业 定 义 从 事 可 编 程 序 控 制 器 (PLC) 选 型 编 程, 并 对 应 用 系 统 进 行 设 计 集 成 和 运 行 管 理 的 人 员 1.3 职 业 等 级 本 职 业 共 设 四
北京信息科技大学本科学生成绩管理办法
北 京 信 息 科 技 大 学 文 件 校 教 发 2012 113 号 关 于 印 发 北 京 信 息 科 技 大 学 本 科 学 生 成 绩 管 理 办 法 的 通 知 各 相 关 单 位 : 现 将 北 京 信 息 科 技 大 学 本 科 学 生 成 绩 管 理 办 法 印 发 给 你 们, 请 遵 照 执 行 北 京 信 息 科 技 大 学 2012 年 12 月 17 日 1 北 京 信
精 勤 求 学 自 强 不 息 Born to win! 解 析 : 由 极 限 的 保 号 性 知 存 在 U ( a) 当 a 时 f ( ) f ( a) 故 f ( ) 在 点 a 不 取 极 值 f ( ) f ( a) f ( ) f ( a) lim lim a a a a ( a)
年 考 研 数 学 二 模 拟 题 ( 二 ) 参 考 答 案 本 试 卷 满 分 5 考 试 时 间 8 分 钟 一 选 择 题 :~8 小 题 每 小 题 分 共 分 下 列 每 小 题 给 出 的 四 个 选 项 中 只 有 一 项 符 合 题 目 要 求 的 请 将 所 选 项 前 的 字 母 填 在 答 题 纸 指 定 位 置 上 () 在 点 处 不 存 在 极 限 的 函 数 是 (
3 复 试 如 何 准 备 4 复 试 成 绩 计 算 5 复 试 比 例 6 复 试 类 型 7 怎 么 样 面 对 各 种 复 试 04 05
1 复 试 流 程 2 复 试 考 查 形 式 02 03 3 复 试 如 何 准 备 4 复 试 成 绩 计 算 5 复 试 比 例 6 复 试 类 型 7 怎 么 样 面 对 各 种 复 试 04 05 2 怎 样 给 导 师 留 下 良 好 的 第 一 印 象 把 握 进 门 时 机 1 面 试 中 穿 着 的 瞒 天 过 海 3 无 声 胜 有 声 的 肢 体 语 言 育 4 眼 睛 是 心
试 论 后 民 权 时 代 美 国 黑 人 的 阶 层 分 化 和 族 裔 特 征 学 者 年 代 黑 人 中 产 阶 层 定 义 弗 瑞 泽 毕 林 斯 勒 马 克 艾 德 威 尔 逊 科 林 斯 兰 德 里 奥 力 威 夏 佩 罗 帕 锑 罗 收 入 来 源 于 从 事 可 以 定 义 为 白
蒿 琨 黑 人 中 产 阶 层 试 论 后 民 权 时 代 美 国 黑 人 的 阶 层 分 化 和 族 裔 特 征 学 者 年 代 黑 人 中 产 阶 层 定 义 弗 瑞 泽 毕 林 斯 勒 马 克 艾 德 威 尔 逊 科 林 斯 兰 德 里 奥 力 威 夏 佩 罗 帕 锑 罗 收 入 来 源 于 从 事 可 以 定 义 为 白 领 工 作 的 服 务 行 业 中 产 阶 层 的 成 就 由 教 育
际 联 考 的 非 美 术 类 本 科, 提 前 批 本 科 体 育 类 第 一 批 第 二 批 第 三 批 的 理 工 类 和 文 史 类 本 科 平 行 志 愿, 考 生 可 以 填 报 6 所 院 校 志 愿 符 合 贫 困 地 区 专 项 计 划 和 农 村 考 生 专 项 计 划 报 考
第 四 部 分 平 行 志 愿 57. 什 么 是 平 行 志 愿?/ 32 58. 我 省 在 哪 个 批 次 实 行 平 行 志 愿? 考 生 最 多 可 以 填 报 几 所 院 校 志 愿?/ 32 59. 第 一 二 三 批 本 科 平 行 志 愿 如 何 投 档?/ 32 60. 艺 术 本 科 ( 二 ) 艺 术 本 科 ( 三 ) 和 体 育 本 科 的 平 行 志 愿 如 何 投 档?/
