MITK

Size: px
Start display at page:

Download "MITK"

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 年 春 季 期 教 务 处 2015 年 10 月 27 日 说 明 为 了 反 映 教 运 行 的 基 本 状 态, 为 校 和 院 制 定 相 关 政 策 和 进 行 教 建 设 与 改 革 提 供 据 依 据, 校 从 程 资 源 ( 开 类 别 开 量 规 模 ) 教 师 结 构 程 考 核 等 维 度,

More information

何 秋 琳 张 立 春 视 觉 学 习 研 究 进 展 视 觉 注 意 视 觉 感 知

何 秋 琳 张 立 春 视 觉 学 习 研 究 进 展 视 觉 注 意 视 觉 感 知 第 卷 第 期 年 月 开 放 教 育 研 究 何 秋 琳 张 立 春 华 南 师 范 大 学 未 来 教 育 研 究 中 心 广 东 广 州 随 着 图 像 化 技 术 和 电 子 媒 体 的 发 展 视 觉 学 习 也 逐 步 发 展 为 学 习 科 学 的 一 个 研 究 分 支 得 到 研 究 人 员 和 教 育 工 作 者 的 广 泛 关 注 基 于 此 作 者 试 图 对 视 觉 学 习

More information

,,,,, :,, (.,, );, (, : ), (.., ;. &., ;.. &.., ;, ;, ),,,,,,, ( ) ( ),,,,.,,,,,, : ;, ;,.,,,,, (., : - ),,,, ( ),,,, (, : ),, :,

,,,,, :,, (.,, );, (, : ), (.., ;. &., ;.. &.., ;, ;, ),,,,,,, ( ) ( ),,,,.,,,,,, : ;, ;,.,,,,, (., : - ),,,, ( ),,,, (, : ),, :, : 周 晓 虹 : - -., - - - -. :( ), -,.( ),,, -. - ( ).( ) ', -,,,,, ( ).( ),,, -., '.,, :,,,, :,,,, ,,,,, :,, (.,, );, (, : ), (.., ;. &., ;.. &.., ;, ;, ),,,,,,, ( ) ( ),,,,.,,,,,, : ;, ;,.,,,,, (., : - ),,,,

More information

0 年 上 半 年 评 价 与 考 核 细 则 序 号 部 门 要 素 值 考 核 内 容 考 核 方 式 考 核 标 准 考 核 ( 扣 原 因 ) 考 评 得 3 安 全 生 产 目 30 无 同 等 责 任 以 上 道 路 交 通 亡 人 事 故 无 轻 伤 责 任 事 故 无 重 大 质 量

0 年 上 半 年 评 价 与 考 核 细 则 序 号 部 门 要 素 值 考 核 内 容 考 核 方 式 考 核 标 准 考 核 ( 扣 原 因 ) 考 评 得 3 安 全 生 产 目 30 无 同 等 责 任 以 上 道 路 交 通 亡 人 事 故 无 轻 伤 责 任 事 故 无 重 大 质 量 0 年 上 半 年 评 价 与 考 核 细 则 序 号 部 门 要 素 值 考 核 内 容 考 核 方 式 考 核 标 准 无 同 等 责 任 以 上 道 路 交 通 亡 人 事 故 3 无 轻 伤 责 任 事 故 目 标 30 及 事 无 重 大 质 量 工 作 过 失 故 管 无 其 他 一 般 责 任 事 故 理 在 公 司 文 明 环 境 创 建 中, 无 工 作 过 失 及 被 追 究 的

More information

<433A5C446F63756D656E747320616E642053657474696E67735C41646D696E6973747261746F725CD7C0C3E65CC2DBCEC4CFB5CDB3CAB9D3C3D6B8C4CFA3A8BCF2BBAFA3A95CCAB9D3C3D6B8C4CF31302D31392E646F63>

<433A5C446F63756D656E747320616E642053657474696E67735C41646D696E6973747261746F725CD7C0C3E65CC2DBCEC4CFB5CDB3CAB9D3C3D6B8C4CFA3A8BCF2BBAFA3A95CCAB9D3C3D6B8C4CF31302D31392E646F63> ( 一 ) 系 统 整 体 操 作 流 程 简 述 3 ( 二 ) 系 统 中 各 角 色 操 作 功 能 说 明 5 1. 学 院 管 理 员 5 2. 教 学 院 长 8 3. 指 导 教 师 10 4. 答 辩 组 组 长 12 5. 学 生 12 6. 系 统 管 理 员 15 ( 一 ) 论 文 系 统 常 见 问 题 16 ( 二 ) 论 文 查 重 常 见 问 题 22 1 2 主

More information

文 化 记 忆 传 统 创 新 与 节 日 遗 产 保 护 根 据 德 国 学 者 阿 斯 曼 的 文 化 记 忆 理 论 仪 式 与 文 本 是 承 载 文 化 记 忆 的 两 大 媒 体 在 各 种 仪 式 行 为 中 节 日 以 其 高 度 的 公 共 性 有 组 织 性 和 历 史 性 而 特 别 适 用 于 文 化 记 忆 的 储 存 和 交 流 节 日 的 文 化 功 能 不 仅 在 于

More information

评 委 : 李 炎 斌 - 个 人 技 术 标 资 信 标 初 步 审 查 明 细 表 序 号 投 标 单 位 投 标 函 未 按 招 标 文 件 规 定 填 写 漏 填 或 内 容 填 写 错 误 的 ; 不 同 投 标 人 的 投 标 文 件 由 同 一 台 电 脑 或 同 一 家 投 标 单

评 委 : 李 炎 斌 - 个 人 技 术 标 资 信 标 初 步 审 查 明 细 表 序 号 投 标 单 位 投 标 函 未 按 招 标 文 件 规 定 填 写 漏 填 或 内 容 填 写 错 误 的 ; 不 同 投 标 人 的 投 标 文 件 由 同 一 台 电 脑 或 同 一 家 投 标 单 评 委 : 李 炎 斌 - 个 人 清 标 评 审 明 细 表 评 审 因 素 序 号 投 标 单 位 清 标 评 审 1 深 圳 市 创 捷 科 技 有 限 合 格 2 四 川 川 大 智 胜 软 件 股 份 有 限 合 格 3 北 京 航 天 长 峰 科 技 工 业 集 团 有 限 公 司 合 格 4 深 圳 中 兴 力 维 技 术 有 限 合 格 5 深 圳 键 桥 通 讯 技 术 股 份 有

More information

龚 亚 夫 在 重 新 思 考 基 础 教 育 英 语 教 学 的 理 念 一 文 中 援 引 的 观 点 认 为 当 跳 出 本 族 语 主 义 的 思 维 定 式 后 需 要 重 新 思 考 许 多 相 连 带 的 问 题 比 如 许 多 发 音 的 细 微 区 别 并 不 影 响 理 解 和

龚 亚 夫 在 重 新 思 考 基 础 教 育 英 语 教 学 的 理 念 一 文 中 援 引 的 观 点 认 为 当 跳 出 本 族 语 主 义 的 思 维 定 式 后 需 要 重 新 思 考 许 多 相 连 带 的 问 题 比 如 许 多 发 音 的 细 微 区 别 并 不 影 响 理 解 和 语 音 语 篇 语 感 语 域 林 大 津 毛 浩 然 改 革 开 放 以 来 的 英 语 热 引 发 了 大 中 小 学 英 语 教 育 整 体 规 划 问 题 在 充 分 考 虑 地 区 学 校 和 个 体 差 异 以 及 各 家 观 点 的 基 础 上 遵 循 实 事 求 是 逐 级 定 位 逐 层 分 流 因 材 施 教 的 原 则 本 研 究 所 倡 导 的 语 音 语 篇 语 感 语 域

More information

一 公 共 卫 生 硕 士 专 业 学 位 论 文 的 概 述 学 位 论 文 是 对 研 究 生 进 行 科 学 研 究 或 承 担 专 门 技 术 工 作 的 全 面 训 练, 是 培 养 研 究 生 创 新 能 力, 综 合 运 用 所 学 知 识 发 现 问 题, 分 析 问 题 和 解 决

一 公 共 卫 生 硕 士 专 业 学 位 论 文 的 概 述 学 位 论 文 是 对 研 究 生 进 行 科 学 研 究 或 承 担 专 门 技 术 工 作 的 全 面 训 练, 是 培 养 研 究 生 创 新 能 力, 综 合 运 用 所 学 知 识 发 现 问 题, 分 析 问 题 和 解 决 上 海 市 公 共 卫 生 硕 士 专 业 学 位 论 文 基 本 要 求 和 评 价 指 标 体 系 ( 试 行 ) 上 海 市 学 位 委 员 会 办 公 室 二 O 一 二 年 三 月 一 公 共 卫 生 硕 士 专 业 学 位 论 文 的 概 述 学 位 论 文 是 对 研 究 生 进 行 科 学 研 究 或 承 担 专 门 技 术 工 作 的 全 面 训 练, 是 培 养 研 究 生 创

More information

<4D F736F F D D323630D6D0B9FAD3A6B6D4C6F8BAF2B1E4BBAFB5C4D5FEB2DFD3EBD0D0B6AF C4EAB6C8B1A8B8E6>

<4D F736F F D D323630D6D0B9FAD3A6B6D4C6F8BAF2B1E4BBAFB5C4D5FEB2DFD3EBD0D0B6AF C4EAB6C8B1A8B8E6> 中 国 应 对 气 候 变 化 的 政 策 与 行 动 2013 年 度 报 告 国 家 发 展 和 改 革 委 员 会 二 〇 一 三 年 十 一 月 100% 再 生 纸 资 源 目 录 前 言... 1 一 应 对 气 候 变 化 面 临 的 形 势... 3 二 完 善 顶 层 设 计 和 体 制 机 制... 4 三 减 缓 气 候 变 化... 8 四 适 应 气 候 变 化... 20

