MITK



Similar documents
说 明 为 了 反 映 教 运 行 的 基 本 状 态, 为 校 和 院 制 定 相 关 政 策 和 进 行 教 建 设 与 改 革 提 供 据 依 据, 校 从 程 资 源 ( 开 类 别 开 量 规 模 ) 教 师 结 构 程 考 核 等 维 度, 对 2015 年 春 季 期 教 运 行 基

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

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

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

<433A5C446F63756D656E E E67735C41646D696E F725CD7C0C3E65CC2DBCEC4CFB5CDB3CAB9D3C3D6B8C4CFA3A8BCF2BBAFA3A95CCAB9D3C3D6B8C4CF31302D31392E646F63>


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

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

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

<4D F736F F D D323630D6D0B9FAD3A6B6D4C6F8BAF2B1E4BBAFB5C4D5FEB2DFD3EBD0D0B6AF C4EAB6C8B1A8B8E6>

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


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

Microsoft Word - 文件汇编.doc

( ) 信 号 与 系 统 Ⅰ 学 科 基 础 必 修 课 教 周 2016 年 06 月 13 日 (08:00-09:35) ( )


I

采 取 行 动 的 机 会 90% 开 拓 成 功 的 道 路 2

修改版-操作手册.doc

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

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

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

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

untitled

课程类 别

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

<433A5C C6B73625C B746F705CB9FABCCAD6D0D2BDD2A9D7A8D2B5B8DFBCB6BCBCCAF5D6B0B3C6C6C0C9F3C9EAC7EBD6B8C4CFA3A CDA8D3C3B0E6A3A92E646F63>

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

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

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

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

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

随着执业中医师资格考试制度的不断完善,本着为我校中医学专业认证服务的目的,本文通过对我校中医类毕业生参加2012年和2013年的中医执业医师考试成绩及通过率、掌握率进行分析,并与全国的平均水平进行差异比较分析,以此了解我校执业中医师考试的现状,进而反映我校中医类课程总体教学水平,发现考核知识模块教学中存在的不足,反馈给相关学院和教学管理部门,以此提高教学和管理水平。

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

中 国 软 科 学 年 第 期!!!

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

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

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

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


 编号:

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


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

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

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

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

<4D F736F F D C4EAB9A4B3CCCBB6CABFCAFDD1A7D7A8D2B5BFCEBFBCCAD4B4F3B8D9D3EBD2AAC7F3>

!!!!!


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

抗 日 战 争 研 究 年 第 期

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

上证指数

<4D F736F F D20B9D8D3DAB0BABBAAA3A8C9CFBAA3A3A9D7D4B6AFBBAFB9A4B3CCB9C9B7DDD3D0CFDEB9ABCBBE C4EAC4EAB6C8B9C9B6ABB4F3BBE1B7A8C2C9D2E2BCFBCAE92E646F6378>

第二讲 数列

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

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

!!!!!!!!!!

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


数 学 标 准 不 练 习 1.1 理 解 问 题 并 坚 持 解 决 这 些 问 题 1.2 以 抽 象 和 定 量 方 式 推 理 1.3 建 构 可 行 参 数 和 评 判 他 人 的 推 理 1.4 使 用 数 学 方 法 建 模 1.5 策 略 性 地 使 用 合 适 的 工 具 1.6


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

教师上报成绩流程图

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

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

三武一宗灭佛研究

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

!!

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

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

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

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

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

证券代码: 证券简称:长城电脑 公告编号:

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

学 年 第 二 学 期 集 中 考 试 安 排 (18 周 ) 考 试 日 期 :6 月 27 日 星 期 一 8:10-9:50 第 二 公 共 教 学 楼 A 区 A 高 等 数 学 ( 理 二 2) 复 材 材 料 科 学 与 工 程

Template BR_Rec_2005.dot

附件1:

<4D F736F F D20322EC9F3BACBC8CBD4B1D7CAB8F1D7A2B2E1B9DCC0EDB9E6B7B6B8BDB1ED2E646F63>

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

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

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


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

<4D F736F F D20BFC9B1E0B3CCD0F2BFD8D6C6CFB5CDB3C9E8BCC6CAA6B9FABCD2D6B0D2B5B1EAD7BC2E646F63>

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

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

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

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

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

Transcription:

集 成 化 医 学 影 像 算 法 平 台 MITK 的 研 究 与 实 现 i

集 成 化 医 学 影 像 算 法 平 台 MITK 的 研 究 与 实 现 目 录 1 绪 论...1 1.1 医 学 影 像 算 法 平 台 研 究 的 背 景 及 意 义...1 1.2 医 学 影 像 算 法 平 台 研 究 的 内 容...2 1.2.1 整 体 框 架 的 研 究...3 1.2.2 医 学 影 像 算 法 的 研 究...3 1.3 医 学 影 像 算 法 平 台 的 国 内 外 研 究 现 状...5 1.3.1 VTK 简 介...5 1.3.2 ITK 简 介...6 1.3.3 VTK 和 ITK 的 局 限 性...6 1.4 本 书 的 主 要 内 容...7 2 MITK 的 总 体 设 计...12 2.1 MITK 的 设 计 目 标...12 2.1.1 统 一 的 风 格...12 2.1.2 有 限 目 标...13 2.1.3 可 移 植 性...13 2.1.4 代 码 优 化...13 2.2 MITK 的 整 体 计 算 框 架...13 2.2.1 基 于 数 据 流 模 型 的 整 体 框 架...14 2.2.2 数 据 模 型...15 2.2.3 算 法 模 型...16 2.3 MITK 的 基 础 设 施 搭 建...17 2.3.1 Object 提 供 的 服 务...17 2.3.2 内 存 管 理...23 2.3.3 跨 平 台 的 实 现...25 2.3.4 SSE 加 速 的 实 现...26 2.4 小 结...27 ii

集 成 化 医 学 影 像 算 法 平 台 MITK 的 研 究 与 实 现 3 面 绘 制 (SURFACE RENDERING) 的 框 架 与 实 现...28 3.1 表 面 重 建 算 法 及 其 在 MITK 中 的 实 现...28 3.1.1 传 统 的 Marching Cubes 算 法...29 3.1.2 基 于 分 割 的 Marching Cubes 方 法 [1]...50 3.2 MITK 中 的 表 面 绘 制 框 架...51 3.2.1 表 面 绘 制 框 架 的 设 计...51 3.2.2 表 面 绘 制 框 架 的 实 现...53 3.3 小 结...75 4 体 绘 制 (VOLUME RENDERING) 的 框 架 与 实 现...77 4.1 体 绘 制 算 法 综 述...77 4.2 MITK 中 的 体 绘 制 算 法 框 架...78 4.3 体 绘 制 算 法 在 MITK 中 的 实 现...81 4.3.1 View 中 绘 制 操 作 的 实 现...81 4.3.2 VolumeModel 的 实 现...85 4.3.3 VolumeProperty 的 实 现...92 4.3.4 VolumeRenderer 的 实 现...96 4.3.5 Ray Casting 算 法 的 实 现...99 4.4 小 结...129 5 三 维 人 机 交 互 的 设 计 与 实 现...132 5.1 背 景 介 绍...132 5.2 以 3D WIDGETS 为 核 心 的 三 维 人 机 交 互 的 框 架 设 计...133 5.2.1 3D Widgets 的 设 计 准 则...133 5.2.2 以 3D Widgets 为 核 心 的 三 维 交 互 框 架 总 体 结 构...133 5.2.3 以 3D Widgets 为 核 心 的 三 维 交 互 框 架 设 计...134 5.3 以 3D WIDGETS 为 核 心 的 三 维 人 机 交 互 的 实 现...136 5.3.1 Manipulator 的 实 现...136 5.3.2 实 现 具 体 的 WidgetModel...141 5.4 三 维 交 互 的 应 用 实 例...149 iii

集 成 化 医 学 影 像 算 法 平 台 MITK 的 研 究 与 实 现 5.4.1 mitklinewidgetmodel3d 的 应 用 实 例...149 5.4.2 mitkanglewidgetmodel3d 的 应 用 实 例...149 5.4.3 mitkclippingplanewidget 的 应 用 实 例...150 5.5 小 结...151 6 分 割 算 法 的 设 计 与 实 现...153 6.1 MITK 中 的 分 割 算 法 框 架...153 6.1.1 数 据 模 块...154 6.1.2 数 据 获 取 模 块...155 6.1.3 数 据 输 出 模 块...156 6.1.4 数 据 处 理 模 块...157 6.2 基 于 阈 值 的 分 割 算 法 在 MITK 中 的 实 现...158 6.2.1 原 理 概 述...158 6.2.2 阈 值 分 割 算 法 开 发 包 设 计 与 实 现...159 6.2.3 阈 值 分 割 结 果 示 意 图...160 6.3 区 域 增 长 算 法 在 MITK 中 的 实 现...160 6.3.1 原 理 概 述...160 6.3.2 区 域 生 长 算 法 开 发 包 的 设 计 与 实 现...161 6.3.3 区 域 生 长 分 割 结 果...165 6.4 交 互 式 分 割 在 MITK 中 的 实 现...168 6.4.1 原 理 概 述...168 6.4.2 交 互 式 分 割 算 法 开 发 包 的 设 计 与 实 现...169 6.4.3 交 互 式 分 割 算 法 的 分 割 结 果...172 6.5 LIVE WIRE 算 法 在 MITK 中 的 实 现...173 6.5.1 原 理 概 述...173 6.5.2 live Wire 算 法 包 的 设 计 与 实 现...177 6.5.3 live wire 分 割 结 果...182 6.6 FAST MARCHING 算 法 在 MITK 中 的 实 现...184 6.6.1 原 理 概 述...184 6.6.2 Fast Marching 算 法 开 发 包 的 设 计 与 实 现...186 iv

集 成 化 医 学 影 像 算 法 平 台 MITK 的 研 究 与 实 现 6.6.3 Fast Marching 分 割 结 果...194 6.7 LEVEL SET 算 法 在 MITK 中 的 实 现...195 6.7.1 原 理 概 述...195 6.7.2 level set 算 法 开 发 包 的 设 计 与 实 现...198 6.7.3 level set 分 割 结 果...205 7 配 准 算 法 的 设 计 与 实 现...209 7.1 配 准 算 法 简 介...209 7.2 MITK 中 的 配 准 算 法 框 架...211 7.3 几 何 变 换...215 7.3.1 刚 性 变 换 算 法...215 7.3.2 线 性 变 换 与 一 对 一 变 换...216 7.3.3 变 换 算 法 在 MITK 中 的 实 现...217 7.4 图 像 插 值...218 7.4.1 最 近 邻 插 值...219 7.4.2 线 性 插 值...220 7.4.3 PV 插 值...220 7.4.4 插 值 算 法 在 MITK 中 的 实 现...221 7.5 相 似 性 测 度...222 7.5.1 灰 度 平 均 差 测 度...223 7.5.2 归 一 化 相 关 系 数...223 7.5.3 Pattern Intensity...223 7.5.4 互 信 息...223 7.5.5 相 似 性 测 度 在 MITK 中 的 实 现...225 7.6 函 数 优 化...227 7.7 配 准 算 法 实 现...228 7.8 应 用 实 例...229 7.9 小 结...230 8 DICOM 标 准 的 实 现...232 v

集 成 化 医 学 影 像 算 法 平 台 MITK 的 研 究 与 实 现 8.1 DICOM 标 准 简 介...232 8.1.1 DICOM 标 准 的 产 生 和 演 化...232 8.1.2 DICOM 标 准 的 主 要 特 点...235 8.1.3 DICOM 标 准 的 总 体 结 构 和 主 要 内 容...237 8.2 MITK 中 DICOM 标 准 的 实 现...241 8.2.1 DICOM 数 据 编 码 方 式 和 文 件 结 构 [1] [1]...242 8.2.2 DICOM 文 件 读 写 模 块 (DICOM Utility) 的 实 现...254 8.2.3 DICOM Utility 在 MITK 中 的 封 装...265 8.3 小 结...269 9 应 用 MITK 开 发 实 际 项 目...271 9.1 开 发 环 境 的 设 置...271 9.2 一 个 简 单 的 图 像 浏 览 器...279 9.3 用 MITK 进 行 表 面 重 建...305 9.4 一 个 比 较 完 善 的 例 子...314 10 扩 充 MITK 功 能...340 10.1 扩 充 MITK 功 能 的 预 备 知 识...340 10.2 实 例 之 一 : 扩 充 READER 功 能...343 10.2.1 扩 充 Reader 功 能 的 一 般 步 骤...343 10.2.2 实 例 程 序 的 功 能...343 10.2.3 实 例 程 序 的 制 作...345 10.3 实 例 之 二 : 扩 充 FILTER 功 能...352 10.3.1 扩 充 Filter 功 能 的 一 般 步 骤...352 10.3.2 实 例 程 序 的 功 能...352 10.3.3 实 例 程 序 的 制 作...353 10.4 小 结...364 11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMED 的 设 计 与 实 现..365 11.1 背 景 介 绍...365 11.2 相 关 工 作...365 vi

集 成 化 医 学 影 像 算 法 平 台 MITK 的 研 究 与 实 现 11.2.1 3DVIEWNIX 系 统 简 介...365 11.2.2 VolView 系 统 简 介...366 11.3 3DMED 的 整 体 设 计...366 11.3.1 3DMed 的 设 计 目 标...366 11.3.2 3DMed 提 供 的 功 能 简 介...368 11.4 3DMED 的 PLUGIN 整 体 框 架 的 实 现...370 11.4.1 Plugin SDK 的 实 现...371 11.4.2 Plugins 的 实 现...373 11.4.3 3DMed Kernel 的 实 现...373 11.5 应 用 实 例...376 11.6 小 结...378 12 开 发 3DMED 的 PLUGIN...379 12.1 总 体 介 绍...379 12.2 PLUGIN 实 例 : 使 用 MITK...383 12.2.1 工 程 的 建 立 及 设 置...383 12.2.2 实 例 制 作...386 12.2.3 插 入 到 3DMed...389 12.3 PLUGIN 实 例 : 不 使 用 MITK...391 12.3.1 工 程 的 建 立 及 设 置...391 12.3.2 实 例 制 作...393 12.3.3 插 入 到 3DMed...402 12.4 小 结...404 附 录 A 医 学 影 像 数 据 集...406 附 录 B MITK 网 站 介 绍...407 vii

1 绪 论 1 绪 论 1.1 医 学 影 像 算 法 平 台 研 究 的 背 景 及 意 义 自 从 德 国 科 学 家 伦 琴 在 1895 年 发 明 X 射 线 以 来,CT( 计 算 机 断 层 成 像 ) MRI( 核 磁 共 振 成 像 ) CR( 计 算 机 X 线 成 像 ) B 超 电 子 内 窥 镜 等 现 代 医 学 影 像 设 备 先 后 出 现, 使 得 传 统 的 医 学 诊 断 方 式 发 生 了 革 命 性 的 变 化 使 用 计 算 机 对 医 学 影 像 设 备 采 集 到 的 影 像 进 行 处 理 这 一 技 术 被 称 为 医 学 影 像 处 理 与 分 析, 它 可 以 辅 助 医 生 进 行 更 好 更 准 确 的 诊 断 随 着 现 代 计 算 机 科 学 技 术 的 发 展, 医 学 影 像 处 理 与 分 析 越 来 越 多 地 受 到 人 们 的 重 视, 现 在 已 经 成 为 一 门 新 兴 的 发 展 迅 速 的 交 叉 科 学 领 域 [1] 21 世 纪 是 以 人 为 中 心 的 世 纪, 如 何 更 全 面 地 掌 握 和 更 有 效 地 利 用 人 的 信 息 将 成 为 许 多 高 新 技 术 产 业 的 关 键 因 素, 而 跟 全 社 会 人 民 的 医 疗 保 健 和 健 康 事 业 息 息 相 关 的 医 学 影 像 处 理 与 分 析 学 科 也 将 在 21 世 纪 得 到 快 速 的 发 展 医 学 影 像 处 理 与 分 析 是 计 算 机 信 息 学 物 理 学 和 医 学 等 相 结 合 的 产 物, 在 20 世 纪 内 经 历 了 学 科 形 成 发 展 和 快 速 发 展 的 过 程, 如 果 说 20 世 纪 是 医 学 影 像 形 成 和 快 速 发 展 的 世 纪, 在 21 世 纪 就 将 是 医 学 影 像 广 泛 应 用 的 世 纪 目 前 随 着 改 革 开 放 和 国 力 不 断 提 高, 我 国 从 国 外 进 口 了 越 来 越 多 的 高 精 密 的 医 疗 设 备 并 在 医 疗 临 床 上 广 泛 使 用 然 而, 由 于 国 内 缺 乏 配 套 的 开 发 队 伍, 也 没 有 形 成 开 展 跨 学 科 开 发 研 究 的 机 制, 一 般 使 用 的 都 是 国 外 公 司 随 机 器 附 带 的 软 件, 使 得 我 国 对 这 些 高 精 密 医 疗 设 备 利 用 及 研 发 的 速 度 缓 慢 考 虑 到 目 前 以 硬 件 设 备 为 主 的 医 疗 器 械 都 必 须 配 套 相 应 的 计 算 机 软 件, 而 软 件 的 成 本 和 开 支 将 逐 步 超 过 硬 件, 目 前 我 国 已 经 逐 步 具 备 了 医 疗 器 械 的 生 产 能 力, 但 是 高 质 量 的 配 套 软 件 仍 然 相 当 缺 乏, 因 为 软 件 的 编 制 依 赖 于 对 科 学 问 题 的 数 学 描 述 和 计 算 方 法 21 世 纪 的 产 业, 医 学 影 像 的 计 算 模 型 和 计 算 方 法 将 成 为 制 约 医 学 软 件 业 的 首 要 因 素, 抓 住 这 一 契 机 将 成 为 我 国 医 疗 信 息 高 质 量 稳 定 发 展 和 参 与 国 际 竞 争 的 重 中 之 重 而 积 累 发 展 具 有 我 国 自 主 知 识 产 权 的 高 质 量 的 医 学 影 像 软 件 平 台, 尤 其 是 底 层 的 算 法 研 发 平 台, 对 促 进 我 国 医 疗 仪 器 设 备 的 应 用 尤 其 是 医 学 影 像 软 件 业 的 持 续 发 展, 并 直 接 造 福 于 人 民 的 医 疗 保 健 和 健 康 事 业 是 非 常 重 要 的 1

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

1 绪 论 1.2.1 整 体 框 架 的 研 究 医 学 影 像 处 理 与 分 析 的 目 的 是 从 数 据 到 知 识, 其 中 在 算 法 层 面 包 含 医 学 影 像 分 割 医 学 影 像 配 准 ( 包 括 图 像 信 息 融 合 ) 三 维 可 视 化 ( 包 括 表 面 绘 制 体 绘 制 和 数 字 几 何 处 理 ) 等 ; 在 数 据 表 达 层 面, 医 学 影 像 数 据 具 有 多 源 (CT MRI PET 等 ) 多 维 ( 二 维 三 维 四 维 及 更 高 维 ) 多 模 态 ( 形 态 生 理 心 理 病 理 ) 异 构 信 息 ( 像 素 矩 阵 表 示 的 图 像 像 素 集 合 表 示 的 目 标 符 号 表 示 的 知 识 ) 等 特 点 如 何 对 这 么 多 的 算 法 以 及 这 么 复 杂 的 数 据 进 行 抽 象, 得 到 统 一 的 数 据 表 达 和 高 效 处 理 方 法, 以 便 能 够 在 一 个 统 一 的 软 件 框 架 中 高 效 精 确 地 来 处 理 是 一 个 比 较 困 难 的 问 题 另 外, 目 前 研 究 人 员 在 各 个 算 法 分 支 已 经 提 出 了 多 种 算 法, 并 且 现 在 对 于 各 种 算 法 性 能 的 评 价 的 研 究 也 越 来 越 受 到 重 视, 比 如 对 分 割 算 法 准 确 性 的 评 价 已 经 成 为 一 个 非 常 前 沿 的 问 题 如 果 将 这 些 算 法 置 入 一 个 统 一 的 计 算 框 架, 组 成 一 条 管 道 式 的 流 水 线, 在 统 一 的 数 据 表 达 统 一 的 算 法 框 架 以 及 统 一 的 参 数 设 置 下, 去 研 究 各 个 算 法 的 性 能 前 一 算 法 对 后 续 算 法 性 能 的 影 响 等 等, 不 仅 能 更 好 地 在 局 部 上 去 理 解 单 个 算 法 的 性 能 指 标 不 同 算 法 的 性 能 差 异, 还 有 助 于 在 整 体 上 去 理 解 和 把 握 不 同 算 法 之 间 的 影 响 整 体 的 瓶 颈 所 在 不 同 信 息 的 整 合 等 1.2.2 医 学 影 像 算 法 的 研 究 目 前 医 学 影 像 算 法 方 面 的 研 究 主 要 分 为 医 学 影 像 分 割 医 学 影 像 配 准 三 维 可 视 化 这 三 大 研 究 领 域, 其 中 每 一 类 下 面 又 可 根 据 一 定 的 原 则 分 为 不 同 的 子 类, 并 且 每 一 个 子 类 都 有 非 常 多 不 同 的 算 法 (1) 医 学 影 像 分 割 算 法 医 学 影 像 分 割 的 主 要 目 的 是 将 医 学 影 像 中 感 兴 趣 的 物 体 ( 一 般 是 病 灶 区 ) 提 取 出 来, 以 前 医 生 往 往 是 通 过 自 己 的 经 验 手 工 分 割 病 灶 区, 而 分 割 的 目 的 就 是 尽 量 自 动 准 确 地 将 这 些 病 灶 区 提 取 出 来 图 像 分 割 是 图 像 处 理 图 像 分 析 和 计 算 机 视 觉 等 领 域 最 经 典 的 研 究 课 题 之 一, 也 是 最 大 的 难 点 之 一, 其 理 论 和 方 法 至 今 尚 未 获 得 圆 满 的 解 决 在 医 学 领 域, 图 像 分 割 也 一 直 是 一 个 非 常 活 跃 的 研 究 课 题, 吸 引 着 很 多 的 研 究 人 员 去 探 索 这 个 问 题 医 学 影 像 分 割 可 以 大 致 分 为 以 下 几 种 类 型 : 基 于 区 域 的 分 割 方 法, 包 括 阈 值 分 割 [3][4] 区 域 生 长 等 [5]; 基 于 边 缘 的 分 割 方 法, 包 括 梯 度 算 子 Roberts 算 子 3

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

1 绪 论 格 平 滑 等 算 法 1.3 医 学 影 像 算 法 平 台 的 国 内 外 研 究 现 状 如 上 所 述, 医 学 影 像 处 理 算 法 的 三 个 主 要 研 究 领 域 目 前 都 已 经 有 非 常 多 的 成 熟 的 算 法, 并 且 新 的 算 法 还 在 不 断 出 现 除 了 在 算 法 研 究 方 面 的 努 力 外, 一 些 研 究 组 织 为 了 更 好 地 利 用 现 有 的 算 法, 避 免 重 复 的 劳 动, 开 发 了 许 多 算 法 平 台 (Algorithms Toolkit), 这 些 算 法 平 台 不 仅 封 装 了 他 们 自 己 的 算 法, 还 封 装 了 很 多 已 经 成 熟 的 相 关 算 法 这 些 算 法 平 台 极 大 地 便 利 了 医 学 影 像 领 域 的 研 究 者, 他 们 可 以 在 这 些 平 台 上 来 创 建 自 己 的 实 验 环 境, 验 证 自 己 的 算 法, 而 不 用 从 头 再 写 一 些 已 经 成 熟 的 算 法 可 以 作 个 比 喻, 这 些 平 台 的 作 用 就 相 当 于 Matlab 对 于 研 究 人 员 的 作 用 一 样, 不 过 它 们 是 专 门 针 对 医 学 影 像 领 域 的 目 前 在 医 学 影 像 研 究 人 员 中 使 用 最 广 泛 的 两 个 算 法 平 台 是 VTK(Visualization Toolkit) 和 ITK, 虽 然 还 有 其 它 一 些 平 台, 但 是 它 们 都 不 系 统, 只 是 针 对 某 一 个 特 定 的 领 域 的, 另 外,VTK 和 ITK 也 是 对 MITK 的 设 计 影 响 最 大, 最 相 关 的 两 个 算 法 平 台, 所 以 下 面 分 别 介 绍 一 下 它 们 的 发 展 历 程 和 发 展 现 状, 并 给 出 它 们 的 局 限 性 1.3.1 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

1 绪 论 影 像 领 域 内 的 研 究 有 所 帮 助 发 展 到 现 在,VTK 的 稳 定 版 本 已 经 发 行 到 4.2 版 本, 并 且 新 的 5.0 版 本 也 在 持 续 地 开 发 中, 已 经 成 为 通 用 可 视 化 领 域 内 最 负 盛 名 的 软 件 开 发 包, 也 在 医 学 影 像 领 域 内 赢 得 了 尊 敬 1.3.2 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], 并 且 现 在 还 一 直 在 持 续 地 进 化, 它 已 经 并 且 将 继 续 为 医 学 影 像 领 域 内 的 研 究 人 员 提 供 一 个 分 割 与 配 准 算 法 的 仓 库 和 基 础 1.3.3 VTK 和 ITK 的 局 限 性 VTK 和 ITK 目 前 已 经 成 为 国 际 上 非 常 知 名 的 可 视 化 与 医 学 影 像 分 割 与 配 准 的 算 法 研 发 平 台, 已 经 并 且 正 在 为 研 究 人 员 提 供 着 非 常 多 的 便 利 但 是 由 于 一 些 原 因, 导 致 VTK 或 者 ITK 或 者 VTK+ITK 有 一 些 自 己 的 缺 陷, 影 响 了 其 在 更 大 范 围 的 广 泛 使 用 首 先, 因 为 ITK 并 不 提 供 可 视 化 的 能 力, 所 以 一 般 要 与 VTK 联 合 起 来 使 用 6

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

