第 23 章:vtkHttpDataSetReader 的数组选择与按需加载
学习目标
本章围绕高频对象 vtkHttpDataSetReader 展开。你将读懂 index.json 与外置数组的关系,分清“元数据已读取”“数组已启用”“数组已下载并注册”三个状态,实际写出先看清单、后选 Point Data 数组的代码,并用浏览器网络面板验证下载时机。示例基于 @kitware/vtk.js 37.4.2。
23.1 它读的是一个数据集目录
一个常见布局是:
sample/
index.json
data/
Density
Contrast
index.json 描述数据对象类型、空间结构和可选属性数组;data/Density、data/Contrast 分别存放二进制数值。Reader 可先读清单,再按需读取指定数组。这在属性很多、单个数组很大的科学数据中有用:先知道“有哪些变量”,再决定观察哪个。对于 vtkPolyData 等数据集,点坐标与拓扑属于构建数据对象所必需的几何数组,在读取元数据阶段仍可能下载。**延迟加载的是可选属性数组,不保证整个数据集只有一次小型 JSON 请求。**本阶段的 vtkImageData 几何由 extent、origin、spacing 和 direction 描述,因此它的元数据阶段只需请求 index.json。
这个格式是 vtk.js 的 HTTP 数据集格式。.vti 文件虽然也可以表示 ImageData,但要交给 vtkXMLImageDataReader,不是这里的 Reader。
23.2 读懂最小清单
本阶段的清单完整文件见示例 index.json。核心结构可缩写为:
{
"vtkClass": "vtkImageData",
"origin": [-16, -16, -16],
"spacing": [1, 1, 1],
"direction": [1,0,0, 0,1,0, 0,0,1],
"extent": [0,31, 0,31, 0,31],
"pointData": {
"vtkClass": "vtkDataSetAttributes",
"arrays": [{
"data": {
"vtkClass": "vtkDataArray",
"name": "Density",
"numberOfComponents": 1,
"dataType": "Float32Array",
"size": 32768,
"ranges": [{ "component": 0, "min": 0, "max": 200 }],
"ref": {
"registration": "addArray",
"encode": "LittleEndian",
"basepath": "data",
"id": "Density"
}
}
}]
},
"cellData": { "vtkClass": "vtkDataSetAttributes", "arrays": [] },
"fieldData": { "vtkClass": "vtkFieldData", "arrays": [] }
}
上面 ranges.max 只为解释字段而写,不是本阶段文件的精确数值;实际清单由生成脚本计算。ref.basepath 与 ref.id 相对数据集目录拼接成 /sample/data/Density。encode 必须与二进制字节序一致;size 是标量值个数,若 numberOfComponents 为 3,则元组数是 size/3。registration: "addArray" 表示数组下载后加入对应的 Point Data。它不会自动把该数组指定为 active scalars,因此后面还要显式选择。
这里的数组有 32³=32768 个 Float32 数,单个文件恰好 32768×4=131072 字节。这个估算能用于检查网络面板中的传输资源大小,但 HTTP 压缩和浏览器显示的“传输大小”可能不同于资源原始大小。extent 描述 32³ 个点,而不是 32³ 个体素单元。
23.3 三步加载:清单、选择、数值
关键代码如下,完整可运行版本在阶段示例:
import vtkHttpDataSetReader from '@kitware/vtk.js/IO/Core/HttpDataSetReader';
const reader = vtkHttpDataSetReader.newInstance({ enableArray: false });
await reader.setUrl('/sample/index.json', { loadData: false });
const image = reader.getOutputData();
console.log(image.isA('vtkImageData'), image.getDimensions());
console.table(reader.getArrays());
reader.enableArray('pointData', 'Density', true);
await reader.loadData();
const pointData = image.getPointData();
pointData.setActiveScalars('Density');
image.modified();
console.log(pointData.getScalars().getRange());
enableArray: false 把所有属性数组的初始选择设为关闭。setUrl(..., {loadData:false}) 下载并解析清单,在本例不会下载 Point Data 二进制数组;这不等于 Reader 没有输出,此时它已有一个不带属性数值的 vtkImageData。getArrays() 列出发现的数组,每项包含 location、name、enable,可用于构建“变量选择”界面。注意 enable 只是是否参与下一次加载,不是“已下载”的标志。要判断是否进入数据对象,应检查 image.getPointData().getArrayByName(name)。
enableArray('pointData','Density',true) 只改变选择,不发请求。loadData() 才下载当前已启用、仍有外置 ref 的数组并将其注册到数据对象。示例使用 addArray 注册,所以再调用 setActiveScalars('Density'),让切片、等值面等读取 active scalars 的算法找到正确数组。随后标记 image.modified(),通知下游计算图数据发生变化。若已经加载 Density,再加载 Contrast,前者的 ref 已消失,不会因为仍处于 enabled 状态而重复请求。
本版本 setUrl() 和实际运行的 loadData() 都可 await。安装包里某些类型声明或旧代码示例与运行实现可能存在差别;本章的行为按 37.4.2 实际源码和阶段示例核对。不要把普通同步 reader.update() 当作等待网络请求完成的替代品。
23.4 切换变量与节省流量
设页面上有 Density 与 Contrast 两个变量。首次下载第二个变量:
reader.enableArray('pointData', 'Contrast', true);
await reader.loadData();
image.getPointData().setActiveScalars('Contrast');
image.modified();
两个数组此时都已在数据对象里。随后只切回 Density:
image.getPointData().setActiveScalars('Density');
image.modified();
切换已加载数组不用再次调用 loadData()。反过来,enableArray(...,false) 只是关闭未来的下载选择,不会删去已经注册到 Point Data 的数组,也不保证释放其内存。如果想真正控制浏览器内存,需要另行设计数据对象生命周期、缓存和界面策略。
这里的“按需”是按数组。一次选中 Density 会下载它的完整二进制数组;这个 Reader 的上述用法不等于只取一个切片、一个空间块或一个时间步。大数据集要考虑多分辨率、分块、服务端切片或专门的流式格式,不能期待 enableArray 自动解决所有体积问题。
23.5 用 Network 面板证实,而不是凭画面猜测
运行阶段示例并打开浏览器开发者工具,选择 Network,过滤 sample,必要时勾选 Disable cache 后刷新:
| 操作 | 应看到的新增请求 | 内存数据状态 |
|---|---|---|
| 页面首次打开 | /sample/index.json |
有 ImageData 结构,两个 Point Data 数组都未注册 |
| 点击“加载 Density” | /sample/data/Density |
Density 已注册并成为 active scalars |
| 点击“加载 Contrast” | /sample/data/Contrast |
两个数组都已注册,可切换 active scalars |
| 在下拉框切换已加载数组 | 无新的 sample/data 请求 |
只改变当前用于过滤的数值数组 |
若不关闭浏览器缓存,重复刷新时可能看到请求来自 memory cache,不能把“没有网络传输”误判为代码从未发起读取。也可以看页面中的数组列表和标量范围,分别验证“发现”“下载”“激活”三个层次。阶段示例的两个字段形状不同,因此切换后等值面几何也会变化。
23.6 常见失败模式
- 404:
setUrl('/sample/index.json')的根路径要与 Vite 的public/sample对应;ref.basepath、ref.id也要与实际文件路径和大小写一致。 - CORS 或授权失败:跨域服务器应允许网页来源访问;含凭据或受限数据还需由应用明确处理身份验证。
- 数组长度不符:
size、dataType、二进制字节数和extent之间要一致。一个 32³ 的单分量 Float32 Point Data 应有 32768 个值和 131072 个原始字节。 - 错误字节序或类型:清单写
Float32Array/LittleEndian,文件也必须按该类型和字节序写出,否则范围可能荒谬或出现 NaN。 - 画面为空:清单读取成功但尚未
loadData(),或数组已注册但未设为 active scalars;也可能是下游阈值、相机或 Mapper 问题。 - 以为禁用会卸载:
enableArray控制接下来的读取,不删除已下载数组。
本章小结
vtkHttpDataSetReader 让数据集结构和属性数组分时抵达浏览器。最可靠的心智模型是:setUrl() 得到清单和必要几何,getArrays() 告诉你可以选择什么,enableArray() 记录选择,loadData() 真正下载,setActiveScalars() 决定下游读取哪个已在内存的数组。下一章把选定数组送进过滤器。