More information

《C语言基础入门》课程教学大纲

《C语言基础入门》课程教学大纲 C 语 言 开 发 入 门 教 程 课 程 教 学 大 纲 课 程 编 号 :201409210011 学 分 :5 学 分 学 时 :58 学 时 ( 其 中 : 讲 课 学 时 :39 学 时 上 机 学 时 :19 学 时 ) 先 修 课 程 : 计 算 机 导 论 后 续 课 程 :C++ 程 序 设 计 适 用 专 业 : 信 息 及 其 计 算 机 相 关 专 业 开 课 部 门 : 计

More information

张 荣 芳 中 山 大 学 历 史 系 广 东 广 州 张 荣 芳 男 广 东 廉 江 人 中 山 大 学 历 史 系 教 授 博 士 生 导 师 我 们 要 打 破 以 前 学 术 界 上 的 一 切 偶 像 以 前 学 术 界 的 一 切 成 见 屏 除 我 们 要 实 地 搜 罗 材 料 到 民 众 中 寻 方 言 到 古 文 化 的 遗 址 去 发 掘 到 各 种 的 人 间 社 会 去

More information

评 委 : 徐 岩 宇 - 个 人 技 术 标 资 信 标 初 步 审 查 明 细 表 序 号 投 标 单 位 投 标 函 未 按 招 标 文 件 规 定 填 写 漏 填 或 内 容 填 写 错 误 的 ; 不 同 投 标 人 的 投 标 文 件 由 同 一 台 电 脑 或 同 一 家 投 标 单

评 委 : 徐 岩 宇 - 个 人 技 术 标 资 信 标 初 步 审 查 明 细 表 序 号 投 标 单 位 投 标 函 未 按 招 标 文 件 规 定 填 写 漏 填 或 内 容 填 写 错 误 的 ; 不 同 投 标 人 的 投 标 文 件 由 同 一 台 电 脑 或 同 一 家 投 标 单 评 委 : 徐 岩 宇 - 个 人 清 标 评 审 明 细 表 评 审 因 素 序 号 投 标 单 位 清 标 评 审 1 深 圳 市 创 捷 科 技 有 限 合 格 2 四 川 川 大 智 胜 软 件 股 份 有 限 合 格 3 北 京 航 天 长 峰 科 技 工 业 集 团 有 限 公 司 合 格 4 深 圳 中 兴 力 维 技 术 有 限 合 格 5 深 圳 键 桥 通 讯 技 术 股 份 有

More information

Microsoft Word - 文件汇编.doc

Microsoft Word - 文件汇编.doc 北 京 市 中 医 管 理 局 二 一 五 年 四 月 ... 1... 18 2015... 30 京 中 医 政 字 [2014]160 号 1 2 一 充 分 认 识 中 医 健 康 乡 村 建 设 工 作 的 重 要 意 义 二 建 立 健 全 工 作 保 障 机 制 2014 12 15 三 做 好 工 作 启 动 的 准 备 事 宜 1 2014 12 15 5-10 2014 12 15

More information

