最近在研究如何利用cornerstone3D (v1.70.13) 来实现MPR功能,找到它的一个demo -- volumeBasic, 运行效果如下图
看了下主程序的示例代码,非常简单,可以说corestone3D这个库把很多细节都封装起来了,使得调用者可以很简单的快速实现多平面重建,为了便于在它的基础上进行集成和调整,还是有必要深入了解它的内部实现,现将笔者的理解整理出来分享给读者。
笔者研究的cornerstone3D的版本是 v1.70.13, 代码入口路径在cornerstone3D/packages/core/examples/volumeBasic/index.ts
import {RenderingEngine,Types,Enums,volumeLoader,CONSTANTS,
} from '@cornerstonejs/core';
import {initDemo,createImageIdsAndCacheMetaData,setTitleAndDescription,setCtTransferFunctionForVolumeActor,
} from '../../../../utils/demo/helpers';// This is for debugging purposes
console.warn('Click on index.ts to open source code for this example --------->'
);const { ViewportType } = Enums;// ======== Set up page ======== //
setTitleAndDescription('Basic Volume','Displays a DICOM series in a Volume viewport.'
);const content = document.getElementById('content');
const element = document.createElement('div');
element.id = 'cornerstone-element';
element.style.width = '500px';
element.style.height = '500px';content.appendChild(element);
// ============================= ///*** Runs the demo*/
async function run() {// Init Cornerstone and related librariesawait initDemo();// Get Cornerstone imageIds and fetch metadata into RAMconst imageIds = await createImageIdsAndCacheMetaData({StudyInstanceUID:'1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463',SeriesInstanceUID:'1.3.6.1.4.1.14519.5.2.1.7009.2403.226151125820845824875394858561',wadoRsRoot: 'https://d3t6nz73ql33tx.cloudfront.net/dicomweb',});// Instantiate a rendering engineconst renderingEngineId = 'myRenderingEngine';const renderingEngine = new RenderingEngine(renderingEngineId);// Create a stack viewportconst viewportId = 'CT_SAGITTAL_STACK';const viewportInput = {viewportId,type: ViewportType.ORTHOGRAPHIC,element,defaultOptions: {orientation: Enums.OrientationAxis.SAGITTAL,background: <Types.Point3>[0.2, 0, 0.2],},};renderingEngine.enableElement(viewportInput);// Get the stack viewport that was createdconst viewport = <Types.IVolumeViewport>(renderingEngine.getViewport(viewportId));// Define a unique id for the volumeconst volumeName = 'CT_VOLUME_ID'; // Id of the volume less loader prefixconst volumeLoaderScheme = 'cornerstoneStreamingImageVolume'; // Loader id which defines which volume loader to useconst volumeId = `${volumeLoaderScheme}:${volumeName}`; // VolumeId with loader id + volume id// Define a volume in memoryconst volume = await volumeLoader.createAndCacheVolume(volumeId, {imageIds,});// Set the volume to loadvolume.load();// Set the volume on the viewportviewport.setVolumes([{ volumeId, callback: setCtTransferFunctionForVolumeActor },]);// Render the imageviewport.render();
}run();
下图是对这个代码的基本分析
下图是针对imageLoader.loadImage做了更细致的分析,描述是如何从后端得到影像原始数据并转换到Volume里
对整个过程可以总结来说就是:
- 初始化和注册imageLoader(cornerstoneDICOMImageLoader),这个是专门用于下载并解码DICOM图像并加载到Volume的图像buffer里。初始化和注册VolumeLoader, 这个是用于将二维数据重建为Volume。
- 通过dicomwebClient从后端获取meta信息
- 初始化renderEnginee(专门处理渲染)及viewport和canvas
- 使用volumeLoader(cornerstoneStreamingImageVolumeLoader)创建volume实例(streamingImageVolume), 并初始化volume的scalarData和imageData的数据结构 (基于vtk.js)
- 加载streamingImageVolume,触发cornerstoneDICOMImageLoader去从后端加载每张图片的原始图像数据并拷贝到volume的内部buffer里
- 将viewport与volume绑定,调用vtk的内部方法去实现mapper,actor的初始化
- 调用viewport的render方法,其实最终是调用vtk的渲染方法
因笔者能力有限,代码也仅分析到这个层面,权当抛砖引玉,更多细节请读者自行研究