1 绪 论 三 维 可 视 化 等 算 法, 弥 补 VTK+ITK 的 缺 憾 MITK 并 不 是 要 取 代 VTK 和 ITK, 而 只 是 给 医 学 影 像 领 域 内 的 研 究 人 员 和 开 发 人 员 提 供 另 外 的 一 个 可 用 选 择, 用 来 丰 富 国 际 上 的 医 学 影 像 算 法 平 台 另 外, 为 了 验 证 使 用 MITK 开 发 复 杂 医 学 影 像 实 用 系 统 的 能 力, 我 们 还 基 于 MITK 设 计 并 实 现 了 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed Kitware 公 司 也 曾 经 使 用 VTK 来 开 发 了 一 个 医 学 影 像 可 视 化 系 统 VolView, 但 可 惜 的 是 这 是 一 个 商 业 软 件, 即 使 用 于 科 研 目 的 也 是 需 要 购 买 License 的 MITK 和 3DMed 均 作 为 免 费 软 件 (Freeware) 在 Internet 上 发 布 (www.mitk.net,www.3dmed.net) 本 书 工 作 的 另 外 一 层 意 义 就 是 希 望 能 够 推 广 医 学 影 像 软 件 在 国 内 的 广 泛 使 用 具 体 来 讲, 本 书 介 绍 的 内 容 主 要 集 中 在 两 个 方 面 : 第 一 个 方 面 是 算 法 研 究 方 面, 在 表 面 绘 制 的 算 法 尤 其 是 大 规 模 医 学 数 据 的 可 视 化 算 法 的 研 究 方 面 作 了 一 些 探 讨, 这 个 部 分 主 要 在 第 三 章 中 介 绍 ; 第 二 个 方 面 是 平 台 的 框 架 设 计 以 及 分 割 配 准 可 视 化 算 法 在 MITK 中 的 实 现 方 面, 主 要 是 设 计 并 实 现 集 成 化 的 医 学 影 像 处 理 与 分 析 算 法 平 台 MITK, 这 个 部 分 的 内 容 分 散 在 本 书 的 各 个 章 节 本 书 的 主 要 内 容 包 括 两 个 方 面 : 一 是 研 究 并 设 计 实 现 了 一 个 集 成 化 的 医 学 影 像 处 理 与 分 析 算 法 平 台 MITK(Medical Imaging ToolKit) MITK 在 一 个 统 一 的 框 架 里 面 实 现 了 医 学 影 像 处 理 与 分 析 的 三 大 研 究 领 域 的 算 法, 包 括 医 学 影 像 分 割 医 学 影 像 配 准 以 及 三 维 可 视 化, 而 国 际 上 相 同 类 型 的 平 台 如 VTK 和 ITK 均 只 实 现 了 一 部 分 功 能, 并 且 它 们 还 具 有 完 全 不 同 的 框 架 和 风 格 另 外,MITK 是 专 门 针 对 医 学 影 像 这 一 特 定 领 域 的, 遵 循 小 而 精 的 原 则, 对 一 些 重 要 算 法 作 了 特 定 的 优 化, 支 持 新 的 CPU 指 令 集 SSE 的 优 化 以 及 新 的 显 卡 中 的 GPU 的 利 用 为 了 最 大 限 度 地 使 MITK 得 到 使 用, 其 在 Internet 上 面 向 国 际 免 费 发 行, 相 关 科 研 人 员 可 以 免 费 地 下 载 并 在 自 己 的 科 研 工 作 中 进 行 二 次 开 发 二 是 基 于 MITK 设 计 并 开 发 了 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed, 一 方 面 证 明 了 MITK 可 以 胜 任 现 实 世 界 当 中 医 学 影 像 领 域 复 杂 软 件 系 统 对 算 法 平 台 的 需 求, 另 一 方 面 的 目 的 是 为 相 关 科 研 人 员 和 普 通 用 户 提 供 一 个 易 于 使 用 的 软 件 工 具 目 前 3DMed 也 作 为 免 费 软 件 发 行, 如 果 MITK 和 3DMed 能 够 推 广 医 学 影 像 软 件 在 国 内 的 广 泛 使 用, 那 么 也 就 体 现 出 本 书 工 作 的 意 义 所 在 了 本 书 以 MITK 的 研 究 与 设 计 为 主 线 贯 穿 始 终, 在 软 件 设 计 框 图 的 绘 制 上, 始 8

1 绪 论 终 遵 循 UML( 统 一 建 模 语 言 )[28] 的 规 范 内 容 安 排 如 下 : 第 二 章 介 绍 MITK 的 总 体 框 架 的 设 计 ; 第 三 章 侧 重 介 绍 在 面 绘 制 算 法 研 究 方 面 的 工 作, 包 括 基 于 单 层 表 面 跟 踪 的 重 建 算 法 和 基 于 点 的 重 建 算 法, 并 且 给 出 了 面 绘 制 算 法 在 MITK 中 的 实 现 ; 第 四 章 介 绍 MITK 中 的 一 个 重 要 部 分, 即 体 绘 制 的 框 架 设 计 及 具 体 的 算 法 实 现 ; 第 五 章 给 出 了 在 三 维 人 机 交 互 方 面 的 一 些 探 索, 并 给 出 了 其 在 MITK 中 的 框 架 和 实 现 ; 第 六 七 章 分 别 给 出 了 不 同 的 分 割 算 法 和 配 准 算 法 在 MITK 中 的 实 现 ; 第 八 章 介 绍 了 DICOM 标 准 以 及 其 在 MITK 中 的 实 现, 第 九, 十 两 章 分 别 介 绍 应 用 MITK 开 发 项 目 和 MITK 的 扩 展 功 能, 以 便 读 者 可 以 方 便 的 使 用 MITK; 第 十 一 章 介 绍 基 于 MITK 算 法 平 台 的 三 维 医 学 影 像 处 理 与 分 析 实 用 软 件 系 统 3DMed 的 设 计 与 实 现 ; 第 十 二 章 介 绍 了 3DMed 中 的 Plugin, 这 样 读 者 也 可 以 将 自 己 的 算 法 用 plugin 开 发 出 来, 集 成 到 3dMed 中 附 录 A 介 绍 一 些 医 学 影 像 数 据 集 的 资 源, 方 便 读 者 用 来 测 试 自 己 算 法 附 录 B 介 绍 了 MITK 论 坛 的 情 况 参 考 文 献 1. 田 捷, 包 尚 联, 周 明 全. 医 学 影 像 处 理 与 分 析. 北 京 : 电 子 工 业 出 版 社,2003. 2. Insight Segmentation and Registration Toolkit, http://www.itk.org. 3. Y. J. Zhang, J. J. Gerbrands. Transition region determination based thresholding. Pattern Recognition Letter, 1991, 12:13-23. 4. P.Sahoo, C.Wilkins and J.Yeager, Threshold selection using Renyi's entropy. Pattern Recognition, 1997, 30(1):71-84. 5. 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:393 412. 6. M. Kass, A. Witkin, and D. Terzopoulos, Snakes - Active Contour Models, International Journal of Computer Vision, 1(4): 321-331, 1987. 7. S. Osher and J. Sethian, Fronts propagating with curvature dependent speed, J. Comput. Phys., Vol.79, pp.12-49, 1988. 8. J.A.Sethian, Fast marching methods, SIAM Rev., 41(1999), pp.199-235. 9. 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:233-260. 10. J. B. A. Maintz and M. A. Viergever, A survey of medical image registration, Medical Image Analysis, 2(1):1-36, 1998. 9

1 绪 论 11. W. Lorensen and H. Cline, Marching cubes: a high resolution 3D surface construction algorithm. ACM Computer Graphics, 21(4): pp. 163 170, 1987. 12. Gregory M. Nielson, On Marching Cubes, IEEE Transaction on Visualization and Computer Graphics, Vol. 9, No. 3, pp. 283-297, 2003. 13. G. T. Herman, H. K. Liu, Three-Dimensional Display of Human Organs form Computed Tomography, Computer Graphics & Image Processing, 1979, Vol. 9, pp. 1-29. 14. M. Levoy, Display of surfaces from volume data, IEEE Transaction on Computer Graphics and Applications, 1988, 8(3): 29-37. 15. D. Laur and P. Hanrahan, Hierarchical Splatting: A Progressive Refinement Algorithm for Volume Rendering, ACM Computer Graphics, Proc. SIGGRAPH 93, 25(4):285 288, July 1991. 16. P. Lacroute and M. Levoy, Fast volume rendering using a shear-warp factorization of the viewing transformation, Proc. SIGGRAPH 94, pp. 451-458, 1994. 17. 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 2001. 18. 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 2000. 19. Wim Sweldens, Peter Schr oder, Digital Geometry Processing, Course Notes for SigGraph'2001, Los Angeles, California, Aug. 12, 2001. 20. 何 晖 光, 数 字 几 何 的 研 究 及 其 在 医 学 可 视 化 中 的 应 用,[ 博 士 论 文 ], 北 京 : 中 科 院 自 动 化 所,2002. 21. Visualization Toolkit, http://www.vtk.org. 22. Will Schroeder, Ken Martin, Bill Lorensen Schroeder, The Visualization Toolkit: An Object Oriented Approach to 3D Graphics 3rd Edition, Kitware, Inc. Publisher, 2003. 23. 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 '96. 24. 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. 20-27, 2000. 25. Ackerman, M. J., The Visible Human Project, Medicine Meets Virtual Reality II: Interactive Technology and Healthcare, pp. 5-7 26. Matthew H. Austern, Generic Programming and the Stl : Using and Extending the C++ 10

1 绪 论 Standard Template Library, Addison-Wesley Professional Computing Series, 1998. 27. Luis Ibanez, Will Schroeder, Lydia Ng, Josh Cates. The ITK Software Guide: The Insight Segmentation and Registration Toolkit (version 1.4), Kitware, Inc. Publisher, 2003. 28. Fowler M., Scott K. UML Distilled Second Edition: A Brief Guide to the Standard Object Modeling Language, Addison Wesley, 2000. 11

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 从 一 开 始 设 计, 就 始 终 追 求 以 下 几 个 高 层 的 设 计 目 标 : 2.1.1 统 一 的 风 格 VTK 和 ITK 由 于 历 史 性 的 原 因, 使 用 了 不 同 的 编 程 风 格 VTK 在 1998 年 ANSI C++ 标 准 制 定 之 前 就 已 经 比 较 成 型, 所 以 使 用 的 是 传 统 的 Object-Oriented( 面 向 对 象 ) 的 设 计 和 开 发 方 法 ; 而 ITK 是 在 1999 年 才 开 始 开 发 的, 所 以 运 用 了 许 多 新 的 C++ 标 准 规 定 的 语 言 特 性, 以 及 Generic Programming( 范 型 编 程 ) 的 设 计 和 开 发 方 法 这 种 编 程 风 格 上 的 不 一 致, 给 VTK+ITK 的 使 用 者 带 来 了 很 大 的 不 方 12

2 MITK 的 总 体 设 计 便 而 MITK 使 用 统 一 的 面 向 对 象 的 设 计 方 法, 再 加 上 一 些 Design Patterns( 设 计 模 式 )[3] 的 使 用, 提 供 一 个 统 一 的 编 程 风 格 和 整 体 框 架 2.1.2 有 限 目 标 MITK 是 专 门 面 向 医 学 影 像 领 域 的, 只 关 注 于 这 一 特 定 领 域 内 的 算 法, 不 追 求 大 而 全, 只 追 求 少 而 精 例 如 MITK 中 可 视 化 算 法 只 包 括 规 则 数 据 场 ( 医 学 影 像 设 备 得 到 的 数 据 场 即 为 此 类 ) 的 支 持, 分 割 算 法 的 输 出 也 只 限 于 是 一 个 二 值 数 据 场 这 样 的 设 计 准 则 简 化 了 整 个 MITK, 使 得 其 保 持 在 一 个 中 等 的 规 模, 但 同 时 提 供 了 必 须 的 功 能, 包 括 主 流 的 可 视 化 分 割 和 配 准 算 法 的 实 现 2.1.3 可 移 植 性 为 了 使 MITK 能 够 得 到 最 广 泛 的 应 用, 可 移 植 性 是 非 常 重 要 的 一 个 环 节 整 个 MITK 的 代 码 全 部 使 用 ANSI C++ 编 写, 没 有 使 用 任 何 编 译 器 提 供 的 特 殊 关 键 字 或 者 特 殊 函 数, 并 且 尽 量 降 低 平 台 相 关 的 代 码 量 在 整 个 MITK 中, 与 平 台 相 关 的 部 分 就 是 与 窗 口 系 统 打 交 道 的 部 分, 此 处 针 对 不 同 的 操 作 系 统 写 了 不 同 的 代 码, 目 前 支 持 Windows 系 列 操 作 系 统 Unix Linux 等 而 MITK 目 前 可 以 在 多 数 主 流 的 C++ 编 译 器 下 编 译 通 过, 包 括 对 模 板 支 持 不 完 善 的 编 译 器 2.1.4 代 码 优 化 因 为 医 学 影 像 处 理 与 分 析 算 法 中 很 多 算 法 计 算 量 大, 尤 其 是 可 视 化 算 法, 对 实 时 性 要 求 很 高, 这 些 就 需 要 对 代 码 进 行 优 化 因 为 MITK 的 规 模 保 持 在 中 等, 这 就 使 得 对 一 些 关 键 算 法 进 行 优 化 成 为 可 能 MITK 支 持 对 CPU 的 扩 展 指 令 集 的 使 用, 比 如 Intel 的 MMX SSE 指 令 集, 为 了 不 至 于 违 背 可 移 植 性 目 标,MITK 中 在 使 用 SSE 等 指 令 集 时, 并 没 有 直 接 使 用 汇 编 语 言, 而 是 使 用 了 编 译 器 提 供 的 Intrinsics 指 令, 目 前 MITK 当 中 实 现 了 SSE 加 速 的 矩 阵 和 矢 量 运 算 双 线 性 和 三 线 性 插 值 计 算 等 ; 另 外 MITK 还 支 持 对 目 前 主 流 显 卡 中 GPU 的 编 程, 实 现 了 使 用 纹 理 映 射 进 行 Volume Rendering( 体 绘 制 ) 的 加 速 算 法 [4][5] 2.2 MITK 的 整 体 计 算 框 架 医 学 影 像 处 理 与 分 析 技 术 包 含 可 视 化 分 割 配 准 三 大 类 算 法, 每 一 类 下 面 又 都 有 很 多 不 同 的 方 法, 医 学 影 像 数 据 本 身 也 包 含 多 维 ( 一 维 二 维 三 维 ) 多 源 (CT MRI 等 ) 多 态 ( 以 像 素 矩 阵 形 式 表 达 的 图 像 信 息 以 像 素 集 合 形 式 13

2 MITK 的 总 体 设 计 表 达 的 目 标 信 息 以 及 以 几 何 形 状 形 式 表 达 的 知 识 信 息 ) 的 特 点, 这 些 都 决 定 了 将 其 整 合 进 一 个 统 一 的 框 架 之 内 是 一 个 比 较 复 杂 的 过 程, 必 须 经 过 抽 象 建 模, 才 能 得 到 一 个 比 较 灵 活 可 用 的 整 体 计 算 框 架 有 了 计 算 框 架 以 后, 还 可 以 在 相 同 的 条 件 之 下 比 较 同 一 种 类 不 同 算 法 的 性 能 效 果, 进 行 算 法 的 比 较 ; 另 外 还 可 以 进 行 算 法 的 评 价, 比 如 对 分 割 算 法 配 准 算 法 的 准 确 性 进 行 评 价 2.2.1 基 于 数 据 流 模 型 的 整 体 框 架 正 如 前 面 提 到 的 一 样,MITK 的 计 算 框 架 采 用 基 于 数 据 流 的 模 型, 以 数 据 处 理 为 中 心, 将 算 法 和 数 据 对 象 分 开 考 虑, 这 种 模 型 很 适 合 那 些 牵 涉 大 量 不 同 的 算 法 需 要 处 理 不 同 类 型 数 据 的 领 域 的 应 用 在 MITK 的 数 据 流 模 型 中, 数 据 和 算 法 均 使 用 对 象 表 示 ; 一 个 算 法 被 抽 象 成 一 个 滤 波 器 (Filter), 它 接 受 一 个 输 入, 生 成 一 个 输 出, 其 中 输 入 和 输 出 均 为 数 据 对 象 (Data), 一 个 算 法 的 输 出 可 以 作 为 另 外 一 个 算 法 的 输 入 通 过 这 样 的 模 型, 一 连 串 的 算 法 可 以 被 串 成 一 个 流 水 线, 组 成 统 一 的 计 算 框 架, 如 图 2-1 所 示 图 2-1 MITK 的 计 算 框 架 在 图 中,Data 表 示 抽 象 的 数 据 对 象,Source Filter 和 Target 分 别 表 示 三 种 不 同 的 抽 象 算 法 对 象, 它 们 的 意 义 分 别 如 下 所 示 : Data: Data 是 对 医 学 影 像 领 域 内 要 处 理 的 数 据 所 进 行 的 抽 象 对 于 医 学 影 像 处 理 与 分 析 系 统 来 说, 要 能 够 处 理 多 种 不 同 的 数 据, 在 MITK 中 通 过 Data 的 各 个 具 体 的 子 类 来 描 述 不 同 的 数 据 对 象, 代 表 了 数 据 流 模 型 中 最 重 要 的 数 据 对 象 在 下 一 小 节 将 会 具 体 讲 述 MITK 中 的 数 据 模 型 Source: Source 是 算 法 类 的 一 种, 但 是 它 只 有 输 出, 没 有 输 入, 代 表 一 个 流 水 线 的 起 源 Source 的 作 用 是 负 责 生 成 整 个 流 水 线 的 起 始 数 据, 比 如 从 磁 盘 上 14

2 MITK 的 总 体 设 计 读 文 件, 或 者 用 某 些 算 法 生 成 数 据 Filter: Filter 是 算 法 类 的 一 种, 它 负 责 对 数 据 对 象 进 行 处 理, 代 表 各 种 各 样 的 算 法 Filter 有 一 个 输 入, 一 个 输 出, 为 了 概 念 上 简 单,MITK 中 不 支 持 多 输 入 多 输 出 的 Filter, 而 是 通 过 Filter 的 具 体 子 类 提 供 的 辅 助 函 数 来 实 现 在 2.2.3 小 节 中 将 具 体 讲 述 MITK 中 的 算 法 模 型 Target:Target 是 算 法 类 的 一 种, 顾 名 思 义, 它 代 表 整 个 流 水 线 数 据 的 终 点 Target 只 有 输 入, 没 有 输 出, 它 的 作 用 是 将 最 后 的 数 据 放 在 一 个 合 适 的 位 置, 终 止 流 水 线 的 执 行 比 如 将 得 到 的 结 果 数 据 保 存 至 磁 盘, 或 者 将 得 到 的 结 果 在 屏 幕 上 显 示 出 来 有 了 这 些 高 层 的 概 念 以 后, 就 可 以 很 容 易 地 将 这 些 组 件 联 系 在 一 块, 组 成 一 个 实 用 的 系 统 如 图 2-1 所 示, 整 个 流 水 线 从 一 个 Source 开 始, 经 过 中 间 n 个 Filter 的 处 理, 最 终 终 结 于 Target 这 种 抽 象 关 系 足 以 描 述 医 学 影 像 处 理 与 分 析 这 类 以 数 据 处 理 为 中 心 的 应 用 程 序 的 需 求, 并 且 在 概 念 上 非 常 简 洁 清 晰, 可 以 提 供 一 个 统 一 的 模 型 来 设 计 MITK 的 整 个 计 算 框 架 2.2.2 数 据 模 型 数 据 表 达 是 数 据 流 模 型 中 的 一 个 核 心 的 内 容, 数 据 对 象 起 着 连 接 算 法 流 水 线 的 重 要 作 用 考 虑 到 MITK 的 设 计 目 标 之 一 是 有 限 目 标, 只 关 注 于 医 学 影 像 这 个 特 定 的 领 域, 此 处 就 简 化 了 数 据 模 型 的 建 立 通 过 对 医 学 影 像 数 据 进 行 仔 细 的 分 析 以 后,MITK 中 的 数 据 对 象 (Data) 可 以 被 具 体 化 为 Volume 和 Mesh 两 种 不 同 的 具 体 数 据 对 象,Data 的 类 继 承 关 系 如 图 2-2 所 示 : 图 2-2 MITK 的 数 据 模 型 在 图 中,Volume 用 来 表 达 一 个 医 学 影 像 数 据 集, 提 供 一 个 多 维 ( 包 括 一 15

2 MITK 的 总 体 设 计 二 三 维 ) 多 模 态 (CT MRI 等 ) 的 规 则 数 据 场 的 抽 象 Volume 提 供 了 丰 富 的 接 口 来 给 算 法 对 象 使 用, 图 中 给 出 了 一 部 分 最 常 用 的 函 数 接 口, 这 些 接 口 可 以 使 得 其 它 的 对 象 可 以 很 方 便 地 访 问 其 内 部 的 实 际 数 据 Volume 是 MITK 中 的 核 心 对 象 之 一 Mesh 用 来 表 达 一 个 几 何 数 据, 尤 其 是 以 三 角 面 片 网 格 形 式 表 达 的 几 何 模 型 Mesh 内 部 的 结 构 基 于 半 边 数 据 结 构 [6], 它 提 供 了 对 一 维 线 段 二 维 平 面 图 形 三 维 三 角 网 格 以 及 通 用 多 边 形 的 支 持, 提 供 了 丰 富 的 接 口 来 给 相 关 的 算 法 对 象 使 用, 上 图 中 给 出 了 一 部 分 最 常 用 的 函 数 接 口 不 同 于 Volume,Mesh 并 不 对 应 于 医 学 影 像 设 备 所 直 接 采 集 得 到 的 数 据, 而 是 对 医 学 影 像 数 据 集 处 理 后 得 到 的 结 果 所 依 赖 的 表 达 形 式, 它 是 可 视 化 算 法 中 面 绘 制 和 某 些 分 割 算 法 所 处 理 的 对 象, 是 MITK 中 的 核 心 对 象 之 一 2.2.3 算 法 模 型 有 了 一 个 设 计 良 好 的 数 据 模 型, 下 一 步 就 是 算 法 模 型 的 抽 象 了 因 为 Source 和 Target 只 是 特 殊 目 的 的 算 法 对 象, 真 正 的 算 法 是 由 Filter 对 象 来 代 表 的 按 照 一 个 Filter 的 输 入 和 输 出 数 据 对 象 的 类 型, 它 可 以 被 具 体 化 为 四 种 不 同 的 算 法 种 类, Filter 的 类 继 承 关 系 如 图 2-3 所 示 : 图 2-3 MITK 的 算 法 模 型 从 图 中 可 以 看 出 Filter 被 具 体 化 为 四 种 不 同 的 种 类 :VolumeToVolumeFilter 指 的 是 输 入 数 据 和 输 出 数 据 都 为 Volume 数 据 对 象 的 算 法, 在 其 下 可 以 更 细 分 出 图 像 处 理 算 法 图 像 配 准 算 法 一 部 分 图 像 分 割 算 法 等, 在 第 六 七 章 中 实 现 的 算 法 就 全 部 是 从 VolumeToVolumeFilter 继 承 下 来 的 ;VolumeToMeshFilter 指 的 是 输 入 数 据 为 Volume 数 据 对 象, 而 输 出 数 据 为 Mesh 数 据 对 象 的 算 法, 包 括 三 维 可 视 化 中 的 面 重 建 算 法 一 部 分 图 像 分 割 算 法, 在 第 三 章 中 实 现 的 算 法 就 是 16

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 前 缀 省 掉 2.3.1 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

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

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

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

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

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

2 MITK 的 总 体 设 计 必 须 知 道 内 部 算 法 的 状 态, 比 如 算 法 执 行 的 进 度 等 信 息 为 了 达 到 MITK 底 层 算 法 库 和 上 层 的 应 用 程 序 之 间 的 通 信,MITK 里 面 实 现 了 Observer 这 一 设 计 模 式, 其 在 Object 里 面 的 函 数 如 图 2-8 所 示,AddObserver 将 一 个 Observer 插 入 到 内 部 维 护 的 一 个 队 列 中 ;RemoveObserver 将 一 个 指 定 的 Observer 从 内 部 的 队 列 中 删 除 ;RemoveAllObservers 将 内 部 的 队 列 清 空 而 MITK 中 也 单 独 定 义 了 Observer 这 个 类 层 次, 所 有 从 Observer 继 承 的 子 类 必 须 实 现 其 虚 函 数 Update, 从 而 更 新 界 面 元 素 一 个 Observer 就 是 一 个 单 独 的 对 象, 在 外 面 观 察 一 个 MITK 对 象 的 内 部 状 态, 并 且 在 必 要 的 时 候 被 更 新 图 2-8 Object 中 提 供 的 Observer 设 计 模 式 实 现 2.3.2 内 存 管 理 因 为 MITK 的 结 构 比 较 复 杂, 所 以 实 现 一 个 有 效 的 内 存 管 理 方 案 是 非 常 必 要 的 上 面 介 绍 的 引 用 计 数 和 智 能 指 针 的 联 合 使 用, 形 成 了 MITK 中 的 第 一 层 内 存 管 理, 用 来 保 证 不 出 现 内 存 泄 漏 另 外,MITK 中 实 现 了 一 个 简 单 的 垃 圾 回 收 (Garbage Collection) 机 制, 用 于 在 程 序 退 出 之 前 将 所 有 可 能 泄 漏 的 内 存 释 放 掉, 此 功 能 是 在 GarbageCollection 类 里 面 实 现 的, 它 的 接 口 函 数 如 图 2-9 所 示, AddObject 函 数 将 一 个 MITK 的 对 象 的 指 针 加 入 到 内 部 维 护 的 一 张 列 表 中, 而 RemoveObject 函 数 将 指 定 的 对 象 从 内 部 的 列 表 中 删 除 由 于 GarbageCollection 在 整 个 系 统 中 只 能 存 在 一 份 实 例, 所 以 MITK 中 使 用 了 设 计 模 式 中 的 Singleton 来 达 到 这 一 目 的 有 了 GarbageCollection 类 提 供 的 基 本 功 能 以 后,Object 里 面 也 必 须 提 供 相 应 的 配 合 才 能 实 现 垃 圾 回 收 机 制 在 Object 的 构 造 函 数 里 面, 通 过 GetGarbageCollector 函 数 来 得 到 系 统 里 面 唯 一 的 GarbageCollection 实 例, 并 且 将 自 身 加 入 到 GarbageCollection 的 列 表 里 面, 代 码 如 下 : 23

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

2 MITK 的 总 体 设 计 m_objectcollection->clear(); m_objectcollection->delete(); 图 2-9 MITK 中 的 垃 圾 回 收 接 口 2.3.3 跨 平 台 的 实 现 MITK 的 所 有 代 码 均 使 用 符 合 ANSI C++ 标 准 的 特 性, 这 保 证 了 代 码 的 可 移 植 性 但 是 对 一 些 操 作 系 统 相 关 的 代 码, 比 如 窗 口 的 事 件 处 理 虚 拟 内 存 管 理 等, 必 须 对 每 一 个 操 作 系 统 写 一 套 代 码 为 了 能 够 透 明 地 封 装 这 些 操 作 系 统 相 关 的 代 码, 得 到 一 个 优 雅 的 解 决 方 案,MITK 中 再 次 使 用 了 一 种 设 计 模 式, 名 字 为 桥 (Bridge), 下 面 以 View 这 一 对 象 为 例 进 行 介 绍 在 MITK 中,View 这 一 对 象 是 唯 一 跟 图 形 用 户 界 面 相 关 的 部 分, 给 用 户 提 供 了 一 个 用 来 显 示 图 像 或 者 三 维 图 形 的 窗 口, 这 也 就 必 然 导 致 它 跟 具 体 的 操 作 系 统 相 关 为 了 使 操 作 系 统 相 关 的 代 码 相 对 独 立, 并 且 添 加 对 新 的 操 作 系 统 的 支 持 的 时 候 也 不 影 响 到 客 户 端 的 代 码,View 这 一 部 分 的 框 架 结 构 图 如 图 2-10 所 示 25

