体数据渐进式加载
体数据视口的交错解码
对体数据视口来说,我们大多是在渲染实际体数据的重建视图(MPR), 因此理想情形是尽快拿到该体数据最初的那些影像(即便是有损的), 以免渲染出一整块灰色的体数据。这一点可以通过交错取回请求来实现。
影像交错对体数据的任何编码方式都适用。也就是说,先取每第 N 张影像, 就能显示出一个频率为 1/N 的影像。交错代码随后只需把这些影像复制到 缺失的位置上,就得到一个纵向低分辨率的结果。
这种交错还可以与任何「离散取回某张影像的有损版本」的方式结合—— 也就是那种非流式的解码方式,一次请求整体返回。
性能
在体数据视口上使用渐进式加载所带来的性能提升, 会随数据规模和 DICOMweb 服务端组件的能力有相当大的差异。
注意下面这些时间都不包含加载解码器的时间,那只在首次渲染时出现, 而且两种方式在这一点上是相近的。
| 类型 | 数据量 | 网络 | 首帧渲染 | 完成 |
|---|---|---|---|---|
| HTJ2K 流式 | 33 M | 4g | 2503 ms | 8817 ms |
| HTJ2K 字节范围 | 33 M | 4g | 1002 ms | 8813 ms |
HTJ2K 字节范围比纯 JLS 略慢一点,但它可以在任何支持 HTJ2K 和字节范围请求的 DICOMweb 服务器上使用。
- 4g 速率 —— 下行 30 mbit/s,上行 5 mbit/s,延迟 10 ms
- 完整尺寸影像为 512×512×174
- 降分辨率影像为 128×128 且经有损压缩
HTJ2K 流式
注意这套阶段模型会通过选择队列和请求优先级, 把不同视口在各个阶段的请求相互交错起来。这种交错并不完美—— 它交错的是阶段而不是单个请求——但呈现效果相当不错, 而且不需要在多份体数据之间编写复杂的协调逻辑。
正如在进阶取回配置中学到的,
我们可以借助 decimate、offset 和不同的优先级来实现交错。
抽取(Decimation)是指以偏移量 F 每隔 N 张取一张影像,记作 N/F。
例如 4/3 表示位置 3,7,11,...。这是通过按顺序执行以下各阶段实现的:
- 初始影像 —— 位置 0、50%、100% 处的影像
- 用 multipleFast 取回类型取 4/3 抽取的影像
- 这一步完成后,就能显示出一份低分辨率的完整体数据
- 用 multipleFast 取回类型取 4/1 抽取的影像
- 把初始体数据的分辨率提升一倍
- 用 multipleFinal 取 4/2 和 4/0 抽取的影像
- 把那些复制出来的影像替换为全分辨率影像
- 用 multipleFinal 取 4/3 和 4/1 抽取的影像
- 把低分辨率影像替换为全分辨率影像
配置大致长这样:
stages: [
{
id: 'initialImages',
// positions 用于选取特定位置 —— 中间那张,以及第一张和最后一张
positions: [0.5, 0, -1],
// 这些用默认的渲染类型,它应当取回全分辨率
retrieveType: 'default',
// 使用 Interaction 队列
requestType: RequestType.INTERACTION,
// 优先级 10,最先执行
priority: 10,
// 用这份数据填充附近的帧
nearbyFrames: {....},
},
{
id: 'quarterThumb',
decimate: 4,
offset: 3,
retrieveType: 'multipleFast',
priority: 9,
nearbyFrames,
},
... 其他版本
// 用最终数据替换最初那批数据
{
id: 'finalFull',
decimate: 4,
offset: 3,
priority: 4,
retrieveType: 'multipleFinal',
},
],
- 以全分辨率取回最初就要显示的那些影像(第一张和最后一张)
- 先取每第 4 张影像的前
initialByteRange个字节
- 取字节范围 [0,64000]
- 立刻显示部分分辨率版本
- 用部分分辨率版本来显示附近的切片
- 其他步骤
- 这里还有其他的部分分辨率与全分辨率视图,用来把数据补齐
- 取回第 2 步剩下的数据(不重新取已有数据)
- 用完整数据替换掉第 2 步的低分辨率数据
HTJ2K 字节范围
体数据的渐进式加载在基础的堆栈加载之上做了扩展, 增加了交错取回各张影像的能力,并在影像内部和影像之间 都从降分辨率版本做插值。也就是说,单张影像最初可能按 1/4 尺寸取回 (对 CT 来说是 256×256),而且只取最初要显示的那张加上每第 4 张, 其余影像由插值得出。这里采用的是复制插值,以把插值开销降到最低。 最后,在有损的初始版本取回完成后,再取回剩余的影像。
默认的取回顺序如下,其中 Decimate 描述的是所取影像之间的间隔 以及在该集合中的偏移量。
- 初始影像,全分辨率
- 4/3 抽取,部分分辨率
- 对 -2…+1 范围内的影像做插值(最近邻)
- 4/1 抽取,部分分辨率
- 4/2 抽取,全分辨率
- 4/4 抽取,全分辨率
- 4/3 抽取,全分辨率
- 4/1 抽取,全分辨率
若未配置部分分辨率,则顺序相同,只是最后两个阶段永远不会执行—— 因为部分分辨率那几步已经把它们加载过了。 这套做法确实能让插值结果非常快地呈现出来。