🍀 引言
当我们刚接触Cornerstone
或拿到一组Dicom文件时,如果没有ImageID
和后台接口,可能只是想简单测试Cornerstone能否加载这些Dicom文件。在这种情况下,可以使用本地文件加载的方法。之前我们介绍了通过node启动服务器请求文件,在本地提供接口的方式。今天,我们再介绍一下另一种实现方式——直接使用文件输入框点击上传文件,通过文件流让Cornerstone加载并渲染这些Dicom文件。以下将围绕实现效果、实现思路及具体流程展开。
🔥 效果速览
本篇整体相关代码已更新至代码库:https://github.com/jianyaoo/vue-cornerstone-demo/blob/main/src/views/basicUsage/LocalFile.vue,欢迎Star。
实现效果如下:
⛳️ 具体实现流程
实现流程
我们想要通过点击input
文件框上传本地的文件进行渲染,在进行实际开发前,我们先梳理一下大概的思路:
-
需要一个 type 为file 的 input 输入框组件,点击上传选择本地要渲染的Dicom文件
-
我们可能想要展示一张Dicom或者一组Dicom文件,所以 input 需要设置多选属性
-
监听文件更改事件,当上传文件后进行渲染
-
将本地上传的file文件流传递给Cornerstone进行渲染(核心步骤,Cornerstone如何渲染Blob文件流)
按照上面的流程,我们就开始准备写代码
UI组件
<div class="form">
<label for="">点击上传文件:</label>
<input
type="file"
multiple
@change="handleChange"
>
</div>
监听文件改变
function handleChange(evt) {
// 阻止事件冒泡
evt.stopPropagation();
// 阻止默认事件
evt.preventDefault();
// 更新文件前清除之前的缓存,避免更新上传文件时直接从缓存中获取Volume对象
cache.purgeCache();
const files = evt.target.files;
const imageIds = [];
Array.from(files).forEach(file => {
// 将文件流放到dicomLoader的文件管理器中并返回对应的ImageID => Blob流加载的核心代码1
const imageId = cornerstoneDICOMImageLoader.wadouri.fileManager.add(file);
imageIds.push(imageId);
});
// 渲染Imageid
loadAndViewImage(imageIds);
}
在监听文件改变的代码中,核心代码主要有2点:
-
在每次更新文件时进行缓存清除:
cache.purgeCache()
,避免再次上传文件时由于VolumeId没有改变而导致直接从缓存中获取数据 -
获取到file文件流后,可以通过 cornerstoneDICOMImageLoader 的
fileManager
对象将文件流转换成ImageId,我们可以看一下fileManager中的相关方法,通过add方法将我们上传的文件流添加到文件列表中,并获取到以dicomfile
为前缀的ImageId
// 单例文件列表,用于管理上传的文件列表
let files: Blob[] = [];
// add:向文件列表中添加文件并返回指定格式的ImageId
// 注意这里返回的id前缀,后面解析的代码中会用这个前缀作为本地文件加载的标志。
function add(file: Blob): string {
const fileIndex = files.push(file);
return `dicomfile:${fileIndex - 1}`;
}
// get: 根据文件索引值获取文件流
function get(index: number): Blob {
return files[index];
}
// remove: 根据文件索引值删除文件
function remove(index: number): void {
files[index] = undefined;
}
// purge: 清空文件列表
function purge(): void {
files = [];
}
export default {
add,
get,
remove,
purge,
};
根据ImageId加载渲染影像
- 加载图像
async function loadAndViewImage(imageIds) {
await prefetchMetadataInformation(imageIds);
if (type.value === "stack") {
await renderStack(imageIds);
} else {
await renderVolume(imageIds);
}
}
async function prefetchMetadataInformation(imageIdsToPrefetch) {
for (let i = 0; i < imageIdsToPrefetch.length; i++) {
// 通过loadImage加载文件
await cornerstoneDICOMImageLoader.wadouri.loadImage(imageIdsToPrefetch[i])
.promise;
}
}
- 渲染栈图像
async function renderStack(imageIds) {
if (imageIds?.length === 0) {
return;
}
const renderingEngine = getRenderingEngine(renderingEngineId);
const viewportInput = {
viewportId: viewportId0,
type: csEnums.ViewportType.STACK,
element: document.querySelector("#element0")
};
renderingEngine.enableElement(viewportInput);
const toolGroup = ToolGroupManager.getToolGroup(groupId);
toolGroup.addViewport(viewportId0, renderingEngineId);
const viewport = renderingEngine.getViewport(viewportId0);
await viewport.setStack(imageIds);
activeTools();
viewport.render();
}
- 渲染Volume图像
async function renderVolume(imageIds) {
...以上初始化内容省略,完整代码可在git中查看
const volume = await volumeLoader.createAndCacheVolume(volumeId, {
imageIds
});
volume.load();
await setVolumesForViewports(
renderingEngine,
[
{
volumeId
}
],
[viewportId1, viewportId2, viewportId3]
);
activeTools();
renderingEngine.render();
}
我们在使用 add()
函数返回的ImageId直接渲染前,还需要使用loadImage
加载图像 cornerstoneDICOMImageLoader.wadouri.loadImage(imageId)
,那接下来我们看一下loadImage
函数是如何执行并获取到文件的。以下是loadImage函数的简约版代码(整体源码可在Cornerstone 官方git:https://github.com/cornerstonejs/cornerstone3D/blob/d8f12e46d478b251c5515b3ee960629dd4e5d6e2/packages/dicomImageLoader/src/imageLoader/wadouri/loadImage.ts文件中查看)
function loadImage(
imageId: string,
options: DICOMLoaderImageOptions = {}
): Types.IImageLoadObject {
const parsedImageId = parseImageId(imageId);
...
const schemeLoader = getLoaderForScheme(parsedImageId.scheme);
...
const dataSetPromise = dataSetCacheManager.load(
parsedImageId.url,
schemeLoader,
imageId
);
return loadImageFromPromise(
dataSetPromise,
imageId,
parsedImageId.frame,
parsedImageId.url,
options
);
}
-
对ImageId进行解析,获取到ImageId的加载规则等信息
-
根据解析到的ImageId theme属性获取加载方式,如果是正常接口的ImageId,使用的是xhrRequest HTTP请求,对于我们上传文件的为loadFileRequest请求,由上文我们可以得到add()函数返回的ImageId格式为
dicomfile:${fileIndex - 1}
-
获取到加载方式后,我们可以看一下
loadFileRequest
函数是如何加载文件的。通过解析ImageId可以获取到对应的文件Blob,读取并返回文件流
在获取到文件流后还会有一系列的dicom文件处理与从接口返回操作一致,这里就不展开详细说明了,大致流程如下,后续学习源码时会展开详细说明。
🌾 结语
由此,一个点击上传本地文件的Demo演示就结束了,主要知识点为
-
cornerstoneDICOMImageLoader.wadouri.fileManager
对象管理本地上传的文件并可以返回ImageId -
cornerstoneDICOMImageLoader.wadouri.loadImage
可以根据不同的请求规则调用不同的请求方式,用于加载处理文件
配套可运行代码演示:https://github.com/jianyaoo/vue-cornerstone-demo clone到本地后直接运行 npm run serve 即可启动,持续更新,欢迎star~