2 MITK 的 总 体 设 计 图 2-10 MITK 中 View 的 结 构 从 图 中 可 以 看 出,View 里 面 维 护 着 Implementor 基 类 的 指 针, 并 且 View 把 所 有 与 操 作 系 统 相 关 的 代 码 都 委 托 给 Implementor 来 实 现,Implementor 通 过 其 子 类 来 实 现 具 体 的 代 码 通 过 这 种 结 构, 客 户 端 只 知 道 有 View, 而 不 知 道 有 Implementor 的 存 在, 因 此 降 低 了 藕 合 ; 并 且 要 添 加 对 一 个 新 的 操 作 系 统 的 支 持 的 时 候, 只 需 要 在 Implementor 分 支 下 面 再 增 加 对 应 的 子 类 即 可, 无 需 更 改 客 户 端 的 代 码 2.3.4 SSE 加 速 的 实 现 Intel 在 其 奔 腾 3 以 后 的 CPU 中 增 加 了 SSE 扩 展 指 令 集, 用 以 提 高 三 维 程 序 以 及 多 媒 体 程 序 的 性 能 因 为 MITK 中 三 维 可 视 化 是 一 个 比 较 重 要 的 部 分, 对 性 能 要 求 也 比 较 高, 因 此 内 部 提 供 了 对 SSE 的 支 持, 并 且 已 经 实 现 了 一 部 分 算 法 的 SSE 优 化 加 速 实 现 SSE 指 令 集 的 支 持 有 一 些 问 题 必 须 考 虑, 因 为 传 统 上 是 使 用 汇 编 语 言 对 SSE 指 令 集 进 行 编 程, 这 显 然 将 会 违 背 MITK 的 可 移 植 性 这 一 设 计 目 标 ; 另 外, 并 不 是 所 有 的 CPU 都 支 持 SSE 指 令 集, 因 此 必 须 有 一 种 手 段 能 在 运 行 时 检 测 当 前 CPU 信 息, 并 动 态 决 定 使 用 SSE 加 速 版 本 还 是 使 用 普 通 版 本 在 MITK 中, 对 第 一 个 问 题 的 解 决 方 案 是 使 用 编 译 器 提 供 的 Intrinsics 去 对 SSE 指 令 集 进 行 编 程, 这 样 虽 然 没 有 使 用 汇 编 语 言 的 效 率 高, 但 是 目 前 的 主 流 编 译 器 ( 包 括 Visual C++ 6.0 + Processor Pack,Visual C++ 7.0,gcc,Intel Compiler 等 ) 都 提 供 了 对 Intrinsics 的 支 持, 因 此 可 移 植 性 得 到 了 保 证 ; 对 第 二 个 问 题 的 解 决 方 案 是 提 供 了 两 套 分 离 的 动 态 链 接 库, 一 套 是 包 含 SSE 加 速 算 法 26

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, 1991. 2. Meyer, B. Object-Oriented Software Construction. Prentice Hall, 1997. 3. Erich Gamma, Richard Helm, Ralph Johnson, et al. Design Patterns, Elements of Reusable Object-Oriented Software. Pearson Education Publisher, 1994. 4. 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 2001. 5. 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 2000. 6. S. Campagna, L. Kobbelt, H. P. Seidel. Directed Edges - A Scalable Representation For Triangle Meshes. ACM Journal of Graphics Tools 3 (4), 1998, pp. 1-12 27

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

图 3-3 体 数 据 示 意 图 图 3-4 以 三 角 网 格 表 示 的 表 面 模 型 mitkmesh 中 的 三 角 面 片 数 据 以 点 表 和 面 表 来 组 织 点 表 是 一 个 float 型 数 组, 每 6 个 数 保 存 一 个 顶 点 的 信 息, 包 括 顶 点 坐 标 和 法 向 量, 按 (x, y, z; nx, ny, nz) 存 放 ; 面 表 是 一 个 unsigned int 型 数 组, 每 3 个 数 保 存 一 个 三 角 片 的 信 息, 分 别 是 组 成 该 三 角 片 的 3 个 顶 点 在 点 表 中 的 索 引 MITK 中 的 表 面 重 建 主 要 采 用 了 目 前 得 到 广 泛 应 用 的 Marching Cubes 算 法, 下 面 几 节 就 对 该 算 法 的 原 理 和 实 现 做 一 个 介 绍 3.1.1 传 统 的 Marching Cubes 算 法 Marching Cubes 算 法 是 W. Lorensen 等 人 于 1987 年 提 出 来 的 一 种 三 维 重 建 方 法 [1], 因 为 它 的 原 理 简 单, 并 且 很 容 易 实 现, 因 此 得 到 了 广 泛 的 应 用, 并 且 此 算 法 在 美 国 已 经 申 请 专 利, 它 被 认 为 是 至 今 为 止 最 流 行 的 面 显 示 算 法 之 一 Marching Cubes 算 法 是 面 显 示 算 法 中 的 一 种, 因 为 它 的 本 质 是 从 一 个 三 维 的 29

数 据 场 中 抽 取 出 一 个 等 值 面, 所 以 也 被 称 为 等 值 面 提 取 (Isosurface Extraction) 算 法 对 于 一 个 标 准 的 医 学 图 像 的 体 数 据 集, 它 往 往 是 由 一 系 列 的 二 维 切 片 数 据 组 成 的, 而 每 张 切 片 都 有 空 间 上 的 分 辨 率 假 设 有 一 个 体 数 据 集, 包 含 58 张 切 片, 每 张 切 片 的 分 辨 率 是 512 512, 那 么 它 可 以 被 认 为 是 一 个 连 续 函 数 f(x,y,z) 在 x y z 三 个 方 向 上 按 一 定 的 间 隔 分 别 采 样 了 512 512 58 次 所 得 到 的 而 所 谓 的 等 值 面, 实 际 上 是 指 空 间 中 的 一 张 曲 面, 在 该 曲 面 上 函 数 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

图 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

图 3-7 15 种 基 本 Cubes 的 拓 扑 形 状 根 据 这 15 种 基 本 的 Cubes, 可 以 造 出 一 个 查 找 表 (Look-up Table) 表 的 长 度 为 256, 记 录 了 所 有 情 况 下 的 等 值 面 连 接 方 式 所 以 此 时 只 需 分 别 比 较 一 个 Cube 的 八 个 顶 点 与 域 值 之 间 的 大 小 关 系, 即 可 得 出 一 个 0-255 之 间 的 索 引 值 (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

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

; 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

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 所 示, 按 该 图, 可 以 很 容 易 的 生 成 这 两 个 表 7 6 6 7 5 z 4 4 5 10 11 y 9 8 3 2 2 3 1 0 0 1 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

i 位 置 1, 否 则 置 0 对 于 g_tritable, 其 每 一 项 是 一 个 由 16 个 8 bits 整 数 组 成 的 数 组, 记 录 了 与 索 引 值 对 应 的 三 角 面 片 分 割 情 况 比 如, 对 于 索 引 9(00001001 2 ), 顶 点 0 和 3 的 灰 度 值 在 低 阈 值 和 高 阈 值 之 间, 则 第 0 2 8 和 11 条 边 与 等 值 面 有 交 点,g_EdgeTable[9] 的 值 就 为 100100000101 2 =905 16, 而 g_tritable[9] 则 以 顶 点 所 在 边 的 编 号 记 录 了 这 4 个 交 点 所 构 成 的 两 个 三 角 片 :0-11-2 和 8-11-0, 如 图 3-10 所 示 注 意, 生 成 的 三 角 片 正 向 须 保 持 一 致 11 8 2 0 图 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

// 包 围 盒 的 尺 寸 float min[3]; float max[3]; min[0] = min[1] = min[2] = 30000; max[0] = max[1] = max[2] = -30000; // 当 前 扫 描 层 的 切 片 数 据 // 因 为 要 计 算 法 向 量, 前 后 共 设 计 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

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

// 计 算 某 一 层 顶 点 和 三 角 片 时 所 需 的 一 些 变 量 // 一 些 循 环 变 量 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

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

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

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

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

- 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

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

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

// 计 算 两 端 点 处 的 法 向 量 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

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+100000); 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

facepos ++; fnumber ++; m = output->getfacenumber(); if (fnumber > m) output->setfacenumber(m+100000); 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

[1] 3.1.2 基 于 分 割 的 Marching Cubes 方 法 众 所 周 知, 医 学 图 像 具 有 模 糊 性 首 先, 医 学 图 像 具 有 灰 度 上 的 模 糊 性 在 同 一 种 组 织 中 密 度 值 会 出 现 大 幅 度 的 变 化 如 骨 骼 中 股 骨, 鼻 窦 骨 骼 和 牙 齿 的 密 度 就 有 很 大 差 别 ; 在 同 一 个 物 体 中 密 度 值 也 不 均 匀 如 股 骨 外 表 面 和 内 部 的 骨 髓 的 密 度 其 次, 医 学 图 像 具 有 几 何 上 的 模 糊 性 在 一 个 边 界 上 的 大 体 素 中 常 常 同 时 包 含 边 界 和 物 体 两 种 物 质 ; 图 像 中 物 体 的 边 缘, 拐 角 及 区 域 间 的 关 系 都 难 以 精 确 的 加 以 描 述 一 些 病 变 组 织 由 于 侵 袭 周 围 组 织 其 边 缘 无 法 明 确 界 定 然 后, 还 有 不 确 定 性 知 识 的 影 响 通 常 正 常 组 织 或 部 位 没 有 的 结 构 在 病 变 情 况 下 出 现, 如 脏 器 表 面 的 肿 物, 骨 骼 表 面 的 骨 刺, 它 的 出 现 给 建 造 模 型 带 来 困 难 医 学 图 像 的 这 些 特 点 为 它 的 三 维 重 建 带 来 很 多 困 难 基 于 以 上 考 虑, 我 们 采 用 了 基 于 分 割 的 等 值 面 生 成 算 法 (SEGMC), 该 算 法 将 分 割 结 果 作 为 MC 的 输 入, 这 样 可 以 根 据 图 像 特 征 选 择 最 恰 当 的 分 割 方 法, 利 用 分 割 结 果 构 造 等 值 面 图 像 分 割 是 进 行 表 面 重 建 的 基 础, 分 割 的 效 果 直 接 影 响 到 表 面 重 建 的 速 度 和 重 建 后 模 型 的 视 觉 效 果 通 过 分 割 可 以 帮 助 医 生 将 感 兴 趣 的 物 体 ( 病 变 组 织 等 ) 提 取 出 来, 减 少 了 三 维 体 数 据 的 数 据 量, 为 表 面 重 建 和 显 示 提 供 了 方 便, 并 使 得 医 生 能 够 对 病 变 组 织 进 行 定 性 及 定 量 的 分 析, 从 而 提 高 医 学 诊 断 的 准 确 性 和 科 学 性 这 里 所 介 绍 的 算 法 SEGMC 由 于 将 分 割 与 MC 相 结 合, 它 摆 脱 了 SMC 只 能 用 阈 值 分 割 的 局 限 性, 该 算 法 的 模 块 性 和 可 扩 充 性 好, 可 以 将 各 种 分 割 算 法 集 成 到 算 法 中 其 算 法 的 简 易 流 程 如 图 3-11 所 示 Source Volume Segmentation Filter Segmented Volume SEGMC Mesh 图 3-11 SEGMC 简 易 流 程 示 意 该 算 法 在 MITK 中 由 mitkbinmarchingcubes 实 现 mitkbinmarchingcubes 50

类 接 受 两 个 输 入 : 一 个 是 原 始 的 Volume, 用 于 计 算 法 向 量 ; 另 一 个 是 原 始 Volume 经 分 割 算 法 所 产 生 的 二 值 数 据, 用 于 计 算 等 值 点 坐 标, 只 要 当 前 Cube 的 某 边 两 端 点 灰 度 值 不 同, 则 认 为 该 边 与 等 值 面 相 交 交 点 坐 标 与 法 向 量 均 采 用 中 点 选 择 的 方 法 计 算 算 法 的 具 体 实 现 与 3.1.1 所 述 类 似, 只 是 在 判 断 有 无 交 点 及 交 点 坐 标 与 法 向 量 的 计 算 上 有 所 区 别, 这 里 就 不 再 详 细 说 明, 感 兴 趣 的 读 者 可 以 自 己 修 改 3.1.1 中 的 代 码 实 现 该 算 法 3.2 MITK 中 的 表 面 绘 制 框 架 3.2.1 表 面 绘 制 框 架 的 设 计 在 介 绍 面 绘 制 框 架 之 前, 首 先 介 绍 一 下 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

网 格 的 方 法 多 种 多 样, 而 且 加 光 照 模 型 时 设 置 表 面 材 质 属 性 的 参 数 众 多, 特 别 是 针 对 不 同 级 别 的 硬 件 配 置 有 不 同 的 硬 件 加 速 算 法, 而 这 些 算 法 往 往 不 是 通 用 的 为 了 最 大 程 度 的 提 升 面 绘 制 的 整 体 性 能 并 且 保 证 最 大 限 度 的 灵 活 性, 我 们 设 计 了 如 图 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

从 SurfaceModel 再 派 生 出 相 应 的 子 类 就 可 以 了, 而 通 过 SurfaceModel 的 SetRenderer() 接 口 可 以 随 意 更 换 当 前 SurfaceModel 的 绘 制 方 法 3.2.2 表 面 绘 制 框 架 的 实 现 (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

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; //------------------------------------------------------------------ 54

可 以 看 到, 在 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

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

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

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

// 是 否 需 要 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

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

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

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

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

边 表 所 存 储 的 边 应 该 是 无 重 复 的, 但 是 考 虑 到 要 创 建 这 样 的 边 表, 算 法 复 杂 度 比 较 高, 故 采 用 直 接 从 面 表 创 建 的 方 法, 这 样, 被 两 个 面 共 用 的 边 会 出 现 两 次, 占 用 的 存 储 空 间 最 多 是 理 想 情 况 的 两 倍, 但 创 建 的 速 度 要 快 的 多 这 部 分 的 工 作 在 _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

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

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

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

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

glbindbufferarb = (PFNGLBINDBUFFERARBPROC)wglGetProcAddress("glBindBufferARB"); glbufferdataarb = (PFNGLBUFFERDATAARBPROC)wglGetProcAddress("glBufferDataARB"); gldeletebuffersarb = (PFNGLDELETEBUFFERSARBPROC)wglGetProcAddress("glDeleteBuffersARB"); 关 于 PFNGLGENBUFFERSARBPROC PFNGLBINDBUFFERARBPROC PFNGLBUFFERDATAARBPROC 和 PFNGLDELETEBUFFERSARBPROC 的 声 明 参 见 OpenGL 扩 展 的 头 文 件 glext.h, 该 文 件 的 最 新 版 本 可 以 在 http://oss.sgi.com/projects/ogl-sample/sdk.html 得 到 这 些 初 始 化 工 作 在 构 造 函 数 中 完 成 : 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

在 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

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

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

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

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

gldeletebuffersarb(1, &m_vertvbo); gldeletebuffersarb(1, &m_facevbo); gldeletebuffersarb(1, &m_edgevbo); 3.3 小 结 MITK 所 实 现 的 面 绘 制 功 能 采 用 了 改 进 后 的 Marching Cubes 算 法 进 行 表 面 重 建, 并 提 供 了 一 个 灵 活 的 易 于 扩 充 的 绘 制 框 架 来 显 示 重 建 所 得 的 表 面 图 3-14 就 是 运 用 MITK 所 提 供 的 面 绘 制 功 能 做 出 的 一 些 多 层 表 面 重 建 及 显 示 的 实 例 图 3-14 多 层 表 面 重 建 及 绘 制 实 例 参 考 文 献 1. 田 捷, 包 尚 联, 周 明 全. 医 学 影 像 处 理 与 分 析. 北 京 : 电 子 工 业 出 版 社,2003. 2. W. Lorensen and H. Cline. Marching cubes: a high resolution 3D surface construction algorithm. ACM Computer Graphics, 21(4): pp. 163 170,1987. 3. M. J. Durst. Letters: Additional reference to Marching Cubes. ACM Computer Graphics, 22(4): pp. 72 73,1988. 75

4. Nielson G. M.,Hamann B. The Asymptotic Decider: Resolving the Ambiguity in Marching Cubes. IEEE Proceedings of Visualization 91, pp. 83-91. 5. J. Wilhelms and A. Van Gelder. Octrees for faster isosurface generation. ACM Computer Graphics, 24(5): pp. 57 62,Nov.1990. 6. 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 2004 7. 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, 1999. 8. Dave Shreiner, OpenGL Architecture Review. OpenGL Reference Manual: The Official Reference Document to OpenGL, Version 1.2 (3rd Edition). Addison-Wesley Professional, 2000. 9. NVIDIA Corporation. White Paper: Using Vertex Buffer Objects (VBOs). NVIDIA Corporation, 2003 76

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

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

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

4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 图 4-2 MITK 的 体 绘 制 框 架 80

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 中 绘 制 时 的 操 作, 但 侧 重 点 在 整 个 体 绘 制 流 程 的 实 现 以 及 具 体 体 绘 制 算 法 的 实 现 4.3.1 View 中 绘 制 操 作 的 实 现 因 为 整 个 绘 制 过 程 是 由 View 得 到 重 绘 消 息 以 后 发 起 的, 所 以 为 了 更 好 地 理 解 整 个 绘 制 过 程, 这 里 简 单 给 出 View 中 绘 制 操 作 的 实 现 由 图 4-1 可 以 得 知, 在 View 中 有 一 个 Model 的 列 表,View 负 责 维 护 这 个 列 表 ( 添 加 删 除 等 ), 并 且 在 绘 制 的 时 候 也 是 绘 制 这 些 Model, 因 此 Model 的 角 色 81

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

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

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

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 由 于 体 绘 制 算 法 的 本 质 特 点, 决 定 了 它 是 一 个 透 明 的 物 体 4.3.2 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

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

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

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

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

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

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

4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 4.3.3 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

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

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

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

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; 4.3.4 VolumeRenderer 的 实 现 VolumeRenderer 的 职 责 是 实 现 VolumeModel 委 托 给 它 的 Render 接 口, 也 就 是 将 模 型 绘 制 出 来, 为 了 允 许 其 它 算 法 的 无 缝 集 成, 所 以 VolumeRenderer 仍 然 只 是 个 虚 基 类, 各 个 具 体 的 算 法 通 过 继 承 VolumeRenderer 来 实 现 Render 接 口 VolumeRenderer 为 其 子 类 提 供 了 两 个 方 面 的 能 力 : 设 置 不 同 的 分 类 方 法, 设 置 裁 剪 平 面 在 体 绘 制 算 法 中, 如 果 分 类 方 法 是 PreClassification, 那 么 就 是 先 使 用 阻 光 度 传 递 函 数 进 行 分 类, 然 后 再 插 值 计 算 ; 如 果 分 类 方 法 是 PostClassification, 那 么 就 是 先 进 行 插 值 计 算, 然 后 再 根 据 阻 光 度 传 递 函 数 进 行 分 类 另 外,VolumeRenderer 也 支 持 平 面 裁 剪, 用 一 系 列 的 平 面 去 裁 剪 Volume, 从 而 可 以 得 到 切 割 效 果, 为 了 和 OpenGL 保 持 兼 容, 这 里 规 定 裁 剪 平 面 最 多 不 能 超 过 六 个 因 为 分 类 方 法 和 裁 剪 平 面 是 所 有 体 绘 制 算 法 都 公 用 的 东 西, 因 此 96

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

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

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); 4.3.5 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

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

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

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

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

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

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

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

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

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---0 2 4 voxelpoint.ele[0] = bounds[0]; voxelpoint.ele[1] = bounds[2]; voxelpoint.ele[2] = bounds[4]; 108

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---1 2 4 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---1 3 4 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

4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 //Fourth point---0 3 4 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---0 3 5 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---1 3 5 voxelpoint.ele[0] = bounds[1]; viewpoint[5] = *m_voxelstoviewmatrix * voxelpoint; viewpoint[5] *= (1.0f/viewPoint[5].ele[3]); 110

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---1 2 5 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---0 2 5 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

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 < -0.9999f maxz > 0.9999f ) 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

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

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

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

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) (x1 + 1.5f); 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

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

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

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

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

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

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

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

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

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

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

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

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

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] = 1.0 - 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): 29-37. 129

4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 2. R. Drebin, L. Carpenter, P. Hanrahan. Volume Rendering. Computer Graphics, Vol. 22, No. 4, pp. 65-74, Aug. 1988. 3. L. Westover. Footprint evaluation for volume rendering. Computer Graphics (SIGGRAPH '90 Proceedings), 24(4):367-376, August 1990. 4. D. Laur and P. Hanrahan, Hierarchical Splatting: A Progressive Refinement Algorithm for Volume Rendering, ACM Computer Graphics, Proc. SIGGRAPH 93, 25(4):285 288, July 1991. 5. J. Huang, K. Mueller, N. Shareef, et al. FastSplats: Optimized Splatting on Rectilinear Grids. In Proceedings of IEEE Visualization 2000, pp. 219-226, October 2000. 6. P. Lacroute and M. Levoy, Fast volume rendering using a shear-warp factorization of the viewing transformation, Proc. SIGGRAPH 94, pp. 451-458, 1994. 7. 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. 15-22, 1995. 8. 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. 95-104. 9. 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. 109-118. 10. Brian Cabral, Nancy Cam, Jim Foran. Accelerated Volume Rendering and Tomographic Reconstruction Using Texture Mapping Hardware. In Proc. Symposium on Volume Visualization 94, pages 91 98. ACM SIGGRAPH, 1994. 11. R. Westermann, T. Ertl. Efficiently Using Graphics Hardware in Volume Rendering Applications. In Computer Graphics, SigGraph Annual Conference Series, pages 169 177, 1998. 12. 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, 1999. 13. 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 2000. 14. 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

4 体 绘 制 (Volume Rendering) 的 框 架 与 实 现 Graphics Hardware 2001. 15. 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. 270 285, 2002 16. Roettger S., Guthe S., Weiskopf D., et al. Smart hardware accelerated volume rendering. In EUROGRAPHICS/IEEE Symposium on Visualization (2003), pp. 231-238. 17. J. Krüger, R. Westermann. Acceleration Techniques for GPU-based Volume Rendering. In Proc. of IEEE Visualization 2003, pp. 287 292. 18. 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. 270 285, 2002 131

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

5 三 维 人 机 交 互 的 设 计 与 实 现 的 角 度 来 讲, 三 维 人 机 交 互 在 可 视 化 领 域 具 有 非 常 重 要 的 价 值, 尤 其 在 医 学 影 像 可 视 化 中 三 维 交 互 对 于 更 方 便 准 确 地 使 用 可 视 化 的 结 果 辅 助 医 生 进 行 诊 断 和 手 术 模 拟 具 有 十 分 重 要 的 意 义 因 此 在 MITK 中 设 计 并 实 现 一 个 实 用 的 模 块 化 的 可 扩 展 的 三 维 交 互 框 架 是 十 分 必 要 的 5.2 以 3D Widgets 为 核 心 的 三 维 人 机 交 互 的 框 架 设 计 5.2.1 3D Widgets 的 设 计 准 则 Scott S. Snibbe 等 在 [2] 中 提 出 了 3D Widgets 设 计 中 应 遵 循 的 一 些 基 本 原 则 : (1) 行 为 自 明 即 Widget 的 外 观 应 该 能 够 直 观 的 反 映 出 它 的 行 为 ; (2) 去 除 不 必 要 的 自 由 度 即 对 Widget 的 行 为 方 式 做 尽 可 能 多 的 限 定, 这 样 不 仅 能 极 大 地 降 低 实 现 难 度, 而 且 便 于 用 户 理 解 和 使 用 该 Widget 此 外, 对 于 不 同 应 用,Widget 的 设 计 要 求 也 是 不 同 的, 比 如 [2] 中 提 到 对 于 一 般 的 用 于 展 示 或 设 计 用 途 的 造 型 工 具,Widget 的 设 计 只 要 能 达 到 让 需 展 示 的 三 维 物 体 看 起 来 对 就 可 以 了 ; 而 在 机 械 或 工 业 制 造 领 域,Widget 必 须 能 够 精 确 地 调 节 零 件 的 参 数 值, 并 且 对 任 何 参 数 的 更 改 必 须 是 可 再 现 的 而 医 学 影 像 可 视 化 中 3D Widgets 的 设 计 要 求 更 接 近 于 后 者, 一 方 面, 让 受 控 于 Widget 的 三 维 物 体 正 确 地 显 示 是 基 本 要 求 ; 另 一 方 面, 更 重 要 的 是 Widget 所 反 映 或 调 节 的 三 维 物 体 的 物 理 参 量 必 须 达 到 足 够 的 精 度 5.2.2 以 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

5 三 维 人 机 交 互 的 设 计 与 实 现 有 一 个 Observer 模 块 对 Model 进 行 观 察, 随 时 反 映 Model 状 态 的 改 变, 比 如 用 于 测 量 的 WidgetModel 就 通 过 Observer 将 测 量 结 果 返 回 给 用 户 Object Observer Model Manipulator View DataModel WidgetModel 图 5-1 以 3D Widgets 为 核 心 的 三 维 交 互 框 架 结 构 5.2.3 以 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

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

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 在 整 个 交 互 过 程 中 并 不 起 决 定 性 的 作 用, 并 且 设 计 相 对 简 单, 这 里 就 不 再 赘 述 5.3.1 Manipulator 的 实 现 Manipulator 的 关 键 是 拣 选 功 能, 由 于 整 个 交 互 框 架 采 用 OpenGL 绘 制 Model, 因 此 很 自 然 的 拣 选 功 能 最 终 也 将 基 于 OpenGL 提 供 的 选 择 和 反 馈 机 制 交 互 操 作 绝 大 部 分 情 况 下 采 用 鼠 标 作 为 输 入 设 备, 对 各 种 鼠 标 事 件, Manipulator 将 做 如 下 处 理 : 当 OnMouseDown() 被 触 发 时 ( 鼠 标 按 键 按 下 ): 首 先 判 断 当 前 是 否 有 WidgetModel 处 于 选 中 状 态, 若 有 则 调 用 其 Release() 接 口 释 放 它 ; 然 后 在 OpenGL 选 择 模 式 下 绘 制 整 个 场 景, 即 遍 历 场 景 中 的 每 个 Model, 依 次 调 用 其 Select() 接 口,Model 基 类 中 缺 省 的 Select() 什 么 也 不 做, 只 有 可 被 选 择 的 Model 才 实 现 它 自 己 的 Select(), 在 这 里 就 是 直 接 调 用 Render(); 如 果 选 择 模 式 的 绘 制 过 程 返 回 了 某 个 WidgetModel, 则 调 用 其 Pick() 接 口 选 中 它, 然 后 调 用 其 OnMouseDown() 接 口 转 移 控 制, 否 则 按 常 规 处 理 当 OnMouseMove() 被 触 发 时 ( 移 动 鼠 标 ): 若 当 前 有 WidgetModel 处 于 选 136

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

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

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

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(); //------------------------------------------------------------------ 140

5 三 维 人 机 交 互 的 设 计 与 实 现 5.3.2 实 现 具 体 的 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

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

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

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);... 144

