cornerstone 加载本地 Dicom 文件
前面已有多篇文章介绍 cornerstone 加载预览网络 dicom 文件并进行一些处理与操作,那么,如果遇到本地的 dicom 文件,如何通过 cornerstone 实现加载呢?
如果可以实现加载本地的 dicom 文件的话,那么加载本地的 dicom 文件也能向加载网络 dicom 文件一样实现多种测量与操作吗?
加载本地的 dicom 文件的性能如何,比加载网络 dicom 文件更占用硬件资源吗?
带着这些疑问,我们进入本篇文章,通过 cornerstone 实现加载本地 dicom 文件,并实现与加载网络 dicom 文件一样的功能。
Image Loaders
这里,我们在重新看一下 cornerstone 中 Image Loaders 图像加载器,Image Loaders 具体的工作是将 dicom 资源与 cornerstone 进行关联,也就是将 dicom 文件通过网络或者本地获取的方式提供给 cornerstone 进行使用。
咦?直接说出来了。。。
没错,cornerstone 中确实支持加载本地 dicom 文件,在 cornerstoneWADOImageLoader 库里面,wadouri 协议的加载器中,有一个 fileManager 文件管理器专门处理本地 dicom 文件。那 fileManager 是如何工作的呢?
我们先看一下 fileManager 的源码:
let files = [];
function add(file) {
const fileIndex = files.push(file);
return `dicomfile:${fileIndex - 1}`;
}
function get(index) {
return files[index];
}
function remove(index) {
files[index] = undefined;
}
function purge() {
files = [];
}
export default {
add,
get,
remove,
purge
};
不会吧,fileManager 这不就是一个文件的集合,然后增加了一些“增、删、查”的方法?它怎么可以加载本地文件呢?
是的,fileManager 从名字上面看,就是一个文件管理器,只是将文件进行了集中管理。但是,fileManager 在 add 添加文件时做了一件事情,这个将影响后续的文件加载:
const fileIndex = files.push(file);
// 此处返回了文件的 imageId
return `dicomfile:${fileIndex - 1}`;
前面的文章讲过,在 cornerstoneWADOImageLoader 加载影像时,会根据 imageId 的前缀判断,采用哪种加载器进行处理。显然,在 fileManager 进行 add 时,返回了带有 dicomfile 前缀的 imageId。
我们在来回顾一下,cornerstoneWADOImageLoader 中 loadImage 函数对于选择加载器的代码判断:
if (scheme === "dicomweb" || scheme === "wadouri") {
return xhrRequest;
} else if (scheme === "dicomfile") {
return loadFileRequest;
}
在使用网络资源时,我们一般会用 dicomweb 或者 wadouri 的前缀,这时实际使用的加载器为 xhrRequest,本地 dicom 文件使用的前缀为 dicomfile,使用的则是 loadFileRequest。
那么 xhrRequest 与 loadFileRequest 的区别是什么呢:
1、获取文件的方式不一样,xhrRequest 通过 ajax 请求的方式获取到 file 字节流,loadFileRequest 通过 FileReader 获取到 file 字节流; 2、xhrRequest 受网络环境的影响,相对于 loadFileRequest 性能会差一些。
然而,无论通过 xhrRequest 还是 loadFileRequest 获取到文件后,cornerstoneWADOImageLoader 后续的处理方式并没有区别。再这样情况下,我们在使用本地 dicom 文件进行影像预览时,和使用网络资源一样,并不需要进行多余的处理。
具体的使用
由于浏览器的安全性策略,无法直接通过 javascript 读取文件系统,需要通过 inpiut 标签,由用户进行选取文件。
<input type="file" id="selectFile" />
用户选取文件后,通过 fileManager 生成 imageId,就可直接进行影像的显示。
const selectFile = document.getElementById("selectFile");
const imageIds = [];
selectFile.addEventListener("change", (e) => {
const files = e.target.files;
files.forEach((file) => {
const imageId = cornerstoneWADOImageLoader.wadouri.fileManager.add(file);
imageIds.push(imageId);
});
});
async function loadImage() {
const image = await cornerstone.loadImage(imageIds[0]);
cornerstoneTools.addStackStateManager(element, ["stack"]);
const stack = {
currentImageIdIndex: 0,
imageIds
};
// 为启用元素添加 stack 工具状态
cornerstoneTools.addToolState(element, "stack", stack);
cornerstone.displayImage(element, image);
}
添加工具:
const StackScrollMouseWheelTool = cornerstoneTools.StackScrollMouseWheelTool;
const props = {
configuration: {
// 是否在序列内循环
loop: true,
// 是否跳帧
allowSkipping: true,
// 倒转方向
invert: false
}
};
cornerstoneTools.addTool(StackScrollMouseWheelTool, props);
cornerstoneTools.setToolActive("StackScrollMouseWheel", { mouseButtonMask: 4 });
最简单的例子
老规矩,最后将案例放上,大家可以通过查看详情查看码上掘金的源码。另外,本案例应一位 jy 的要求,加上了一些看似并不相关的功能。