下面是对使用Cornerstone3D.js浏览我的第一个医学影像的深入分析,结合Cornerstone3D.js的源码来了解一下Cornerstone3D.js是如何渲染stack类型的影像数据的,完整代码请参见这篇文章,下面是分析:
1.初始化cornerstone-core
await coreInit();
这个是调用的core\src下的init.ts的init方法,作用是:初始化cornerstone-core,检测客户端是否支持WebGL,默认情况下是支持中等GPU层级.另外一个重要的是实例化WebWorkManager.WebWorkManager类似于Java的线程池,是用来对Web Worker进行管理的管理器。可以看下init.ts的init源码(看英文注释):
/**
* Initialize the cornerstone-core. This function checks for WebGL context availability
* to determine if GPU rendering is possible. By default, it assumes a medium GPU tier.
*
* It's the responsibility of the consumer application to provide accurate GPU tier information
* if needed. Libraries like 'detect-gpu' can be used for this purpose, and the result can be
* passed in the configuration object.
*
* If a WebGL context is available, GPU rendering will be used. Otherwise, it will fall back
* to CPU rendering for supported operations.
*
* @param configuration - A configuration object, which can include GPU tier information
* @returns A promise that resolves to true if cornerstone has been initialized successfully.
* @category Initialization
*/
function init(configuration = config): boolean {
if (csRenderInitialized) {
return csRenderInitialized;
}
canUseNorm16Texture = _hasNorm16TextureSupport();
// merge configs
config = deepMerge(defaultConfig, configuration);
// mobile safe
if (config.isMobile) {
config.rendering.webGlContextCount = 1;
}
if (isIOS()) {
if (configuration.rendering?.preferSizeOverAccuracy) {
config.rendering.preferSizeOverAccuracy = true;
} else {
console.log(
'norm16 texture not supported, you can turn on the preferSizeOverAccuracy flag to use native data type, but be aware of the inaccuracy of the rendering in high bits'
);
}
}
const hasWebGLContext = _hasActiveWebGLContext();
if (!hasWebGLContext) {
console.log('CornerstoneRender: GPU not detected, using CPU rendering');
config.rendering.useCPURendering = true;
} else {
console.log('CornerstoneRender: using GPU rendering');
}
csRenderInitialized = true;
//实例化WebWorkManager
if (!webWorkerManager) {
webWorkerManager = new CentralizedWebWorkerManager();
}
return csRenderInitialized;
}
2.dicomImageLoader初始化
await dicomImageLoaderInit();
初始化dicomImageLoader,对应dicomImageLoader\src下的init.ts的init(),在这里主要注册了wadors和wadouri两个dicomImageLoader,其次是获取上面的WebWorkerManager。init()方法如下:
function init(options: LoaderOptions = {
}): void {
// setting options should happen first, since we use the options in the
// cornerstone set
// DO NOT CHANGE THE ORDER OF THESE TWO LINES!
setOptions(options);
//注册imageLoader
registerLoaders();
//获取WebWorkerManager
//这就可以理解为什么在Core中要调用init()初始化的原因了:)
const workerManager = getWebWorkerManager();
const maxWorkers = options?.maxWebWorkers || getReasonableWorkerCount();
workerManager.registerWorker('dicomImageLoader', workerFn, {
maxWorkerInstances: maxWorkers,
});
}
3.实例化RenderingEngine
renderingEngine = new RenderingEngine(renderingEngineId);
const viewportId = 'CT_AXIAL_STACK';
const viewportInput = {
viewportId,
element,
type: ViewportType.STACK,
defaultOptions: {
background: [0.4, 0, 0.4]
},
};
renderingEngine.enableElement(viewportInput);
实例化RenderingEngine,指定渲染的类型为 ViewportType.STACK,背景颜色为[0.4, 0, 0.4]
4.请求元数据
const studySearchOptions = {
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'
};
const wadoRsRoot='https://d14fa38qiwhyfd.cloudfront.net/dicomweb';
const StudyInstanceUID='1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463';
const SeriesInstanceUID= '1.3.6.1.4.1.14519.5.2.1.7009.2403.226151125820845824875394858561';
const SOP_INSTANCE_UID = '00080018';
const SERIES_INSTANCE_UID = '0020000E';
const MODALITY = '00080060';
const SOPInstanceUID = null;
const client = new api.DICOMwebClient({
url: "https://d14fa38qiwhyfd.cloudfront.net/dicomweb"
});
let instances = await client.retrieveSeriesMetadata(studySearchOptions);
console.log("instances[0]",instances[0]);
console.log("----------------------------");
console.log("instances[1]",instances[1]);
//return imagesId
let imageIds= instances .map((instanceMetaData) => {
const SeriesInstanceUID = instanceMetaData[SERIES_INSTANCE_UID].Value[0];
const SOPInstanceUIDToUse =
SOPInstanceUID || instanceMetaData[SOP_INSTANCE_UID].Value[0];
const prefix = 'wadors:';
const imageId =
prefix +
wadoRsRoot +
'/studies/' +
StudyInstanceUID.trim() +
'/series/' +
SeriesInstanceUID.trim() +
'/instances/' +
SOPInstanceUIDToUse.trim() +
'/frames/1';
cornerstoneDICOMImageLoader.wadors.metaDataManager.add(
imageId,
instanceMetaData
);
return imageId;
});
使用DICOMwebClient获取影像文件的元数据,然后将它们缓存到imageLoader的metaDataManager中,在上面我们可以输出元数据看一下是什么。metaDataManager的add方法如下:
function add(imageId: string, metadata: WADORSMetaData) {
const imageURI = imageIdToURI(imageId);
Object.defineProperty(metadata, 'isMultiframe', {
value: isMultiframe(metadata),
enumerable: false,
});
metadataByImageURI[imageURI] = metadata;
}
4.异步请求和渲染
viewport = renderingEngine.getViewport(viewportId);
//console.log("viewport is: ",viewport);
//做了很多事情,最重要的是,获取image的metaData,也就是上面cornerstoneDICOMImageLoader.wadors.metaDataManager.add的元数据,这样才能正确显示出image
//在setStack里,才真正使用dicom-image-loader异步获取影像数据,具体在_loadAndDisplayImage方法里
await viewport.setStack(imageIds);
setStack方法调用的是core\src\RenderingEngine\StackViewport.ts的setStack方法,这个方法是一个异步方法,默认显示的是第一张stack影像,代码如下:
/**
* Sets the imageIds to be visualized inside the stack viewport. It accepts
* list of imageIds, the index of the first imageId to be viewed. It is a
* asynchronous function that returns a promise resolving to imageId being
* displayed in the stack viewport.
*
*
* @param imageIds - list of strings, that represents list of image Ids
* @param currentImageIdIndex - number representing the index of the initial image to be displayed
*/
public async setStack(
imageIds: string[],
currentImageIdIndex = 0
): Promise<string> {
this._throwIfDestroyed();
this.imageIds = imageIds;
if (currentImageIdIndex > imageIds.length) {
throw new Error(
'Current image index is greater than the number of images in the stack'
);
}
this.imageKeyToIndexMap.clear();
imag


4553

被折叠的 条评论
为什么被折叠?