5 三 维 人 机 交 互 的 设 计 与 实 现 // 对 应 于 上 面 的 glpushmatrix() glpopmatrix(); // 返 回 1 表 示 绘 制 成 功 return 1; 值 得 一 提 的 是 MITK 为 三 维 模 型 的 绘 制 环 境 维 护 了 三 个 变 换 矩 阵 及 其 逆 矩 阵, 它 们 是 模 型 变 换 矩 阵 及 其 逆 矩 阵 视 图 变 换 矩 阵 及 其 逆 矩 阵 和 投 影 变 换 矩 阵 及 其 逆 矩 阵 模 型 变 换 矩 阵 及 其 逆 矩 阵 可 以 通 过 mitkmodel 类 提 供 的 GetModelMatrix() 及 GetInverseOfModelMatrix() 得 到, 视 图 变 换 矩 阵 和 投 影 变 换 矩 阵 及 其 逆 矩 阵 可 以 通 过 mitkcamera 类 提 供 的 GetViewMatrix() GetInverseOfViewMatrix() GetProjectionMatrix() 及 GetInverseOfProjectionMatrix() 得 到, 而 指 向 mitkcamera 的 指 针 可 以 通 过 mitkview 的 GetCamera() 得 到 前 面 两 个 矩 阵 的 乘 积 就 相 当 于 OpenGL 中 的 MODELVIEW MATRIX, 最 后 一 个 矩 阵 和 OpenGL 中 的 投 影 矩 阵 意 义 相 同 有 了 这 三 个 矩 阵 和 它 们 的 逆 矩 阵, 不 仅 可 以 很 方 便 地 得 到 屏 幕 坐 标 和 模 型 空 间 三 维 坐 标 之 间 的 对 应 关 系 ( 这 一 点 在 5.3.2 的 第 一 条 中 有 更 具 体 的 体 现 ), 而 且 在 绘 制 时 不 需 要 再 繁 琐 地 调 用 gltranslate*() glrotate*() 等 函 数 来 对 模 型 进 行 几 何 变 换, 而 只 要 将 所 需 变 换 的 矩 阵 作 为 参 数 调 用 一 次 glmultmatrixf() 就 可 以 了, 正 如 上 面 给 出 的 例 子 所 示 另 外, 在 mitkwidgetmodel2d 及 mitkwidgetmodel3d 中 均 已 实 现 了 一 些 标 准 部 件 的 绘 制, 比 如 mitkwidgetmodel3d 中 的 圆 锥 (_drawcone()) 圆 柱 (_drawcylinder()) 球 (_drawsphere()) 等, 如 果 直 接 用 这 些 部 件 构 成 Widget, 在 绘 制 每 一 个 部 件 时 就 不 需 要 再 显 式 地 调 用 glloadname(id) 了, 而 只 要 把 id 当 作 参 数 传 给 标 准 部 件 的 绘 制 函 数 就 可 以 了 由 上 述 介 绍 可 见, 与 Manipulator 相 比, 实 现 具 体 的 WidgetModel 要 显 得 更 自 由 一 些, 在 遵 循 三 维 交 互 框 架 的 接 口 规 范 的 前 提 下, 其 行 为 的 定 义 完 全 是 开 放 的 实 现 具 体 的 WidgetModel, 其 关 键 在 于 在 现 有 的 框 架 下, 如 何 使 WidgetModel 的 行 为 效 果 与 其 预 定 义 的 一 致, 下 面 通 过 MITK 中 几 个 已 经 实 现 的 3D Widgets 来 说 明 这 一 点 145

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

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 1 2.0 ( xsnew ox ) 1.0 2.0 ( 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

5 三 维 人 机 交 互 的 设 计 与 实 现 息, 该 Widget 便 能 根 据 坐 标 以 及 各 坐 标 轴 方 向 上 的 单 位 长 度 ( 由 SourceModel 提 供 ) 给 出 在 三 维 重 建 的 模 型 空 间 中 对 应 于 原 始 物 体 的 比 较 准 确 的 测 量 长 度, 保 证 了 测 量 的 精 度 (2) 测 量 角 度 的 Widget(mitkAngleWidgetModel3D) 该 Widget 的 功 能 是 取 得 模 型 空 间 中 任 意 三 点 组 成 的 角 的 角 度 值, 其 外 观 即 为 由 两 条 线 段 和 三 个 控 制 点 ( 两 个 箭 头 和 一 个 球 状 的 端 点 ) 构 成 的 一 个 张 开 的 角 采 用 与 mitklinewidgetmodel3d 相 同 的 方 式 可 以 自 由 地 控 制 三 个 端 点 的 位 置 从 而 准 确 地 作 出 所 需 的 角, 同 时 Observer 可 以 通 过 GetAngleInDegree() 或 GetAngleInRadian() 得 到 以 角 度 或 弧 度 表 示 的 角 度 值 (3) 裁 剪 平 面 Widget(mitkClippingPlaneWidgetModel) 该 Widget 的 功 能 是 对 当 前 场 景 中 显 示 的 三 维 物 体 在 任 意 位 置 和 任 意 方 向 上 进 行 裁 剪, 其 外 观 包 括 一 个 由 4 个 圆 柱 体 和 4 个 球 体 围 成 的 边 框 和 边 框 内 一 个 半 透 明 的 平 面 该 Widget 对 于 观 察 三 维 物 体 的 内 部 构 造 比 较 有 用 圆 柱 体 和 球 体 都 是 控 制 点, 四 个 球 体 分 别 实 现 四 种 对 裁 剪 平 面 的 控 制, 包 括 : 绕 中 心 的 任 意 旋 转, 外 框 相 对 中 心 的 的 缩 放, 垂 直 于 平 面 的 平 移 以 及 沿 平 面 的 平 移, 圆 柱 体 也 是 实 现 沿 平 面 的 平 移 其 中 缩 放 比 较 简 单, 而 限 制 裁 剪 平 面 的 平 移 路 径 ( 垂 直 于 裁 剪 平 面 或 平 行 于 裁 剪 平 面 ) 则 要 费 一 番 功 夫, 这 里 采 用 的 方 式 是 先 反 推 出 鼠 标 在 屏 幕 上 的 平 移 向 量 所 对 应 的 在 原 模 型 空 间 中 的 平 移 向 量, 计 算 方 法 基 本 上 与 mitklinewidgetmodel 计 算 端 点 坐 标 的 方 法 相 同, 然 后 将 平 移 向 量 在 移 动 路 径 方 向 ( 裁 剪 平 面 的 法 线 方 向 或 裁 剪 平 面 本 身 ) 上 进 行 投 影, 所 得 结 果 即 为 最 终 的 平 移 向 量, 将 裁 剪 平 面 的 中 心 坐 标 加 上 这 个 平 移 向 量 即 可 此 外, 为 保 证 旋 转 基 本 上 与 鼠 标 的 移 动 保 持 一 致, 这 里 采 用 了 一 个 虚 拟 跟 踪 球 来 跟 踪 和 控 制 裁 剪 平 面 的 旋 转 [10] 对 物 体 进 行 裁 剪 的 功 能 是 通 过 激 活 OpenGL 中 的 附 加 裁 剪 平 面 实 现 的 激 活 后 将 该 Widget 的 平 面 法 线 方 向 和 中 心 点 坐 标 作 为 OpenGL 中 裁 剪 平 面 的 参 数, 就 可 以 达 到 裁 剪 的 目 的 148

5 三 维 人 机 交 互 的 设 计 与 实 现 5.4 三 维 交 互 的 应 用 实 例 5.4.1 mitklinewidgetmodel3d 的 应 用 实 例 图 5-4 给 出 了 一 组 使 用 LineWidgetModel3D 对 用 等 值 面 提 取 算 法 进 行 表 面 重 建 的 结 果 进 行 测 量 的 实 例, 原 始 数 据 是 部 分 头 部 的 CT 扫 描 切 片 图 像, 其 中 Observer 采 用 类 似 Tool Tip 的 方 式 实 时 返 回 测 量 的 结 果 图 5-4 mitklinewidgetmodel3d 的 使 用 实 例 5.4.2 mitkanglewidgetmodel3d 的 应 用 实 例 图 5-5 给 出 了 一 组 使 用 AngleWidgetModel3D 对 用 等 值 面 提 取 算 法 进 行 表 面 重 建 的 结 果 进 行 测 量 的 实 例, 原 始 数 据 与 4.1 中 的 相 同,Observer 也 使 用 相 同 的 方 式 返 回 结 果 149

5 三 维 人 机 交 互 的 设 计 与 实 现 图 5-5 mitkanglewidgetmodel3d 的 使 用 实 例 5.4.3 mitkclippingplanewidget 的 应 用 实 例 图 5-6 给 出 了 一 组 使 用 ClippingPlaneWidget 对 用 等 值 面 提 取 算 法 进 行 表 面 重 建 的 结 果 进 行 裁 剪 的 实 例, 原 始 数 据 与 4.1 中 所 用 的 相 同 裁 剪 平 面 采 用 半 透 明 显 示, 这 样 可 以 使 物 体 之 间 的 前 后 位 置 关 系 显 得 更 清 楚 一 些, 当 需 要 更 清 楚 地 观 察 被 裁 剪 物 体 的 内 部 时 可 以 将 其 透 明 度 调 至 最 大 ( 即 完 全 透 明 ) 图 5-6 mitkclippingplanewidget 的 使 用 实 例 150

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. 183-188. 2. 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. 351-352. 3. 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. 1-6. 4. J. Döllner, K. Hinrichs. Interactive, Animated 3D Widgets. Proceedings of Computer Graphics International 98, 1998, IEEE, pp. 278-286. 5. 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. 270 285, 2002 6. Michael J. McGuffin, Liviu Tancau, Ravin Balakrishnan. Using Deformations for Browsing Volumetric Data. Proceedings of the IEEE Visualization Conference, 2003, pp. 401-408. 7. Fred Dech, Jonathan C. Silverstein. Rigorous Exploration of Medical Data in 151

5 三 维 人 机 交 互 的 设 计 与 实 现 Collaborative Virtual Reality Applications. Sixth International Conference on Information Visualisation, 2002, pp. 32-38 8. Marc P. Stevens, Robert C. Zeleznik, John F. Hughes. An architecture for an extensible 3D interface toolkit. Proceedings of UIST 94, 1994, ACM, pp. 59-67. 9. 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. 81-84. 10. 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. 121-129 11. 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 2004 12. Kenneth P. Herndon, Tom Meyer. 3D widgets for exploratory scientific visualization. Proceedings of UIST 94. 1994, ACM, pp. 69-70. 13. Mark R. Mine. Virtual environment interaction techniques. UNC Chapel Hill CS Dept.: Technical Report TR95-018. 1995. 14. 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. 96-108. 15. Paul S. Strauss, Rikk Carey. An object-oriented 3D graphics toolkit. Proceedings of SIGGRAPH 92, 1992, ACM, pp. 341-347. 16. 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), 2002 17. 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. 141-148. 152

6 分 割 算 法 的 设 计 与 实 现 6 分 割 算 法 的 设 计 与 实 现 MITK 的 设 计 初 衷 是 为 了 给 医 学 影 像 领 域 的 研 究 者 提 供 一 套 具 有 一 致 接 口 的 可 复 用 的 包 括 可 视 化 分 割 配 准 功 能 的 集 成 化 的 医 学 影 像 开 发 包, 弥 补 VTK 和 ITK 的 一 些 缺 憾, 并 引 入 一 些 新 的 特 性, 使 得 MITK 能 够 成 为 除 了 VTK+ITK 以 外 的 另 外 一 个 选 择 医 学 影 像 分 割 是 医 学 影 像 处 理 与 分 析 中 的 一 个 重 点 课 题 和 难 点, 分 割 的 结 果 是 三 维 可 视 化 和 定 量 分 析 等 后 续 处 理 的 基 础 近 几 年 来 虽 然 仍 然 有 很 多 研 究 人 员 致 力 于 图 像 分 割 的 研 究, 发 表 了 很 多 的 研 究 成 果, 但 由 于 问 题 本 身 的 困 难 性, 与 八 十 年 代 相 比 并 没 有 取 得 多 少 实 质 性 的 进 展 本 章 旨 在 将 分 割 算 法 集 成 在 一 个 统 一 的 框 架 内, 力 图 设 计 一 个 具 有 良 好 可 扩 充 性 和 可 重 用 性 的 算 法 包, 使 得 算 法 开 发 人 员 能 利 用 算 法 开 发 包 深 入 研 究 并 改 进 各 种 算 法, 工 程 技 术 人 员 能 利 用 开 发 包 方 便 的 生 成 自 己 的 应 用 程 序 目 前 MITK 中 主 要 提 供 了 一 些 主 流 算 法, 如 level set 算 法 fast marching 算 法 live wire 算 法 区 域 增 长 算 法 交 互 式 分 割 算 法 和 阈 值 分 割 算 法 下 面 详 细 介 绍 各 个 算 法 的 设 计 与 实 现, 对 于 每 种 算 法, 将 从 如 下 三 方 面 加 以 阐 述 : 原 理 概 述 该 部 分 将 简 要 介 绍 该 算 法 的 理 论 依 据 数 学 方 法 该 算 法 开 发 包 的 设 计 与 实 现 该 部 分 主 要 介 绍 该 算 法 的 算 法 流 程 分 割 算 法 开 发 包 的 设 计 和 实 现 方 法, 重 点 在 于 如 何 按 照 面 向 对 象 的 软 件 设 计 方 法 来 组 织 各 个 类, 以 使 算 法 模 块 具 有 最 大 的 灵 活 性 可 重 用 性 和 可 扩 充 性 实 验 结 果 该 部 分 以 系 统 界 面 的 形 式 给 出 每 个 算 法 的 分 割 结 果 6.1 MITK 中 的 分 割 算 法 框 架 分 割 算 法 开 发 包 既 可 以 作 为 MITK 的 一 部 分, 也 可 以 独 立 出 来, 作 为 一 个 153

6 分 割 算 法 的 设 计 与 实 现 单 独 的 开 发 包 使 用 它 是 一 个 基 于 面 向 对 象 方 法 的 ANSI C++ 开 发 包, 目 标 是 为 用 户 提 供 一 个 具 有 一 致 接 口 的 算 法 包 我 们 采 用 了 基 于 设 计 模 式 的 软 件 开 发 方 法, 这 种 方 法 强 调 代 码 模 块 化 和 对 象 间 相 互 独 立 性, 使 得 软 件 具 有 很 高 的 灵 活 性 同 时, 由 于 开 发 包 使 用 标 准 C++ 来 书 写, 它 还 大 量 使 用 了 C++ 标 准 模 板 库 STL 来 简 化 一 些 繁 琐 的 编 程 工 作 算 法 包 主 要 分 为 四 大 模 块 : 数 据 模 块 数 据 获 取 模 块 数 据 输 出 模 块 和 数 据 处 理 模 块 他 们 分 别 负 责 数 据 表 示 文 件 读 取 文 件 输 出 和 数 据 处 理 工 作 定 义 类 mitkobject 作 为 开 发 包 中 所 有 类 的 基 类, 在 mitkobject 中 定 义 了 开 发 包 中 所 有 类 的 一 些 共 同 属 性 行 为 和 接 口, 如 有 关 类 自 身 的 一 些 信 息 调 试 信 息 和 内 存 管 理 接 口 等 6.1.1 数 据 模 块 数 据 模 块 主 要 用 于 数 据 的 表 示 分 割 算 法 处 理 的 数 据 主 要 是 一 些 有 多 张 切 片 组 成 的 体 数 据, 在 分 割 算 法 包 中 用 类 mitkvolume 为 他 们 提 供 一 个 统 一 的 表 示 方 法 mitkobject mitkvolume -m_width -m_height -m_imagenum -m_datatype -m_data -... +SetWidth() +SetHeight() +SetImageNum() +SetDataType() +SetData() +GetWidth() +GetHeight() +GetImageNum() +GetDataType() +GetData() +...() 图 6-1 154

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 时, 代 表 三 维 体 数 据 6.1.2 数 据 获 取 模 块 数 据 获 取 模 块 的 作 用 是 从 磁 盘 上 读 取 输 入 文 件, 将 其 写 入 内 存, 并 转 化 为 统 一 的 表 示 模 式 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

6 分 割 算 法 的 设 计 与 实 现 GetOutput() 获 取 mitkreader 的 输 出 结 果 一 个 mitkvolume 类 型 的 数 据 例 如, 欲 将 路 径 为 e:\ttt.im0 的 Im0 类 型 的 文 件 读 取 到 volume 中, 可 用 如 下 代 码 : mitkim0reader *reader = new mitkim0reader; reader->addfile( e:\\ttt.im0 ); reader->run(); mitkvolume *volume = reader->getoutput(); 6.1.3 数 据 输 出 模 块 数 据 输 出 模 块 的 作 用 是 将 内 存 中 的 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

6 分 割 算 法 的 设 计 与 实 现 如 下 代 码 : mitkim0writer *writer = new mitkim0writer; writer ->AddFile( e:\\ttt.im0 ); writer->setinput(volume); writer ->Run(); 6.1.4 数 据 处 理 模 块 数 据 处 理 模 块 是 分 割 算 法 包 的 核 心 部 分 它 的 输 入 是 一 个 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

6 分 割 算 法 的 设 计 与 实 现 filter->sethighthreshold(vh); filter->run(); OutputVolume = filter->getoutput(); 如 图 6-5 所 示, 一 个 Filter 的 输 出 可 以 作 为 另 一 个 Filter 的 输 入,N 个 Filter 可 以 组 合 成 一 个 大 的 Filter 图 6-5 6.2 基 于 阈 值 的 分 割 算 法 在 MITK 中 的 实 现 6.2.1 原 理 概 述 阈 值 分 割 是 最 常 见 的 并 行 的 直 接 检 测 区 域 的 分 割 方 法 [1] 如 果 只 用 选 取 一 个 阈 值 称 为 单 阈 值 分 割, 它 将 图 像 分 为 目 标 和 背 景 两 大 类 ; 如 果 用 多 个 阈 值 分 割 称 为 多 阈 值 方 法, 图 像 将 被 分 割 为 多 个 目 标 区 域 和 背 景, 为 区 分 目 标, 还 需 要 对 各 个 区 域 进 行 标 记 阈 值 分 割 方 法 基 于 对 灰 度 图 像 的 一 种 假 设 : 目 标 或 背 景 内 的 相 邻 象 素 间 的 灰 度 值 是 相 似 的, 但 不 同 目 标 或 背 景 的 象 素 在 灰 度 上 有 差 异, 反 映 在 图 像 直 方 图 上, 不 同 目 标 和 背 景 则 对 应 不 同 的 峰 选 取 的 阈 值 应 位 于 两 个 峰 之 间 的 谷, 从 而 将 各 个 峰 分 开 ( 见 图 6-6) 图 6-6 阈 值 分 割 示 意 图 158

6 分 割 算 法 的 设 计 与 实 现 阈 值 分 割 的 优 点 是 简 单, 同 时 对 于 不 同 类 的 物 体 灰 度 值 或 其 他 特 征 值 相 差 很 大 时, 它 能 很 有 效 的 对 图 像 进 行 分 割 阈 值 分 割 通 常 作 为 预 处 理, 在 其 后 应 用 其 他 一 系 列 分 割 方 法 进 行 处 理, 它 常 被 用 于 CT 图 像 中 皮 肤 骨 骼 的 分 割 阈 值 分 割 的 缺 点 是 不 适 用 于 多 通 道 图 像 和 特 征 值 相 差 不 大 的 图 像, 对 于 图 像 中 不 存 在 明 显 的 灰 度 差 异 或 各 物 体 的 灰 度 值 范 围 有 较 大 重 叠 的 图 像 分 割 问 题 难 以 得 到 准 确 的 结 果 另 外, 由 于 它 仅 仅 考 虑 了 图 像 的 灰 度 信 息 而 不 考 虑 图 像 的 空 间 信 息, 阈 值 分 割 对 噪 声 和 灰 度 不 均 匀 很 敏 感 针 对 阈 值 分 割 方 法 的 缺 点, 不 少 学 者 提 出 了 许 多 改 进 方 法 在 噪 声 图 像 的 分 割 中, 一 些 阈 值 分 割 方 法 还 利 用 了 一 些 象 素 邻 域 的 局 部 信 息, 如 基 于 过 渡 区 的 方 法 [2], 还 有 利 用 像 素 点 空 间 位 置 信 息 的 变 化 阈 值 法 [3], 结 合 局 部 灰 度 [4] 和 连 通 信 息 [5] 的 阈 值 方 法 6.2.2 阈 值 分 割 算 法 开 发 包 设 计 与 实 现 mitkthresholdimagefilter +SetLowThreshold() +SetHighThreshold() +Run() 图 6-7 阈 值 分 割 类 示 意 图 mitkthresholdimagefilter() 用 来 实 现 阈 值 分 割 函 数 SetLowThreshold(): 设 置 低 阈 值 ; 函 数 SetHighThreshold(): 设 置 高 阈 值 ; 函 数 Run(): 启 动 与 之 分 割 算 法 像 素 值 在 低 阈 值 和 高 阈 值 之 间 的 保 留 原 值, 其 他 的 赋 零 值 159

6 分 割 算 法 的 设 计 与 实 现 6.2.3 阈 值 分 割 结 果 示 意 图 图 6-8 阈 值 分 割 结 果 实 验 结 果 如 图 6-8 以 系 统 界 面 的 形 式 给 出 了 与 之 分 割 的 结 果 6.3 区 域 增 长 算 法 在 MITK 中 的 实 现 6.3.1 原 理 概 述 区 域 生 长 是 典 型 的 串 行 区 域 分 割 方 法, 其 特 点 是 将 分 割 过 程 分 解 为 多 个 顺 序 的 步 骤, 其 中 后 续 步 骤 要 根 据 前 面 步 骤 的 结 果 进 行 判 断 而 确 定 区 域 生 长 的 基 本 思 想 是 将 具 有 相 似 性 质 的 像 素 集 中 起 来 构 成 区 域, 该 方 法 需 要 先 选 取 一 个 种 子 点, 然 后 依 次 将 种 子 像 素 周 围 的 相 似 像 素 合 并 到 种 子 像 素 所 160

6 分 割 算 法 的 设 计 与 实 现 在 的 区 域 中 区 域 生 长 算 法 的 研 究 重 点 一 是 特 征 度 量 和 区 域 增 长 规 则 的 设 计, 二 是 算 法 的 高 效 性 和 准 确 性 区 域 生 长 算 法 的 优 点 是 计 算 简 单, 特 别 适 用 于 分 割 小 的 结 构 如 肿 瘤 和 伤 疤 [6] 与 阈 值 分 割 类 似, 区 域 生 长 也 很 少 单 独 使 用, 往 往 是 与 其 他 分 割 方 法 一 起 使 用 图 6-9 区 域 生 长 算 法 示 意 图 区 域 生 长 的 缺 点 是 它 需 要 人 工 交 互 以 获 得 种 子 点, 这 样 使 用 者 必 须 在 每 个 需 要 抽 取 出 的 区 域 中 植 入 一 个 种 子 点 同 时, 区 域 生 长 方 法 也 对 噪 声 敏 感, 导 致 抽 取 出 的 区 域 有 空 洞 或 者 在 局 部 体 效 应 的 情 况 下 将 原 本 分 开 的 区 域 连 接 起 来 为 了 解 决 这 些 缺 点,J.F. Mangin 等 提 出 了 一 种 同 伦 的 (homotopic) 区 域 生 长 方 法 [7], 以 保 证 初 始 区 域 和 最 终 抽 取 出 的 区 域 的 拓 扑 结 构 相 同 另 外, 模 糊 连 接 度 理 论 与 区 域 生 长 相 结 合 也 是 一 个 发 展 方 向 [8] 在 区 域 合 并 方 法 中, 输 入 图 像 往 往 先 被 分 为 多 个 相 似 的 区 域, 然 后 类 似 的 相 邻 区 域 根 据 某 种 判 断 准 则 迭 代 地 进 行 合 并 在 区 域 分 裂 技 术 中, 整 个 图 像 先 被 看 成 一 个 区 域, 然 后 区 域 不 断 被 分 裂 为 四 个 矩 形 区 域, 直 到 每 个 区 域 内 部 都 是 相 似 的 在 区 域 的 分 裂 合 并 方 法 中 [9], 先 从 整 幅 图 像 进 行 分 裂, 然 后 将 相 邻 的 区 域 进 行 合 并 分 裂 合 并 方 法 不 需 要 预 先 指 定 种 子 点, 它 的 研 究 重 点 是 分 裂 和 合 并 规 则 的 设 计 但 是, 分 裂 可 能 会 使 分 割 区 域 的 边 界 被 破 坏 6.3.2 区 域 生 长 算 法 开 发 包 的 设 计 与 实 现 (1) 算 法 流 程 如 图 6-10 给 出 了 区 域 生 长 算 法 的 算 法 流 程, 主 要 分 为 两 个 模 块 : 初 始 化 部 分 : 该 部 分 主 要 工 作 有 1. 由 用 户 选 取 初 始 种 子 点 ; 2. 初 始 化 堆 栈, 将 种 子 点 压 入 队 列 ; 161

6 分 割 算 法 的 设 计 与 实 现 循 环 部 分 : 该 部 分 主 要 工 作 有 1. 循 环 结 束 条 件 : 队 列 为 空 时 循 环 结 束 ; 2. 从 队 列 中 取 出 队 顶 元 素, 获 取 它 的 邻 域 点, 对 于 二 维 图 像, 取 八 邻 域, 对 于 三 维 图 像, 取 六 邻 域 ; 3. 相 似 度 条 件 : 有 很 多 种 定 义 方 法, 这 里 采 取 比 较 简 单 的 定 义 方 法, 设 当 前 队 列 顶 端 元 素 的 灰 度 值 为 gc, 当 前 邻 点 灰 度 值 为 gn, 种 子 点 的 灰 度 值 为 gs, nv cv 为 用 户 设 定 的 值, 定 义 当 gc gn < nv 且 gs gn < cv 时, 满 足 相 似 度 条 件 ; 4. 如 果 邻 点 满 足 相 似 度 条 件, 则 将 其 压 入 堆 栈 继 续 循 环, 如 果 不 满 足 则 跳 出 循 环, 结 束 程 序 ; 162

6 分 割 算 法 的 设 计 与 实 现 图 6-10 区 域 生 长 算 法 流 程 (2) 类 协 作 图 如 图 6-11 给 出 了 区 域 生 长 算 法 的 类 协 作 图 mitkregiongrowimagefilter 定 义 了 按 照 图 6-10 定 义 了 区 域 生 长 算 法 的 主 框 架 流 程 ; mitk2dregiongrowimagefilter 和 mitk3dregiongrowimagefilter 是 mitkregiongrowimagefilter 的 子 类, 分 别 定 义 了 二 维 和 三 维 区 域 生 长 算 法 的 框 架 ;mitkregiongrowtemplatefunction 为 所 有 区 域 生 长 算 法 中 的 相 似 度 function 定 义 了 一 个 基 类 ;mitkregiongrowfunction 是 mitkregiongrowtemplatefunction 的 一 163

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

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 时 返 回 值 为 真, 6.3.3 区 域 生 长 分 割 结 果 下 面 以 系 统 界 面 的 形 式 给 出 分 割 结 果 : 165

6 分 割 算 法 的 设 计 与 实 现 图 6-13 脑 肿 瘤 的 二 维 分 割 结 果 图 6-14 脑 肿 瘤 三 维 分 割 重 建 后 的 结 果 166

6 分 割 算 法 的 设 计 与 实 现 图 6-15 脑 肿 瘤 三 维 分 割 中 的 一 张 切 片 167

6 分 割 算 法 的 设 计 与 实 现 图 6-16 膝 盖 骨 质 的 二 维 分 割 结 果 6.4 交 互 式 分 割 在 MITK 中 的 实 现 6.4.1 原 理 概 述 本 节 所 指 的 交 互 式 分 割 指 : 由 用 户 指 定 一 些 作 为 多 边 形 的 顶 点, 系 统 自 动 将 多 边 形 内 部 填 充 作 为 分 割 结 果 填 充 多 边 形 时 要 用 到 图 形 学 中 的 边 填 充 算 法, 下 面 做 以 简 要 介 绍 边 填 充 算 法 的 基 本 思 想 是 : 对 于 每 一 条 扫 描 线 和 每 条 多 边 形 边 的 交 点 ( y 1 1 x, ), 将 该 扫 描 线 上 交 点 右 方 的 所 有 像 素 取 补 对 多 边 行 的 每 条 边 做 此 处 理, 多 边 行 的 顺 序 随 意 如 图 6-17 所 示, 为 应 用 最 简 单 的 边 填 充 算 法 填 充 一 个 多 边 形 的 示 意 图 168

6 分 割 算 法 的 设 计 与 实 现 图 6-17 边 填 充 算 法 示 意 图 6.4.2 交 互 式 分 割 算 法 开 发 包 的 设 计 与 实 现 (1) 算 法 流 程 如 图 6-18 给 出 了 交 互 式 分 割 算 法 的 算 法 流 程 图, 算 法 的 输 入 是 原 始 图 像 和 一 组 多 边 形 的 顶 点, 输 出 是 分 割 好 的 图 像 算 法 分 为 三 大 模 块 : 初 始 化 部 分, 该 部 分 主 要 进 行 如 下 操 作 : 1. 初 始 化 图 像 : 将 标 记 图 像 的 所 有 点 初 始 化 为 零 点 ; 2. 离 散 化 每 条 边 : 设 扫 描 线 的 方 向 为 水 平 方 向, 相 邻 两 条 扫 描 线 间 的 间 距 为 图 像 垂 直 方 向 的 单 位 距 离 如 图 6-19 所 示, 求 出 每 条 扫 描 线 与 多 边 形 各 边 的 交 点 P1 P2 P3 P4 并 将 他 们 标 值 为 非 零 值 ; 多 边 形 填 充 部 分, 该 部 分 按 照 如 下 为 代 码 工 作 : Inside = FALSE; For ( 扫 描 线 上 的 每 个 像 素 ) if ( 该 像 素 值 为 非 零 ) Inside =!Inside; 169

