mcpField
M
matlab-display-volume

matlab-display-volume

github.com/matlab数据与数据库1.2k73 次安装1 天前更新
代码仓库

关于 matlab-display-volume

这是一项用于交互式显示体数据、表面网格和叠加层的 MATLAB 技能,面向应用和可视化工作流。它指导使用 `volshow`、3D 查看器及图形对象进行展示。

主要功能

  1. 01使用 `volshow` 渲染体数据
  2. 02在 3D 中显示表面网格
  3. 03添加叠加层和注释
  4. 04配置交互式查看器回调
  5. 05更新数据以流式显示

适用场景

  1. 01构建体数据查看应用
  2. 02检查医学图像体数据
  3. 03叠加分割标签
  4. 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);

关键对象

对象构造函数关键回调
Viewerviewer3d(parent)CameraMovedFcn、ObjectClickedFcn
Volumevolshow(data, Parent=viewer)
Surfaceimages.ui.graphics.Surface(viewer, Data=tri)
交互式注释uidraw(parent, "shape")AnnotationMovedFcn(在 Viewer 上)

实用工具: linkviewers(viewers) 可在多个 Viewer 之间同步相机移动。title(viewer, "text") 可设置 Viewer 标题。

工作流

  1. 创建 Viewer — 调用 viewer3d(),或在应用场景中调用 viewer3d(parent)
  2. 添加体积数据 — 调用 volshow(V, Parent=viewer) 显示体积数据
  3. (可选) 添加曲面 — 调用 images.ui.graphics.Surface(viewer, Data=tri) 显示网格
  4. (可选) 添加注释 — 调用 uidraw(volObj, "line") 创建交互式 ROI
  5. (可选) 配置回调 — 在 Viewer 上设置 CameraMovedFcn、AnnotationMovedFcn 或 ObjectClickedFcn
  6. 更新数据 — 设置 obj.Data = newV 以进行流式处理;使用 waitfor(viewer,"Busy",false) 进行同步

应避免的旧模式

不要使用改用原因
isosurface + patchimages.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)符合临床惯例,符合放射科医生的使用习惯
使用窗宽/窗位的 CTgray(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.


评论

支持 Markdown