第 22 章:Reader 对象与数据格式
学习目标
此前的数据都在 JavaScript 中构造。本章把数据从文件带进管线。读完后,你应该能根据文件格式选择 Reader,区分磁盘上的编码与内存中的数据对象,检查读取后的维度、坐标、数组和数值范围,并知道读入成功并不等于可以直接显示。第 7 阶段示例使用固定公式生成的教学文件,模拟实际项目的 HTTP 读取流程;它不是测量数据。
22.1 三个层次:文件、Reader、数据对象
Three.js 开发者熟悉 GLTFLoader → Scene/Mesh。vtk.js 中也有加载步骤,但结果通常先进入一个可计算的数据对象,而非直接出现一个可渲染的场景对象:
文件或 URL ──> Reader ──> vtkImageData / vtkPolyData / …
│
├──> Filter ──> 新数据对象
└──> Mapper ──> Actor / Volume ──> Renderer
文件格式规定如何保存字节、数组和元数据;Reader负责解析;输出数据对象规定算法和 Mapper 在内存中怎样访问点、单元和属性。这三个名字不能互换。例如 .vti 是 VTK XML ImageData 文件,解析后得到 vtkImageData;vtk.js 的 index.json 加外置数组同样可以得到 vtkImageData。两条路径的文件组织不同,后续计算却可复用同一种数据对象。
Reader 在数据管线中通常是源头,没有上游输入,并提供 getOutputData() 和 getOutputPort()。前者给当前输出对象,后者用于下游算法的 setInputConnection(...)。HTTP 读取还包含异步下载;在 await 完成前就取数据,往往只得到占位输出或缺少数组的数据对象。
22.2 先识别手里的数据是什么
| 手里的文件或服务 | 常见 Reader | 常见输出 | 典型下一步 |
|---|---|---|---|
vtk.js 数据集目录,入口为 index.json,数组为独立文件 |
vtkHttpDataSetReader |
由清单中的 vtkClass 决定,如 vtkImageData、vtkPolyData |
选择数组后进入体、切片或表面管线 |
VTK XML .vti |
vtkXMLImageDataReader |
vtkImageData |
切片、体渲染、等值面 |
VTK XML .vtp |
vtkXMLPolyDataReader |
vtkPolyData |
vtkMapper / vtkActor,或表面过滤器 |
| 其他格式,如 NRRD、DICOM、STL | 对应格式和场景的专用 Reader,需先查当前版本支持范围 | 随格式而变 | 检查结构与属性后再选 Mapper |
文件后缀只是入口线索。不要把 .vti URL 交给 vtkHttpDataSetReader:它预期的是 vtk.js 专用的 JSON 清单,而非 XML。相反,index.json 也不能由 vtkXMLImageDataReader 解析。若原始文件是某种 vtk.js 暂不支持的医学或工程格式,通常先用合适工具转换,并保存原始数值单位、空间信息及来源。
22.3 一个 .vti 读取例子
以下代码展示 Reader 的位置;/data/scan.vti 需要实际存在于 HTTP 服务器。它不属于第 7 阶段的本地示例文件。
import vtkXMLImageDataReader from '@kitware/vtk.js/IO/XML/XMLImageDataReader';
const reader = vtkXMLImageDataReader.newInstance();
await reader.setUrl('/data/scan.vti');
const image = reader.getOutputData();
console.log(image.isA('vtkImageData')); // true
console.log(image.getDimensions(), image.getSpacing());
不同 Reader 的异步方法并不完全相同,不能把这一段机械套到 vtkHttpDataSetReader 的数组按需加载流程上。对于用户本地选择的 File,也可用文件 API 得到 ArrayBuffer,再调用该 XML Reader 的 parseAsArrayBuffer(buffer);这属于另一种输入来源。仍应检查实际文件是否使用当前版本支持的编码与压缩形式。
如果读入的是表面:
import vtkXMLPolyDataReader from '@kitware/vtk.js/IO/XML/XMLPolyDataReader';
const reader = vtkXMLPolyDataReader.newInstance();
await reader.setUrl('/data/surface.vtp');
const surface = reader.getOutputData();
console.log(surface.getNumberOfPoints(), surface.getNumberOfCells());
这里的 surface 是数据,不是 Actor。把它交给 vtkMapper,再由 vtkActor 放入 Renderer,才会在画面中出现。
22.4 从真实文件读出后检查什么
先不要急着调材质。对 vtkImageData,至少检查:
const image = reader.getOutputData();
const pointData = image.getPointData();
console.log('dimensions', image.getDimensions());
console.log('extent', image.getExtent());
console.log('origin', image.getOrigin());
console.log('spacing', image.getSpacing());
console.log('direction', image.getDirection());
console.log('point arrays', pointData.getArrays().map(a => a.getName()));
console.log('active scalars', pointData.getScalars()?.getName());
extent 是各轴的点索引范围,例如 [0,31,0,31,0,31] 对应 32×32×32 个采样点、31×31×31 个体素单元。origin、spacing 和 direction 一起确定点在世界坐标中的位置;它们不只是让图像摆得好看。三个轴的 spacing 可能不同,真实影像也可能不是单位方向矩阵。不能只看数组长度就猜测空间尺度。
Point Data 的元组数量应与点数匹配,Cell Data 则与单元数匹配。一个三分量数组可能是向量或 RGB,并非可以直接当单分量密度做等值面。逐项确认数组名、getNumberOfComponents()、getNumberOfTuples()、getRange(),以及哪个数组是当前 active scalars。getRange() 也要结合数值单位理解:同样的 100,在 CT、仿真压力和人为归一化场中含义完全不同。
对 vtkPolyData,检查点数、单元数、getPolys() 等拓扑和 Point Data / Cell Data。点数大于零不保证有可见三角形;若只有线或顶点,映射结果与预期表面不同。真实文件还有坐标系、单位、缺失值、NaN、异常极值、数据来源和使用许可等实际问题,最好在进入可视化前记录。
22.5 读取、过滤、显示要各自验证
一个空白画面可能出在多个环节。按顺序检查比盲目调相机更快:
- 网络和解析:URL 是否 200,是否有跨域限制,Promise 是否完成,文件是否交给正确 Reader。
- 数据对象:
vtkClass、维度、点数、单元数、数组名及 active scalars 是否符合预期。 - 过滤结果:等值面阈值是否落在数组范围内,输出
vtkPolyData是否有单元。 - 显示管线:是否用了匹配的 Mapper/Prop,是否加入 Renderer,相机是否复位,标量着色是否使表面意外透明或暗淡。
例如读取 vtkImageData 后直接连 vtkMapper / vtkActor,不能期望自动得到体渲染。切片用 vtkImageMapper / vtkImageSlice;体渲染用 vtkVolumeMapper / vtkVolume;等值面先用过滤器转为 vtkPolyData,再使用表面 Mapper。
22.6 教学文件和真实数据的边界
第 7 阶段示例里的两个 Float32Array 由 scripts/generate-data.mjs 固定生成,并保存在项目中。浏览器通过 HTTP 请求读取这些已存在的文件,真实演示 Reader、数组选择和 Filter 的运行时行为;数值本身没有物理单位,也不能用来作医学或科学结论。换成真实数据时,读取方式之外,还应保留数据来源、空间变换、单位、时间或通道含义,并与原始软件或标注作交叉检查。
本章小结
Reader 解决“字节怎样变成 vtk.js 数据对象”。数据对象再进入 Filter 和 Mapper。先认格式、再选 Reader、最后检查输出的结构与属性,可以避免把读取失败、属性缺失和渲染设置问题混在一起。下一章专门讲 vtkHttpDataSetReader 的清单及独立数组请求。