6 分 割 算 法 的 设 计 与 实 现 If (Inside == true) 将 该 像 素 赋 值 非 零 ; 图 6-18 边 填 充 算 法 流 程 170

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

6 分 割 算 法 的 设 计 与 实 现 6.4.3 交 互 式 分 割 算 法 的 分 割 结 果 图 6-21 交 互 式 分 割 结 果 一 图 6-22 交 互 式 分 割 结 果 二 172

6 分 割 算 法 的 设 计 与 实 现 图 6-23 交 互 式 分 割 结 果 三 6.5 Live Wire 算 法 在 MITK 中 的 实 现 live wire 算 法 是 用 来 确 定 图 像 中 轮 廓 线 的 方 法, 如 果 想 把 轮 廓 线 外 或 轮 廓 线 内 的 部 分 分 割 出 来, 还 要 用 到 种 子 点 填 充 算 法 因 此 在 下 面 的 原 理 概 述 和 算 法 包 的 设 计 部 分, 将 对 他 们 分 别 予 以 阐 述 6.5.1 原 理 概 述 (1) live wire 基 本 原 理 live wire 方 法 属 于 交 互 式 分 割 广 义 上 讲, 图 像 分 割 可 分 为 两 大 类 : 自 动 分 割 和 交 互 式 分 割 自 动 分 割 方 法 可 以 避 免 用 户 的 交 互, 但 很 难 保 证 他 们 总 是 有 效 的 在 交 互 式 分 割 方 法 中, 有 的 完 全 需 要 有 由 用 户 来 完 成, 如 那 些 由 用 户 来 话 轮 廓 线 的 分 割 方 式, 有 的 只 需 要 很 少 的 用 户 交 互, 如 本 节 要 介 绍 的 live wire 分 割 方 法 自 动 分 割 目 前 已 被 广 泛 采 用, 但 当 一 些 新 的 图 像 出 现 时, 他 们 往 往 不 能 顺 利 工 作, 这 方 面 还 有 大 量 的 研 究 工 作 要 做 在 这 种 情 况 下, 往 往 应 用 交 173

6 分 割 算 法 的 设 计 与 实 现 互 式 分 割, 它 能 够 使 用 户 完 全 控 制 分 割 过 程, 而 且 在 任 何 情 况 下 他 都 可 以 正 常 工 作 在 live wire 算 法 中, 将 图 像 看 成 是 一 个 连 通 图, 图 像 中 的 像 素 当 作 连 通 图 中 的 节 点, 相 邻 像 素 间 的 边 当 作 连 接 节 点 的 边 在 边 上 定 义 一 个 代 价 函 数, 使 强 边 缘 具 有 较 小 的 代 价 值, 非 边 缘 具 有 较 大 的 代 价 值, 两 个 节 点 间 的 距 离 可 用 代 价 值 表 示 然 后 通 过 图 搜 索 来 找 物 体 的 边 界, 把 用 户 指 定 的 物 体 边 界 上 的 两 点 之 间 的 最 短 路 径 ( 即 该 条 路 径 上 所 有 边 的 代 价 值 总 和 最 小 ) 当 作 物 体 的 边 界 一 般 用 动 态 规 划 来 查 找 连 通 图 中 两 点 之 间 的 最 短 路 径, 如 图 6-24 所 示 图 6-24 连 接 两 个 点 之 间 的 最 短 路 径 由 上 面 的 分 析 可 知, 图 像 中 目 标 物 体 的 边 缘 跟 踪 问 题 可 以 被 转 换 成 赋 权 图 的 最 优 路 径 搜 索 问 题 为 此, 需 对 一 幅 n n 的 灰 度 图 像 进 行 如 下 处 理 一 幅 n n 的 图 像 被 描 述 成 具 有 4 邻 域 象 素 的 象 素 阵 列, 每 个 象 素 被 描 述 成 一 个 正 方 形, 相 邻 象 素 有 一 条 公 共 边, 称 为 元 边 对 于 G 中 的 每 一 条 元 边, 根 据 一 定 的 规 则 赋 予 其 相 应 的 特 征 值, 用 以 描 述 该 元 边 属 于 物 体 边 缘 的 可 能 性, 元 边 的 特 征 值 经 过 特 征 转 换 函 数 转 变 成 一 定 的 代 价 值 我 们 定 义 图 中 任 意 两 个 节 点 间 的 最 优 路 径, 由 两 个 节 点 间 累 积 代 价 和 最 小 的 连 续 元 边 组 成 此 时, 该 图 像 确 定 了 一 个 赋 权 图 : G=(V,E) 其 中 V 为 图 像 的 象 素 点 集 合,E 为 元 边 的 集 合 174

6 分 割 算 法 的 设 计 与 实 现 175 图 6-25 相 邻 像 素 如 图 6-25 所 示, 相 邻 象 素 p 与 q 之 间 的 公 共 边 b,b 的 特 征 值 根 据 图 像 的 边 缘 信 息 确 定, 边 缘 信 息 越 强, 特 征 值 越 大 利 用 特 征 转 换 函 数 可 以 将 特 征 值 转 换 为 边 的 代 价 值 文 献 [9] 用 如 下 几 式 来 确 定 边 的 特 征 值, 和 特 征 转 换 函 数 特 征 值 为 : ) ( ) ( 1 q g p g f = (6-1) ) ( ) ( ) ( ) ( ) ( ) ( 3 1 2 w g u g q g v g t g p g f + + = (6-2) ) ( 2 1 ) ( 2 1 ) ( ) ( 2 1 ) ( 2 1 ) ( 2 1 3 w g u g q g v g t g p g f + + = (6-3) ( ) ) ( ) ( ) ( ) ( ) ( ) ( ) ( ) ( 4 1 4 q g v g w g p g q g t g u g p g f + + + = (6-4) 特 征 转 换 函 数 为 : > < + + = 2 2 2 1 1 2 2 1 0, 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)

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

6 分 割 算 法 的 设 计 与 实 现 (2) 种 子 点 填 充 基 本 原 理 种 子 点 填 充 是 图 形 学 中 的 算 法, 是 轮 廓 提 取 算 法 的 逆 过 程 它 首 先 假 定 封 闭 轮 廓 线 内 某 点 是 已 知 的, 然 后 算 法 开 始 搜 索 与 种 字 点 相 邻 且 位 于 轮 廓 线 内 的 点 如 果 相 邻 点 不 再 轮 廓 线 内, 那 么 就 到 达 轮 廓 线 的 边 界 ; 如 果 相 邻 点 位 于 轮 廓 线 之 内, 那 么 这 一 点 就 成 为 新 的 种 子 点, 然 后 继 续 搜 索 下 去 种 子 点 填 充 区 域 的 连 通 情 况 又 有 四 连 通 和 八 连 通 之 分 6.5.2 live Wire 算 法 包 的 设 计 与 实 现 (1) 算 法 流 程 live wire 算 法 流 程 由 上 面 的 介 绍 可 知,live wire 算 法 的 关 键 在 于 如 何 确 定 边 的 特 征 值 和 特 征 转 换 函 数 在 live wire 算 法 包 中, 采 用 式 (6-1)-(6-4) 来 确 定 边 的 特 征 值, 采 用 式 (6-5) 来 确 定 边 的 特 征 转 换 函 数 如 图 6-27 给 出 了 live wire 算 法 的 框 架 图 他 的 输 入 是 原 始 图 像, 输 出 是 一 幅 二 值 图, 从 初 始 点 到 终 止 点 的 最 优 路 径 上 的 点 为 非 零 值, 其 他 的 点 为 零 值 有 图 中 可 见, 该 算 法 主 要 分 为 两 大 模 块, 初 始 化 部 分 和 动 态 规 划 部 分 : 177

6 分 割 算 法 的 设 计 与 实 现 图 6-27 live wire 算 法 框 架 图 初 始 化 部 分 : 该 部 分 主 要 完 成 如 下 工 作 计 算 由 图 6-26 确 定 的 每 条 边 的 代 价 值 c (b) ; 用 与 输 入 图 像 大 小 相 同 的 一 幅 图 像 记 录 图 像 中 各 个 点 的 累 积 代 价 值 成 这 幅 图 像 为 代 价 图 像 在 代 价 图 像 中, 将 初 始 节 点 v s 的 累 积 代 价 值 cc (v s ) 设 为 0, 其 余 节 点 的 累 积 代 价 值 为 ; 初 始 化 队 列 Q, 把 初 始 节 点 v s 置 入 待 处 理 的 节 点 队 列 Q 中 ; 178

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

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

6 分 割 算 法 的 设 计 与 实 现 如 图 6-29 给 出 了 live wire 算 法 包 的 内 部 组 成 结 构 mitklivewirefunction: 他 为 所 有 计 算 边 代 价 的 数 学 方 法 提 供 了 一 个 基 类 用 户 通 过 为 他 添 加 不 同 的 子 类 以 实 现 不 同 的 计 算 边 代 价 的 方 法 函 数 Initialize(): 主 要 做 一 些 初 始 化 工 作 函 数 CostCompute(): 计 算 每 条 边 的 代 价 值 这 两 个 函 数 都 为 虚 函 数, 由 子 类 重 载 mitklivewireconcretefunction: 由 mitklivewirefunction 继 承 而 来, 根 据 式 6-18 6-19 实 现 了 一 种 计 算 边 代 价 的 方 法 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

6 分 割 算 法 的 设 计 与 实 现 mitkseedfillfilter +SetSeedPoint() +Run() 图 6-30 如 图 对 于 种 子 点 填 充 算 法, 用 类 mitkseedfill 来 实 现 输 入 图 像 是 一 幅 二 值 图, 轮 廓 线 上 的 点 非 零, 其 他 点 为 零 点 进 行 填 充, 输 出 图 像 也 为 二 值 图, 被 填 充 的 部 分 值 为 非 零, 其 他 部 分 为 零 值 6.5.3 live wire 分 割 结 果 图 6-31 给 定 起 始 点 和 终 止 点 下 找 出 的 轮 廓 线 图 6-31 给 出 了 在 如 图 所 示 的 起 始 点 和 终 止 点 下,live wire 算 法 找 出 的 轮 廓 线 ; 182

6 分 割 算 法 的 设 计 与 实 现 图 6-32 用 live wire 算 法 生 成 的 几 条 轮 廓 线 图 6-32 给 出 了 几 条 用 live wire 算 法 找 出 的 轮 廓 线 ; 183

6 分 割 算 法 的 设 计 与 实 现 图 6-33 对 轮 廓 线 进 行 种 子 点 填 充 后 的 分 割 结 果 图 6-33 给 出 了 种 子 点 选 在 各 轮 廓 线 间 时 的 种 子 点 填 充 结 果 6.6 Fast Marching 算 法 在 MITK 中 的 实 现 6.6.1 原 理 概 述 Fast Marching 算 法 是 一 种 基 于 几 何 形 变 模 型 的 医 学 影 像 分 割 方 法 几 何 形 变 模 型 是 由 Irasel 的 Caselles 等 [10] 和 Florida 的 Malladi 等 [11] 提 出 的, 这 些 模 型 的 理 论 基 础 是 曲 线 演 化 (curve evolution) 理 论 [12] [13] [14] [15] 和 水 平 集 (level set) 方 法 [16] [17] 几 何 形 变 模 型 的 基 本 思 想 是 将 曲 线 的 形 状 变 化 用 曲 线 演 化 理 论 来 描 述, 即 用 曲 率 或 法 向 量 等 几 何 度 量 表 示 曲 线 或 曲 面 演 化 的 速 度 函 数, 并 将 速 度 函 数 与 图 像 数 据 关 联 起 来, 从 而 使 曲 线 在 对 象 边 缘 处 停 止 演 化 由 于 曲 线 的 演 化 与 参 数 无 关, 几 何 形 变 模 型 能 被 自 动 处 理 对 象 拓 扑 的 变 化, 演 化 过 程 中 的 184

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

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 方 法 依 赖 于 后 一 种 方 法 6.6.2 Fast Marching 算 法 开 发 包 的 设 计 与 实 现 (1) Fast Marching 的 算 法 流 程 公 式 (6-13) 是 著 名 的 Eikonal 方 程 的 一 种 形 式,Sethian 在 文 [19] 中 指 出, 要 得 186

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

6 分 割 算 法 的 设 计 与 实 现 图 6-36 Fast Marching 算 法 主 框 架 流 程 该 算 法 的 输 入 为 一 幅 速 度 图 像, 该 图 像 上 每 个 像 素 点 的 值 代 表 轮 廓 线 在 该 点 的 扩 散 速 度 ; 输 出 为 一 幅 时 间 图, 该 图 像 上 每 个 像 素 点 的 值 代 表 轮 廓 线 扩 散 到 该 点 所 需 的 时 间 如 图 6-36 所 示, 算 法 主 要 分 为 两 大 模 块 : 初 始 化 部 分 和 循 环 部 分 初 始 化 部 分 : 设 置 活 动 点 : 活 动 点 就 是 所 有 网 格 点 中 时 间 T 固 定 的 点 可 以 有 多 种 指 定 方 法, 在 分 割 算 法 包 目 前 提 供 的 算 法 中, 也 就 是 用 户 指 定 的 种 子 点, 时 间 T(x, y)=0 188

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

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

6 分 割 算 法 的 设 计 与 实 现 ProcessFarpoints() Halt() GetCurrentNode() ProcessCurrentNode() 和 Update() 这 些 虚 函 数 在 mitkfastmarchingtemplate 中 没 有 定 义, 子 类 通 过 重 载 这 些 函 数 来 来 完 成 改 算 法 的 一 种 实 现 ; mitkfastmarchigntemplatefunction 为 所 有 的 function 提 供 了 一 个 基 类 函 数 Update(): 用 来 更 新 当 前 点 的 时 间 值, 为 虚 函 数, 由 子 类 重 载, 以 实 现 一 种 数 学 方 法 ; mitkfastmarchingfunction 为 mitkfastmarchingtemplatefunction 的 一 个 子 类 函 数 Update(): 定 义 了 一 种 解 方 程 6-13 的 方 法 mitkfastmarchingimagefilter 是 mitkfastmarchingtemplate 的 一 个 子 类, 重 载 了 它 的 一 部 分 虚 函 数 : m_nodeheap: 是 一 个 mitkminimumheap 类 型 的 指 针 191

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

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

6 分 割 算 法 的 设 计 与 实 现 6.6.3 Fast Marching 分 割 结 果 图 6-41 Fast Marching 分 割 结 果 一 194

6 分 割 算 法 的 设 计 与 实 现 图 6-42 Fast Marching 插 件 分 割 结 果 6.7 Level Set 算 法 在 MITK 中 的 实 现 6.7.1 原 理 概 述 下 面 介 绍 曲 线 演 化 的 水 平 集 实 现 方 法 水 平 集 方 法 用 于 解 决 拓 扑 的 自 动 变 化, 它 也 提 供 了 几 何 形 变 模 型 的 数 学 实 现 基 础 Osher and Sethian 最 先 将 水 平 集 方 法 用 于 实 现 曲 线 的 演 化 [16] [21] [22] 图 6-43 将 一 条 曲 线 嵌 入 为 一 个 水 平 集 195

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

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

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 方 法 实 现 图 像 分 类 等 6.7.2 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

6 分 割 算 法 的 设 计 与 实 现 Φ t Φ t + 1 t i, j i, j Δt Φ (6-24) ( ) 2 2 2 2 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

6 分 割 算 法 的 设 计 与 实 现 图 6-46 窄 带 算 法 示 意 图 开 始 初 始 化 初 始 化 是 否 满 足 停 止 条 件? 循 环 计 算 变 化 应 用 更 新 后 处 理 后 处 理 结 束 图 6-47 Level Set 算 法 主 框 架 图 6-47 Level Set 算 法 主 框 架 给 出 了 level set 算 法 的 主 框 架 流 程 该 算 法 的 输 200

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

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

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

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

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 6.7.3 level set 分 割 结 果 图 6-50 图 4.19 封 装 后 的 level set 开 发 包 图 6-51 level set 插 件 分 割 结 果 205

6 分 割 算 法 的 设 计 与 实 现 图 6-51 以 系 统 界 面 的 形 式 给 出 了 level set 插 件 的 分 割 结 果, 其 中 fast marching 算 法 的 阈 值 设 为 750, level set 内 循 环 的 次 数 为 3, 外 循 环 的 次 数 为 3, 窄 带 的 宽 度 为 5 6.8 小 结 医 学 图 像 分 割 是 医 学 影 像 处 理 与 分 析 中 的 一 个 重 点 课 题 和 难 点, 分 割 的 结 果 是 三 维 可 视 化 和 定 量 分 析 等 后 续 处 理 的 基 础 本 章 针 对 医 学 图 像 中 的 一 些 主 流 分 割 算 法, 对 其 原 理 进 行 了 介 绍, 并 给 出 了 类 的 结 构 框 图 说 明 其 是 如 何 在 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:233-260. 2. Y. J. Zhang, J. J. Gerbrands. Transition region determination based thresholding. Pattern Recognition Letter, 1991, 12:13-23. 3. Y. Nakagawa, A. Rosenfeld. Some experiments on variable thresholding. Pattern Recognition, 1979, 11:191~204. 4. 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:565 576. 5. C. Lee, S. Hun, T.A. Ketter, and M. Unser. Unsupervised connectivity-based thresholding 206

6 分 割 算 法 的 设 计 与 实 现 segmentation of midsaggital brain MR images. Comput. Biol. Med., 1998, 28:309 338. 6. 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:1337 1345. 7. 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:297 318. 8. 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): 246-261. 9. 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:393 412. 10. V. Caselles, F. Catte, T. Coll, and F. Dibos. A geometric model for active contours. Numerische Mathematik, 1993, 66:1 31. 11. 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):158-175. 12. 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):199 257. 13. G. Sapiro and A. Tannenbaum. Affine invariant scale-space. Proc. Intl. Conf. on Computer Vision, 1993, 11(1):25 44. 14. 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):635 640. 15. 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:189 224. 16. S. Osher and J. A. Sethian. Fronts propagating with curvature-dependent speed: algorithms based on Hamilton-Jacobi formulations. Journal of Computational Physics, 1988, 79:12 49. 17. 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., 1999. 18. J.A.Sethian. A fast marching level set method for monotonically advancing fronts. Proc. Natl. Acad. Sci. USA, 93(1996): pp.1591-1595. 207

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. 9389-9392. 20. J. A. Sethian. Level Set Methods. Cambridge University Press, 1996. 21. J. A. Sethian. Curvature and evolution of fronts. Communication: Mathematics & Physics, 1985, 101:487 499. 22. J. A. Sethian. A review of recent numerical algorithms for hypersurfaces moving with curvature dependent speed. Journal of Differential Geometry, 1989, 31:131 161. 23. V. Caselles, R. Kimmel, and G. Sapiro. Geodesic active contours. in Proc. 5th Int l Conf. Computer Vision, 1995, 694 699. 24. V. Caselles, R.Kimmel, and G.Sapiro. Geodesic active contours. International Journal of Computer Vision, 1997, 22(1): 61-79. 25. 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, 810-815. 26. 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:199 209. 27. Bertalmio M, Sapiro G, Randall G. Region tracking on level-set methods. IEEE Transactions on Medical Imaging, 1999, 18(5): 448-451. 28. Masouri A-R, Sirivong B, Konrad J. Multiple motion segmentation with level sets. Proceedings of SPIE, 2000, Vol.3974, pp.584-595. 29. 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): 266-280. 30. Samon C, Blanc-Feraud L, Aubert G, Josiane Z. Level set model for image classification. International Journal of computer Vision, 2000, 40(3): 187-197. 208

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

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

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

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

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

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

7 配 准 算 法 的 设 计 与 实 现 以 上 Transform InterpolateFilter Metric Optimizer 四 模 块 是 从 高 层 概 念 上 实 现 了 相 应 的 算 法, 提 供 基 本 的 数 据 成 员 和 成 员 函 数 接 口 不 同 类 型 的 各 种 具 体 算 法 通 过 它 们 的 派 生 类 在 虚 函 数 中 实 现 用 户 通 过 设 置 具 体 的 几 何 变 换 类 型 插 值 方 法 相 似 性 度 量 准 则 优 化 策 略 就 能 实 现 不 同 的 配 准 算 法 下 面 几 节 详 细 介 绍 MITK 中 采 用 的 具 体 几 何 变 换 类 型 插 值 方 法 相 似 性 度 量 准 则 和 优 化 策 略 7.3 几 何 变 换 空 间 映 射 T 描 述 了 一 幅 图 像 中 的 位 置 与 另 一 幅 图 像 中 的 相 应 位 置 之 间 的 关 系 这 里 的 图 像 可 以 是 二 维 的 (2D), 也 可 以 是 三 维 的 (3D), 所 以 这 种 映 射 可 能 是 从 二 维 空 间 到 二 维 空 间 从 三 维 空 间 到 三 维 空 间 或 者 是 在 三 维 和 二 维 之 间 的 变 换 然 而 在 所 有 上 述 情 况 中, 象 源 体 人 体 的 部 分 或 全 部 都 是 三 维 的 所 以, 对 大 多 数 情 况 而 言, 二 维 空 间 内 部 的 变 换 无 法 满 足 配 准 的 要 求 目 前 最 广 泛 的 配 准 应 用 中 就 包 含 三 维 图 像 对 之 间 的 配 准 另 外, 一 个 更 加 重 要 的 应 用 就 是 二 维 图 像 与 三 维 图 像 之 间 的 配 准 (2D-3D 配 准 ) 在 2D-3D 的 配 准 过 程 中,T 包 含 了 从 3D 物 体 到 2D 平 面 的 投 影, 以 及 3D-3D 的 变 换 7.3.1 刚 性 变 换 算 法 如 果 用 以 配 准 的 图 像 包 含 相 同 的 内 容, 只 是 位 置 有 所 不 同, 那 么 就 可 以 用 旋 转 和 平 移 来 描 述 配 准 变 换 这 就 是 刚 性 变 换 在 三 维 情 况 下, 刚 性 变 换 包 含 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

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 0 0 0 1 (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) 0 0 1 0 在 各 向 同 性 坐 标 系 (x y z 1) T 中 的 3D 坐 标 同 这 个 矩 阵 相 乘, 得 到 向 量 (λ u, λv,λ) T 其 中 λ 代 表 缩 放 因 子, 投 影 到 2D 平 面 的 结 果 可 以 由 该 向 量 中 的 前 两 项 分 别 除 以 缩 放 因 子 λ 得 到 2D-3D 刚 性 配 准 所 需 要 的 变 换 T 2D-3D 可 以 由 投 影 变 换 和 刚 体 变 换 组 合 而 成 : T 2D-3D =T projection T rigid (7-7) 当 我 们 考 虑 骨 质 或 者 与 之 接 近 的 结 构 时, 这 种 刚 性 变 换 可 以 正 确 的 将 反 映 相 同 内 容 的 图 像 对 应 起 来 这 种 假 设 对 在 处 理 脑 部 图 像 的 时 候 非 常 适 用, 因 为 脑 颅 骨 限 制 了 大 脑 的 形 变 然 而 对 身 体 内 大 多 数 组 织 而 言, 刚 性 变 换 远 远 满 足 不 了 配 准 的 需 要, 我 们 需 要 更 多 的 自 由 度 来 足 够 精 确 地 描 述 组 织 的 形 变 7.3.2 线 性 变 换 与 一 对 一 变 换 线 性 变 换 : 许 多 作 者 认 为 仿 射 (affine) 变 换 是 线 性 的 从 严 格 意 义 上 讲, 这 是 不 正 确 的 线 性 变 换 是 一 种 满 足 以 下 条 件 的 特 殊 变 换 : L(αx A +βx A )=αl(x A )+βl(x A ) x x R (7-8) A, ' A 216

7 配 准 算 法 的 设 计 与 实 现 而 仿 射 变 换 的 平 移 部 分 不 符 合 这 一 条 件 确 切 的 说, 仿 射 变 化 是 一 种 线 性 变 换 与 平 移 变 换 的 复 合 变 换 更 进 一 步, 反 射 (reflection) 变 换 也 是 一 种 线 性 变 换, 但 是 它 们 在 图 像 配 准 中 很 少 用 到 举 例 而 言, 如 果 在 图 像 指 导 的 神 经 外 科 中 所 使 用 的 配 准 算 法 包 含 反 射 变 换, 那 么 就 有 可 能 导 致 在 穿 颅 手 术 中 定 位 到 错 误 的 一 边 如 果 怀 疑 到 算 法 中 可 能 包 括 反 射 变 换, 那 么 就 一 定 要 在 应 用 之 前 得 出 确 证, 以 免 造 成 事 故 一 对 一 变 换 : 在 对 同 一 目 标 的 配 准 中, 病 人 通 过 不 同 的 设 备 进 行 成 像 这 种 配 准 中 所 要 求 的 变 换 T 似 乎 是 一 对 一 的 这 意 味 着 图 像 A 中 的 点 经 过 变 换 后 与 图 像 B 中 唯 一 确 定 的 点 对 应, 反 之 亦 然 在 有 些 情 况 下, 这 种 规 则 并 不 适 用 首 先, 如 果 配 准 图 像 的 维 数 并 不 相 同, 例 如 x 光 照 片 和 CT 成 像 之 间 的 配 准, 一 对 一 变 换 就 是 不 可 能 的 其 次, 在 一 幅 图 像 中 的 采 样 数 据 并 没 有 反 映 在 另 一 幅 图 像 的 采 样 数 据 中 对 于 各 种 非 仿 射 变 换 配 准, 一 对 一 的 变 换 都 是 不 适 用 的 举 例 而 言, 在 对 不 同 目 标 的 配 准 中, 或 者 在 对 同 一 目 标 手 术 前 和 手 术 后 的 配 准 中,A 图 像 中 就 可 能 存 在 B 图 像 中 所 不 包 含 的 结 构, 反 之 亦 然 7.3.3 变 换 算 法 在 MITK 中 的 实 现 从 图 7-2 配 准 算 法 框 架 图 中 可 以 看 出, 变 换 模 块 Transform 接 受 优 化 模 块 Optimizer 的 输 出 作 为 输 入, 即 输 入 是 一 个 变 换 参 数 集 合 而 Transform 的 输 出 是 一 个 数 据 集, 它 记 载 着 变 换 后 点 的 坐 标 位 置, 其 组 织 形 式 与 volume 相 同 这 个 输 出 进 一 步 作 为 插 值 模 块 Interpolator 的 输 入 参 数 之 一 抽 象 类 Transform 的 框 架 图 如 图 7-5 示 : 217

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

7 配 准 算 法 的 设 计 与 实 现 一 个 空 间 映 射 到 另 一 个 空 间 时, 目 标 点 的 坐 标 通 常 不 在 网 格 点 上 在 这 种 情 况 下, 插 值 算 法 就 需 要 用 来 估 计 目 标 点 的 灰 度 值 变 换 示 意 图 见 图 7-6: 图 7-6 固 定 图 像 网 格 点 与 变 换 后 的 浮 动 图 像 非 网 格 点 插 值 方 法 影 像 着 图 像 的 平 滑 性, 优 化 的 搜 索 空 间, 以 及 总 体 的 计 算 时 间 因 为 在 一 个 优 化 周 期 之 中, 插 值 算 法 将 被 执 行 成 千 上 万 次 所 以, 在 指 定 插 值 方 案 时, 我 们 需 要 在 计 算 复 杂 性 和 图 像 平 滑 性 之 间 做 一 个 权 衡 7.4.1 最 近 邻 插 值 图 7-7 最 近 邻 域 法 插 值 最 近 邻 域 法 是 指 把 距 离 非 相 网 点 (u,v) 最 近 的 u-- v 坐 标 系 中 的 格 网 点 的 灰 度 值 设 为 (u,v) 点 灰 度 值 的 算 法 如 图 7-7 所 示, 其 不 足 是 会 使 细 线 状 目 标 边 界 产 生 锯 齿 219

