迁移
这份迁移指南只针对通用视口架构,不是一份通用的 Cornerstone 迁移指南。
目标是把应用代码从「依赖具体视口类的行为」迁移到「逻辑数据 id、推断出的渲染路径、 绑定、视图状态和数据显示状态」这一套上。
如果你需要从扩展中添加自定义视口类型,请使用 Enums.ViewportTypes
(先用 registerViewportType 注册,然后访问 Enums.ViewportTypes.<NAME>),
具体见
5.x 通用视口迁移指南。
堆栈视口还是体数据视口
以前,视口类型通常直接编码了数据形态:
renderingEngine.enableElement({
viewportId,
type: Enums.ViewportType.STACK,
element,
});
await viewport.setStack(imageIds);
renderingEngine.enableElement({
viewportId,
type: Enums.ViewportType.ORTHOGRAPHIC,
element,
});
await viewport.setVolumes([{ volumeId }]);
现在,平面二维浏览统一用 PLANAR_NEXT,由数据和渲染路径来决定源数据
究竟是堆栈类还是体数据切面类:
renderingEngine.enableElement({
viewportId,
type: Enums.ViewportType.PLANAR_NEXT,
element,
});
const viewport = renderingEngine.getViewport(viewportId) as PlanarViewport;
堆栈数据
以前:
await stackViewport.setStack(imageIds, 0);
stackViewport.setProperties({
voiRange: { lower: -1500, upper: 2500 },
});
stackViewport.render();
现在:
const displaySetId = 'ct-stack';
utilities.genericViewportDisplaySetMetadataProvider.add(displaySetId, {
kind: 'planar',
imageIds,
initialImageIdIndex: 0,
});
await viewport.setDisplaySets({
displaySetId,
});
viewport.setDisplaySetPresentation(displaySetId, {
voiRange: { lower: -1500, upper: 2500 },
});
viewport.render();
体数据切面数据
以前:
await volumeViewport.setVolumes([
{
volumeId,
callback: ({ volumeActor }) => {
volumeActor.getProperty().setRGBTransferFunction(0, cfun);
},
},
]);
现在:
const displaySetId = 'ct-volume';
utilities.genericViewportDisplaySetMetadataProvider.add(displaySetId, {
kind: 'planar',
imageIds,
initialImageIdIndex: Math.floor(imageIds.length / 2),
volumeId,
});
await viewport.setDisplaySets({
displaySetId,
options: {
orientation: Enums.OrientationAxis.AXIAL,
},
});
viewport.setDisplaySetPresentation(displaySetId, {
voiRange,
colormap,
});
viewport.render();
融合叠加层
以前,融合往往依赖体数据 actor、混合模式的设置,以及由视口持有的渲染器状态:
await volumeViewport.setVolumes([
{ volumeId: ctVolumeId },
{ volumeId: ptVolumeId },
]);
volumeViewport.setProperties(
{
colormap: { name: 'hsv' },
voiRange: ptVoiRange,
},
ptVolumeId
);
现在,源数据和叠加数据是显式的数据绑定:
await viewport.setDisplaySets(
{
displaySetId: ctDataId,
options: {
orientation: Enums.OrientationAxis.SAGITTAL,
role: 'source',
},
},
{
displaySetId: ptDataId,
options: {
orientation: Enums.OrientationAxis.SAGITTAL,
role: 'overlay',
},
}
);
viewport.setDisplaySetPresentation(ptDataId, {
colormap: {
name: 'hsv',
opacity: 0.4,
},
});
添加叠加影像
以前:
viewport.addImages([{ imageId }]);
现在,优先注册叠加数据并使用数据显示状态:
utilities.genericViewportDisplaySetMetadataProvider.add(overlayDataId, {
kind: 'planar',
imageIds: [imageId],
initialImageIdIndex: 0,
});
await viewport.addDisplaySet(overlayDataId, {
role: 'overlay',
});
viewport.setDisplaySetPresentation(overlayDataId, {
opacity: 0.5,
visible: true,
});
兼容性的 addImages() 路径对影像叠加仍然可用,但新代码应当直接使用
显示集 id 和绑定。
VOI、颜色映射表、不透明度与可见性
以前:
viewport.setProperties({
voiRange,
colormap,
invert: true,
});
现在:
viewport.setDisplaySetPresentation(displaySetId, {
voiRange,
colormap,
invert: true,
visible: true,
});
这样显示状态就明确归属到每个显示集绑定上了。当视口同时挂载了源显示集和 叠加显示集时,这一点尤其重要。
平移、缩放、旋转与翻转
以前,代码常常直接给相机对象打补丁:
const camera = viewport.getCamera();
viewport.setCamera({
...camera,
parallelScale: camera.parallelScale * 0.8,
});
现在,使用语义化的视口 API:
viewport.setScale(viewport.getScale() * 1.25);
viewport.setPan([40, -20]);
viewport.updateViewState(({ rotation = 0 }) => ({
rotation: rotation + 30,
}));
const nextViewState = viewportProjection.withPresentation(viewport, {
rotation: 90,
});
if (nextViewState) {
viewport.setViewState(nextViewState);
}
viewport.setViewState({ flipHorizontal: true });
viewport.render();
旧版适配器仍为老代码支持相机式调用。它们只是一个临时迁移层,
应当预期会在后续某个破坏性版本中移除,不要把它们当作 Next 的长期控制接口。
干净的 Next 代码应当使用视图状态和视口投影 API。
直接使用的 Next 视口不会把 getViewPresentation()、setViewPresentation()、
getCamera()、setCamera() 作为长期控制 API 暴露出来。
以前:
const presentation = viewport.getViewPresentation();
viewport.setViewPresentation({
...presentation,
zoom: presentation.zoom * 2,
});
现在:
const presentation = viewportProjection.getPresentation(viewport);
const nextViewState = viewportProjection.withPresentation(viewport, {
zoom: (presentation?.zoom ?? 1) * 2,
});
if (nextViewState) {
viewport.setViewState(nextViewState);
}
以前:
viewport.setCamera({
focalPoint,
position,
});
现在,空间导航用视图引用,显示导航用显示状态补丁:
targetViewport.setViewReference(sourceViewport.getViewReference());
targetViewport.render();
const nextViewState = viewportProjection.withPresentation(viewport, {
zoom: 1.5,
});
if (nextViewState) {
viewport.setViewState(nextViewState);
}
如果你要改的是某个原生字段——例如平面方位、视频媒体锚点或 ECG 信号范围——
请直接用 setViewState() 或 updateViewState() 更新原生视图状态。
干净的 Next 重置操作用 resetViewState();resetCamera() 属于临时的旧版适配器,
应当预期会在后续某个破坏性版本中移除。
切片导航
以前:
await stackViewport.setImageIdIndex(index);
volumeViewport.setCamera({
focalPoint,
position,
});
现在:
await viewport.setImageIdIndex(index);
对基于堆栈的数据,这里存的是堆栈索引。对基于体数据的数据, 视口会把索引解析为一个体数据切面点,使状态中只有一个切片定位符。
跨视口的空间导航:
const viewReference = sourceViewport.getViewReference();
targetViewport.setViewReference(viewReference);
targetViewport.render();
分割
以前,体数据标签图通常作为体数据 actor 渲染。这对某些工作流是有用的, 但即使只是单切面的平面工作流,它也可能分配整份三维标签图纹理。
await segmentation.addSegmentationRepresentations(viewportId, [
{
segmentationId,
type: SegmentationRepresentations.Labelmap,
},
]);
现在,对兼容的平面体数据切面工作流,启用切面渲染:
await segmentation.addLabelmapRepresentationToViewportMap({
[viewportId]: [
{
segmentationId,
type: SegmentationRepresentations.Labelmap,
config: {
useSliceRendering: true,
},
},
],
});
启用 useSliceRendering 后,标签图表示形式会通过影像/切面叠加路径投射出来。
它作为一个叠加绑定跟随源数据的视图状态,而不再需要一条独立的体数据渲染叠加路径。
推荐的迁移顺序
- 平面二维的堆栈与体数据切面工作流,把视口创建改为
ViewportType.PLANAR_NEXT。 - 把每一份源数据或叠加数据注册为一个逻辑显示集 id。
- 用
setDisplaySets()或addDisplaySet()替换setStack()和setVolumes()。 - 把 VOI、颜色映射表、不透明度、混合模式和可见性迁移到
setDisplaySetPresentation(displaySetId, ...)。 - 把干净代码里的相机补丁替换为视图状态、视口投影、平移、缩放和视图引用这几套 API。
- 为那些应当走切面路径渲染的标签图分割叠加层启用
useSliceRendering。