(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-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 周

More information

科 学 出 版 社 科 学 出 版 社 前 言 本 书 是 针 对 普 通 高 等 院 校 经 济 类 和 工 商 管 理 类 本 科 专 业 财 务 管 理 学 的 教 学 需 求, 结 合 教 育 部 经 济 管 理 类 本 科 财 务 管 理 学 课 程 教 学 大 纲 编 写 而 成 的 本 书 执 笔 者 都 是 长 期 工 作 在 财 务 管 理 教 学 一 线 的 专 业 教 师,

More information

I

I 机 电 一 级 注 册 建 造 师 继 续 教 育 培 训 广 东 培 训 点 网 上 报 名 操 作 使 用 手 册 (2013 年 1 月, 第 一 版 ) 第 一 章 个 人 注 册 与 个 人 信 息 管 理 1. 个 人 注 册 ( 请 每 人 只 申 请 一 个 注 册 号, 如 果 单 位 批 量 报 班 单 位 帮 申 请 注 册, 不 需 个 人 再 注 册 ) 首 次 报 班,

More information

1600 1000 40 50 2030 2000 采 取 行 动 的 机 会 90% 开 拓 成 功 的 道 路 2

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% 开 拓 成 功 的 道 路

More information

修改版-操作手册.doc

修改版-操作手册.doc 职 称 信 息 系 统 升 级 指 南 须 使 用 IE9 及 其 以 上 版 本 浏 览 器 或 谷 歌 浏 览 器 登 录 www.njrs.gov.cn 南 京 市 职 称 ( 职 业 资 格 ) 工 作 领 导 小 组 办 公 室 2016 年 5 月 目 录 一 申 报 人 员 操 作 指 南...1 1.1 职 称 初 定 申 报...1 1.1.1 职 称 初 定 基 础 信 息 填

More information

抗 战 时 期 国 民 政 府 的 银 行 监 理 体 制 探 析 % # % % % ) % % # # + #, ) +, % % % % % % % %

抗 战 时 期 国 民 政 府 的 银 行 监 理 体 制 探 析 % # % % % ) % % # # + #, ) +, % % % % % % % % 抗 战 时 期 国 民 政 府 的 银 行 监 理 体 制 探 析 王 红 曼 抗 战 时 期 国 民 政 府 为 适 应 战 时 经 济 金 融 的 需 要 实 行 由 财 政 部 四 联 总 处 中 央 银 行 等 多 家 机 构 先 后 共 同 参 与 的 多 元 化 银 行 监 理 体 制 对 战 时 状 态 下 的 银 行 发 展 与 经 营 安 全 进 行 了 大 规 模 的 设 计 与

More information

Microsoft Word - 第7章 图表反转形态.doc

Microsoft Word - 第7章 图表反转形态.doc 第 七 章 图 表 反 转 形 态 我 们 知 道 市 场 趋 势 共 有 三 种 : 上 升 趋 势 下 降 趋 势 和 横 向 整 理 市 场 的 价 格 波 动 都 是 运 行 在 这 三 种 趋 势 中, 所 有 的 走 势 都 是 这 三 种 趋 势 的 排 列 组 合 如 图 市 场 趋 势 结 构 示 意 图 7-1 所 示 市 场 趋 势 结 构 示 意 图 7-1 图 市 场 趋

More information

深圳市新亚电子制程股份有限公司

深圳市新亚电子制程股份有限公司 证 券 代 码 :002388 证 券 简 称 : 新 亚 制 程 公 告 编 号 :2016-053 深 圳 市 新 亚 电 子 制 程 股 份 有 限 公 司 2016 年 第 二 次 临 时 股 东 大 会 决 议 公 告 本 公 司 及 董 事 会 全 体 成 员 保 证 公 告 内 容 真 实 准 确 和 完 整, 不 存 在 虚 假 记 载 误 导 性 陈 述 或 者 重 大 遗 漏 特

More information

新, 各 地 各 部 门 ( 单 位 ) 各 文 化 事 业 单 位 要 高 度 重 视, 切 实 加 强 领 导, 精 心 组 织 实 施 要 根 据 事 业 单 位 岗 位 设 置 管 理 的 规 定 和 要 求, 在 深 入 调 查 研 究 广 泛 听 取 意 见 的 基 础 上, 研 究 提

新, 各 地 各 部 门 ( 单 位 ) 各 文 化 事 业 单 位 要 高 度 重 视, 切 实 加 强 领 导, 精 心 组 织 实 施 要 根 据 事 业 单 位 岗 位 设 置 管 理 的 规 定 和 要 求, 在 深 入 调 查 研 究 广 泛 听 取 意 见 的 基 础 上, 研 究 提 广 西 壮 族 自 治 区 人 事 厅 广 西 壮 族 自 治 区 文 化 厅 文 件 桂 人 发 2009 42 号 关 于 印 发 广 西 壮 族 自 治 区 文 化 事 业 单 位 岗 位 设 置 结 构 比 例 指 导 标 准 的 通 知 各 市 人 事 局 文 化 局, 区 直 各 部 门 ( 单 位 ): 根 据 人 事 部 印 发 的 事 业 单 位 岗 位 设 置 管 理 试 行 办

More information

untitled

untitled ( 一 ) 深 刻 认 识 学 习 教 育 的 重 大 意 义 : - 3 - ( 二 ) 明 确 学 习 教 育 的 任 务 目 标 ( 三 ) 把 握 特 点 方 法 - 4 - ( 四 ) 坚 持 六 项 原 则 在 - 5 - ( 五 ) 着 力 解 决 问 题 - 6 - - 7 - - 8 - ( 一 ) 学 党 章 党 规, 进 一 步 明 确 党 员 标 准 树 立 行 为 规 范

More information

课程类 别

课程类 别 美 声 演 唱 方 向 培 养 方 案 一 培 养 目 标 本 方 向 要 求 学 生 德 智 体 美 全 面 发 展, 培 养 能 在 文 艺 团 体 从 事 声 乐 演 唱 及 能 在 艺 术 院 校 从 事 本 方 向 教 学 的 高 级 门 人 才 二 培 养 规 格 本 方 向 学 生 应 系 统 掌 握 声 乐 演 唱 方 面 的 理 论 和 技 能, 具 备 较 高 的 声 乐 演 唱

More information

ETF、分级基金规模、份额变化统计20130816

ETF、分级基金规模、份额变化统计20130816 ETF 分 级 基 金 规 模 份 额 变 化 统 计 截 至 上 周 末, 全 市 场 股 票 型 ETF 规 模 约 1451 亿, 份 额 约 1215 亿,ETF 总 份 额 及 规 模 的 周 变 动 值 分 别 为 -23-44 亿, 份 额 与 规 模 均 下 降 ; 分 级 基 金 规 模 约 438 亿, 份 额 572 亿, 总 份 额 及 规 模 的 周 变 动 值 分 别 为

More information

<433A5C55736572735C6B73625C4465736B746F705CB9FABCCAD6D0D2BDD2A9D7A8D2B5B8DFBCB6BCBCCAF5D6B0B3C6C6C0C9F3C9EAC7EBD6B8C4CFA3A832303136CDA8D3C3B0E6A3A92E646F63>

<433A5C55736572735C6B73625C4465736B746F705CB9FABCCAD6D0D2BDD2A9D7A8D2B5B8DFBCB6BCBCCAF5D6B0B3C6C6C0C9F3C9EAC7EBD6B8C4CFA3A832303136CDA8D3C3B0E6A3A92E646F63> 附 件 1 国 际 中 药 专 业 高 级 技 术 职 称 评 审 条 件 及 报 名 材 料 一 系 列 ( 一 ) 中 1 高 级 专 科 ( 副 ) 高 级 专 科 ( 副 ) 1 取 得 中 专 科 职 称 后, 独 立 从 事 中 临 床 实 践 5 年 以 上 2 取 得 中 博 士 学 位 后, 临 床 实 践 2 年 以 上 3 取 得 中 硕 士 学 位 后, 临 床 实 践 7

More information

一 从 分 封 制 到 郡 县 制 一 从 打 虎 亭 汉 墓 说 起

一 从 分 封 制 到 郡 县 制 一 从 打 虎 亭 汉 墓 说 起 县 乡 两 级 的 政 治 体 制 改 革 如 何 建 立 民 主 的 合 作 新 体 制 县 乡 人 大 运 行 机 制 研 究 课 题 组 引 言 一 从 分 封 制 到 郡 县 制 一 从 打 虎 亭 汉 墓 说 起 二 密 县 在 周 初 是 两 个 小 国 密 国 和 郐 国 三 密 县 的 第 一 任 县 令 卓 茂 四 明 清 时 代 的 密 县 二 从 集 中 的 动 员 体

More information

马 克 思 主 义 公 正 观 的 基 本 向 度 及 方 法 论 原 则!! # #

马 克 思 主 义 公 正 观 的 基 本 向 度 及 方 法 论 原 则!! # # 马 克 思 主 义 公 正 观 的 基 本 向 度 及 方 法 论 原 则 马 俊 峰 在 社 会 公 正 问 题 的 大 讨 论 中 罗 尔 斯 诺 齐 克 哈 耶 克 麦 金 泰 尔 等 当 代 西 方 思 想 家 的 论 述 被 反 复 引 用 和 申 说 而 将 马 克 思 恩 格 斯 等 经 典 作 家 的 观 点 置 于 一 种 被 忽 视 甚 至 被 忘 却 的 状 态 形 成 这 种

More information

名 称 生 命 科 学 学 院 083001 环 境 科 学 1 生 物 学 仅 接 收 院 内 调 剂, 初 试 分 数 满 足 我 院 生 物 学 复 试 最 低 分 数 线 生 命 科 学 学 院 071300 生 态 学 5 生 态 学 或 生 物 学 生 命 科 学 学 院 040102

名 称 生 命 科 学 学 院 083001 环 境 科 学 1 生 物 学 仅 接 收 院 内 调 剂, 初 试 分 数 满 足 我 院 生 物 学 复 试 最 低 分 数 线 生 命 科 学 学 院 071300 生 态 学 5 生 态 学 或 生 物 学 生 命 科 学 学 院 040102 华 中 师 范 大 学 2016 年 接 收 校 内 外 优 秀 硕 士 研 究 生 调 剂 信 息 表 名 称 经 济 与 工 商 管 理 学 院 020101 政 治 经 济 学 1 经 济 学 类 毕 业 学 校 与 报 考 学 校 不 低 于 我 校 办 学 层 次 经 济 与 工 商 管 理 学 院 020105 世 界 经 济 学 1 经 济 学 类 毕 业 学 校 与 报 考 学 校

More information

( 二 ) 现 行 统 一 高 考 制 度 不 利 于 培 养 人 的 创 新 精 神,,,,,,,,,,,,, [ ],,,,,,,,,,, :, ;,,,,,,? ( 三 ) 现 行 统 一 高 考 制 度 不 利 于 全 体 学 生 都 获 得 全 面 发 展,, [ ],,,,,,,,,,,

( 二 ) 现 行 统 一 高 考 制 度 不 利 于 培 养 人 的 创 新 精 神,,,,,,,,,,,,, [ ],,,,,,,,,,, :, ;,,,,,,? ( 三 ) 现 行 统 一 高 考 制 度 不 利 于 全 体 学 生 都 获 得 全 面 发 展,, [ ],,,,,,,,,,, ( ) ( )... 李 雪 岩, 龙 耀 (. 广 西 民 族 大 学 商 学 院, 广 西 南 宁 ;. 中 山 大 学 教 育 学 院, 广 东 广 州 ) : 高 等 教 育 是 专 业 教 育 高 考 是 为 高 等 教 育 服 务 的, 是 为 高 等 专 业 教 育 选 拔 有 专 业 培 养 潜 质 的 人 才 现 行 高 考 制 度 忽 略 专 业 潜 质 的 因 素, 过 份 强

More information

18 上 报 该 学 期 新 生 数 据 至 阳 光 平 台 第 一 学 期 第 四 周 至 第 六 周 19 督 促 学 习 中 心 提 交 新 增 专 业 申 请 第 一 学 期 第 四 周 至 第 八 周 20 编 制 全 国 网 络 统 考 十 二 月 批 次 考 前 模 拟 题 第 一 学

18 上 报 该 学 期 新 生 数 据 至 阳 光 平 台 第 一 学 期 第 四 周 至 第 六 周 19 督 促 学 习 中 心 提 交 新 增 专 业 申 请 第 一 学 期 第 四 周 至 第 八 周 20 编 制 全 国 网 络 统 考 十 二 月 批 次 考 前 模 拟 题 第 一 学 1 安 排 组 织 全 国 网 络 统 考 九 月 批 次 网 上 考 前 辅 导 第 一 学 期 第 一 周 统 考 考 前 半 个 月 2 下 发 全 国 网 络 统 考 九 月 批 次 准 考 证 第 一 学 期 第 一 周 导 出 下 半 年 成 人 本 科 学 士 学 位 英 语 统 一 考 试 报 考 3 信 息 第 一 学 期 第 一 周 4 教 学 计 划 和 考 试 计 划 上 网,

More information

金 不 少 于 800 万 元, 净 资 产 不 少 于 960 万 元 ; (3) 近 五 年 独 立 承 担 过 单 项 合 同 额 不 少 于 1000 万 元 的 智 能 化 工 程 ( 设 计 或 施 工 或 设 计 施 工 一 体 ) 不 少 于 2 项 ; (4) 近 三 年 每 年

金 不 少 于 800 万 元, 净 资 产 不 少 于 960 万 元 ; (3) 近 五 年 独 立 承 担 过 单 项 合 同 额 不 少 于 1000 万 元 的 智 能 化 工 程 ( 设 计 或 施 工 或 设 计 施 工 一 体 ) 不 少 于 2 项 ; (4) 近 三 年 每 年 工 程 设 计 与 施 工 资 质 标 准 一 总 则 建 筑 智 能 化 工 程 设 计 与 施 工 资 质 标 准 ( 一 ) 为 了 加 强 对 从 事 建 筑 智 能 化 工 程 设 计 与 施 工 企 业 的 管 理, 维 护 建 筑 市 场 秩 序, 保 证 工 程 质 量 和 安 全, 促 进 行 业 健 康 发 展, 结 合 建 筑 智 能 化 工 程 的 特 点, 制 定 本 标

More information

中 国 软 科 学 年 第 期!!!

中 国 软 科 学 年 第 期!!! 山 寨 模 式 的 形 成 机 理 及 其 对 组 织 创 新 的 启 示 山 寨 模 式 的 形 成 机 理 及 其 对 组 织 创 新 的 启 示 陶 厚 永 李 燕 萍 骆 振 心 武 汉 大 学 经 济 与 管 理 学 院 武 汉 大 学 中 国 产 学 研 合 作 问 题 研 究 中 心 湖 北 武 汉 北 京 大 学 经 济 研 究 所 光 华 天 成 博 士 后 工 作 站 北 京 本

More information

中 中 中 中 部 中 岗 位 条 件 历 其 它 历 史 师 地 理 师 生 物 师 体 与 健 康 师 04 05 06 07 从 事 中 历 史 工 从 事 中 地 理 工 从 事 中 生 物 工 从 事 中 体 与 健 康 工 2. 课 程 与 论 ( 历 史 ); 2. 科 ( 历 史 )

中 中 中 中 部 中 岗 位 条 件 历 其 它 历 史 师 地 理 师 生 物 师 体 与 健 康 师 04 05 06 07 从 事 中 历 史 工 从 事 中 地 理 工 从 事 中 生 物 工 从 事 中 体 与 健 康 工 2. 课 程 与 论 ( 历 史 ); 2. 科 ( 历 史 ) 中 中 中 部 中 26 年 系 统 事 业 公 开 计 划 岗 位 条 件 历 其 它 数 师 英 语 师 物 理 师 02 0 从 事 中 数 工 从 事 中 英 语 工 从 事 中 物 理 工 2. 课 程 与 论 ( 数 ); 2. 科 ( 数 );. 数 ; 4. 基 础 数 ; 5. 计 算 数 ; 6. 概 率 论 与 数 理 统 计 ; 7. 应 用 数 ; 8. 数. 课 程 与

More information

2006年顺德区高中阶段学校招生录取分数线

2006年顺德区高中阶段学校招生录取分数线 2014 年 顺 德 区 高 中 阶 段 学 校 考 试 提 前 批 第 一 批 第 二 批 学 校 录 取 根 据 佛 山 市 办 提 供 的 考 生 数 据, 现 将 我 区 2014 年 高 中 阶 段 学 校 考 试 提 前 批 第 一 批 第 二 批 学 校 的 录 取 公 布 如 下 : 一 顺 德 一 中 录 取 分 第 1 志 愿, 总 分 585, 综 合 表 现 评 价 A, 考

More information

¹ º ¹ º 农 业 流 动 人 口 是 指 户 口 性 质 为 农 业 户 口 在 流 入 地 城 市 工 作 生 活 居 住 一 个 月 及 以 上 的 流 动 人 口 非 农 流 动 人 口 是 指 户 口 性 质 为 非 农 户 口 在 流 入 地 城 市 工 作 生 活 居 住 一 个

¹ º ¹ º 农 业 流 动 人 口 是 指 户 口 性 质 为 农 业 户 口 在 流 入 地 城 市 工 作 生 活 居 住 一 个 月 及 以 上 的 流 动 人 口 非 农 流 动 人 口 是 指 户 口 性 质 为 非 农 户 口 在 流 入 地 城 市 工 作 生 活 居 住 一 个 ¹ 改 革 开 放 年 来 人 口 流 动 规 模 持 续 增 加 对 我 国 社 会 经 济 的 持 续 发 展 起 到 了 重 要 作 用 为 全 面 了 解 我 国 流 动 人 口 生 存 状 况 准 确 把 握 流 动 人 口 发 展 规 律 和 趋 势 不 断 加 强 流 动 人 口 服 务 管 理 引 导 人 口 有 序 流 动 合 理 分 布 国 家 人 口 计 生 委 于 年 月 启

More information

工 程 勘 察 资 质 标 准 根 据 建 设 工 程 勘 察 设 计 管 理 条 例 和 建 设 工 程 勘 察 设 计 资 质 管 理 规 定, 制 定 本 标 准 一 总 则 ( 一 ) 本 标 准 包 括 工 程 勘 察 相 应 专 业 类 型 主 要 专 业 技 术 人 员 配 备 技 术

工 程 勘 察 资 质 标 准 根 据 建 设 工 程 勘 察 设 计 管 理 条 例 和 建 设 工 程 勘 察 设 计 资 质 管 理 规 定, 制 定 本 标 准 一 总 则 ( 一 ) 本 标 准 包 括 工 程 勘 察 相 应 专 业 类 型 主 要 专 业 技 术 人 员 配 备 技 术 住 房 和 城 乡 建 设 部 关 于 印 发 工 程 勘 察 资 质 标 准 的 通 知 建 市 [2013]9 号 各 省 自 治 区 住 房 和 城 乡 建 设 厅, 北 京 市 规 划 委, 天 津 上 海 市 建 设 交 通 委, 重 庆 市 城 乡 建 设 委, 新 疆 生 产 建 设 兵 团 建 设 局, 总 后 基 建 营 房 部 工 程 局, 国 务 院 有 关 部 门 建 设 司,

More information

反 学 校 文 化 与 阶 级 再 生 产 小 子 与 子 弟 之 比 较 周 潇 作 者 通 过 对 北 京 某 打 工 子 弟 学 校 的 田 野 调 查 后 发 现 在 农 民 工 子 弟 中 间 盛 行 着 类 似 学 做 工 中 所 描 述 的 工 人 阶 级 小 子 的 反 学 校 文 化 但 是 由 于 制 度 安 排 与 社 会 条 件 的 差 异 子 弟 与 小 子 的 反 学 校

More information

 编号:

 编号: 编 号 : 企 业 内 高 技 能 人 才 培 养 评 价 实 施 方 案 ( 仅 适 用 于 企 业 特 有 行 业 特 有 工 种 ) 实 施 单 位 ( 公 章 ) 申 报 日 期 年 _ 月 日 1 企 业 内 高 技 能 人 才 培 养 评 价 项 目 实 施 方 案 申 报 表 项 目 名 称 等 级 项 目 性 质 课 时 申 报 单 位 联 系 人 通 讯 地 址 电 话 手 机 电

More information

西 南 民 族 学 院 学 报 哲 学 社 会 科 学 版 第 卷 资 料 来 源 中 国 统 计 年 鉴 年 年 新 中 国 五 十 年 统 计 资 料 汇 编 中 国 人 口 统 计 年 鉴 年 数 据 资 料 来 源 中 国 统 计 年 鉴 中 国 统 计 出 版 社 年 版 资 料 来 源

西 南 民 族 学 院 学 报 哲 学 社 会 科 学 版 第 卷 资 料 来 源 中 国 统 计 年 鉴 年 年 新 中 国 五 十 年 统 计 资 料 汇 编 中 国 人 口 统 计 年 鉴 年 数 据 资 料 来 源 中 国 统 计 年 鉴 中 国 统 计 出 版 社 年 版 资 料 来 源 郑 长 德 教 育 的 发 展 人 力 资 源 的 开 发 是 决 定 西 部 民 族 地 区 未 来 发 展 的 关 键 因 素 之 一 是 实 施 西 部 大 开 发 战 略 提 高 其 经 济 竞 争 力 和 综 合 实 力 的 重 要 保 障 本 文 从 西 部 民 族 地 区 教 育 发 展 的 现 状 入 手 指 出 中 华 人 民 共 和 国 成 立 多 年 来 西 部 民 族 地 区

More information

西 南 大 学 硕 士 学 位 论 文 网 络 购 物 动 机 问 卷 的 编 制 及 实 测 姓 名 : 曹 建 英 申 请 学 位 级 别 : 硕 士 专 业 : 基 础 心 理 学 指 导 教 师 : 张 进 辅 20090401 网 络 购 物 动 机 问 卷 的

More information

全国建筑市场注册执业人员不良行为记录认定标准(试行).doc

全国建筑市场注册执业人员不良行为记录认定标准(试行).doc - 1 - - 2 - 附 件 全 国 建 筑 市 场 注 册 执 业 人 员 不 良 记 录 认 定 标 准 ( 试 行 ) 说 明 为 了 完 善 建 筑 市 场 注 册 执 业 人 员 诚 信 体 系 建 设, 规 范 执 业 和 市 场 秩 序, 依 据 相 关 法 律 法 规 和 部 门 规 章, 根 据 各 行 业 特 点, 我 部 制 订 了 全 国 建 筑 市 场 注 册 执 业 人

More information

一 开 放 性 的 政 策 与 法 规 二 两 岸 共 同 的 文 化 传 承 三 两 岸 高 校 各 自 具 有 专 业 优 势 远 见 杂 志 年 月 日

一 开 放 性 的 政 策 与 法 规 二 两 岸 共 同 的 文 化 传 承 三 两 岸 高 校 各 自 具 有 专 业 优 势 远 见 杂 志 年 月 日 河 北 师 范 大 学 学 报 新 时 期 海 峡 两 岸 高 校 开 放 招 生 问 题 探 讨 郑 若 玲 王 晓 勇 海 峡 两 岸 高 校 开 放 招 生 是 新 时 期 推 进 海 峡 两 岸 高 等 教 育 交 流 与 合 作 的 重 要 尝 试 系 统 梳 理 改 革 开 放 以 来 两 岸 招 生 政 策 与 就 学 人 数 发 展 变 化 的 历 史 进 程 可 发 现 促 进 两

More information

3 月 30 日 在 中 国 证 券 报 上 海 证 券 报 证 券 时 报 证 券 日 报 和 上 海 证 券 交 易 所 网 站 上 发 出 召 开 本 次 股 东 大 会 公 告, 该 公 告 中 载 明 了 召 开 股 东 大 会 的 日 期 网 络 投 票 的 方 式 时 间 以 及 审

3 月 30 日 在 中 国 证 券 报 上 海 证 券 报 证 券 时 报 证 券 日 报 和 上 海 证 券 交 易 所 网 站 上 发 出 召 开 本 次 股 东 大 会 公 告, 该 公 告 中 载 明 了 召 开 股 东 大 会 的 日 期 网 络 投 票 的 方 式 时 间 以 及 审 北 京 市 君 致 律 师 事 务 所 关 于 浪 潮 软 件 股 份 有 限 公 司 2015 年 度 股 东 大 会 的 法 律 意 见 书 致 : 浪 潮 软 件 股 份 有 限 公 司 北 京 市 君 致 律 师 事 务 所 ( 以 下 简 称 本 所 ) 受 浪 潮 软 件 股 份 有 限 公 司 ( 以 下 简 称 公 司 ) 的 委 托, 指 派 律 师 出 席 2016 年 4 月

More information

导 数 和 微 分 的 概 念 导 数 的 几 何 意 义 和 物 理 意 义 函 数 的 可 导 性 与 连 续 性 之 间 的 关 系 平 面 曲 线 的 切 线 和 法 线 导 数 和 微 分 的 四 则 运 算 基 本 初 等 函 数 的 导 数 复 合 函 数 反 函 数 隐 函 数 以

导 数 和 微 分 的 概 念 导 数 的 几 何 意 义 和 物 理 意 义 函 数 的 可 导 性 与 连 续 性 之 间 的 关 系 平 面 曲 线 的 切 线 和 法 线 导 数 和 微 分 的 四 则 运 算 基 本 初 等 函 数 的 导 数 复 合 函 数 反 函 数 隐 函 数 以 2015 年 考 研 数 学 二 考 试 大 纲 考 试 科 目 : 高 等 数 学 线 性 代 数 考 试 形 式 和 试 卷 结 构 一 试 卷 满 分 及 考 试 时 间 试 卷 满 分 为 150 分, 考 试 时 间 为 180 分 钟. 二 答 题 方 式 答 题 方 式 为 闭 卷 笔 试. 三 试 卷 内 容 结 构 高 等 教 学 约 78% 线 性 代 数 约 22% 四 试 卷

More information

<4D6963726F736F667420576F7264202D2032303133C4EAB9A4B3CCCBB6CABFCAFDD1A7D7A8D2B5BFCEBFBCCAD4B4F3B8D9D3EBD2AAC7F3>

<4D6963726F736F667420576F7264202D2032303133C4EAB9A4B3CCCBB6CABFCAFDD1A7D7A8D2B5BFCEBFBCCAD4B4F3B8D9D3EBD2AAC7F3> 工 程 硕 士 数 学 考 试 大 纲 与 要 求 ( 包 括 高 等 数 学 和 线 性 代 数 ) 一 函 数 极 限 与 连 续 第 一 部 分 : 高 等 数 学 考 试 内 容 函 数 的 概 念 及 表 示 法 函 数 的 有 界 性 单 调 性 周 期 性 和 奇 偶 性 复 合 函 数 反 函 数 分 段 函 数 和 隐 函 数 基 本 初 等 函 数 的 性 质 及 其 图 形 初

More information

!!!!!

!!!!! 美 国 旧 金 山 湾 区 田 野 调 查 札 记 !!!!! ! 个 案 一 男 士 年 龄 岁 籍 贯 沈 阳! !! 个 案 二 女 士 年 龄 岁 籍 贯 沈 阳!! !!! 一 新 古 典 经 济 学 移 民 理 论 的 解 释!! 二 制 度 层 面 的 原 因! 三 社 会 资 本 理 论 与 东 北 人 移 民 网 络 !!!!!! 四 社 会 关 系 网 络 资 源 配 置 理 论

More information

作 为 生 产 者 式 文 本 的 女 性 主 义 通 俗 小 说 梅 丽 本 文 借 鉴 文 化 研 究 理 论 家 约 翰 费 斯 克 的 生 产 者 式 文 本 这 一 概 念 考 察 女 性 主 义 通 俗 小 说 的 文 本 特 征 写 作 策 略 和 微 观 政 治 意 义 女 性 主 义 通 俗 小 说 通 过 对 传 统 通 俗 小 说 的 挪 用 和 戏 仿 传 播 女 性 主 义

More information

2014年中央财经大学研究生招生录取工作简报

2014年中央财经大学研究生招生录取工作简报 2015 年 中 央 财 经 大 学 研 究 生 招 生 录 取 工 作 简 报 一 硕 士 研 究 生 招 生 录 取 情 况 2015 年 共 有 8705 人 报 考 我 校 硕 士 研 究 生, 其 中 学 术 型 研 究 生 报 考 3657 人, 专 业 硕 士 研 究 生 报 考 5048 人 ; 总 报 考 人 数 较 2014 年 增 长 1.4%, 学 术 型 报 考 人 数 较

More information

抗 日 战 争 研 究 年 第 期

抗 日 战 争 研 究 年 第 期 田 子 渝 武 汉 抗 战 时 期 是 国 共 第 二 次 合 作 的 最 好 时 期 在 国 共 合 作 的 基 础 上 出 现 了 抗 日 救 亡 共 御 外 侮 的 局 面 这 个 大 好 局 面 的 出 现 与 中 共 长 江 局 的 丰 功 伟 绩 是 分 不 开 的 但 长 期 以 来 由 于 有 一 个 王 明 的 右 倾 错 误 直 接 影 响 了 对 它 的 全 面 科 学 准 确

More information

HSK( 一 级 ) 考 查 考 生 的 日 常 汉 语 应 用 能 力, 它 对 应 于 国 际 汉 语 能 力 标 准 一 级 欧 洲 语 言 共 同 参 考 框 架 (CEF) A1 级 通 过 HSK( 一 级 ) 的 考 生 可 以 理 解 并 使 用 一 些 非 常 简 单 的 汉 语

HSK( 一 级 ) 考 查 考 生 的 日 常 汉 语 应 用 能 力, 它 对 应 于 国 际 汉 语 能 力 标 准 一 级 欧 洲 语 言 共 同 参 考 框 架 (CEF) A1 级 通 过 HSK( 一 级 ) 的 考 生 可 以 理 解 并 使 用 一 些 非 常 简 单 的 汉 语 新 汉 语 水 平 考 试 HSK 为 使 汉 语 水 平 考 试 (HSK) 更 好 地 服 务 于 汉 语 学 习 者, 中 国 国 家 汉 办 组 织 中 外 汉 语 教 学 语 言 学 心 理 学 和 教 育 测 量 学 等 领 域 的 专 家, 在 充 分 调 查 了 解 海 外 实 际 汉 语 教 学 情 况 的 基 础 上, 吸 收 原 有 HSK 的 优 点, 借 鉴 近 年 来 国

More information

上证指数

上证指数 上 证 与 修 正 方 法 一 ( 一 ) 计 算 公 式 1. 上 证 指 数 系 列 均 采 用 派 许 加 权 综 合 价 格 指 数 公 式 计 算 2. 上 证 180 指 数 上 证 50 指 数 等 以 成 份 股 的 调 整 股 本 数 为 权 数 进 行 加 权 计 算, 计 算 公 式 为 : 报 告 期 指 数 =( 报 告 期 样 本 股 的 调 整 市 值 / 基 期 )

More information

<4D6963726F736F667420576F7264202D20B9D8D3DAB0BABBAAA3A8C9CFBAA3A3A9D7D4B6AFBBAFB9A4B3CCB9C9B7DDD3D0CFDEB9ABCBBE32303132C4EAC4EAB6C8B9C9B6ABB4F3BBE1B7A8C2C9D2E2BCFBCAE92E646F6378>

<4D6963726F736F667420576F7264202D20B9D8D3DAB0BABBAAA3A8C9CFBAA3A3A9D7D4B6AFBBAFB9A4B3CCB9C9B7DDD3D0CFDEB9ABCBBE32303132C4EAC4EAB6C8B9C9B6ABB4F3BBE1B7A8C2C9D2E2BCFBCAE92E646F6378> 上 海 德 载 中 怡 律 师 事 务 所 关 于 昂 华 ( 上 海 ) 自 动 化 工 程 股 份 有 限 公 司 二 〇 一 二 年 年 度 股 东 大 会 法 律 意 见 书 上 海 德 载 中 怡 律 师 事 务 所 上 海 市 银 城 中 路 168 号 上 海 银 行 大 厦 1705 室 (200120) 电 话 :8621-5012 2258 传 真 :8621-5012 2257

More information

第二讲 数列

第二讲   数列 Togisu XueD Persolized Eduio Developme Ceer 高 考 中 不 等 式 问 题 的 解 决 方 法 通 润 达 久 王 力 前 言 : 近 年 来 不 等 式 问 题 正 越 来 越 多 的 出 现 在 调 研 题 和 高 考 试 题 中 而 且 大 多 出 现 在 江 苏 高 考 的 填 空 压 轴 题 中 是 高 考 考 察 的 重 点 和 难 点 由 于

More information

收 入 支 出 项 目 2016 年 预 算 项 目 2016 年 预 算 预 算 01 表 单 位 : 万 元 ( 保 留 两 位 小 数 ) 一 公 共 财 政 预 算 拨 款 50.06 一 人 员 经 费 23.59 1 一 般 财 力 50.06 1 人 员 支 出 21.95 2 成 品

收 入 支 出 项 目 2016 年 预 算 项 目 2016 年 预 算 预 算 01 表 单 位 : 万 元 ( 保 留 两 位 小 数 ) 一 公 共 财 政 预 算 拨 款 50.06 一 人 员 经 费 23.59 1 一 般 财 力 50.06 1 人 员 支 出 21.95 2 成 品 100.12 2016 年 龙 岩 市 部 门 预 算 表 报 送 日 期 : 年 月 日 单 位 负 责 人 签 章 : 财 务 负 责 人 签 章 : 制 表 人 签 章 : 收 入 支 出 项 目 2016 年 预 算 项 目 2016 年 预 算 预 算 01 表 单 位 : 万 元 ( 保 留 两 位 小 数 ) 一 公 共 财 政 预 算 拨 款 50.06 一 人 员 经 费 23.59

More information

第2章 数据类型、常量与变量

第2章  数据类型、常量与变量 第 2 章 数 据 类 型 常 量 与 变 量 在 计 算 机 程 序 中 都 是 通 过 值 (value) 来 进 行 运 算 的, 能 够 表 示 并 操 作 值 的 类 型 为 数 据 类 型 在 本 章 里 将 会 介 绍 JavaScript 中 的 常 量 (literal) 变 量 (variable) 和 数 据 类 型 (data type) 2.1 基 本 数 据 类 型 JavaScript

More information

!!!!!!!!!!

!!!!!!!!!! 有 限 理 性 动 物 精 神 及 市 场 崩 溃 对 情 绪 波 动 与 交 易 行 为 的 实 验 研 究 林 树 俞 乔 资 本 市 场 的 经 验 表 明 市 场 参 与 主 体 投 资 者 的 情 绪 波 动 对 资 产 交 易 与 价 格 决 定 产 生 了 不 可 忽 视 的 影 响 但 是 现 有 文 献 尚 缺 乏 对 这 一 重 要 因 素 的 研 究 因 此 本 文 的 目 的

More information

物 流 从 业 人 员 职 业 能 力 等 级 证 书 分 为 四 个 级 别, 分 别 为 初 级 助 理 级 中 级 和 高 级 ; 采 购 从 业 人 员 职 业 能 力 等 级 证 书 分 为 三 个 级 别, 分 别 为 中 级 高 级 和 注 册 级 请 各 有 关 单 位 按 照 通

物 流 从 业 人 员 职 业 能 力 等 级 证 书 分 为 四 个 级 别, 分 别 为 初 级 助 理 级 中 级 和 高 级 ; 采 购 从 业 人 员 职 业 能 力 等 级 证 书 分 为 三 个 级 别, 分 别 为 中 级 高 级 和 注 册 级 请 各 有 关 单 位 按 照 通 物 联 培 字 2016 16 号 各 有 关 单 位 : 为 适 应 国 家 一 带 一 路 战 略 实 施 和 物 流 产 业 转 型 升 级 对 人 才 的 新 要 求, 确 保 物 流 采 购 人 才 培 养 工 作 有 序 衔 接 和 持 续 健 康 发 展, 参 照 国 际 惯 例, 中 国 物 流 与 采 购 联 合 会 ( 以 下 简 称 中 物 联 ) 经 研 究 决 定, 以 物

More information

解 决 困 扰 事 业 单 位 高 效 运 行 的 人 员 编 制 难 题 应 摒 弃 既 有 经 验 化 判 断 的 思 维 限 囿 经 由 规 范 化 程 式 化 维 度 专 注 于 事 业 单 位 人 员 编 制 的 标 准 管 理 考 虑 到 事 业 单 位 人 员 编 制 的 复 杂 性 和 公 益 导 向 宜 在 编 制 标 准 定 位 上 确 定 整 体 性 发 展 性 公 益 性 取

More information

数 学 标 准 不 练 习 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.1 提 问 1.2 开 发 和 使 用 模 型 1.3 规 划 和 开 展 调 查 1.4 分 析 和 解 释 数 据 1.5 使 用 数 学 运 算 信 息 和 计 算 机 技 术 以 及 计 算 思 维 1.6 构 造 解 释 和 设 计 解 决 方 案 1.7 通 过 证 据 进 行 论 证 1.8 获 取 评 估 和 交 流 信 息 跨 领 域

More information

朱 丽 明 柯 美 云 周 丽 雅 袁 耀 宗 罗 金 燕 候 晓 华 陈 旻 湖 滥 用 安 非 他 命 会 增 加 得 心 脏 病 的 风 险 据 美 国 科 技 新 闻 网 报 道 根 据 纽 约 路 透 社 报 道 一 份 新 的 研 究 显 示 青 年 及 成 年 人 若 滥 用 安 非 他 命 会 增 加 得 心 脏 病 的 风 险 美 国 德 州 大 学 西 南 医 学 中 心

More information

公 开 刊 物 须 有 国 内 统 一 刊 (CN), 发 表 文 章 的 刊 物 需 要 在 国 家 新 闻 出 版 广 电 总 局 (www.gapp.gov.cn 办 事 服 务 便 民 查 询 新 闻 出 版 机 构 查 询 ) 上 能 够 查 到 刊 凡 在 有 中 国 标 准 书 公 开

公 开 刊 物 须 有 国 内 统 一 刊 (CN), 发 表 文 章 的 刊 物 需 要 在 国 家 新 闻 出 版 广 电 总 局 (www.gapp.gov.cn 办 事 服 务 便 民 查 询 新 闻 出 版 机 构 查 询 ) 上 能 够 查 到 刊 凡 在 有 中 国 标 准 书 公 开 杭 教 人 2014 7 杭 州 市 教 育 局 关 于 中 小 学 教 师 系 列 ( 含 实 验 教 育 管 理 ) 晋 升 高 级 专 业 技 术 资 格 有 关 论 文 要 求 的 通 知 各 区 县 ( 市 ) 教 育 局 ( 社 发 局 ), 直 属 学 校 ( 单 位 ), 委 托 单 位 : 为 进 一 步 规 范 杭 州 市 中 小 学 教 师 系 列 ( 含 实 验 教 育 管

More information

教师上报成绩流程图

教师上报成绩流程图 教 务 管 理 系 统 使 用 说 明 学 生 端 用 户 1 在 校 内 任 何 一 台 连 接 校 园 网 的 计 算 机 上 登 录 教 务 处 主 页 教 务 处 主 页 地 址 : http://jw.stdu.edu.cn/homepage 随 后 点 击 按 钮 ( 见 下 图 所 示 ), 即 可 进 入 综 合 教 务 管 理 系 统 2 在 综 合 教 务 管 理 区 域 内 键

More information

伊 犁 师 范 学 院 611 语 言 学 概 论 全 套 考 研 资 料 <2016 年 最 新 考 研 资 料 > 2-2 语 言 学 纲 要 笔 记, 由 考 取 本 校 本 专 业 高 分 研 究 生 总 结 而 来, 重 点 突 出, 借 助 此 笔 记 可 以 大 大 提 高 复 习 效

伊 犁 师 范 学 院 611 语 言 学 概 论 全 套 考 研 资 料 <2016 年 最 新 考 研 资 料 > 2-2 语 言 学 纲 要 笔 记, 由 考 取 本 校 本 专 业 高 分 研 究 生 总 结 而 来, 重 点 突 出, 借 助 此 笔 记 可 以 大 大 提 高 复 习 效 伊 犁 师 范 学 院 611 语 言 学 概 论 全 套 考 研 资 料 ......2 伊 犁 师 范 学 院 802 文 学 概 论 全 套 考 研 资 料 ......2 伊 犁 师 范 学 院 702 普 通 物 理 全 套 考 研 资 料 ......3 伊 犁

More information

目 录 关 于 图 标... 3 登 陆 主 界 面... 3 工 单 管 理... 5 工 单 列 表... 5 搜 索 工 单... 5 工 单 详 情... 6 创 建 工 单... 9 设 备 管 理 巡 检 计 划 查 询 详 情 销 售 管

目 录 关 于 图 标... 3 登 陆 主 界 面... 3 工 单 管 理... 5 工 单 列 表... 5 搜 索 工 单... 5 工 单 详 情... 6 创 建 工 单... 9 设 备 管 理 巡 检 计 划 查 询 详 情 销 售 管 宝 汇 德 Turbocare 微 服 务 系 统 客 户 操 作 手 册 Version 2.0 北 京 宝 汇 德 技 术 服 务 器 有 限 公 司 技 术 研 发 部 目 录 关 于 图 标... 3 登 陆 主 界 面... 3 工 单 管 理... 5 工 单 列 表... 5 搜 索 工 单... 5 工 单 详 情... 6 创 建 工 单... 9 设 备 管 理... 10 巡

More information

三武一宗灭佛研究

三武一宗灭佛研究 四 川 大 学 博 士 学 位 论 文 三 武 一 宗 灭 佛 研 究 姓 名 : 张 箭 申 请 学 位 级 别 : 博 士 专 业 : 中 国 古 代 史 指 导 教 师 : 杨 耀 坤 20020101 三

More information

微 积 分 ( 二 ) 教 学 大 纲 2 (2010 版 ) 课 程 编 码 :110861 课 程 名 称 : 微 积 分 学 时 / 学 分 :36/2 先 修 课 程 : 初 等 数 学 立 体 几 何 平 面 解 析 几 何 微 积 分 ( 一 ) 适 用 专 业 : 人 力 资 源 管

微 积 分 ( 二 ) 教 学 大 纲 2 (2010 版 ) 课 程 编 码 :110861 课 程 名 称 : 微 积 分 学 时 / 学 分 :36/2 先 修 课 程 : 初 等 数 学 立 体 几 何 平 面 解 析 几 何 微 积 分 ( 一 ) 适 用 专 业 : 人 力 资 源 管 微 积 分 ( 二 ) 教 学 大 纲 2 (2010 版 ) 课 程 编 码 :110861 课 程 名 称 : 微 积 分 学 时 / 学 分 :36/2 先 修 课 程 : 初 等 数 学 立 体 几 何 平 面 解 析 几 何 微 积 分 ( 一 ) 适 用 专 业 : 人 力 资 源 管 理 等 专 业 开 课 教 研 室 : 大 学 数 学 教 研 室 执 笔 : 庄 乐 森 审 定 :

More information

!!

!! 梁 运 文 霍 震 刘 凯 本 文 利 用 奥 尔 多 中 心 的 调 查 数 据 从 三 个 方 面 对 我 国 城 乡 居 民 财 产 分 布 状 况 进 行 了 详 细 的 实 证 分 析 首 先 刻 画 了 我 国 城 乡 居 民 财 产 分 布 的 总 体 统 计 特 征 然 后 从 财 产 构 成 出 发 对 我 国 城 乡 居 民 财 产 分 布 进 行 了 结 构 分 解 最 后 通

More information

黄 金 原 油 总 持 仓 增 长, 同 比 增 幅 分 别 为 4.2% 和 4.1% 而 铜 白 银 以 及 玉 米 则 出 现 减 持, 减 持 同 比 减 少 分 别 为 9.4%,9.4% 以 及 6.5% 大 豆, 豆 粕 结 束 连 续 4 周 总 持 仓 量 增 长, 出 现 小 幅

黄 金 原 油 总 持 仓 增 长, 同 比 增 幅 分 别 为 4.2% 和 4.1% 而 铜 白 银 以 及 玉 米 则 出 现 减 持, 减 持 同 比 减 少 分 别 为 9.4%,9.4% 以 及 6.5% 大 豆, 豆 粕 结 束 连 续 4 周 总 持 仓 量 增 长, 出 现 小 幅 小 麦 净 多 持 仓 增 加, 豆 油 豆 粕 净 多 持 仓 减 少 美 国 CFTC 持 仓 报 告 部 门 : 市 场 研 究 与 开 发 部 类 型 : 量 化 策 略 周 报 日 期 :212 年 5 月 7 日 电 话 :592-5678753 网 址 :www.jinyouqh.com 主 要 内 容 : 根 据 美 国 CFTC 公 布 的 数 据, 本 报 告 中 的 11 个

More information

附 件 : 上 海 市 建 筑 施 工 企 业 施 工 现 场 项 目 管 理 机 构 关 键 岗 位 人 员 配 备 指 南 二 一 四 年 九 月 十 一 日 2

附 件 : 上 海 市 建 筑 施 工 企 业 施 工 现 场 项 目 管 理 机 构 关 键 岗 位 人 员 配 备 指 南 二 一 四 年 九 月 十 一 日 2 公 开 上 海 市 城 乡 建 设 和 管 理 委 员 会 文 件 沪 建 管 2014 758 号 上 海 市 城 乡 建 设 和 管 理 委 员 会 关 于 印 发 上 海 市 建 筑 施 工 企 业 施 工 现 场 项 目 管 理 机 构 关 键 岗 位 人 员 配 备 指 南 的 通 知 各 区 县 建 设 和 交 通 委 员 会 : 为 进 一 步 加 强 对 建 设 工 程 施 工 现

More information

第1篇 道路桥梁工程技术核心专业课程标准及学习绩效考评体系

第1篇 道路桥梁工程技术核心专业课程标准及学习绩效考评体系 陕 西 铁 路 工 程 职 业 技 术 学 院 课 程 标 准 ( 适 用 建 筑 工 程 技 术 专 业 ) 课 程 名 称 : 单 位 工 程 施 工 组 织 设 计 执 笔 人 : 王 恒 博 审 定 人 : 编 制 时 间 : 年 月 日 陕 西 铁 路 工 程 职 业 技 术 学 院 制 表 二 〇 一 一 年 九 月 课 程 标 准 一 课 程 基 本 信 息 课 程 编 码 略 开 设

More information

类 似 地, 又 可 定 义 变 下 限 的 定 积 分 : ( ). 与 ψ 统 称 为 变 限 积 分. f ( ) d f ( t) dt,, 注 在 变 限 积 分 (1) 与 () 中, 不 可 再 把 积 分 变 量 写 成 的 形 式 ( 例 如 ) 以 免 与 积 分 上 下 限 的

类 似 地, 又 可 定 义 变 下 限 的 定 积 分 : ( ). 与 ψ 统 称 为 变 限 积 分. f ( ) d f ( t) dt,, 注 在 变 限 积 分 (1) 与 () 中, 不 可 再 把 积 分 变 量 写 成 的 形 式 ( 例 如 ) 以 免 与 积 分 上 下 限 的 5 ( 一 ) 微 积 分 学 基 本 定 理 当 函 数 的 可 积 性 问 题 告 一 段 落, 并 对 定 积 分 的 性 质 有 了 足 够 的 认 识 之 后, 接 着 要 来 解 决 一 个 以 前 多 次 提 到 过 的 问 题 在 定 积 分 形 式 下 证 明 连 续 函 数 必 定 存 在 原 函 数. 一 变 限 积 分 与 原 函 数 的 存 在 性 设 f 在 [,] 上

More information

四川省卫生厅关于开展医疗美容主诊医师资格考试及换证工作的通知

四川省卫生厅关于开展医疗美容主诊医师资格考试及换证工作的通知 四 川 省 医 学 会 文 件 川 学 会 医 字 [2014]161 号 四 川 省 医 学 会 关 于 开 展 医 疗 美 容 主 诊 医 师 资 格 考 试 及 换 证 工 作 的 通 知 各 市 ( 州 ) 卫 生 局 医 学 会 省 卫 生 和 计 划 生 育 委 员 会 直 属 医 疗 机 构 国 家 卫 生 和 计 划 生 育 委 员 会 驻 川 医 疗 机 构 : 根 据 四 川 省

More information

证券代码:000066 证券简称:长城电脑 公告编号:2014-000

证券代码:000066         证券简称:长城电脑        公告编号:2014-000 证 券 代 码 :000066 证 券 简 称 : 长 城 电 脑 公 告 编 号 :2016-092 中 国 长 城 计 算 机 深 圳 股 份 有 限 公 司 2016 年 度 第 三 次 临 时 股 东 大 会 决 议 公 告 本 公 司 及 其 董 事 会 全 体 成 员 保 证 信 息 披 露 内 容 的 真 实 准 确 完 整, 没 有 虚 假 记 载 误 导 性 陈 述 或 重 大 遗

More information

正 规 培 训 达 规 定 标 准 学 时 数, 并 取 得 结 业 证 书 二 级 可 编 程 师 ( 具 备 以 下 条 件 之 一 者 ) (1) 连 续 从 事 本 职 业 工 作 13 年 以 上 (2) 取 得 本 职 业 三 级 职 业 资 格 证 书 后, 连 续 从 事 本 职 业

正 规 培 训 达 规 定 标 准 学 时 数, 并 取 得 结 业 证 书 二 级 可 编 程 师 ( 具 备 以 下 条 件 之 一 者 ) (1) 连 续 从 事 本 职 业 工 作 13 年 以 上 (2) 取 得 本 职 业 三 级 职 业 资 格 证 书 后, 连 续 从 事 本 职 业 1. 职 业 概 况 1.1 职 业 名 称 可 编 程 师 1.2 职 业 定 义 可 编 程 师 国 家 职 业 标 准 从 事 可 编 程 序 控 制 器 (PLC) 选 型 编 程, 并 对 应 用 进 行 集 成 和 运 行 管 理 的 人 员 1.3 职 业 等 级 本 职 业 共 设 四 个 等 级, 分 别 为 : 四 级 可 编 程 师 ( 国 家 职 业 资 格 四 级 ) 三

More information

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 区 A303 10811046 高 等 数 学 ( 理 二 2) 复 材 1501-2 材 料 科 学 与 工 程 考 试 时 间 2015-2016 学 年 第 二 学 期 集 中 考 试 安 排 (18 周 ) 考 试 日 期 :6 月 27 日 星 期 一 考 场 所 在 教 学 楼 ( 教 学 区 ) 考 试 教 室 课 程 号 课 程 名 考 生 所 在 专 业 ( 班 级 ) 考 生 所 属 学 院 8:10-9:50 第 二 公 共 教 学 楼 A 区 A101 10811026 高 等 数 学 (

More information

Template BR_Rec_2005.dot

Template BR_Rec_2005.dot ITU-R BT.1789 建 议 书 1 ITU-R BT.1789 建 议 书 在 分 组 视 频 传 输 中 利 用 传 输 误 码 信 息 重 建 接 收 视 频 的 方 法 (ITU-R 44/6 和 ITU-R 109/6 课 题 ) (2007 年 ) 范 围 本 建 议 书 对 业 务 提 供 商 重 建 接 收 视 频 的 方 法 做 了 详 细 介 绍, 以 便 利 用 传 输

More information

附件1:

附件1: 附 件 5 增 列 硕 士 专 业 学 位 授 权 点 申 请 表 硕 士 专 业 学 位 类 别 ( 工 程 领 域 ): 工 程 硕 士 ( 控 制 工 程 领 域 ) 申 报 单 位 名 称 : 上 海 工 程 技 术 大 学 一 申 请 增 列 硕 士 专 业 学 位 授 权 点 论 证 报 告 申 请 增 列 硕 士 专 业 学 位 授 权 点 论 证 报 告 一 专 业 人 才 需 求

More information

<4D F736F F D20322EC9F3BACBC8CBD4B1D7CAB8F1D7A2B2E1B9DCC0EDB9E6B7B6B8BDB1ED2E646F63>

<4D F736F F D20322EC9F3BACBC8CBD4B1D7CAB8F1D7A2B2E1B9DCC0EDB9E6B7B6B8BDB1ED2E646F63> 审 核 人 员 资 格 管 理 规 范 版 次 :F/0 附 表 1: QMS/EMS/OHSMS 管 理 体 系 审 核 员 通 用 要 求 申 请 条 件 初 次 综 合 素 质 考 核 越 级 晋 升 条 件 实 习 审 核 员 审 核 员 主 任 审 核 员 1. 高 等 教 育 : 大 学 本 科 以 上 学 历, 或 大 专 学 历 及 相 2. 工 作 1 : ; 3. 专 业 工 作

More information

第 四 条 建 设 单 位 对 可 能 产 生 职 业 病 危 害 的 建 设 项 目, 应 当 依 照 本 办 法 向 安 全 生 产 监 督 管 理 部 门 申 请 职 业 卫 生 三 同 时 的 备 案 审 核 审 查 和 竣 工 验 收 建 设 项 目 职 业 卫 生 三 同 时 工 作 可

第 四 条 建 设 单 位 对 可 能 产 生 职 业 病 危 害 的 建 设 项 目, 应 当 依 照 本 办 法 向 安 全 生 产 监 督 管 理 部 门 申 请 职 业 卫 生 三 同 时 的 备 案 审 核 审 查 和 竣 工 验 收 建 设 项 目 职 业 卫 生 三 同 时 工 作 可 第 51 号 建 设 项 目 职 业 卫 生 三 同 时 监 督 管 理 暂 行 办 法 已 经 2012 年 3 月 6 日 国 家 安 全 生 产 监 督 管 理 总 局 局 长 办 公 会 议 审 议 通 过, 现 予 公 布, 自 2012 年 6 月 1 日 起 施 行 国 家 安 全 生 产 监 督 管 理 总 局 骆 琳 二 一 二 年 四 月 二 十 七 日 建 设 项 目 职 业

More information

保健食品功能目录管理办法

保健食品功能目录管理办法 保 健 食 品 保 健 功 能 目 录 与 原 料 目 录 管 理 办 法 ( 征 求 意 见 稿 ) 第 一 章 总 则 第 一 条 [ 目 的 依 据 ] 为 规 范 保 健 食 品 保 健 功 能 目 录 和 保 健 食 品 原 料 目 录 的 管 理 工 作, 根 据 中 华 人 民 共 和 国 食 品 安 全 法, 制 定 本 办 法 第 二 条 [ 适 用 范 围 ] 中 华 人 民 共

More information

抗 日 战 争 研 究! 年 第 期 # # # # #!!!!!!!! #!!

抗 日 战 争 研 究! 年 第 期 # # # # #!!!!!!!! #!! 洪 小 夏 中 美 合 作 所 是 抗 战 时 期 中 美 两 国 在 反 法 西 斯 统 一 战 线 背 景 下 建 立 的 一 个 抗 日 军 事 合 作 机 构 但 过 去 由 文 学 影 视 作 品 给 人 造 成 的 印 象 似 乎 是 一 个 美 蒋 反 动 派 勾 结 的 集 中 营 中 共 十 一 届 三 中 全 会 以 后 逐 渐 有 人 为 其 正 名 但 长 期 宣 传 形 成

More information

易 迪 拓 培 训 专 注 于 微 波 射 频 天 线 设 计 人 才 的 培 养 网 址 :http://www.edatop.com 射 频 和 天 线 设 计 培 训 课 程 推 荐 易 迪 拓 培 训 (www.edatop.com) 由 数 名 来 自 于 研 发 第 一 线 的 资 深 工 程 师 发 起 成 立, 致 力 并 专 注 于 微 波 射 频 天 线 设 计 研 发 人 才

More information

目 录 一 系 统 访 问... 1 二 门 户 首 页 申 报 用 户 审 核 用 户... 2 三 系 统 登 录 用 户 名 密 码 登 录 新 用 户 注 册 用 户 登 录 已 注 册 用

目 录 一 系 统 访 问... 1 二 门 户 首 页 申 报 用 户 审 核 用 户... 2 三 系 统 登 录 用 户 名 密 码 登 录 新 用 户 注 册 用 户 登 录 已 注 册 用 水 路 运 输 建 设 综 合 管 理 信 息 系 统 - 门 户 系 统 用 户 手 册 二 零 一 五 年 十 一 月 目 录 一 系 统 访 问... 1 二 门 户 首 页... 1 1. 申 报 用 户... 1 2. 审 核 用 户... 2 三 系 统 登 录... 4 1. 用 户 名 密 码 登 录... 4 1.1 新 用 户 注 册... 4 1.2 用 户 登 录... 7

More information

<4D6963726F736F667420576F7264202D20BFC9B1E0B3CCD0F2BFD8D6C6CFB5CDB3C9E8BCC6CAA6B9FABCD2D6B0D2B5B1EAD7BC2E646F63>

<4D6963726F736F667420576F7264202D20BFC9B1E0B3CCD0F2BFD8D6C6CFB5CDB3C9E8BCC6CAA6B9FABCD2D6B0D2B5B1EAD7BC2E646F63> 国 家 职 业 标 准 1 可 编 程 序 控 制 系 统 设 计 师 国 家 职 业 标 准 1. 职 业 概 况 1.1 职 业 名 称 可 编 程 序 控 制 系 统 设 计 师 1.2 职 业 定 义 从 事 可 编 程 序 控 制 器 (PLC) 选 型 编 程, 并 对 应 用 系 统 进 行 设 计 集 成 和 运 行 管 理 的 人 员 1.3 职 业 等 级 本 职 业 共 设 四

More information

北京信息科技大学本科学生成绩管理办法

北京信息科技大学本科学生成绩管理办法 北 京 信 息 科 技 大 学 文 件 校 教 发 2012 113 号 关 于 印 发 北 京 信 息 科 技 大 学 本 科 学 生 成 绩 管 理 办 法 的 通 知 各 相 关 单 位 : 现 将 北 京 信 息 科 技 大 学 本 科 学 生 成 绩 管 理 办 法 印 发 给 你 们, 请 遵 照 执 行 北 京 信 息 科 技 大 学 2012 年 12 月 17 日 1 北 京 信

More information

精 勤 求 学 自 强 不 息 Born to win! 解 析 : 由 极 限 的 保 号 性 知 存 在 U ( a) 当 a 时 f ( ) f ( a) 故 f ( ) 在 点 a 不 取 极 值 f ( ) f ( a) f ( ) f ( a) lim lim a a a a ( a)

精 勤 求 学 自 强 不 息 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 小 题 每 小 题 分 共 分 下 列 每 小 题 给 出 的 四 个 选 项 中 只 有 一 项 符 合 题 目 要 求 的 请 将 所 选 项 前 的 字 母 填 在 答 题 纸 指 定 位 置 上 () 在 点 处 不 存 在 极 限 的 函 数 是 (

More information

3 复 试 如 何 准 备 4 复 试 成 绩 计 算 5 复 试 比 例 6 复 试 类 型 7 怎 么 样 面 对 各 种 复 试 04 05

3 复 试 如 何 准 备 4 复 试 成 绩 计 算 5 复 试 比 例 6 复 试 类 型 7 怎 么 样 面 对 各 种 复 试 04 05 1 复 试 流 程 2 复 试 考 查 形 式 02 03 3 复 试 如 何 准 备 4 复 试 成 绩 计 算 5 复 试 比 例 6 复 试 类 型 7 怎 么 样 面 对 各 种 复 试 04 05 2 怎 样 给 导 师 留 下 良 好 的 第 一 印 象 把 握 进 门 时 机 1 面 试 中 穿 着 的 瞒 天 过 海 3 无 声 胜 有 声 的 肢 体 语 言 育 4 眼 睛 是 心

More information

试 论 后 民 权 时 代 美 国 黑 人 的 阶 层 分 化 和 族 裔 特 征 学 者 年 代 黑 人 中 产 阶 层 定 义 弗 瑞 泽 毕 林 斯 勒 马 克 艾 德 威 尔 逊 科 林 斯 兰 德 里 奥 力 威 夏 佩 罗 帕 锑 罗 收 入 来 源 于 从 事 可 以 定 义 为 白

试 论 后 民 权 时 代 美 国 黑 人 的 阶 层 分 化 和 族 裔 特 征 学 者 年 代 黑 人 中 产 阶 层 定 义 弗 瑞 泽 毕 林 斯 勒 马 克 艾 德 威 尔 逊 科 林 斯 兰 德 里 奥 力 威 夏 佩 罗 帕 锑 罗 收 入 来 源 于 从 事 可 以 定 义 为 白 蒿 琨 黑 人 中 产 阶 层 试 论 后 民 权 时 代 美 国 黑 人 的 阶 层 分 化 和 族 裔 特 征 学 者 年 代 黑 人 中 产 阶 层 定 义 弗 瑞 泽 毕 林 斯 勒 马 克 艾 德 威 尔 逊 科 林 斯 兰 德 里 奥 力 威 夏 佩 罗 帕 锑 罗 收 入 来 源 于 从 事 可 以 定 义 为 白 领 工 作 的 服 务 行 业 中 产 阶 层 的 成 就 由 教 育

More information

际 联 考 的 非 美 术 类 本 科, 提 前 批 本 科 体 育 类 第 一 批 第 二 批 第 三 批 的 理 工 类 和 文 史 类 本 科 平 行 志 愿, 考 生 可 以 填 报 6 所 院 校 志 愿 符 合 贫 困 地 区 专 项 计 划 和 农 村 考 生 专 项 计 划 报 考

际 联 考 的 非 美 术 类 本 科, 提 前 批 本 科 体 育 类 第 一 批 第 二 批 第 三 批 的 理 工 类 和 文 史 类 本 科 平 行 志 愿, 考 生 可 以 填 报 6 所 院 校 志 愿 符 合 贫 困 地 区 专 项 计 划 和 农 村 考 生 专 项 计 划 报 考 第 四 部 分 平 行 志 愿 57. 什 么 是 平 行 志 愿?/ 32 58. 我 省 在 哪 个 批 次 实 行 平 行 志 愿? 考 生 最 多 可 以 填 报 几 所 院 校 志 愿?/ 32 59. 第 一 二 三 批 本 科 平 行 志 愿 如 何 投 档?/ 32 60. 艺 术 本 科 ( 二 ) 艺 术 本 科 ( 三 ) 和 体 育 本 科 的 平 行 志 愿 如 何 投 档?/

More information