7 配 准 算 法 的 设 计 与 实 现 7.4.2 线 性 插 值 图 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) 7.4.3 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

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 是 整 数,ƒ 是 核 函 数 7.4.4 插 值 算 法 在 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

7 配 准 算 法 的 设 计 与 实 现 在 Interoplator 中 设 置 完 图 像 数 据 和 变 换 结 果 之 后, 就 可 以 通 过 Run() 调 用 Excute() 执 行 具 体 的 插 值 操 作 7.5 相 似 性 测 度 相 似 性 测 度 (Similarity Measure) 定 量 化 地 衡 量 了 两 幅 图 像 匹 配 的 效 果, 它 是 图 像 配 准 过 程 中 十 分 重 要 的 一 部 分 [4] 一 般 情 况 下 待 配 准 的 图 像 是 在 不 同 时 间 不 同 条 件 甚 至 不 同 成 像 技 术 下 获 取 的, 图 像 描 述 的 信 息 可 能 存 在 本 质 的 差 别, 这 种 情 况 下 就 没 有 绝 对 的 配 准 问 题, 那 么 我 们 的 任 务 就 是 寻 找 一 种 准 则, 使 两 幅 图 像 在 这 种 准 则 下 达 到 最 佳 的 匹 配 效 果 这 里 的 准 则 称 之 为 相 似 性 测 度, 在 一 些 非 刚 性 配 准 中 还 加 上 形 变 约 束 准 则 的 选 择 和 配 准 目 的 具 体 的 图 像 形 态 几 何 变 换 类 型 等 有 关 例 如, 有 些 准 则 允 许 很 大 的 几 何 变 换 搜 索 范 围, 而 有 些 准 则 要 求 初 始 位 置 和 最 优 的 配 准 结 果 比 较 接 近 才 能 得 到 正 确 结 果 ; 有 些 准 则 仅 仅 适 用 于 同 一 模 态 图 像 间 的 配 准, 而 有 些 准 则 能 处 理 不 同 模 态 的 图 像 配 准 遗 憾 的 是 现 在 还 没 有 一 个 明 确 的 准 则 来 指 导 在 各 种 情 况 下 如 何 选 择 配 准 的 相 似 度 量 准 则, 更 不 存 在 各 种 情 况 下 都 通 用 的 相 似 度 准 则 既 然 配 准 只 是 在 某 种 准 则 下 取 得 相 对 最 优, 准 则 的 选 择 直 接 影 响 着 配 准 的 效 果 因 此, 如 何 选 择 合 适 的 相 似 性 度 量 准 则 就 成 为 图 像 配 准 中 一 个 十 分 关 键 的 研 究 问 题, 大 量 的 研 究 论 文 也 表 明 了 这 一 点 从 发 表 的 论 文 来 看, 主 要 有 两 种 相 似 性 度 量 准 则 : 基 于 特 征 (feature-based) 和 基 于 体 素 (voxel-based) 基 于 特 征 的 准 则 一 般 是 最 小 化 两 图 像 相 应 特 征 间 的 距 离, 常 用 的 特 征 有 对 应 解 剖 结 构 中 的 控 制 点 二 维 边 缘 线 三 维 表 面 等 这 种 准 则 下, 通 常 先 要 提 取 特 征, 利 用 这 些 局 部 的 特 征 信 息 进 行 配 准 特 征 提 取 的 准 确 性 直 接 影 响 着 配 准 的 精 度 基 于 体 素 的 方 法 是 目 前 的 研 究 热 点 从 理 论 上 来 讲, 这 种 方 法 应 该 是 最 灵 活 的, 因 为 它 利 用 了 图 像 中 的 所 有 信 息 从 总 体 上 来 看, 这 类 准 则 中 较 常 见 的 有 :1 相 关 性 测 度, 包 括 相 关 系 数 傅 立 叶 域 的 互 相 关 和 相 位 相 关 等 ;2 总 体 平 均 差 最 小 化 ;3 灰 度 比 的 方 差 最 小 化 ;4 互 信 息 最 大 化 其 中 基 于 互 信 息 最 大 化 的 方 法 获 得 了 很 大 的 成 功 [5], 大 量 文 献 表 明, 该 方 法 不 仅 适 用 于 单 模 态 图 像 配 准, 对 多 模 态 图 像 配 准 问 题 也 能 取 得 不 错 的 结 果 MITK 中 目 前 实 现 了 下 列 基 于 体 素 的 相 似 性 度 量 准 则 : 灰 度 平 均 差 (Mean Squares Metric) 222

7 配 准 算 法 的 设 计 与 实 现 归 一 化 相 关 系 数 (Normalized Correlation Metric) Pattern Intensity 互 信 息 (Mutual Information Metric) 下 面 我 们 先 简 要 介 绍 这 几 种 准 则, 然 后 看 看 其 在 MITK 中 的 实 现 为 了 书 写 方 便, 我 们 记 参 考 图 f ( X ) 和 变 换 插 值 后 的 浮 动 图 mt ( t( X ) 为 A 和 B A i, B i 分 别 是 图 像 AB, 第 i 个 像 素 的 灰 度 值, N 是 计 算 区 域 的 像 素 个 数 7.5.1 灰 度 平 均 差 测 度 MitkMeanSquaresMetric 类 计 算 图 像 AB, 在 给 定 区 域 的 灰 度 平 均 差 : N 1 2 MS( A, B) = ( Ai Bi) (7-10) N i 理 想 情 况 下 的 最 优 测 度 是 0, 这 是 基 于 以 下 假 设 : 两 图 像 对 应 像 素 点 灰 度 值 相 同 因 此 该 准 则 只 适 用 于 同 模 态 图 像 配 准 该 准 则 计 算 简 单, 相 对 来 说 可 以 在 一 个 比 较 大 的 范 围 内 搜 索 匹 配 但 该 准 则 对 图 像 灰 度 值 的 线 性 变 化 比 较 敏 感 7.5.2 归 一 化 相 关 系 数 MitkNormalizedCorrelationMetric 计 算 图 像 AB, 的 归 一 化 互 相 关 系 数 N ( AB i ) i i NC( A, B) = (7-11) N 2 N 2 A i i B i i 理 想 情 况 下 该 准 则 的 最 优 值 是 1 该 准 则 也 仅 限 于 单 模 态 图 像 配 准, 并 且 产 生 尖 峰 状 极 值, 搜 索 范 围 较 小 7.5.3 Pattern Intensity 1 MitkPatternIntensityMetric 计 算 灰 度 差, 并 代 入 钟 形 函 数 求 和 2 1+ x N 1 PI( A, B) = (7-12) 2 1 1 + λ( Ai Bi) 其 中 λ 是 比 例 系 数, 控 制 搜 索 范 围 该 准 则 的 具 体 性 质 可 参 考 Penney[6] 和 Holden[4] 的 论 文 该 准 则 也 仅 限 于 单 模 态 图 像 配 准, 对 图 像 灰 度 值 的 线 性 变 化 比 较 敏 感 7.5.4 互 信 息 互 信 息 (Mutual Information) 是 信 息 论 中 的 一 个 概 念, 通 常 用 于 描 述 两 个 系 统 223

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

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) 采 用 基 于 体 素 方 法 计 算 相 似 度 时, 对 于 高 分 辨 率 的 三 维 体 数 据 集, 其 包 含 的 数 据 量 极 大, 为 减 少 计 算 量, 常 只 采 用 部 分 而 不 是 全 部 数 据 点, 计 算 前 先 对 原 图 像 进 行 重 采 样 7.5.5 相 似 性 测 度 在 MITK 中 的 实 现 从 图 7-2 可 以 看 出, 算 法 上 相 似 性 测 度 模 块 Metric 接 受 Fixed Volume 和 Image Interpolator 的 输 出 作 为 输 入, 即 输 入 两 个 Volume 体 数 据, 输 出 的 是 计 算 得 到 的 相 似 度 量 值 在 MITK 具 体 实 现 中, 我 们 用 Metic 这 一 抽 象 基 类 来 实 现 相 似 性 测 度, 其 框 架 如 图 7-11 所 示 225

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

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

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

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

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

7 配 准 算 法 的 设 计 与 实 现 1. Brown L, A Survey of Image Registration Techniques, ACM Computing Surveys (CSUR), 1992,24(4):325-376. 2. 田 捷, 包 尚 联, 周 明 全. 医 学 影 像 处 理 与 分 析. 北 京 : 电 子 工 业 出 版 社,2003. 3. 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, 2001. 4. 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):586 595, August 1998. 5. J. Pluim, J. Maintz, M. Viergever, Mutual-Information-Based Registration of Medical Images: A Survey, IEEE Transaction on Medical Image, 22(8):986-1004, 2003. 6. 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 472 477. Springer, 1999. 7. H. Lester, S. Arridge, A Survey of Hierarchical Non-linear Medical Image Registration, Pattern Recognition, 32:129-149, 1999. 231

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] 在 这 个 标 准 中, 增 强 了 对 网 络 的 支 持, 成 为 医 学 影 像 设 备 的 国 际 标 准 通 信 协 议 现 在, 各 个 厂 家 的 医 疗 仪 器 和 医 学 诊 断 系 统 都 已 开 始 使 用 国 际 化 的 通 信 和 数 据 格 式 标 准 DICOM3.0 8.1.1 DICOM 标 准 的 产 生 和 演 化 DICOM 标 准 的 演 变 历 史 大 致 可 以 划 分 为 如 下 三 个 阶 段 : (1) ACR-NEMA 标 准 1.0 版 (1985 年 ) 美 国 放 射 学 会 (ACR) 和 美 国 电 器 制 造 商 协 会 (NEMA) 组 成 的 联 合 委 员 会 经 过 两 年 开 发, 于 1985 年 发 布 了 ACR-NEMA1.0 1986 年 10 月 和 1988 年 1 月 又 分 别 颁 布 了 两 个 修 改 版 本 在 这 些 标 准 当 中, 解 决 了 以 下 问 题 : 统 一 数 据 格 式 和 传 输 标 准, 实 现 了 不 同 厂 家 不 同 设 备 间 数 字 化 医 疗 数 据 的 通 信 问 题 ; 提 供 了 PACS(Picture Archiving and Communication System) 系 统 与 其 它 医 学 信 息 系 统 (Hospital Information System HIS) 的 接 口 [4]; 通 过 将 医 生 诊 断 信 息 数 据 随 同 病 人 图 像 数 据 按 统 一 格 式 提 供 给 不 同 的 232

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

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

8 DICOM 标 准 的 实 现 标 准 定 义 了 大 量 不 同 的 服 务 类 (Service Class), 用 以 完 成 不 同 的 服 务 功 能 因 此, 对 于 不 断 更 新 的 医 疗 设 备 及 其 各 种 类 型 的 数 据, 可 以 通 过 修 改 或 定 义 新 的 IOD 使 得 标 准 与 之 相 适 应 并 得 以 扩 充 ; 而 对 于 新 增 的 功 能, 则 可 以 通 过 定 义 新 的 服 务 类 来 完 成 这 样 就 达 到 了 不 需 对 DICOM 标 准 的 整 体 架 构 进 行 修 改 的 情 况 下, 完 成 标 准 本 身 不 断 扩 充 和 更 新 的 目 的 也 就 是 说,DICOM 标 准 可 以 在 不 断 吸 收 新 特 性 的 同 时, 仍 能 很 好 地 保 持 与 原 先 版 本 的 兼 容 性 [1] 实 际 上,ACR-NEMA 每 年 都 会 公 布 当 年 新 修 订 的 DICOM 版 本 MITK 中 部 分 DICOM 标 准 的 实 现 正 是 基 于 DICOM 3.0 的 2003 年 版 8.1.2 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

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

8 DICOM 标 准 的 实 现 DICOM 定 义 了 自 己 的 文 件 夹 结 构, 用 以 形 成 文 件 集 合 (File-set) 此 外, 允 许 以 媒 体 存 储 特 征 (media profiles) 的 形 式 定 义 对 数 据 的 不 同 媒 体 存 储 策 略 (6) 不 同 级 别 的 一 致 性 声 明 (Conformance Statement) DICOM 标 准 要 求 一 个 实 际 的 通 信 系 统 应 该 有 一 个 一 致 性 声 明 (Conformance Statement), 用 以 说 明 此 系 统 对 DICOM 协 议 的 支 持 程 度, 以 及 支 持 哪 些 类 型 的 数 据 和 服 务 8.1.3 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

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

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

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

8 DICOM 标 准 的 实 现 DICOM 协 议 广 泛 地 支 持 现 有 的 网 络 环 境 和 技 术, 如 ISO8802-3 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

8 DICOM 标 准 的 实 现 Some Filters MITK Volume DICOMReader DICOMWriter File to image Image to file MITK Utilities DICOM Utility DICOM Files 图 8-1 MITK 中 DICOM 文 件 读 写 部 分 的 基 本 框 架 8.2.1 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

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 标 准 规 定 了 具 体 属 性 的 数 据 元 素, 其 含 义 可 以 通 过 数 据 字 典 检 索 得 到 组 号 为 0000 0002 0004 和 0006 的 数 据 元 素 为 DICOM 标 准 所 保 留, 主 要 用 于 DIMSE(DICOM Message Service Element) 命 令 以 及 DICOM 文 件 格 式 ; 私 有 数 据 元 素 : 除 (0001,eeee) (0003,eeee) (0005,eeee) (0007, eeee) 和 (FFFF,eeee) 之 外 组 号 为 奇 数 的 数 据 元 素 这 类 数 据 元 素 可 以 由 用 户 定 义 其 属 性 含 义, 属 于 扩 展 部 分, 不 编 入 数 据 字 典 通 用 的 DICOM 数 据 解 析 程 序 一 般 不 处 理 这 一 部 分 数 据 元 素 组 号 为 0001 0003 0005 0007 和 FFFF 的 数 据 元 素 同 样 为 DICOM 标 准 所 保 留 另 外, 没 一 组 的 第 一 个 元 素, 即 标 签 为 (gggg,0000) 的 数 据 元 素 是 一 个 特 殊 的 数 据 元 素, 它 记 录 了 某 个 数 据 集 中 所 有 属 于 该 组 的 数 据 元 素 的 总 长 度 (2) 数 据 类 型 (Value Representation,VR) 数 据 类 型 是 一 个 2 字 节 的 字 符 串, 表 示 该 数 据 元 素 的 数 据 域 (Value Field) 所 存 储 数 据 的 类 型 该 字 段 是 可 选 字 段 某 个 数 据 集 中 的 数 据 元 素 是 否 包 含 这 个 字 段, 由 该 数 据 集 的 传 输 语 法 (Transfer Syntax) 所 决 定, 即 若 传 输 语 法 规 定 为 隐 式 数 据 类 型 243

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, 用 字 符 串 表 示 的 实 数, 如 12.3-1.234E5 等 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

8 DICOM 标 准 的 实 现 如 123-321 等, 其 取 值 范 围 为 [-2 31, 2 31-1] LO Long String, 长 字 符 串 64 char* LT Long Text, 长 文 本 数 据 10240 char* OB Other Byte String, 编 码 方 式 由 传 输 语 法 所 规 自 定 义 char* 定 的 字 节 流 OF Other Float String,32 位 浮 点 数 组 2 32-4 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 范 围 为 000000-999999 UI Unique Identifier, 唯 一 标 识 符, 数 字 字 符 串, 中 间 以. 分 隔, 如 1.2.840.10008.1.1 16 64 char* (to time_t) char* UL Unsigned Long, 无 符 号 32 位 整 型 数 4 unsigned int 245

8 DICOM 标 准 的 实 现 (unsigned long) UN Unknown, 未 知 类 型 任 意 N/A US Unsigned Short, 无 符 号 16 位 整 型 数 2 unsigned short UT Unlimited Text, 无 限 制 文 本 2 32-2 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

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 位 整 数 987654321, 用 16 进 制 表 示 是 3ADE68B1, 则 在 小 端 序 的 方 式 下 存 储 为 B1 68 DE 3A( 存 储 单 元 地 址 从 左 往 右 递 增 ), 在 大 端 序 下 存 储 为 3A DE 68 B1 不 同 体 系 结 构 的 计 算 机 可 能 采 用 不 同 的 字 节 序 来 存 储 数 据, 比 如 IBM PC 系 列 兼 容 计 算 机 采 用 小 端 序, 而 Apple 的 Power PC 系 列 则 采 用 大 端 序 DICOM 标 准 同 时 支 持 两 种 字 节 序, 具 体 采 用 哪 种 字 节 序 由 传 输 语 法 确 定, 缺 省 采 用 小 端 247

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) 的 项 定 界 符 标 志 该 项 的 结 束, 项 定 界 符 不 包 含 任 何 数 据, 其 数 据 长 度 字 段 必 须 为 00000000H 整 个 VR 为 SQ 的 数 据 元 素 编 码 根 据 数 据 长 度 字 段 的 不 同 也 有 相 应 的 两 种 编 码 方 式 : 显 式 长 度 (Explicit Length): 项 元 素 的 数 据 长 度 字 段 包 含 数 据 域 的 长 度 ( 以 字 节 为 单 位 ), 该 长 度 为 数 据 域 中 所 有 项 的 总 长 度 ; 未 定 义 长 度 (Undefined Length): 项 元 素 的 数 据 长 度 字 段 编 码 为 FFFFFFFFH, 因 为 未 指 明 后 面 数 据 域 的 长 度, 所 以 必 须 紧 跟 一 个 标 签 为 (FFFE,E0DD) 的 序 列 定 界 符 标 志 该 序 列 的 结 束, 序 列 定 界 符 不 包 含 任 何 数 据, 其 数 据 长 度 字 段 必 须 为 00000000H 248

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) 项 长 度 0000 04F8 H 项 值 ( 数 据 集 ) 项 标 签 (FFF E,E0 00) 项 长 度 0000 04F8 H 项 值 ( 数 据 集 ) 项 标 签 (FFF E,E0 00) 项 长 度 0000 04F8 H 项 值 ( 数 据 集 ) 4 字 4 字 4 字 4 字 04F8H 4 字 4 字 04F8H 4 字 4 字 04F8H 节 节 节 节 字 节 节 节 字 节 节 节 字 节 表 8-8 带 两 个 显 式 长 度 项 的 显 式 VR 为 SQ 未 定 义 长 度 的 数 据 元 素 例 子 标 签 数 据 类 型 数 据 长 度 数 据 域 (gg gg,e SQ 00 00 FFF FFF 第 一 项 第 二 项 序 列 定 界 符 eee) VR 为 SQ H 保 留 FFH 未 定 义 长 度 (FFF E,E0 00) 项 长 度 98A 52C 项 值 ( 数 据 集 ) (FFF E,E0 00) 项 长 度 B32 1762 项 值 ( 数 据 集 ) (FFF E,E0 DD) 000 000 00H 68H CH 4 字 2 2 4 字 4 字 4 字 98A52 4 字 4 字 B3217 4 字 4 字 节 字 字 节 节 节 C68H 节 节 62CH 节 节 节 节 字 节 字 节 249

8 DICOM 标 准 的 实 现 表 8-4 带 未 定 义 长 度 项 的 隐 式 VR 为 SQ 且 未 定 义 长 度 的 数 据 元 素 例 子 标 签 数 据 长 度 数 据 域 (gg gg,e FFF FFF 第 一 项 第 二 项 序 列 定 界 符 eee) VR 为 SQ FFH 未 定 义 长 度 (FFF E,E0 00) 项 长 度 0000 17B 项 值 ( 数 据 集 ) (FFF E,E0 00) FFF FFF FFH 未 指 项 值 ( 数 据 集 ) 项 定 界 符 (FFF E,E0 000 000 00H (FFF E,E0 DD) 000 000 00H 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

8 DICOM 标 准 的 实 现 例 如, 分 配 位 数 为 24, 存 储 位 数 为 18, 高 位 为 19, 表 示 每 个 像 素 单 元 占 用 24 位 存 储 空 间, 其 中 的 18 位 存 储 了 实 际 的 像 素 采 样 值, 而 这 18 位 的 最 高 位 对 应 于 24 位 的 第 19 位, 如 8-3 所 示 除 被 像 素 采 样 值 占 用 的 存 储 位 以 外 未 使 用 的 存 储 位 可 以 另 作 它 用 High Bit = 19 23 20 19 2 1 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) 的 图 像, 其 中 记 录 了 每 一 帧 的 起 始 段 ( 项 ) 相 对 于 第 一 项 开 头 的 位 移 量, 以 此 来 区 分 多 帧 图 像 的 多 个 帧, 而 对 于 单 帧 或 只 有 一 帧 的 多 帧 图 像, 该 偏 移 表 的 数 据 域 可 为 空, 数 据 长 度 字 段 相 应 编 码 为 00000000H 而 且 在 封 装 格 式 下,(7FE0,0010) 数 据 元 素 的 数 据 长 度 必 须 是 未 定 义 的, 因 此 整 个 项 的 序 列 以 一 个 序 列 定 界 符 作 为 结 尾 ( 参 考 表 8-8 的 例 子 ) 251

8 DICOM 标 准 的 实 现 此 外, 大 多 数 的 编 码 方 式 会 将 图 像 或 像 素 格 式 的 相 关 信 息 编 码 进 最 终 的 字 节 流 中, 比 如 图 像 的 宽 高, 上 面 讲 到 的 每 个 像 素 单 元 的 分 配 位 数 存 储 位 数 等, 这 时 候 DICOM 标 准 要 求 相 关 数 据 元 素 的 值 必 须 与 编 码 进 像 素 数 据 字 节 流 的 相 关 信 息 保 持 一 致 DICOM 标 准 接 受 JPEG JPEG 2000 JPEG-LS 及 RLE 压 缩 格 式 5. 唯 一 标 识 符 (Unique Identifier,UID) 为 了 在 网 络 环 境 下 唯 一 地 标 识 各 种 信 息,DICOM 采 用 了 UID 的 方 式 UID 的 定 义 基 于 ISO8824 标 准, 并 使 用 ISO9834-3 中 所 注 册 的 值 来 保 证 全 局 唯 一 性 一 个 UID 唯 一 标 识 符 可 以 用 公 式 表 示 为 : UID=<org root>.<suffix> 其 中 org root 代 表 组 织 编 号 ( 如 制 造 商 研 究 单 位 等 ), 而 suffix 部 分 则 是 在 此 组 织 范 围 内 的 唯 一 编 号 这 两 部 分 均 由 一 串 点 号 隔 开 的 数 字 组 成, 如 <org root>=.2.840.10008 代 表 美 国 电 器 制 造 商 协 会 6. 传 输 语 法 (Transfer Syntax) 前 面 已 经 多 次 提 到 传 输 语 法, 所 谓 传 输 语 法 就 是 一 组 编 码 规 则, 用 于 无 歧 义 地 解 读 传 输 中 的 或 存 储 于 存 储 设 备 上 的 DICOM 数 据 不 同 的 传 输 语 法 用 一 组 UID 来 区 分, 常 用 的 传 输 语 法 如 表 8-10 所 示 表 8-10 常 用 传 输 语 法 UID 值 说 明 1.2.840.10008.1.2 隐 式 VR, 小 端 序 ( 缺 省 传 输 语 法 ) 1.2.840.10008.1.2.1 显 式 VR, 小 端 序 1.2.840.10008.1.2.1.99 显 式 VR, 小 端 序, Deflate 压 缩 1.2.840.10008.1.2.2 显 式 VR, 大 端 序 1.2.840.10008.1.2.4.50 JPEG Baseline 压 缩 ( 有 损 JPEG 8-bit 压 缩 的 缺 省 传 输 语 法 ) 252

8 DICOM 标 准 的 实 现 1.2.840.10008.1.2.4.51 JPEG Extended 压 缩 ( 有 损 JPEG 12-bit 压 缩 的 缺 省 传 输 语 法 ) 1.2.840.10008.1.2.4.57 JPEG 无 损 压 缩 (Non-Hierarchical) 1.2.840.10008.1.2.4.70 JPEG 无 损 压 缩 (Non-Hierarchical, First-Order Prediction,JPEG 无 损 压 缩 的 缺 省 传 输 语 法 ) 1.2.840.10008.1.2.4.80 JPEG-LS 无 损 压 缩 1.2.840.10008.1.2.4.81 JPEG-LS 有 损 压 缩 1.2.840.10008.1.2.4.90 JPEG 2000 压 缩 ( 仅 有 损 方 式 ) 1.2.840.10008.1.2.4.91 JPEG 2000 压 缩 1.2.840.10008.1.2.5 RLE 无 损 压 缩 其 中, 所 有 压 缩 编 码 方 式 均 采 用 显 式 VR 和 小 端 序 编 码 1.2.840.10008.1.2.4.51 ~ 1.2.840.10008.1.2.4.56 1.2.840.10008.1.2.4.58 ~ 1.2.840.10008.1.2.4.66 均 为 JPEG 的 各 种 压 缩 格 式, 已 标 记 为 RETIRED, 故 不 再 详 细 说 明 (2) DICOM 文 件 结 构 典 型 的 DICOM 文 件 由 两 部 分 组 成 : 文 件 元 信 息 (File Meta Information): 包 含 此 文 件 所 存 储 的 数 据 集 的 识 别 信 息, 包 括 文 件 识 别 前 缀 数 据 集 标 识 及 存 储 格 式 等 信 息 ; DICOM 数 据 集 (DICOM Data Set): 文 件 所 存 储 的 DICOM 数 据, 是 文 件 的 主 题 其 中, 文 件 元 信 息 有 可 以 分 为 以 下 一 些 部 分 : 文 件 前 同 步 码 (File Preamble): 长 128 字 节, 可 以 存 放 一 些 生 成 该 DICOM 文 件 的 应 用 程 序 的 某 些 指 定 信 息, 如 不 使 用 则 全 部 置 为 00H; 253

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 文 件 结 构 8.2.2 DICOM 文 件 读 写 模 块 (DICOM Utility) 的 实 现 如 前 所 述,MITK 中 关 于 DICOM 文 件 的 读 写 功 能 是 作 为 一 个 独 立 的 Utility 模 块 实 现 的, 该 模 块 的 框 架 如 图 8-5 所 示 254

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

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

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

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

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

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

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

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

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

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

8 DICOM 标 准 的 实 现 8.2.3 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

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

8 DICOM 标 准 的 实 现 象 中 的 所 有 切 片 的 图 像 数 据 分 别 存 储 到 一 个 个 DICOM 文 件 中 该 类 的 处 理 要 比 mitkdicomreader 简 单 的 多, 只 需 循 环 地 调 用 DcmDataProcess 的 ImageToFile() 就 可 以 输 出 所 有 切 片 图 像 到 文 件 中 目 前 MITK 中 的 mitkdicomwriter 采 用 显 式 VR 小 端 序 的 缺 省 DICOM 数 据 格 式 生 成 DICOM 文 件, 即 传 输 语 法 UID 为 1.2.840.10008.1.2.1 下 面 的 代 码 演 示 了 在 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

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

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

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),2003 2. 刘 景 春, 田 捷, 常 红 星, 曹 勇, 邱 峰.PACS 的 结 构 与 实 现. 中 国 医 学 影 像 技 术,2000, 第 16 卷 第 1 期 3. 田 捷, 包 尚 联, 周 明 全. 医 学 影 像 处 理 与 分 析. 北 京 : 电 子 工 业 出 版 社,2003. 4. 邱 峰, 田 捷, 曹 勇 等.PACS 系 统 综 述. 中 国 医 学 影 像 技 术,2000, 第 16 卷 第 1 期 5. Bidgood W.D., Horii S. Introduction to the ACR-NEMA DICOM Standard. RadioGraphics, 1992,12(2):345-355 270

