🍓 前言
在日常开发中经常会遇到除了渲染MPR视图外,还需要渲染3D扫描影像,本文从具体的实现、viewport type解读、场景预设等方面来介绍3D具体的实现及涉及到的相关概念。
🥑 效果演示
🍒 实现及概念
代码实现
关于如何实现一个3D渲染,在我们准备了一组Dicom文件后,整体流程跟普通stack、MPR一致,主要代码如下:
js
async function init() {
await initCornerstone();
const imageIds = await get3DTestImageId();
const renderingEngine = new RenderingEngine(renderingEngineId);
const volume = await volumeLoader.createAndCacheVolume(volumeId, {
imageIds
});
const viewportInputArray = [
{
viewportId: viewportId,
type: csEnums.ViewportType.VOLUME_3D, // ✅ 改动点1:这里修改为volume3D类型
element: document.querySelector("#element1"),
defaultOptions: {
orientation: csEnums.OrientationAxis.CORONAL,
background: CONSTANTS.BACKGROUND_COLORS.slicer3D
}
}
];
renderingEngine.setViewports(viewportInputArray);
await volume.load();
await setVolumesForViewports(renderingEngine, [{ volumeId }], [viewportId]);
const viewport = renderingEngine.getViewport(viewportId);
// ✅ 改动点2:在添加完viewoprt后,需要设置viewport的预设属性
viewport.setProperties({
preset: preset.value,
});
viewport.render();
}
这样,就可以渲染一个3D影像了 🎉 🎉,只关注代码实现的朋友可以直接跳过下面的踩坑记录和概念解读~
视图类型
我们在前面介绍MPR实现时,在创建viewport视图步骤中使用的type固定为 ViewportType.ORTHOGRAPHIC
枚举值,那我们可以来看一下cornerstone中支持的视图类型具体都有哪些,在实现时又如何选择。
ViewportType
中的全部枚举值及适用场景
-
🎉 STACK:适用于呈现一堆图像,这些图像可能属于也可能不属于同一图像。Stack可以包含不同形状、大小和方向的二维图像。
-
🎉 ORTHOGRAPHIC :适用于渲染被认为是一个3D图像的
Volume
数据。拥有VolumeViewport
可以通过设计实现多平面重组或重建(MPR),在这种情况下,可以从不同的方向可视化体积,而不会增加性能成本。 -
PERSPECTIVE:透视图视图,目前Cornerstone暂未时间该视图功能
-
🎉 VOLUME_3D :专门用于3D体积渲染
(Volume Rendering)
,是一种用于可视化和分析三维医学图像数据(如CT或MRI扫描)的技术。VOLUME_3D 视图通过将2D切片数据构建成3D模型,允许用户以三维形式查看、旋转和操作医学影像,以便更好地理解和诊断。 -
🎉 VIDEO :
VIDEO
视图是一种专门用于显示和处理视频数据的视口(viewport
)。与传统的静态图像或三维体积渲染不同,VIDEO
视图处理的是动态的、多帧的影像数据。此类视图在医学影像领域尤其有用,因为许多医学影像设备(如超声波、心脏造影等)生成的都是视频形式的数据 -
WholeSlide:专门用于处理和显示全视野切片图像的视口。全视野切片图像通常应用于数字病理学领域,它是高分辨率的显微镜图像,通常由非常大尺寸的图像拼接而成,涵盖了整个组织切片。
在日常需求中,我们经常遇到的就是上面标注🎉 的四种,在渲染3D时我们用到的就是 VOLUME_3D 视图类型,可以允许将一组dicom文件以3D视图的形式展示
js
const viewportInputArray = [
{
viewportId: viewportId,
type: csEnums.ViewportType.VOLUME_3D, // 设置视图类型
element: document.querySelector("#element1"),
defaultOptions: {
orientation: csEnums.OrientationAxis.CORONAL, // 初始化时想要展示的方向
}
}
];
renderingEngine.setViewports(viewportInputArray);
场景预设
在修改完视图类型后,兴致勃勃的重新执行了代码,结果发现渲染出来的是一个黑色立方体,上面飘着一个黑白色2D图,如下图所示。又重新回去调试了代码,但是发现又没有报错信息及告警提示 😱😱😱 ~
这个时候就需要了解preset
场景预设了,在加载完viewport后,我们可以打印看一下默认情况下viewport的属性值有哪些?默认情况下视图只会有一个voi的默认值,其他的属性都为undefined。
colorMap
属性在 Cornerstone渲染CT+PET融合影像及相关应用场景 一文中已经介绍过了,主要是针对2D场景进行颜色映射,那对于3D场景来说,我们就需要用到 preset
属性了。
preset
属性通常指的是预设的显示参数或配置。这些预设参数用于控制3D模型在Cornerstone3D中的外观和行为。preset
属性可以包括颜色、透明度、阴影、渲染模式等显示设置,以便快速应用特定的视觉效果或设置,而无需手动调整每个参数。
如果了解一些threeJs或者webGl相关的知识,那对于以上显示黑色长方体的情况就能更好的理解了。我们想要现实一个3D物体,那3D模型的颜色、透明度、显示模式、光照强度、视角等等参数都会影响图像的显示。如果没有光照(就类似于我们在晚上处于一间没有任何灯光的屋子里一样),那我们看到的就是漆黑一片。而preset
属性的目的就是为了给viewport
视图添加颜色、光照、透明度等设置,便于更好的显示3D模型。在cornerstone中内设了很多preset设置,支持查看不同需求下的3D影像。
js
import {
CONSTANTS,
} from "@cornerstonejs/core";
const presetOptions = CONSTANTS.VIEWPORT_PRESETS.map(item => item.name); // 获取到所有的预设场景
我们可以看一下预设的场景中都定义了哪些内容(以CT-Bone
为例),便于我们更深入的了解3D显示(以下属性仅限了解即可,目前cornerstone3D暂不支持自定义预设场景,以下大部分概念词来源于threejs)
-
name: 当前预设的名称
-
gradientOpacity:定义了梯度不透明度的映射,通常用于控制图像中不同密度的区域(例如骨骼与软组织之间)的不透明度变化。数值对应于梯度值与其相应的不透明度。
-
specularPower:高光强度的控制参数。值为10表示高光的集中度,数值越高,高光越集中。
-
specular:镜面反射系数,决定表面的光亮度。值0.2表示表面有一定的镜面反射,但不非常亮
-
shade:是否启用阴影效果。值为1表示启用阴影,这样能使3D模型看起来更具立体感。
-
ambient:环境光强度,值0.1表示环境光对整个场景的影响较弱
-
diffuse:漫反射系数,决定光线从表面散射的程度。0.9表示表面具有较强的散射效果,使得表面光照看起来柔和。
-
interpolation:表示启用插值,通常用于在渲染时平滑图像,减少像素化的效果
js
{
name: 'CT-Bone',
gradientOpacity: '4 0 1 255 1',
specularPower: '10',
scalarOpacity: '8 -3024 0 -16.4458 0 641.385 0.715686 3071 0.705882',
specular: '0.2',
shade: '1',
ambient: '0.1',
colorTransfer:
'16 -3024 0 0 0 -16.4458 0.729412 0.254902 0.301961 641.385 0.905882 0.815686 0.552941 3071 1 1 1',
diffuse: '0.9',
interpolation: '1',
},
在了解完什么是预设后,我们在代码中添加viewport的预设属性(🔑🔑🔑虽然上面洋洋洒洒介绍了这么多有的没的,看的懂看不懂的,但是设置起来仅有一行代码)
js
const viewport = renderingEngine.getViewport(viewportId);
viewport.setProperties({
preset: '',
});
🍇 结束语
至此呢,我们既完成了3D影像的渲染,也顺便更深入的了解了Cornerstone中涉及到的一些概念。虽然我们之前一直用viewport type的枚举值,但是具体有哪些怎么用没有详细了解过,这样可能就会遇到新的场景的时候不能举一反三。本文就这样结束啦,祝各位不会遇到奇奇怪怪的问题、爆掉的内存、无法解析的dicom文件、无法使用的工具, emmm,溜了,溜了~ 👋 👋 👋 👋
配套可运行代码演示:github.com/jianyaoo/vu... clone到本地后直接运行 npm run serve 即可启动,持续更新,欢迎star~