关于 matlab-display-volume
这是一项用于交互式显示体数据、表面网格和叠加层的 MATLAB 技能,面向应用和可视化工作流。它指导使用 `volshow`、3D 查看器及图形对象进行展示。
主要功能
- 01使用 `volshow` 渲染体数据
- 02在 3D 中显示表面网格
- 03添加叠加层和注释
- 04配置交互式查看器回调
- 05更新数据以流式显示
适用场景
- 01构建体数据查看应用
- 02检查医学图像体数据
- 03叠加分割标签
- 04可视化并动画呈现表面网格
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ matlab/ matlab-agentic-toolkit --skill matlab-display-volume安装后在 Claude Code 中输入/matlab-display-volume确认已加载。
附带文件manifest.yaml
详细介绍
体积显示
使用 volshow 高性能、高质量地显示体积数据。显示等值面网格、三角剖分和曲面时,请使用 images.ui.graphics.Surface,而不是 isosurface,以实现更高性能、更高质量的网格显示,并让各种大小的网格交互更加流畅。
适用场景
- 用户希望创建 GUI、应用、仪表板或交互式工具,以显示体积数据、等值面或曲面网格
- 用户希望在体积数据或曲面网格上绘制 ROI、注释或其他线条和形状
- 用户希望在体积数据上显示带标签的数据或其他叠加体积
不适用场景
- 用户没有 Image Processing Toolbox——请改用
isosurface+patch,但建议使用volshow以获得更佳性能。
医学图像体积
如果用户有 Medical Imaging Toolbox,请使用 medicalVolume 加载 DICOM/NIfTI/NRRD 文件,然后对该对象调用 volshow 方法。该方法会根据文件元数据自动设置空间 Transformation 和 SpatialUnits。本技能中的其他所有模式(Viewer、叠加、注释、流式处理、应用构建)仍然适用——只有入口点有所不同。
medVol = medicalVolume("brain.nii");
obj = volshow(medVol);
关键对象
| 对象 | 构造函数 | 关键回调 |
|---|---|---|
Viewer | viewer3d(parent) | CameraMovedFcn、ObjectClickedFcn |
Volume | volshow(data, Parent=viewer) | |
Surface | images.ui.graphics.Surface(viewer, Data=tri) | |
| 交互式注释 | uidraw(parent, "shape") | AnnotationMovedFcn(在 Viewer 上) |
实用工具: linkviewers(viewers) 可在多个 Viewer 之间同步相机移动。title(viewer, "text") 可设置 Viewer 标题。
工作流
- 创建 Viewer — 调用
viewer3d(),或在应用场景中调用viewer3d(parent) - 添加体积数据 — 调用
volshow(V, Parent=viewer)显示体积数据 - (可选) 添加曲面 — 调用
images.ui.graphics.Surface(viewer, Data=tri)显示网格 - (可选) 添加注释 — 调用
uidraw(volObj, "line")创建交互式 ROI - (可选) 配置回调 — 在 Viewer 上设置
CameraMovedFcn、AnnotationMovedFcn或ObjectClickedFcn - 更新数据 — 设置
obj.Data = newV以进行流式处理;使用waitfor(viewer,"Busy",false)进行同步
应避免的旧模式
| 不要使用 | 改用 | 原因 |
|---|---|---|
isosurface + patch | images.ui.graphics.Surface(viewer, Data=tri) | 渲染效果更佳、交互性能更高,并支持深度剥离 |
clear(viewer) 后重新添加对象 | 重用对象,更新 Data 属性 | 避免重建开销 |
使用 drawnow 同步体积数据流 | waitfor(viewer,"Busy",false) | 体积数据会异步加载;对于 Surface 动画,drawnow 仍然适用 |
每帧创建新的 volshow | 保留引用,设置 obj.Data = V | 重用对象可避免重新分配 GPU 资源 |
模式
标准体积显示
简单的体积显示场景可以直接调用 volshow,无需指定父对象。所有名称-值参数都可以作为 Volume 对象的属性设置;设置 Data 属性即可更新体积数据。
obj = volshow(V);
根据数据类型和值范围选择 DisplayRangeMode:
| 数据特征 | 建议模式 |
|---|---|
[0, 1] 范围内的归一化 single/double | "data-range"(默认) |
uint8 或 int8 | "data-range"(默认) |
最大值为 1023 的 uint16(例如 10 位 CT) | "10-bit" |
最大值为 4095 的 uint16(例如 12 位 CT/MR) | "12-bit" |
全范围 uint16 或 int16 | "16-bit" |
| 需要自定义窗宽/窗位 | 使用 "manual" 和 DisplayRange=[low high] |
% 12-bit medical volume
obj = volshow(V, DisplayRangeMode="12-bit");
% Manual window/level
obj = volshow(V, DisplayRangeMode="manual", DisplayRange=[200 1200]);
设置 RenderingStyle 以控制体素数据的可视化方式。详细指南请参阅下方专门介绍的 渲染样式 一节。
选择颜色映射表 — 根据数据内容进行选择:
| 数据内容 | 建议使用的颜色映射 | 原因 |
|---|---|---|
| CT / MR(解剖结构) | gray(256) | 符合临床惯例,符合放射科医生的使用习惯 |
| 使用窗宽/窗位的 CT | gray(256) + DisplayRange | 较窄的范围可突出目标组织 |
| 荧光 / 发射 | hot(256) 或 green(256) | hot 可突出强度峰值;green 与荧光团颜色相符 |
| 通用科学数据 | parula(256)(默认) | 感知均匀,易于辨识 |
| 多结构 / 已标注数据 | turbo(256) | 全光谱彩虹色,按感知顺序排列 |
| 带符号的数据(例如流动、应变) | 自定义发散色图(蓝-白-红) | 区分中点两侧的正值和负值 |
obj = volshow(V, Colormap=gray(256));
选择 Alpha 映射 — Alpha 映射是一个 256×1 向量,用于将归一化强度 [0, 1] 映射到不透明度 [0, 1]。它控制哪些结构可见、哪些结构透明。默认值为三次曲线渐变(x.^3),可抑制低强度背景,同时显示明亮结构。
| 目标 | Alpha 映射形状 | 构造方式 |
|---|---|---|
| 显示明亮结构,隐藏背景 | 三次曲线渐变(默认) | linspace(0,1,256)'.^3 |
| 平等显示所有强度 | 线性渐变 | linspace(0,1,256)' |
| 仅显示特定强度范围 | 高斯峰 | exp(-((x-center).^2)/(2*width^2)) |
| 硬阈值(二值可见性) | 阶跃函数 | double((1:256)' > threshold) |
| 隐藏明亮部分,显示暗淡部分(例如腔体) | 反向渐变 | linspace(1,0,256)' |
| 自定义:显示骨骼,隐藏软组织(CT) | 在骨骼 HU 处设置阶跃 | double(linspace(0,1,256)' > 0.3) |
% Gaussian alphamap centered at 60% intensity (width 10%)
x = linspace(0, 1, 256)';
alpha = exp(-((x - 0.6).^2) / (2*0.1^2));
obj = volshow(V, Alphamap=alpha, Colormap=gray(256));
% Hard threshold — only show voxels above 30% of the display range
alpha = double(linspace(0, 1, 256)' > 0.3);
obj = volshow(V, Alphamap=alpha);
若要按体素控制不透明度(例如,无论强度如何都遮罩特定区域),请将 AlphaData 属性设为与 Data 尺寸相同、取值范围为 [0, 1] 的体数据:
obj = volshow(V, AlphaData=double(regionMask));
在一个体数据上显示遮罩、语义分割或其他体数据的叠加层时,请使用 Volume 对象的 OverlayData 属性,以及相应的名称-值参数 OverlayColormap、OverlayAlpha、OverlayRenderingStyle、OverlayDisplayRange 和 OverlayDisplayRangeMode 来调整叠加层显示。与将叠加层和体数据混合后更新 Data 属性,或向 Viewer 添加第二个 Volume 对象相比,这种方式速度更快。叠加层显示的默认属性经过调整,可支持语义标签,但也可以调整以支持其他用途的连续数据。
obj = volshow(V, OverlayData=mask);
如果有空间参考信息,请将其包含在 Transformation 名称-值参数中。根据体素间距和体数据大小构建 imref3d,或使用 affinetform3d 处理任意仿射变换。使用 medicalVolume 时,volshow 方法会根据文件元数据自动设置变换。
% From voxel spacing (e.g., [0.5 0.5 1.0] mm)
R = imref3d(size(V), 0.5, 0.5, 1.0);
obj = volshow(V, Transformation=R);
% From an affine matrix
tform = affinetform3d(A);
obj = volshow(V, Transformation=tform);
对于超出 GPU 纹理内存(通常为 > 2048³ 个体素)或大到无法装入 RAM 的体数据,请创建多级 blockedImage 并将其传递给 volshow。makeMultiLevel3D 函数会创建用于渐进式渲染的分辨率金字塔 — Viewer 会先加载较粗的层级,然后在摄像机稳定后逐步细化。
bim = blockedImage(V, BlockSize=[512,512,512]);
mbim = makeMultiLevel3D(bim, Scales=[2 4 8]);
volshow(mbim);
使用 Scales 控制下采样因子。每个尺度都会创建一个分辨率为 1/N 的层级。对于非常大的体数据(例如,4096³+),请使用更多尺度(例如,[2 4 8 16])。对于仅略微超过纹理限制的较小体数据,所需层级更少(例如,[2 4])。BlockSize 应与 GPU 纹理块大小相同或为其倍数(512³ 是不错的默认值)。
表面网格显示
使用 images.ui.graphics.Surface 显示表面网格,相比 isosurface + patch,渲染效果更好,交互性能也更佳。将 triangulation 对象作为 Data 传入,并将 N×3 的逐顶点 RGB 颜色矩阵作为 Color 传入。
% tri = triangulation(...), cpoints = N-by-3 vertex colors
viewer = viewer3d(BackgroundGradient="off", BackgroundColor="white");
obj = images.ui.graphics.Surface(viewer, Data=tri, Color=cpoints);
为获得最佳性能,建议尽可能使用默认的 Alpha 值 1.0 显示网格。你可以多次调用 images.ui.graphics.Surface,将多个对象添加到场景中;对象会经过排序,并通过深度剥离进行空间正确的渲染。对象透明时,需要更多渲染遍,因此性能会下降。
表面网格可以与体数据一起显示在同一个 Viewer 中。
viewer = viewer3d();
volObj = volshow(V, Parent=viewer);
surfObj = images.ui.graphics.Surface(viewer, Data=tri);
更新 Surface 的位置或大小时,更新 Transformation 属性比用新的三角剖分重新设置 Data 快得多。使用 drawnow 在帧之间刷新图形队列(这适用于 Surface 动画;只有体数据流式传输才需要 waitfor)。
viewer = viewer3d(BackgroundGradient="off", BackgroundColor="white");
obj = images.ui.graphics.Surface(viewer, Data=tri, Color=cpoints);
% Disable auto camera position update
viewer.CameraPositionMode = "manual";
% Animate the surface moving in the z direction
for idx = 1:100
tform = transltform3d(0, 0, idx);
obj.Transformation = tform;
drawnow;
end
渲染样式
Volume 对象上的 RenderingStyle 属性用于控制体素数据的可视化方式。请根据需要显示的结构选择样式。
最大强度投影(MIP) — 沿每条视线将最亮的体素投影到屏幕上。适用于血管成像(血管造影)、荧光显微镜,或任何以亮结构为关注信号的数据。
obj = volshow(V, RenderingStyle="MaximumIntensityProjection");
梯度透明度 — 使较大的均匀区域(低梯度)透明,同时保留强度变化明显的区域(高梯度)。这样可以透过大块组织,显现体数据中的边界和边缘。
obj = volshow(V, RenderingStyle="GradientOpacity");
电影级渲染 — 采用全局光照、环境光遮蔽和柔和阴影的先进照片级真实感渲染。可为 CT 扫描中的骨骼等实体结构提供最高质量的可视化效果。计算开销更大,但能呈现适用于出版物的效果。
obj = volshow(V, RenderingStyle="CinematicRendering");
其他样式:
| RenderingStyle | 使用场景 |
|---|---|
"VolumeRendering" | 默认样式 — 根据强度传递函数显示半透明体数据 |
"MinimumIntensityProjection" | 显示最暗的结构(例如气道、空腔) |
"Isosurface" | 显示阈值处的表面(设置 IsosurfaceValue) |
"SlicePlanes" | 查看体数据的正交切片 |
"LightScattering" | 模拟光散射,呈现软组织外观 |
% Isosurface at a specific threshold (e.g., bone in CT)
obj = volshow(V, RenderingStyle="Isosurface", IsosurfaceValue=300);
% Orthogonal slice planes
obj = volshow(V, RenderingStyle="SlicePlanes");
体数据流式传输
更新显示时,请尽可能重复使用对象。避免在准备重新添加相同类型对象时对 Viewer 使用 clear 方法 — 始终重复使用对象。保留 volshow 返回的 Volume 对象,并更新其 Data 属性。体数据会异步加载,因此 drawnow 可能会在渲染完成前返回。使用 waitfor 等待 Viewer 的 Busy 属性,以阻塞程序,直到数据加载完成。
viewer = viewer3d();
obj = volshow([], Parent=viewer);
for idx = 1:10
V = rand([512,512,512]);
obj.Data = V;
waitfor(viewer, "Busy", false);
end
生成动画
生成动画或捕获帧时,请使用 getframe,并将 Viewer(Volume 对象的父对象)作为第一个参数传入。Viewer 会等待所有待处理的更新完成后再捕获,从而确保每一帧都已完整渲染。
viewer = viewer3d();
obj = volshow([], Parent=viewer);
frames = cell(1, 100);
for idx = 1:100
V = rand([512,512,512]);
obj.Data = V;
frames{idx} = getframe(viewer);
end
在体数据上添加注释
在体数据上显示交互式注释或少量注释时,请使用 uidraw 函数启动交互式绘制,或以编程方式放置注释。
uidraw 适用于少于 100 个注释的情况。省略 Position 可启动交互式绘制。未指定 Label 时,ROI 会显示标准测量值(例如线条的距离)。设置 Viewer 上的 SpatialUnits,可在测量显示中包含单位。
obj = volshow(V);
viewer = obj.Parent;
viewer.SpatialUnits = "mm";
% Interactive drawing (user places the line)
roi = uidraw(obj, "line", Color=[1,0,0]);
% Programmatic placement (no interaction required)
roi2 = uidraw(obj, "point", Position=[20,20,50], Color=[0,0,1], Label="Point of Interest");
% Multi-point polyline from a position matrix (each row is a point)
pos = [20,20,50; 50,60,75; 50,80,100];
roi3 = uidraw(obj, "polyline", Position=pos, Color=[0,1,0]);
% Make an annotation static (disable further interaction)
set(roi3, "Interactions", "none");
响应用户交互
为 Viewer 的回调属性添加函数句柄,以响应用户交互。CameraMovedFcn 会在摄像机移动后触发。AnnotationMovedFcn 会在用户以交互方式移动或调整注释形状后触发。ObjectClickedFcn 会在用户于 Viewer 中单击并释放、且未拖动后触发(单击并拖动会启动默认交互,通常是平移)。使用事件数据确定被单击的对象。
V = rand([100,100,100]);
obj = volshow(V);
viewer = obj.Parent;
% Draw a rectangle ROI interactively
roi = uidraw(obj, "point", Color=[0,1,0], Label="ROI");
% Listen for movement and display the position
viewer.AnnotationMovedFcn = @(~,evt) fprintf("ROI Position: [%.1f, %.1f, %.1f]\n", evt.Position);
构建应用
构建应用时,调用 viewer3d(parent) 创建一个以 uigridlayout 为父级的 Viewer,然后调用 volshow 并将该 Viewer 设为父级。在构造时立即创建 Volume 对象,并使用空数据([])——这样可以避免之后的条件逻辑,并让你在数据可用时直接更新 obj.Data。只有当应用支持在根本不同的显示模式之间切换(例如,体积显示与仅表面显示)时,才延迟创建 Volume。
使用标准的 app classdef(参见 matlab-build-app 技能),并在 createComponents 中添加以下内容:
% Inside createComponents(app) — viewer3d parented to grid layout
app.GridLayout = uigridlayout(app.UIFigure, [1 1]);
app.GridLayout.RowHeight = {"fit"};
app.GridLayout.ColumnWidth = {"fit"};
app.Viewer = viewer3d(app.GridLayout);
title(app.Viewer, "App Display");
app.Viewer.Layout.Row = 1;
app.Viewer.Layout.Column = 1;
% Create Volume eagerly with empty data
app.Volume = volshow([], Parent=app.Viewer);
之后使用 app.Volume.Data = im; 更新数据。
为体积应用添加颜色条
由于 Viewer 原生不支持颜色条,请在相邻的网格列中放置一个带有颜色条的不可见 uiaxes。将坐标区的 Colormap 和 CLim 设为与 Volume 对象匹配,然后在该坐标区上调用 colorbar。
fig = uifigure(Name="Volume with Colorbar", Position=[100 100 800 500]);
gl = uigridlayout(fig, [1 2]);
gl.ColumnWidth = {"1x", 80};
% Volume viewer in left column
viewer = viewer3d(gl);
viewer.Layout.Row = 1;
viewer.Layout.Column = 1;
title(viewer, "Colorbar with Volshow");
V = rand([64 64 64]);
vol = volshow(V, Parent=viewer, Colormap=parula(256));
% Invisible axes with colorbar in right column
ax = uiaxes(gl, Visible="off", HitTest="off", PickableParts="none");
ax.Layout.Row = 1;
ax.Layout.Column = 2;
ax.Toolbar.Visible = "off";
% Match colormap and data range to the Volume object
ax.Colormap = vol.Colormap;
ax.CLim = [min(vol.Data, [], "all"), max(vol.Data, [], "all")];
cb = colorbar(ax, Units="normalized");
链接多个 Viewer
使用 linkviewers 同步多个 Viewer 之间的摄像机移动。用户旋转或缩放一个 Viewer 时,所有已链接的 Viewer 都会更新以匹配。传入 "off" 可取消链接。
viewer1 = viewer3d();
viewer2 = viewer3d();
title(viewer1, "Original");
title(viewer2, "Segmentation");
volshow(V, Parent=viewer1);
volshow(V, Parent=viewer2, OverlayData=mask);
linkviewers([viewer1, viewer2]);
约定
- 始终:将 Viewer 作为第一个位置参数传给
images.ui.graphics.Surface(viewer, ...)——不要使用Parent=viewer作为名称-值参数 - 始终:通过更新
obj.Data来重复使用 Volume 对象——不要在每一帧都重新创建体积 - 始终:当设置
Data后的时序很重要时(截图、帧捕获),使用waitfor(viewer, "Busy", false)进行同步 - 始终:截图时使用
getframe(viewer),而不是getframe(gcf)——Viewer 会处理异步完成 - 始终:当用户拥有 Medical Imaging Toolbox 时,对
medicalVolume对象使用volshow(medVol)——这会自动设置Transformation和SpatialUnits - 绝不:调用
clear(viewer)重置场景——应改为重复使用现有对象 - 绝不:当 Image Processing Toolbox 可用时,使用
isosurface+patch显示网格 - 优先:对遮罩/分割使用
OverlayData属性,而不是混合到Data中或添加第二个体积 - 优先:当仅位置/大小发生变化时,更新
Transformation,而不是重新设置Data
版权所有 2026 The MathWorks, Inc.