9 应 用 MITK 开 发 实 际 项 目 9 应 用 MITK 开 发 实 际 项 目 在 介 绍 了 MITK 的 设 计 与 具 体 实 现 之 后, 本 章 将 以 几 个 实 例 来 说 明 如 何 应 用 MITK 来 开 发 实 际 的 项 目 受 篇 幅 所 限, 不 宜 介 绍 过 于 复 杂 的 大 型 项 目 的 实 现 细 节, 只 能 以 几 个 短 小 精 悍 的 例 子 来 说 明 MITK 的 使 用 规 范 以 及 在 使 用 过 程 中 需 要 注 意 的 一 些 问 题 读 者 可 以 将 其 作 为 使 用 MITK 开 发 实 际 项 目 的 入 门 教 程 本 章 将 以 目 前 使 用 比 较 广 泛 的 VC++6.0 作 为 集 成 开 发 环 境 MITK 开 发 包 可 以 在 http://www.3dmed.net/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

9 应 用 MITK 开 发 实 际 项 目 面 的 编 辑 框 中 输 入 Mitk_dll.lib, 如 图 9-3 所 示 图 9-1 新 建 MFC 工 程 272

9 应 用 MITK 开 发 实 际 项 目 图 9-2 设 置 MITK 头 文 件 路 径 图 9-3 设 置 MITK 库 文 件 路 径 273

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

9 应 用 MITK 开 发 实 际 项 目 图 9-4 添 加 WM_CREATE 的 消 息 处 理 函 数 图 9-5 添 加 WM_SIZE 的 消 息 处 理 函 数 275

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

9 应 用 MITK 开 发 实 际 项 目 图 9-15 在 工 具 栏 添 加 浏 览 切 片 的 按 钮 在 ClassWizard 里 给 上 述 ID 添 加 相 应 的 消 息 处 理 函 数, 如 图 9-16 所 示 图 9-16 给 按 钮 ID 添 加 消 息 处 理 函 数 然 后, 在 MITKTestView.cpp 中 添 加 相 应 代 码 实 现 浏 览 功 能 : void CMITKTestView::OnSliceFirst() 302

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

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

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

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

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;... 307

9 应 用 MITK 开 发 实 际 项 目 接 着 在 工 具 栏 添 加 一 个 按 钮, 其 功 能 就 是 对 读 入 的 Volume 数 据 进 行 表 面 重 建, 如 图 9-18 所 示 图 9-18 添 加 表 面 重 建 按 钮 为 改 按 钮 ID 添 加 消 息 处 理 函 数, 注 意, 要 添 加 在 CMITKTestDoc 类 中, 如 图 9-19 所 示 308

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

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

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

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

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

9 应 用 MITK 开 发 实 际 项 目 图 9-21 重 建 结 果 示 例 9.4 一 个 比 较 完 善 的 例 子 9.2 和 9.3 都 是 比 较 简 单 的 例 子, 功 能 比 较 单 一 在 这 一 节, 我 们 将 综 合 上 面 两 节 所 完 成 的 功 能, 作 出 一 个 比 较 完 善 的 应 用 程 序 先 来 看 一 下 完 成 后 的 整 个 应 用 程 序 的 运 行 状 况 所 图 9-22 示 是 正 在 进 行 表 面 重 建 时 的 显 示 界 面, 在 这 个 例 子 中 增 加 了 重 建 进 度 的 显 示, 如 所 示 图 9-23 所 示 是 重 建 结 束 后 的 程 序 界 面 主 窗 口 的 整 个 客 户 区 被 划 分 为 左 右 两 部 分, 右 边 比 较 大 的 显 示 区 用 来 显 示 经 过 表 面 重 建 生 成 的 三 维 模 型, 左 边 又 被 划 分 为 三 个 小 的 显 示 区, 分 别 显 示 读 入 的 Volume 数 据 X-Y Y-Z 及 Z-X 平 面 的 断 层 图 像 314

9 应 用 MITK 开 发 实 际 项 目 图 9-22 表 面 重 建 时 显 示 进 度 315

9 应 用 MITK 开 发 实 际 项 目 图 9-23 重 建 完 成 后 的 界 面 工 具 栏 中 的 按 钮 大 部 分 都 在 9.2 和 9.3 中 介 绍 过 了, 其 功 能 大 致 相 同 新 增 按 钮 的 功 能 如 下 : : 显 示 读 入 的 Volume 的 相 关 信 息 ; : 显 示 生 成 的 Mesh 的 相 关 信 息 ; : 显 示 生 成 的 Mesh 的 所 有 顶 点, 如 图 9-24 所 示 ; : 显 示 Mesh 的 线 框 模 型, 如 图 9-25 所 示 ; : 显 示 Mesh 的 表 面 模 型, 如 图 9-23 界 面 中 所 示 316

9 应 用 MITK 开 发 实 际 项 目 图 9-24 点 显 示 的 三 维 模 型 317

9 应 用 MITK 开 发 实 际 项 目 图 9-25 线 框 显 示 的 三 维 模 型 有 了 上 面 两 节 的 基 础, 本 节 将 不 再 拘 泥 于 编 程 细 节 的 介 绍, 而 把 主 要 精 力 放 在 如 何 实 现 新 增 的 功 能 上 首 先 是 主 窗 口 的 划 分 在 一 个 父 窗 口 下, 可 以 同 时 安 放 多 个 mitkview, 通 过 mitkview 的 SetLeft() SetTop() SetWidth() 和 SetHeight() 确 定 每 一 个 mitkview 在 父 窗 口 中 的 位 置 ( 以 父 窗 口 左 上 角 为 坐 标 原 点 ) 和 尺 寸 在 本 节 的 例 子 中, 一 共 在 主 窗 口 的 客 户 区 放 了 4 个 mitkview, 左 边 三 个 是 mitkimageview(mitkview 的 子 类 ), 用 来 显 示 三 个 方 向 的 断 层 图 像, 右 边 一 个 mitkview 用 来 显 示 表 面 重 建 生 成 的 三 维 模 型 完 成 这 部 分 功 能 的 代 码 主 要 集 中 在 MITKTestView.h 和 MITKTestView.cpp 中, 如 下 所 示 : 318

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

9 应 用 MITK 开 发 实 际 项 目 下 ( 均 为 inline 函 数 ): #if!defined(afx_progressdlg_h 02135212_7541_402A_9E3D_F62AF713A692 INCLUDED_) #define AFX_PROGRESSDLG_H 02135212_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

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 02135212_7541_402A_9E3D_F62AF713A692 INCLUDED_) 在 MITKTestDoc.cpp 中 添 加 代 码, 为 重 建 算 法 附 加 一 个 显 示 进 度 的 Observer: void CMITKTestDoc::OnMc() // TODO: Add your command handler code here // 产 生 阈 值 设 置 对 话 框 CThresholdDlg dlg; 336

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

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

9 应 用 MITK 开 发 实 际 项 目 以 改 进, 比 如 可 以 增 加 几 个 对 话 框 来 调 节 三 维 模 型 的 表 面 材 质 在 View 中 添 加 一 些 MITK 提 供 的 Widgets 来 丰 富 交 互 功 能 等 等, 甚 至 可 以 像 上 面 提 到 的 ChooseViewManipulator 和 FilterObserver 那 样 扩 展 MITK 实 现 自 定 义 的 功 能, 限 于 篇 幅, 这 些 内 容 不 能 一 一 介 绍 了 读 者 可 以 自 己 动 手 实 践 一 下, 用 MITK 来 开 发 自 己 的 软 件 项 目 另 外, 第 十 一 章 所 介 绍 的 3DMed 就 是 基 于 MITK 开 发 的 一 套 规 模 比 较 大 的 软 件, 读 者 可 以 以 此 作 为 参 考 9.5 小 结 本 章 介 绍 了 MITK 开 发 实 际 项 目 的 例 子, 通 过 开 发 环 境 的 设 置, 二 维 功 能 的 图 像 浏 览 器, 再 到 用 MITK 进 行 表 面 重 建, 最 后 给 出 了 一 个 完 善 的 例 子, 其 目 的 是 让 读 者 能 够 按 照 本 章 的 引 导, 一 步 步 熟 悉 MITK, 使 得 MITK 能 够 帮 助 读 者 更 好 的 理 解 MITK, 更 好 的 使 用 MITK 开 发 自 己 的 应 用 程 序 339

10 扩 充 MITK 功 能 10 扩 充 MITK 功 能 MITK 本 身 是 一 个 开 放 的 架 构, 允 许 用 户 自 己 扩 充 其 功 能, 以 满 足 项 目 开 发 中 的 需 求 在 MITK 中, 可 以 扩 充 的 部 分 包 括 Filter Reader 和 Writer, 通 过 扩 充 Filter, 可 以 添 加 自 己 的 处 理 算 法 ; 通 过 扩 充 Reader, 可 以 添 加 自 己 的 文 件 格 式 的 导 入 功 能 ; 通 过 扩 充 Writer, 可 以 添 加 自 己 的 文 件 格 式 的 保 存 功 能 要 扩 充 这 几 个 部 分, 必 须 在 实 现 自 己 的 算 法 的 时 候 遵 循 一 些 MITK 规 定 的 接 口 本 章 首 先 介 绍 这 些 接 口, 然 后 以 两 个 实 际 的 例 子 来 讲 述 如 何 扩 充 MITK 的 功 能, 这 两 个 例 子 一 个 是 如 何 扩 充 Reader 的 功 能, 另 外 一 个 是 如 何 扩 充 Filter 的 功 能, 读 者 在 阅 读 这 两 个 例 子 之 后, 可 以 对 扩 充 MITK 功 能 有 个 充 分 的 了 解, 并 可 以 在 自 己 的 项 目 中 来 使 用 和 上 一 章 的 风 格 一 样, 这 两 个 例 子 也 使 用 Microsoft Visual C++ 6.0 来 作 为 编 程 环 境, 并 且 一 步 一 步 地 加 以 说 明 10.1 扩 充 MITK 功 能 的 预 备 知 识 在 MITK 中, 是 通 过 类 的 继 承 层 次 来 实 现 不 同 的 功 能 的, 其 中 在 一 些 上 层 的 父 类 里 面 规 定 了 一 些 子 类 必 须 实 现 的 接 口, 子 类 来 实 现 这 些 接 口 从 而 完 成 具 体 的 功 能, 这 是 整 个 的 大 的 原 则 在 第 二 章 中 介 绍 了 MITK 的 整 体 框 架, 整 个 MITK 是 由 数 据 和 算 法 来 构 成 的, 而 算 法 又 是 由 三 个 不 同 的 种 类 :Source Filter 和 Target 来 构 成 的, 其 在 MITK 的 整 个 类 层 次 结 构 中 的 位 置 如 图 10-1 所 示 图 10-1 MITK 中 算 法 类 的 上 层 结 构 其 中, 在 它 们 共 同 的 父 类 ProcessObject 中, 规 定 了 一 个 公 有 的 接 口 bool Run 340

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

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

10 扩 充 MITK 功 能 mitkobjwriter mitkstlwriter 来 完 成 至 于 Filter, 在 2.2.3 节 已 经 有 比 较 详 细 的 介 绍, 这 里 就 不 再 赘 述 了 10.2 实 例 之 一 : 扩 充 Reader 功 能 好 了, 了 解 了 上 面 的 预 备 知 识 之 后, 下 面 本 节 以 一 个 实 际 的 例 子 来 演 示 如 何 在 自 己 的 项 目 里 面 来 扩 充 MITK 的 Reader 所 支 持 的 格 式, 风 格 上 和 第 九 章 的 例 子 一 样, 通 过 一 步 一 步 的 深 入 浅 出 的 讲 解, 加 上 程 序 代 码 的 辅 助, 来 力 图 把 概 念 讲 解 清 楚 10.2.1 扩 充 Reader 功 能 的 一 般 步 骤 要 读 取 一 种 MITK 不 支 持 的 文 件 格 式, 可 以 通 过 扩 充 Reader 的 类 层 次 结 构 来 实 现 扩 充 Reader 功 能 的 一 般 步 骤 如 下 : 第 一 步, 先 判 断 自 己 要 读 取 的 文 件 的 内 容 是 一 个 三 维 的 数 据 集 还 是 三 维 的 面 片 网 格, 比 如 一 系 列 的 BMP 文 件 就 是 一 个 三 维 的 数 据 集, 而 OBJ 和 STL 等 文 件 就 是 三 维 的 面 片 网 格 ; 第 二 步, 写 自 己 的 文 件 格 式 的 Reader, 如 果 要 读 取 的 文 件 是 三 维 数 据 集, 那 么 选 择 从 VolumeReader 继 承, 如 果 要 读 取 的 文 件 是 三 维 网 格, 那 么 选 择 从 MeshReader 继 承 ; 第 三 步, 实 现 虚 函 数 bool Execute(), 在 这 个 函 数 里 面 完 成 文 件 的 实 际 读 入 工 作, 并 将 数 据 填 入 Volume 或 者 Mesh 中 10.2.2 实 例 程 序 的 功 能 下 面 我 们 要 完 成 的 实 例 程 序, 将 要 读 入 一 种 我 们 自 己 定 义 的 格 式 的 文 件, 其 数 据 内 容 为 三 维 数 据 集, 这 种 文 件 有 一 个 文 件 头, 记 录 了 一 些 文 件 大 小 像 素 尺 寸 等 信 息, 然 后 是 实 际 数 据 其 具 体 文 件 格 式 如 图 10-4 所 示 343

10 扩 充 MITK 功 能 图 10-4 文 件 格 式 例 子 程 序 的 功 能 很 简 单, 就 是 打 开 上 面 格 式 的 文 件 并 将 其 读 入 内 存 中, 其 文 件 菜 单 里 面 有 一 项 打 开 自 定 义 文 件, 点 击 此 菜 单 项 后 将 会 弹 出 一 个 打 开 自 定 义 格 式 文 件 对 话 框, 如 图 10-5 所 示 选 中 要 打 开 的 文 件 以 后, 例 子 程 序 会 调 用 我 们 自 己 扩 充 的 Reader, 解 析 文 件 并 将 其 读 入 内 存, 为 了 简 化 清 晰 起 见, 这 个 例 子 并 没 有 显 示 读 进 来 的 数 据, 侧 重 点 在 如 何 扩 充 Reader 的 功 能 上, 请 参 考 8 的 例 子 去 实 现 数 据 的 显 示 等 功 能 344

10 扩 充 MITK 功 能 图 10-5 打 开 文 件 对 话 框 10.2.3 实 例 程 序 的 制 作 还 是 和 第 九 章 一 样, 我 们 通 过 具 体 的 例 子 一 步 一 步 地 展 现 如 何 扩 充 MITK 中 Reader 的 功 能 第 一 步, 是 在 Microsoft Visual C++ 6.0 的 IDE 开 发 环 境 中 新 建 一 个 MFC 工 程, 单 文 档 界 面 的, 工 程 名 字 叫 做 MitkEnhancement, 这 一 步 的 操 作 过 程 这 里 就 不 再 赘 述 了 第 二 步, 修 改 菜 单, 删 除 不 必 要 的 菜 单 项, 只 留 下 文 件 查 看 帮 助 三 个 菜 单 项, 在 文 件 菜 单 里 面 删 除 原 先 的 子 菜 单, 加 入 打 开 自 定 义 文 件 子 菜 单, 修 改 后 的 菜 单 如 图 10-6 所 示 345

10 扩 充 MITK 功 能 图 10-6 修 改 后 的 菜 单 第 三 步, 到 了 添 加 自 己 的 Reader 的 时 候 了, 用 New Class 生 成 一 个 我 们 自 己 的 类, 名 字 叫 做 CMyMitkReader, 如 图 10-7 所 示 然 后 遵 循 10.2.1 节 中 所 述 的 一 般 规 则, 因 为 我 们 要 打 开 的 文 件 是 一 个 三 维 数 据 文 件, 所 以 应 该 从 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

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

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

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

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

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

10 扩 充 MITK 功 能 10.3 实 例 之 二 : 扩 充 Filter 功 能 本 节 接 着 上 一 节 的 例 子, 在 里 面 增 加 格 式 转 换 的 功 能, 来 演 示 如 何 在 自 己 的 项 目 里 面 来 扩 充 MITK 的 Filter 的 功 能, 也 就 是 如 何 扩 充 新 的 算 法 掌 握 了 这 一 节 的 内 容 以 后, 你 就 可 以 将 自 己 的 算 法 集 成 到 MITK 中 去, 并 且 可 以 和 MITK 中 已 有 的 同 类 算 法 作 比 较, 甚 至 可 以 进 行 算 法 的 性 能 评 价 等 工 作 10.3.1 扩 充 Filter 功 能 的 一 般 步 骤 扩 充 Filter 功 能 的 步 骤 和 扩 充 Reader 的 步 骤 相 似, 也 是 通 过 扩 充 Filter 的 类 层 次 结 构 来 实 现 的 扩 充 Filter 功 能 的 一 般 步 骤 如 下 : 第 一 步, 先 判 断 自 己 要 实 现 的 算 法 的 种 类, 这 个 可 以 从 算 法 的 输 入 与 输 出 数 据 类 型 来 判 断 : 如 果 输 入 和 输 出 都 是 Volume 类 型 的, 那 么 此 算 法 属 于 VolumeToVolumeFilter, 如 果 输 入 是 Volume 类 型 的, 而 输 出 是 Mesh 类 型 的, 那 么 此 算 法 属 于 VolumeToMeshFilter, 其 余 的 依 此 类 推 ; 第 二 步, 写 自 己 的 算 法 的 Filter, 如 果 自 己 的 算 法 是 属 于 VolumeToVolumeFilter, 则 从 VolumeToVolumeFilter 公 有 继 承, 如 果 自 己 的 算 法 是 属 于 VolumeToMeshFilter, 则 从 VolumeToMeshFilter 公 有 继 承 ; 第 三 步, 实 现 虚 函 数 bool Execute(), 在 这 个 函 数 里 面 完 成 算 法 的 实 际 过 程, 也 就 是 对 输 入 数 据 的 处 理, 生 成 输 出 数 据 10.3.2 实 例 程 序 的 功 能 下 面 我 们 接 着 10.2 节 所 完 成 的 实 例 程 序, 继 续 在 其 上 增 加 功 能, 可 以 把 读 进 来 的 数 据 的 格 式 进 行 转 换 当 运 行 例 子 程 序 时, 将 会 发 现 在 菜 单 栏 上 多 了 格 式 转 换 菜 单 及 子 菜 单, 首 先 通 过 文 件 菜 单 项 下 面 的 打 开 自 定 义 文 件 打 开 一 个 文 件 后, 我 们 可 以 点 击 格 式 转 换 下 面 的 转 换 成 Double 将 读 入 的 数 据 的 格 式 转 换 成 双 精 度 浮 点 (Double) 型, 而 点 击 转 换 成 Short 将 会 把 读 入 的 数 据 的 格 式 转 换 成 短 整 型 (Short) 同 样, 为 了 简 化 清 晰 起 见, 这 个 例 子 并 没 有 显 示 格 式 转 换 后 的 数 据, 也 没 有 对 其 进 行 进 一 步 的 处 理, 这 里 的 侧 重 点 放 在 如 何 扩 充 Filter 的 功 能 上, 请 参 考 第 九 章 的 例 子 去 实 现 数 据 的 显 示 处 理 等 功 能 352

10 扩 充 MITK 功 能 10.3.3 实 例 程 序 的 制 作 和 上 一 个 例 子 一 样, 我 们 通 过 具 体 的 例 子 一 步 一 步 地 展 现 如 何 扩 充 MITK 中 Filter 的 功 能 第 一 步, 利 用 上 一 个 例 子 程 序 的 工 程 文 件 MitkEnhancement, 直 接 在 Microsoft Visual C++ 集 成 环 境 中 打 开 它 第 二 步, 修 改 菜 单, 在 菜 单 栏 上 增 加 格 式 转 换 菜 单 项, 并 且 在 其 下 增 加 两 个 子 菜 单 : 转 换 成 Double 和 转 换 成 Short, 修 改 后 的 菜 单 如 图 10-8 所 示 图 10-8 格 式 转 换 菜 单 第 三 步, 到 了 添 加 自 己 的 Filter 的 时 候 了, 用 New Class 生 成 一 个 我 们 自 己 的 类, 名 字 叫 做 CMyMitkFilter, 如 图 10-9 所 示 然 后 遵 循 10.3.1 节 中 所 述 的 一 般 规 则, 因 为 我 们 要 实 现 的 算 法 输 入 的 数 据 是 一 个 Volume, 输 出 的 数 据 是 另 外 一 个 数 据 类 型 不 同 的 Volume, 所 以 应 该 从 VolumeToVolumeFilter 继 承, 之 后 实 现 Execute 接 口 CMyMitkFilter 的 声 明 在 MyMitkFilter.h 文 件 中, 其 代 码 如 下 所 示 : class CMyMitkFilter : public mitkvolumetovolumefilter public: CMyMitkFilter(); virtual ~CMyMitkFilter(); 353

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

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

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

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

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

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

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

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

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

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

10 扩 充 MITK 功 能 这 两 个 函 数 的 实 现 大 致 相 同, 都 是 先 生 成 一 个 CMyMitkFilter 的 对 象, 然 后 设 置 输 入 数 据, 设 置 转 换 后 的 数 据 类 型, 运 行 算 法, 得 到 输 出 数 据 并 使 用, 最 后 删 除 自 己 的 Filter 对 象 这 里 为 了 简 化 起 见, 并 没 有 详 细 给 出 对 输 出 Volume 的 显 示 和 处 理, 读 者 有 兴 趣 可 以 自 行 加 上 至 此, 扩 充 Filter 功 能 的 实 例 程 序 已 经 基 本 完 成, 以 这 个 程 序 为 蓝 本, 读 者 可 以 开 发 自 己 的 算 法, 将 其 集 成 到 MITK 层 次 结 构 中 10.4 小 结 本 章 以 实 例 来 描 述 如 何 扩 充 MITK 的 功 能, 正 如 我 们 一 开 始 就 追 求 的 目 标, MITK 的 架 构 是 开 放 式 的, 用 户 可 以 方 便 地 往 里 面 扩 充 自 己 的 算 法, 从 而 不 断 地 增 强 MITK 的 功 能 本 章 首 先 以 一 个 读 入 自 定 义 格 式 的 文 件 为 例 子, 来 讲 解 如 何 扩 充 MITK 中 的 Reader 功 能, 从 而 支 持 更 多 的 数 据 格 式, 并 且 这 个 例 子 的 概 念 可 以 完 全 应 用 到 对 Writer 的 扩 充 中 去 本 章 的 第 二 个 例 子 以 对 数 据 进 行 格 式 转 换 为 例, 来 讲 解 如 何 扩 充 MITK 中 的 Filter 功 能, 也 是 最 核 心 的 功 能, 因 为 所 有 的 算 法 都 要 通 过 Filter 来 实 现 扩 充 Filter 功 能 也 可 以 允 许 不 同 的 算 法 在 同 一 个 框 架 下 实 现, 从 而 进 行 算 法 的 评 价 工 作 364

11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 11.1 背 景 介 绍 前 面 的 章 节 介 绍 了 关 于 MITK 的 设 计 框 架 以 及 各 个 子 模 块 的 功 能 以 及 实 现 细 节 我 们 已 经 知 道,MITK 是 一 个 软 件 开 发 包, 它 的 功 能 类 似 于 VTK 和 ITK, 可 以 用 来 进 行 二 次 开 发 而 本 章 要 介 绍 的 则 是 一 个 医 学 影 像 处 理 与 分 析 系 统, 主 要 面 对 医 生, 为 其 提 供 直 观 易 用 的 辅 助 工 具 来 进 行 更 准 确 的 医 疗 诊 断, 同 时 也 可 以 应 用 于 远 程 医 疗 以 及 医 疗 教 学 中 本 章 主 要 介 绍 我 们 自 主 开 发 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed(3D Medical Image Processing and Analyzing System), 它 的 设 计 思 想 主 要 功 能 以 及 一 些 实 现 细 节 11.2 相 关 工 作 在 国 际 上 有 很 多 医 学 影 像 处 理 与 分 析 的 应 用 系 统, 包 括 商 业 软 件 和 科 研 软 件, 这 里 的 科 研 软 件 指 的 是 软 件 的 目 的 是 为 了 提 供 给 科 研 人 员 或 者 医 疗 人 员 进 行 研 究 和 开 发 使 用, 而 不 是 为 了 纯 粹 的 商 业 目 的, 但 它 不 一 定 是 免 费 软 件, 这 将 在 下 面 介 绍 由 于 3DMed 的 目 的 是 形 成 一 个 科 研 软 件, 所 以 这 里 只 介 绍 相 关 的 国 际 上 的 工 作, 对 于 纯 粹 的 商 业 软 件 并 不 涉 及 11.2.1 3DVIEWNIX 系 统 简 介 3DVIEWNIX 系 统 是 由 美 国 宾 州 大 学 放 射 系 医 学 影 像 处 理 小 组 开 发 的, 提 供 了 医 学 影 像 预 处 理 二 维 和 三 维 可 视 化 图 像 分 析 等 功 能, 它 是 使 用 C 语 言 在 Unix 下 开 发 的, 利 用 X-Window 提 供 用 户 界 面 [5] 3DVIEWNIX 系 统 的 特 色 之 处 就 是 提 供 了 很 多 图 像 分 割 工 具, 包 括 域 值 分 割 基 于 模 糊 连 接 度 的 分 割 Livewire 分 割 等 等, 这 些 工 具 简 化 了 用 户 对 图 像 分 割 的 工 作 量, 非 常 有 价 值 由 于 3DVIEWNIX 开 发 的 比 较 早, 在 上 世 纪 80 年 代 就 已 经 推 出, 所 以 是 国 际 上 相 当 知 名 的 一 个 系 统 但 是 该 软 件 系 统 并 不 是 一 个 免 费 软 件, 甚 至 对 科 研 目 的 和 教 学 目 的 也 不 免 费 提 供, 另 外 加 上 其 只 能 在 Unix 环 境 下 运 行, 用 户 界 面 比 较 复 杂, 所 以 应 用 范 围 收 到 限 制 另 外, 它 现 在 公 开 发 行 的 最 新 版 本 是 1.4, 更 新 比 较 缓 慢, 有 很 多 最 近 国 际 上 同 类 系 统 比 较 流 行 的 一 些 新 的 特 性 都 没 有 吸 收 进 去, 365

11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 显 得 稍 微 有 一 点 过 时 11.2.2 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) 11.3.1 3DMed 的 设 计 目 标 对 于 软 件 设 计, 尤 其 是 医 学 影 像 这 一 特 定 领 域 内 的 复 杂 软 件 设 计, 必 须 事 先 有 一 个 非 常 明 确 的 设 计 目 标 3DMed 从 一 开 始 设 计, 就 始 终 追 求 以 下 几 个 高 层 的 设 计 目 标 : (1) 支 持 跨 平 台 这 一 目 标 是 考 虑 到 3DMed 的 潜 在 用 户 分 为 两 类, 一 类 是 研 究 和 开 发 人 员, 另 外 一 类 是 普 通 用 户 他 们 在 对 操 作 系 统 的 选 择 上 有 着 不 同 的 倾 向, 为 了 使 366

