Cornerstone3D介绍
Cornerstone3D是一个专门为处理三维医学影像而设计的JavaScript库。
它是Cornerstone项目的一部分,旨在为医学影像社区提供高性能、可扩展且易于使用的开源Web工具,专注于提供交互式的3D医学图像浏览体验,适用于多种医学影像格式。
特性
-
健壮的DICOM解析:能够处理和显示各种3D医学影像格式,如CT、MRI和PET扫描等,支持Dicom格式、NifTi格式的影像加载
-
高性能渲染:使用WebGL进行图像渲染、使用多线程进行图像编码,优化了图像的加载和显示速度,从而提供了流畅的用户体验
-
模块化设计:设计了灵活的架构,允许开发者扩展自己的工具和定制功能,以适应特定的医学影像应用需求。
版本对比
Cornerstone3D 和 Cornerstone 版本对比,主要在图像处理、渲染性能上进行了提升。2D版本无法更好的支持复杂场景的使用,例如多平面重建、Series融合等等。以下是从不同场景进行的对比:
Cornerstone(2D版本)
-
图像处理: 专注于处理和展示二维医学影像数据
-
渲染性能:针对2D图像优化,提供高效的加载和显示性能。
-
工具和功能:包括基本的图像操作工具,如缩放、平移、旋转、亮度/对比度调节,以及医学影像中常用的注释和测量工具。
Cornerstone3D(3D版本)
-
图像处理:能够处理和展示三维医学影像数据,如从CT或MRI扫描得到的体积数据集。
-
渲染性能:使用WebGL等技术进行3D渲染,优化了处理和显示大型体积数据集的能力。
-
工具和功能:除了包含2D版本的基本功能外,还提供3D特有的功能,如多平面重建(MPR)、体积渲染、图像分割等等。
依赖项
vtk.js
vtk.js是一个开源的javascript库,用于3D计算机图形,图像处理和可视化。Cornerstone3D的渲染引擎被设计为使用vtk.js进行3D渲染
浏览器支持
Cornerstone3D使用HTML5 canvas元素和WebGL 2.0 GPU渲染来渲染所有现代浏览器都支持的图像。但是并不是所有浏览器都支持volume streaming(使用SharedArrayBuffer创建共享内存)等高级功能。
-
Chrome > 68
-
Firefox > 79
-
Edge > 79
SharedArrayBuffer
需要重点关注 SharedArrayBuffer 问题,如果项目是部署在非安全上下文的环境中,SharedArrayBuffer 默认是不支持的,需要设置跨域隔离。
作用范围
原文:https://www.cornerstonejs.org/docs/getting-started/scope
Cornerstone3D是一个Javascript库,它利用纯粹的网络标准实现医学图像的3D渲染。该库在可能的情况下采用WebGL进行GPU加速渲染。Cornerstone3DTools是Cornerstone3D的同级库,包含了多种操控和注释工具,用于与图像进行交互。
Cornerstone3D的范围 并不包括 处理图像/体积的加载和元数据解析,只作用于图像渲染和缓存。应该使用imageLoader.registerImageLoader和volumeLoader.registerVolumeLoader注册到Cornerstone3D来使用图像/体积的请求加载。
在Cornerstone3D中,发布了第一个volumeLoader,streaming-image-volume-loader,它能够逐个流式传输体积图像。
总结回顾
ImageId及ImageLoader
ImageId
Cornerstone3D中的ImageId是一个用于识别单个图像以供Cornerstone显示的URL。该URL被Cornerstone用来确定调用哪个图像加载器插件来实际加载图像。
值得注意的是,Cornerstone3D将图像的加载工作委托给已注册的图像加载器。
这种策略允许Cornerstone同时显示从不同服务器通过不同协议获取的多个图像。
Image Loader
ImageLoader是一个JavaScript函数,负责接收ImageId并返回一个图像对象。由于加载图像通常需要调用服务器,因此图像加载的API需要是异步的。Cornerstone要求Image Loaders返回一个包含Promise的对象,Cornerstone将使用这个Promise异步接收图像对象,或者在发生错误时接收一个错误信息
支持的Loader类型
-
Cornerstone WADO Image Loader:支持DICOM第10部分图像;支持WADO-URI和WADO-RS;支持多帧DICOM实例;支持从文件对象读取DICOM文件
-
Cornerstone Web Image Loader: 支持 PNG and JPEG 文件
-
Cornerstone-nifti-image-loader:支持 NifTi格式文件
注册一个ImageLoader
使用registerImageLoader注册指定协议的加载器,以下示例表示:wadouri 协议的图像使用 cornerstoneDICOMImageLoader加载器加载
cornerstone.imageLoader.registerImageLoader('wadouri', cornerstoneDICOMImageLoader.wadouri.loadImage);
cornerstoneDICOMImageLoader.wadouri.register(cornerstone);
cornerstoneDICOMImageLoader.external.cornerstone = cornerstone;
cornerstoneDICOMImageLoader.external.dicomParser = dicomParser;
@cornerstonejs/dicom-image-loader
为Cornerstone3D库提供了一个DICOM图像加载器。这是[cornerstoneDICOMImageLoader]的后继产品,支持在3D库中使用。主要提供了以下新增特性:
-
Typescript支持(及类型定义)
-
更佳的开发体验(例如,单一仓库、代码规范检查等)
主要特点为:
-
实现了一个通过HTTP GET请求加载DICOM实例的Cornerstone图像加载器。
-
可与WADO-URI服务器一起使用
-
可与Orthanc的文件终端一起使用
-
可与任何通过HTTP GET返回DICOM P10实例的服务器一起使用
-
-
实现了一个用于WADO-RS(DICOMWeb)的Cornerstone图像加载器
-
支持许多流行的传输语法和光度解释
-
动态地利用WebAssembly(WASM)构建每个编解码器,显著提高图像解码性能,并使我们能够在需要时动态加载编解码器,从而减少构建时间和复杂性
-
用于在Web Workers中执行CPU密集型任务的框架
-
用于图像解码
-
用于CPU密集型任务(例如,图像处理)
-
图像加载流程
-
ImageLoader使用registerImageLoader API在Cornerstone中注册,以加载特定的ImageId URL方案
-
应用程序使用loadImage API来加载堆栈中的图像,或者使用createAndCacheVolume API来加载体积数据。
-
Cornerstone将加载图像的请求委托给已注册相应ImageId URL方案的ImageLoader。
-
ImageLoader将返回一个包含Promise的图像加载对象,
-
一旦获取到像素数据,它将用相应的图像对象解析这个Promise。获取像素数据可能需要通过XMLHttpRequest调用远程服务器,对像素数据进行解压(例如,来自JPEG 2000的数据),并将像素数据转换成Cornerstone能够理解的格式(例如,RGB与YBR颜色)。通过解析Promise返回的图像对象,随后将通过renderingEngine API显示。
总结回顾
ImageId
ImageLoader
Volume及VolumeLoader
Volume
Volume是一个具有空间物理大小和方向的3D数据阵列。
它可以通过组合3D成像系列的像素数据和元数据来构建,或者可以由应用程序从头定义。一个Volume拥有FrameOfReferenceUID、体素间距(x, y, z)、体素尺寸(x, y, z)、原点和方向向量,这些特征唯一地定义了它相对于患者坐标系统的坐标系统
ImageVolume
在Cornerstone3D中,使用ImageVolume基类来表示3D图像体积。所有的Volume都是从这个类派生的。例如StreamingImageVolume,它被用来表示一个图像被流式传输的Volume。
所以所有的Volume都实现了以下这个接口:
interface IImageVolume {
/** 【缓存中Volume的唯一标识符】unique identifier of the volume in the cache */
readonly volumeId: string
/** volume dimensions */
dimensions: Point3
/** volume direction */
direction: Float32Array
/** volume metadata */
metadata: Metadata
/** volume origin - set to the imagePositionPatient of the last image in the volume */
origin: Point3
/** volume scalar data */
scalarData: any
/** volume scaling metadata */
scaling?: {
PET?: {
SUVlbmFactor?: number
SUVbsaFactor?: number
suvbwToSuvlbm?: number
suvbwToSuvbsa?: number
}
}
/** volume size in bytes */
sizeInBytes?: number
/** volume spacing */
spacing: Point3
/** number of voxels in the volume */
numVoxels: number
/** volume image data as vtkImageData */
imageData?: vtkImageData
/** openGL texture for the volume */
vtkOpenGLTexture: any
/** loading status object for the volume containing loaded/loading statuses */
loadStatus?: Record<string, any>
/** imageIds of the volume (if it is built of separate imageIds) */
imageIds?: Array<string>
/** volume referencedVolumeId (if it is derived from another volume) */
referencedVolumeId?: string // if volume is derived from another volume
/** method to convert the volume data in the volume cache, to separate images in the image cache */
convertToCornerstoneImage?: (
imageId: string,
imageIdIndex: number
) => IImageLoadObject
}
Volume Loader
与ImageLoader类似,VolumeLoader接收一个Volume ID和加载Volume所需的其他信息,并返回一个解析为体积的Promise。
这个Volume可以由一组2D图像(例如,imageIds)构建,也可以由一个3D数组对象(如NIFTI格式)构建。我们添加了cornerstoneStreamingImageVolumeLoader库来支持将2D图像(imageIds)流式传输成3D体积。
注册一个Volume Loader
使用registerVolumeLoader来定义一个Volume Loader
import {
cornerstoneStreamingImageVolumeLoader,
cornerstoneStreamingDynamicImageVolumeLoader,
} from '@cornerstonejs/streaming-image-volume-loader';
// 注册体积加载器 => 当 CornerstoneJS 需要加载一个类型为 'cornerstoneStreamingImageVolume' 的体积数据时,它将会使用这个加载器。
volumeLoader.registerVolumeLoader(
'cornerstoneStreamingImageVolume',
cornerstoneStreamingImageVolumeLoader,
);
@cornerstonejs/streaming-image-volume-loader
从图片中创建Volume
由于在StreamingImageVolume中3D Volume是由2D图像组成的,它的体积元数据来源于2D图像的元数据,所以这个loader需要在初始时调用来获取图像元数据。这样做不仅可以在内存中预分配和缓存Volume,还可以在加载2D图像时渲染Volume(渐进式加载)
通过预先从所有图像(imageId)中获取元数据,不需要为每个imageId创建Image对象,将图像的pixelData直接插入到正确位置的volume中即可,这样保证了速度和内存效率。
Volume与Image之间的转换
StreamingImageVolume基于一系列获取的图像(2D)加载Volume,Volume可以实现将其3D像素数据转换为2D图像的功能,而无需通过网络重新请求它们。
同样的,如果一组imageid具有Volume的属性(相同的FromOfReference, origin, dimension, direction和pixelSpacing),那么Cornerstone3D可以从一组imageid创建一个Volume。
使用streaming-image-volume-loader
const imageIds = ['wadors:imageId1', 'wadors:imageId2'];
const ctVolumeId = 'cornerstoneStreamingImageVolume:CT_VOLUME';
const ctVolume = await volumeLoader.createAndCacheVolume(ctVolumeId, {
imageIds: ctImageIds,
});
await ctVolume.load();
自定义加载顺序
由于Volume的创建和缓存(createAndCacheVolume)与图像数据(load)的加载是分离的。所以支持以任何顺序加载图像,以及重新排序图像请求以正确顺序加载图像的能力。
大致的加载流程
-
根据一组imageIds,计算Volume的元数据,如间距、原点、方向等。
-
实例化一个新的StreamingImageVolume
-
StreamingImageVolume实现了加载方法(.load)
-
通过使用imageLoadPoolManager来实现加载请求
-
每个加载的帧(imageId)被放置在3D体积中的正确切片位置
-
返回一个体积加载对象,该对象包含一个解析为体积的promise。
-
总结回顾
Volume
VolumeLoader
RenderingEngine
RenderingEngine允许用户创建Viewports,将这些viewport与屏幕上的 HTML 元素关联,并使用WebGL 画布将数据渲染到这些元素上。
值得注意的是,RenderingEngine 能够渲染多个viewport,而无需创建多个引擎。
在Cornerstone3D 中,从零开始构建了 RenderingEngine,并使用 vtk.js 作为渲染的支撑,vtk.js 是一个 3D 渲染库,能够利用 WebGL 进行 GPU 加速渲染。
特性
1. 渲染优化
在 Cornerstone(2D)中,每个viewport都使用 WebGL 画布处理数据。随着viewport数量的增加,尤其在复杂的影像应用场景中(例如,同步视窗),因为会导致屏幕上画布的大量更新,以及随着视窗数量增加而性能下降。
在 Cornerstone3D 中,在屏幕外处理数据。这意味着我们有一个大型的不可见画布(离屏),它内部包含了所有屏幕上的画布。当用户操纵数据时,离屏画布中相应的像素会被更新,在渲染时,将数据从离屏画布复制到每个视窗的屏幕上画布。由于复制过程比重新渲染每个视窗上的操纵更快,因此解决了性能下降的问题。
2. 共享Volume Mapper
vtk.js 提供了用于渲染的标准渲染功能。此外,在 Cornerstone3D 中,引入了共享体积映射器(Shared Volume Mappers),以便在任何可能需要的视窗中重用数据,而无需复制数据。
使用
- 初始化一个renderingEngine
import { RenderingEngine } from '@cornerstonejs/core';
const renderingEngineId = 'myEngine';
const renderingEngine = new RenderingEngine(renderingEngineId);
- 创建viewport并绑定视图
const viewportInput = [
// CT Volume Viewport - Axial
{
viewportId: 'ctAxial',
type: ViewportType.ORTHOGRAPHIC,
element: htmlElement1,
defaultOptions: {
orientation: Enums.OrientationAxis.AXIAL,
},
},
// CT Volume Viewport - Sagittal
{
viewportId: 'ctSagittal',
type: ViewportType.ORTHOGRAPHIC,
element: htmlElement2,
defaultOptions: {
orientation: Enums.OrientationAxis.SAGITTAL,
},
},
// CT Axial Stack Viewport
{
viewportId: 'ctStack',
type: ViewportType.STACK,
element: htmlElement3,
defaultOptions: {
orientation: Enums.OrientationAxis.AXIAL,
},
},
];
renderingEngine.setViewports(viewportInput);
- 渲染视图
renderingEngine.renderViewports(['ctAxial','ctSagittal','ctStack']);
总结概述
Viewport
在 Cornerstone3D 中,viewport是由 HTML 元素创建的,我们需要提供用于创建viewport的元素。然后进行初始化绑定
分类
viewport主要分为 2D栈视图、Volume体视图、3D视图,根据不同的渲染需求进行选择。无论是哪一种视图类型,都是通过 RenderingEngine API 进行创建
StackViewport
-
适用于呈现一堆图像,这些图像可能属于也可能不属于同一图像。
-
Stack可以包含各种形状、大小和方向的2D图像
const viewport = renderingEngine.getViewport('stackId');
await viewport.setStack(imageIds);
VolumeViewport
-
适合于渲染被认为是一个3D图像的体积数据。
-
使用VolumeViewport可以通过设计实现多平面重组或重建(MPR),可以从不同的方向进行体积可视化,而不会增加性能成本。
-
用于两个series之间的图像融合
3D Viewport
-
适用于实际的三维立体数据渲染。
-
有不同类型的预设,如骨,软组织,肺等。
初始化
所有的Viewport都继承自Viewport类,它提供了一个displayArea字段。该字段可用于以编程方式设置图像的初始缩放/平移。
默认情况下,视口将使dicom图像适合屏幕。displayArea字段支持以下配置内容:
type DisplayArea = {
imageArea: [number, number], // areaX, areaY
imageCanvasPoint: {
imagePoint: [number, number], // imageX, imageY
canvasPoint: [number, number], // canvasX, canvasY
},
storeAsInitialCamera: boolean,
};
设置初始化时的缩放
在初始化时,如果想要设置图像为200%,则设置如下
imageArea: [0.5, 0.5],
设置初始化时的平移
在初始化时,如果想要左对齐图像,则设置如下
imageCanvasPoint: {
imagePoint: [0, 0.5],
canvasPoint: [0, 0.5],
};
这意味着画布上的左(0)中间(0.5)点需要与图像上的左(0)中间(0.5)点对齐。数值基于完整图像的%大小。
如何在实际应用中更改
在创建视图时进行初始化
renderingEngine.setViewports([{
viewportId: 'ctAxial',
type: ViewportType.ORTHOGRAPHIC,
element: htmlElement1,
defaultOptions: {
orientation: Enums.OrientationAxis.AXIAL,
displayArea:{
// 需要更改的配置项
}
},
}]);
总结概述
MetaData
医学影像通常附带大量非像素级的元数据,例如图像的像素间距、患者 ID 或扫描获取日期等等。对于某些文件类型(例如 DICOM),这些信息存储在文件头中,可以被读取、解析并在应用程序中传递。而对于其他类型(例如 JPEG、PNG),这些信息需要独立于实际像素数据提供。
元数据提供器是一个 JavaScript 函数,作为访问 Cornerstone 中与图像相关元数据的接口。用户也可以定义自己的提供器函数,以返回他们希望的每个特定图像的任何元数据。
自定义元数据提供器
提供器需要实现一个get函数,该函数接收一个 type 和 ImageId, 返回当前ImageId中业务需要的数据
function addInstance(imageId, scalingMetaData) {
const imageURI = csUtils.imageIdToURI(imageId);
scalingPerImageId[imageURI] = scalingMetaData;
}
function get(type, imageId) {
if (type === 'scalingModule') {
const imageURI = csUtils.imageIdToURI(imageId);
return scalingPerImageId[imageURI];
}
}
export default { addInstance, get };
在项目中注册使用
通过addProvider函数,在CornerStone中添加提供器
cornerstone.metaData.addProvider(
ptScalingMetaDataProvider.get.bind(ptScalingMetaDataProvider),
10000,
);
优先级
由于可以注册多个元数据提供器,因此在添加提供器时,可以为其定义一个优先级数字。
当需要请求元数据时,Cornerstone 将按照提供器的优先级顺序请求图像的元数据(如果提供器对于图像 ID 返回未定义,则 Cornerstone 将转向下一个提供器)。例如,如果 provider1 注册时优先级为 10,而 provider2 注册时优先级为 100,则首先向 provider2 请求图像 ID 的元数据。上面的示例中我们定义了一个优先级为10000的provider