11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 3DMed 能 够 得 到 最 广 泛 的 应 用, 支 持 跨 平 台 是 非 常 重 要 的 一 个 环 节 为 了 实 现 这 一 目 标,3DMed 的 设 计 是 完 全 分 成 模 块 来 进 行 的, 它 的 整 个 的 结 构 如 图 11-1 所 示 其 中 3DMed 是 建 立 在 两 个 开 发 包 的 基 础 之 上, 一 个 是 我 们 自 己 开 发 的 医 学 影 像 处 理 与 分 析 开 发 包 MITK, 它 负 责 提 供 所 有 的 图 像 处 理 可 视 化 分 割 配 准 等 核 心 算 法, 并 且 我 们 在 设 计 MITK 的 时 候, 目 标 之 一 就 是 实 现 跨 平 台 的 支 持 ; 另 外 一 个 是 用 户 界 面 开 发 包, 为 了 能 够 让 3DMed 跨 平 台 运 行, 这 个 用 户 界 面 开 发 包 也 必 须 能 够 跨 平 台, 幸 运 的 是, 现 在 有 很 多 这 样 的 支 持 Windows Linux 等 多 个 操 作 系 统 的 用 户 界 面 开 发 包 可 以 免 费 得 到 建 立 在 这 两 个 跨 平 台 的 开 发 包 的 基 础 之 上, 整 个 3DMed 的 代 码 也 是 全 部 使 用 ANSI C++ 编 写, 没 有 使 用 任 何 编 译 器 提 供 的 特 殊 关 键 字 或 者 特 殊 函 数, 因 此 3DMed 可 以 很 自 然 地 在 多 个 操 作 系 统 下 运 行 图 11-1 3DMed 的 结 构 图 (2) 强 大 的 可 扩 充 性 前 面 已 经 提 到 过,3DMed 是 属 于 科 研 软 件 性 质 的, 所 以 其 一 个 最 重 要 的 设 计 目 标 就 是 可 扩 充 性, 允 许 第 三 方 的 科 研 机 构 或 者 开 发 人 员 将 自 己 的 功 能 集 成 到 3DMed 中 去 为 此 3DMed 里 面 提 供 了 一 个 灵 活 的 基 于 Plugin 的 框 架, 所 有 3DMed 的 主 要 功 能, 包 括 分 割 可 视 化 等, 都 是 通 过 一 个 一 个 的 Plugin 来 实 现 的, 这 些 Plugin 在 运 行 时 被 动 态 加 载, 因 此 用 户 可 以 按 照 3DMed 事 先 定 义 好 的 Plguin 的 规 范, 来 开 发 自 己 的 Plugin, 并 放 入 特 定 的 目 录, 这 样 3DMed 就 能 367

11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 动 态 地 将 其 加 载 进 来 (3) 易 于 获 取 根 据 我 们 对 先 前 版 本 的 3DMed 的 一 定 范 围 的 免 费 发 行 所 取 得 的 经 验, 为 了 使 3DMed 能 够 得 到 最 广 泛 的 应 用, 必 须 使 其 能 够 很 容 易 地 被 用 户 获 取 现 在 我 们 已 经 将 3DMed 作 为 免 费 软 件 (Freeware) 发 行, 并 且 直 接 放 在 Internet 网 络 上 供 国 内 外 相 关 人 员 免 费 下 载, 除 了 不 能 用 于 商 业 目 的 和 必 须 保 留 版 权 信 息 等 一 些 条 件 外, 用 户 可 以 完 全 免 费 地 在 自 己 的 科 研 工 作 中 使 用 3DMed 11.3.2 3DMed 提 供 的 功 能 简 介 图 11-2 给 出 了 3DMed 所 提 供 的 基 本 的 功 能, 其 中 二 维 操 作 虚 拟 切 割 和 三 维 测 量 等 功 能 是 在 3DMed 的 核 心 中 实 现 的, 相 对 固 定, 同 时 为 了 形 成 一 个 相 对 完 整 的 版 本, 核 心 里 面 也 实 现 了 基 本 的 表 面 绘 制 和 体 绘 制 功 能 ; 而 医 学 影 像 数 据 I/O 医 学 影 像 分 割 医 学 影 像 配 准 表 面 绘 制 和 体 绘 制 等 功 能 是 由 Plugins 动 态 加 载 进 来 的, 也 就 意 味 着 这 一 部 分 的 功 能 是 可 以 根 据 需 要 来 添 加 的, 是 动 态 部 分 下 面 分 别 对 每 一 个 功 能 作 简 单 的 介 绍 图 11-2 3DMed 提 供 的 功 能 列 表 (1) 医 学 影 像 数 据 I/O 这 一 部 分 的 功 能 主 要 是 提 供 对 多 种 医 学 影 像 数 据 格 式 的 支 持, 为 了 能 够 处 理 368

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

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

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 框 架 的 三 个 组 成 部 分 11.4.1 Plugin SDK 的 实 现 Plugin SDK 在 整 个 Plugin 框 架 中 起 着 至 关 重 要 的 作 用, 它 不 仅 为 系 统 核 心 提 供 Plugin 的 调 用 接 口, 同 时 也 为 Plugin 的 开 发 者 ( 可 以 是 使 用 者 ) 规 定 了 撰 写 Plugin 所 必 须 遵 循 的 接 口 要 想 使 第 三 方 开 发 者 能 够 写 出 自 己 的 Plugin, 那 么 必 须 把 3DMed 的 内 部 数 据 通 过 一 个 接 口 暴 露 给 外 部 在 3DMed 中, 使 用 的 数 371

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

11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 图 11-4 Plugin SDK 的 类 层 次 结 构 图 11.4.2 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; 11.4.3 3DMed Kernel 的 实 现 系 统 核 心 的 主 要 作 用 是 在 运 行 时 动 态 加 载 各 个 类 型 的 Plugins, 并 根 据 类 型, 动 态 生 成 菜 单 项, 在 使 用 者 点 击 菜 单 项 时 调 用 对 应 的 Plugin 功 能, 并 且 在 系 统 373

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

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

11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 11.5 应 用 实 例 图 11-6 给 出 了 3DMed 的 主 界 面, 左 边 是 参 数 调 节 区 域, 右 边 上 半 部 分 是 三 维 显 示 区 域, 下 半 部 分 是 二 维 显 示 区 域 三 维 显 示 给 出 了 人 体 大 脑 的 表 面 绘 制, 二 维 显 示 给 出 了 三 个 断 面 上 的 图 像, 同 时 一 部 分 给 以 伪 彩 显 示 图 11-6 3DMed 的 主 界 面 376

11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 图 11-7 肿 瘤 分 割 的 应 用 实 例 图 11-8 配 准 的 应 用 实 例 图 11-9 可 视 化 的 应 用 实 例 图 11-7 给 出 了 一 个 肿 瘤 的 分 割 实 例, 左 边 的 图 像 是 原 始 的 切 片, 中 间 的 图 像 是 分 割 后 的 结 果, 肿 瘤 以 红 色 部 分 显 示, 右 边 的 图 像 是 三 维 重 建 后 的 显 示 结 果 377

11 基 于 MITK 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed 的 设 计 与 实 现 图 11-8 给 出 了 CT 图 像 和 MR 图 像 配 准 的 实 例, 左 边 的 图 像 是 原 始 的 CT 图 像, 中 间 的 图 像 是 原 始 的 MR 图 像, 右 边 的 图 像 是 配 准 并 叠 加 在 一 起 的 图 像 图 11-9 给 出 了 三 个 可 视 化 的 例 子, 左 边 的 图 像 是 对 骨 骼 进 行 面 绘 制 得 到 的 结 果, 中 间 的 图 像 是 对 骨 骼 和 皮 肤 进 行 多 层 面 绘 制 得 出 的 结 果, 右 边 的 图 像 是 体 绘 制 并 经 过 切 割 后 得 到 的 结 果 11.6 小 结 本 书 介 绍 了 我 们 在 前 期 工 作 积 累 的 基 础 上 开 发 的 新 版 的 三 维 医 学 影 像 处 理 与 分 析 系 统 3DMed, 目 前 我 们 已 经 完 成 一 个 初 步 的 版 本, 为 了 更 大 限 度 地 普 及 3DMed 的 用 户, 现 在 3DMed 已 经 作 为 免 费 软 件 发 行 任 何 人 都 可 以 在 http://www.3dmed.net/mitk 免 费 下 载 并 使 用 3DMed, 同 时 在 这 个 网 址 还 可 以 免 费 下 载 我 们 开 发 的 底 层 算 法 包 MITK 在 将 来 的 工 作 中, 我 们 将 不 断 地 收 集 用 户 的 反 馈 信 息, 持 续 地 改 善 3DMed 另 外, 我 们 还 将 不 断 地 往 3DMed 里 面 加 入 更 多 的 Plugins, 如 果 第 三 方 开 发 的 Plugins 达 到 一 定 的 标 准 以 后, 也 可 以 加 进 3DMed 的 发 行 版 本 当 中, 通 过 这 一 手 段 来 不 断 地 补 充 和 丰 富 它 的 功 能 378

12 开 发 3DMed 的 Plugin 12 开 发 3DMed 的 Plugin 上 一 章 介 绍 了 3DMed 的 整 体 功 能 和 Plugin 框 架, 从 中 我 们 可 以 看 到 3DMed 也 是 一 个 开 放 的 架 构, 允 许 用 户 自 己 通 过 撰 写 Plugin 来 扩 充 其 功 能, 以 满 足 实 际 使 用 中 的 需 求 本 章 介 绍 如 何 开 发 3DMed 的 Plugin, 首 先 给 出 总 体 介 绍, 然 后 通 过 两 个 实 际 的 例 子, 以 Microsoft Visual C++ 6.0 来 作 为 编 程 环 境, 一 步 一 步 地 演 示 如 何 撰 写 自 己 的 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

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

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

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

12 开 发 3DMed 的 Plugin 纯 粹 加 入 自 己 的 算 法, 如 果 使 用 MITK 的 话, 那 么 开 发 Plugin 时 还 需 要 MITK 的 头 文 件 以 及 相 应 的 库 文 件 下 面 将 用 两 个 具 体 的 实 例 来 一 步 一 步 地 演 示 3DMed Plugin 的 开 发, 其 中 第 一 个 实 例 使 用 MITK, 而 第 二 个 实 例 不 使 用 MITK, 力 图 将 不 同 的 情 况 以 及 一 些 基 本 的 概 念 阐 明 清 楚 12.2 Plugin 实 例 : 使 用 MITK 本 实 例 使 用 Microsoft Visual C++ 6.0 作 为 工 具, 从 如 何 建 立 工 程, 到 修 改 工 程 的 设 置, 以 及 具 体 的 编 程 实 现, 一 直 到 最 后 集 成 进 3DMed, 给 出 了 一 个 完 整 的 例 子, 演 示 如 何 在 使 用 MITK 的 情 况 下 开 发 3DMed 的 Plugin 为 了 清 晰 和 简 化 起 见, 这 个 Plugin 的 功 能 比 较 简 单, 只 是 读 入 一 系 列 BMP 格 式 的 文 件, 并 送 给 3DMed 处 理 12.2.1 工 程 的 建 立 及 设 置 在 Microsoft Visual C++ 6.0 的 IDE 环 境 下, 新 建 一 个 工 程 IOPlugin, 工 程 的 类 型 选 择 MFC AppWizard(dll) ( 如 图 12-1 所 示 ), 因 为 我 们 要 使 用 MFC 作 GUI 图 形 界 面, 并 且 目 标 是 生 成 一 个 动 态 链 接 库 在 接 下 来 的 一 步 中, 选 择 DLL 类 型 为 Regular DLL using shared MFC DLL, 如 图 12-2 所 示 此 时 可 以 选 择 Finish 按 钮 完 成 工 程 的 创 建 383

12 开 发 3DMed 的 Plugin 图 12-1 新 建 IOPlugin 工 程 图 12-2 Dll 类 型 选 择 384

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

12 开 发 3DMed 的 Plugin 图 12-4 设 置 必 要 的 库 文 件 路 径 12.2.2 实 例 制 作 好 了, 设 置 好 烦 琐 的 工 程 选 项 以 后, 就 可 以 进 入 实 质 部 分 了 下 面 创 建 我 们 的 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

12 开 发 3DMed 的 Plugin ; 当 然, 在 其 前 面 应 该 包 含 PluginsSDK 中 的 medplugin.h 文 件, 里 面 定 义 了 medvolumeimportplugin, 包 含 语 句 如 下 : #include "medplugin.h" 图 12-5 创 建 CMyIOPlugin 类 整 个 Plugin 的 功 能 都 在 最 重 要 的 Show 函 数 里 面 完 成, 其 提 供 一 个 打 开 文 件 387

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

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" 12.2.3 插 入 到 3DMed 经 过 了 上 面 的 步 骤 以 后, 现 在 可 以 编 译 整 个 工 程, 得 到 IOPlugin.dll 文 件, 并 将 其 拷 贝 到 3DMed 的 安 装 目 录 下 的 Plugins 子 目 录 里 面, 这 时 运 行 3DMed 主 程 序, 3DMed 加 载 完 所 有 的 插 件 以 后, 我 们 点 击 文 件 菜 单 下 的 加 载 体 数 据 菜 389

12 开 发 3DMed 的 Plugin 单 时, 就 会 发 现 我 们 的 My BMP Plugin 也 在 其 中, 如 图 12-6 所 示 当 点 击 My BMP Plugin 后, 就 会 有 打 开 对 话 框 弹 出 来, 让 用 户 选 择 BMP 文 件, 如 所 示 选 择 完 以 后 这 些 数 据 就 会 在 3DMed 中 打 开 并 显 示, 一 切 都 如 我 们 想 像 的 一 样 图 12-6 成 功 加 载 的 IOPlugin 图 12-7 IOPlugin 运 行 界 面 390

12 开 发 3DMed 的 Plugin 12.3 Plugin 实 例 : 不 使 用 MITK 在 有 些 情 况 下, 用 户 只 对 3DMed 感 兴 趣, 而 并 不 想 学 习 并 使 用 MITK, 那 么 照 样 可 以 撰 写 3DMed 的 Plugin 本 节 的 实 例 就 是 演 示 如 何 在 没 有 MITK 的 情 况 下, 来 编 写 有 效 的 Plugin 并 集 成 到 3DMed 中 去 本 实 例 仍 然 使 用 Microsoft Visual C++ 6.0 作 为 工 具, 从 如 何 建 立 工 程, 到 修 改 工 程 的 设 置, 以 及 具 体 的 编 程 实 现, 一 直 到 最 后 集 成 进 3DMed, 给 出 了 完 整 的 过 程 为 了 清 晰 和 简 化 起 见, 这 个 Plugin 的 功 能 比 较 简 单, 实 现 了 对 任 意 数 据 类 型 的 Volume 数 据 的 域 值 分 割 算 法, 支 持 设 置 高 域 值 和 低 域 值, 提 供 了 一 个 简 单 的 对 话 框 来 设 置 参 数 12.3.1 工 程 的 建 立 及 设 置 在 Microsoft Visual C++ 6.0 的 IDE 环 境 下, 新 建 一 个 工 程 SegPlugin, 工 程 的 类 型 选 择 MFC AppWizard(dll) ( 这 里 不 再 贴 图, 请 参 看 12.2.1 节 中 的 相 关 截 图 ), 因 为 我 们 要 使 用 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

12 开 发 3DMed 的 Plugin 图 12-8 设 置 必 要 的 头 文 件 路 径 图 12-9 设 置 必 要 的 库 文 件 路 径 392

12 开 发 3DMed 的 Plugin 12.3.2 实 例 制 作 设 置 好 烦 琐 的 工 程 选 项 以 后, 就 可 以 进 入 实 质 部 分 了 下 面 创 建 我 们 的 Plugin 的 类, 用 New Class 创 建 一 个 新 类, 并 将 其 命 名 为 CMySegPlugin, 如 图 12-10 所 示 因 为 我 们 的 Plugin 的 功 能 是 进 行 域 值 分 割, 所 以 很 显 然 是 属 于 SegmentationPlugin, 故 CMySegPlugin 应 该 从 SegmentationPlugin 公 有 继 承, 并 应 该 重 载 Show 函 数, 其 类 声 明 如 下 所 示 : class CMySegPlugin : public medsegmentationplugin public: CMySegPlugin(); virtual ~CMySegPlugin(); virtual bool Show(void); private: bool dosegmentation(float lowthre, float highthre); ; 图 12-10 创 建 CMySegPlugin 类 393

12 开 发 3DMed 的 Plugin 其 中 的 私 有 成 员 函 数 dosegmentation 用 来 完 成 实 际 的 分 割 工 作, 其 在 Show 函 数 里 面 被 调 用 下 面 需 要 作 的 是 GUI 的 图 形 界 面 工 作 了, 我 们 需 要 制 作 一 个 对 话 框, 在 ResourceView 面 板 上 点 右 键, 插 入 一 个 新 的 对 话 框 资 源, 在 Dialog Properties 对 话 框 中 将 其 ID 设 置 为 IDD_DIALOG_PARAMETER,Caption 设 置 为 设 置 高 低 域 值, 如 图 12-11 所 示 然 后 在 对 话 框 上 放 置 两 个 静 态 文 本 控 件 两 个 编 辑 框 控 件, 和 确 定 取 消 按 钮, 其 界 面 如 图 12-12 所 示 图 12-11 对 话 框 属 性 设 置 图 12-12 对 话 框 界 面 394

12 开 发 3DMed 的 Plugin 界 面 创 建 完 以 后, 使 用 ClassWizard 为 这 个 对 话 框 创 建 一 个 新 类, 类 的 名 字 叫 CDialogParameter, 如 图 12-13 所 示 然 后 再 使 用 ClassWizard 对 话 框 中 的 Member Variables 标 签 页, 将 对 话 框 中 的 两 个 编 辑 框 设 置 成 CDialogPapameter 类 的 成 员 变 量, 类 型 均 为 float 型, 分 别 如 图 12-14 和 图 12-15 所 示 图 12-13 创 建 CDialogParameter 类 图 12-14 添 加 低 域 值 成 员 变 量 395

12 开 发 3DMed 的 Plugin 图 12-15 添 加 高 域 值 成 员 变 量 最 后 剩 下 的 是 添 加 成 员 函 数 来 读 取 和 设 置 高 低 域 值 这 两 个 成 员 变 量 了, 分 别 是 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

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

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

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

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

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

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" 12.3.3 插 入 到 3DMed 经 过 了 上 面 的 步 骤 以 后, 现 在 可 以 编 译 整 个 工 程, 得 到 SegPlugin.dll 文 件, 并 将 其 拷 贝 到 3DMed 的 安 装 目 录 下 的 Plugins 子 目 录 里 面, 这 时 运 行 3DMed 主 程 序,3DMed 加 载 完 所 有 的 插 件 以 后, 我 们 点 击 分 割 算 法 菜 单 时, 就 会 发 现 402

12 开 发 3DMed 的 Plugin 我 们 的 My Segmentation Plugin 也 在 其 中, 如 图 12-16 所 示 当 点 击 My Segmentation Plugin 后, 就 会 弹 出 设 置 高 低 域 值 对 话 框, 让 用 户 设 置 参 数, 如 图 12-17 所 示, 设 置 完 并 点 确 定 按 钮 以 后,3DMed 将 利 用 分 割 后 的 数 据 进 行 三 维 重 建 并 显 示 图 12-16 成 功 加 载 的 SegPlugin 图 12-17 SegPlugin 的 运 行 界 面 403

12 开 发 3DMed 的 Plugin 12.4 小 结 本 章 给 出 了 如 何 开 发 3DMed 的 Plugin, 从 而 扩 充 3DMed 的 功 能 以 满 足 自 己 的 实 际 使 用 需 求 开 发 3DMed Plugin 有 两 种 途 径, 一 种 是 使 用 MITK 作 为 底 层 算 法 库, 这 种 方 式 可 以 充 分 利 用 MITK 提 供 的 功 能 和 各 种 算 法, 比 较 简 单 ; 另 外 一 种 不 使 用 MITK, 直 接 从 底 层 开 始 写 自 己 的 算 法, 这 种 适 用 于 在 用 户 手 上 没 有 MITK 的 情 况 下 扩 充 3DMed 的 功 能, 这 种 方 式 要 处 理 一 些 底 层 细 节, 因 此 相 对 于 第 一 种 方 法 来 说, 稍 微 复 杂 一 点 本 章 的 两 个 例 子 分 别 演 示 了 在 这 两 种 情 况 下 如 何 撰 写 自 己 的 3DMed Plugin 并 将 其 集 成 到 3DMed 中, 这 两 个 例 子 也 很 好 地 说 明 了 3DMed 对 这 两 种 方 法 都 支 持 的 很 好 用 户 可 以 根 据 自 己 的 实 际 情 况, 根 据 本 章 提 供 的 例 子, 来 编 写 3DMed 的 Plugin, 从 而 不 断 地 提 升 3DMed 的 功 能 404

405

附 录 A 医 学 影 像 数 据 集 1. http://www.volren.org/, 提 供 很 多 断 层 成 像 数 据 集 2. http://radiology.uiowa.edu/downloads/, 爱 荷 华 大 学 放 射 系 提 供 的 多 套 三 维 医 学 影 像 数 据 集 3. http://graphics.stanford.edu/data/voldata/, 斯 坦 福 大 学 图 形 学 实 验 室 提 供 的 一 些 三 维 数 据 集 4. http://www.nlm.nih.gov/research/visible/visible_human.html, 美 国 虚 拟 人 体 数 据 集 5. http://www.psychology.nottingham.ac.uk/staff/cr1/ct.zip, 头 部 CT 数 据 6. http://www.psychology.nottingham.ac.uk/staff/cr1/mricro.html,mricro 软 件 自 带 的 标 准 的 脑 部 数 据 406

附 录 B MITK 网 站 介 绍 一 如 何 获 取 MITK 和 3DMed? 登 录 http://www.mitk.net, 在 Download 页 面 (http://www.mitk.net/download.htm) 下, 点 击 你 所 需 下 载 的 项 目, 然 后 会 弹 出 一 个 页 面 ( 如 下 图 所 示 ), 要 求 输 入 一 些 必 要 的 用 户 信 息, 包 括 姓 名 (Name) 单 位 (Company/Organization) 使 用 目 的 (Purpose) 电 子 邮 件 地 址 (Email address) 电 话 号 码 (Phone Number), 其 中 前 4 项 是 必 填 项 下 面 是 一 个 询 问 是 否 加 入 邮 件 通 讯 列 表 的 选 择 项, 如 果 选 择 加 入, 就 可 以 在 第 一 时 间 收 到 我 们 的 更 新 信 息 最 后 在 接 受 许 可 协 议 ( I will accept the license! ) 前 打 勾 并 按 Submit 按 钮 提 交 注 册 单 就 可 以 正 常 下 载 了 必 须 提 醒 一 句, 目 前 MITK 和 3DMed 只 是 对 教 育 和 研 究 目 的 免 费 发 放, 切 勿 在 未 经 许 可 的 情 况 下 用 于 商 业 目 的, 违 者 必 究! 二 如 何 得 到 技 术 支 持? 由 于 MITK 和 3DMed 均 为 免 费 软 件, 所 以 我 们 通 过 Web 方 式 提 供 技 术 支 持 我 们 设 置 了 一 个 MITK 论 坛, 对 用 户 的 疑 问 意 见 以 及 建 议 作 出 回 应, 并 不 定 期 地 发 布 一 些 教 程 和 更 新 信 息, 以 满 足 用 户 需 求 同 时 MITK 论 坛 还 设 置 了 许 多 技 术 交 流 的 版 块, 为 MITK 和 3DMed 的 用 户 以 及 医 学 影 像 处 理 领 域 的 研 究 人 员 提 供 一 个 理 论 和 技 术 交 流 的 场 所, 并 希 望 以 此 来 推 动 MITK 和 3DMed 的 进 一 步 发 展 目 前 MITK 论 坛 对 普 通 用 户 也 是 开 放 的, 不 用 注 册 即 可 在 论 坛 提 问 或 发 表 看 法, 但 是 注 册 会 员 比 普 通 用 户 享 有 更 多 的 权 利, 比 如 即 时 从 论 坛 得 到 一 些 重 要 信 息 的 邮 件 通 知 和 获 得 以 附 件 方 式 提 供 的 更 新 程 序 等 407

论 坛 的 网 址 是 http://www.mitk.net/forum/ 三 如 何 成 为 MITK 论 坛 的 注 册 会 员? 登 录 http://www.mitk.net/forum/, 进 入 注 册 页 面, 填 写 一 些 必 要 信 息 ( 如 下 图 所 示 ), 其 中, 必 填 项 中 会 员 名 最 长 为 25 个 字 符 ( 中 文 每 字 按 2 字 符 记 ), 密 码 最 长 为 32 个 字 符 填 完 后 按 注 册 信 息 表 下 方 的 提 交 按 钮, 若 会 员 名 和 电 子 邮 件 地 址 未 与 已 注 册 会 员 重 复, 即 可 完 成 注 册, 否 则, 请 更 换 会 员 名 或 电 子 邮 件 地 址 重 新 注 册 四 如 何 在 论 坛 上 获 得 帮 助? 在 使 用 MITK 和 3DMed 软 件 的 过 程 中 遇 到 任 何 问 题 都 可 以 到 MITK 论 坛 上 寻 求 帮 助, 如 何 快 速 而 有 效 的 获 得 答 案 或 帮 助, 与 提 问 的 方 式 有 很 大 关 系 首 先, 我 们 建 议 您 在 提 问 之 前 先 尝 试 在 我 们 提 供 的 手 册 和 帮 助 文 档 中 寻 找 答 案, 这 些 资 源 包 含 在 MITK 网 站 的 Documentation 页 面 (http://www.mitk.net/document.htm) 和 您 所 下 载 的 软 件 包 中 如 果 您 所 提 的 问 题 可 以 很 容 易 的 在 这 些 文 档 中 找 到 答 案, 您 多 半 也 只 会 得 到 参 见 手 册 ( 或 文 档 ) 之 类 的 回 答 ; 其 次, 在 所 提 的 问 题 中 应 尽 可 能 将 您 所 遇 到 的 问 题 描 述 清 楚, 比 如 当 您 运 行 程 序 时 遇 到 异 常 退 出 的 情 况 ( 通 常 由 程 序 的 bug 引 起 ) 而 自 己 又 无 法 解 决 时, 您 在 所 提 的 问 题 中 应 当 尽 量 包 括 如 下 一 些 内 容 : (1) 出 错 情 况 的 描 述, 包 括 出 错 的 提 示 信 息, 当 时 程 序 的 运 行 参 数 等 ; (2) 出 错 程 序 所 读 取 的 外 部 数 据 的 信 息, 包 括 数 据 来 源 格 式 基 本 参 数 等, 最 好 能 提 供 原 始 数 据 文 件 ; (3) 出 错 程 序 的 运 行 环 境, 主 要 包 括 操 作 系 统 环 境 及 计 算 机 的 硬 件 配 置 等 另 外, 如 果 是 3DMed 运 行 时 的 错 误, 提 供 一 张 出 错 界 面 的 截 图 也 是 一 个 不 错 的 选 择 ; 408

第 三, 寻 找 合 适 的 版 面 提 出 自 己 的 问 题, 比 如 将 上 述 关 于 程 序 出 错 的 问 题 发 到 Bug Report 版, 而 将 关 于 MITK 中 某 个 类 使 用 方 法 的 疑 问 发 到 MITK 版 我 们 在 论 坛 设 立 了 许 多 主 题 版 面, 包 括 一 些 技 术 交 流 版 面, 找 到 合 适 的 板 面 提 出 你 的 疑 问 可 以 确 保 您 在 尽 量 短 的 时 间 内 得 到 回 应 同 时, 我 们 也 欢 迎 您 在 MITK 论 坛 发 表 您 的 意 见 建 议 心 得 体 会 以 及 交 流 一 些 与 医 学 影 像 处 理 相 关 的 技 术 问 题 等, 希 望 这 个 论 坛 能 成 为 一 个 我 们 共 同 拥 有 的 技 术 交 流 园 地 409