OHIF Segmentation 模式全解析:基于 Labelmap 的分割读/编辑/导出实现与扩展指南
2026/9/18 6:12:10 网站建设 项目流程

OHIF Segmentation 模式全解析:基于 Labelmap 的分割读/编辑/导出实现与扩展指南

【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers

导读

本文以 modes/segmentation/README.md 为骨架,深入剖析 OHIF Viewers 中Segmentation 模式(@ohif/mode-segmentation的实现原理。该模式面向医学影像的Labelmap 分割标注场景,开箱即用地提供分割数据的读取(read)、编辑(edit)与导出(export)全流程能力,并同时支持 Labelmap 与 Contour 两种分割表示。读完本文,你将掌握:Segmentation 模式与 Basic 模式的关系与差异、模式布局与视口如何组装、三套工具组的划分与画笔/擦除器/剪刀工具配置、标签图与轮廓分割面板的自动切换机制,以及如何通过modeFactory与自定义定制块快速派生属于自己的分割模式。

模式定位:专注 Labelmap 分割的独立 Mode

Segmentation 模式是 OHIF 平台中与 Basic、Longitudinal 等并列的独立模式(Mode)。按 modes/segmentation/README.md 的定义,它是"OHIF segmentation mode which enables labelmap segmentation read/edit/export",即一个让用户对 Labelmap 分割数据进行读取、编辑与导出的专用模式。

在 modes/segmentation/package.json 中,该模式包名为@ohif/mode-segmentation,版本号与平台主线保持一致,其description字段与 README 定义完全对应。值得注意的依赖结构:

  • peerDependencies覆盖了模式运行所需的全部扩展:@ohif/core@ohif/extension-cornerstone@ohif/extension-cornerstone-dicom-seg@ohif/extension-cornerstone-dicom-rt@ohif/extension-cornerstone-dicom-sr@ohif/extension-default@ohif/extension-dicom-pdf@ohif/extension-dicom-video,以及基础模式@ohif/mode-basic
  • 其中 SEG(分割)与 RT(放疗结构)扩展直接决定了该模式的分割能力边界,SR 扩展则提供测量/报告的显示支持;
  • 包入口为module: "src/index.tsx",即模式实例定义本身。

从实现看,Segmentation 模式不是从零搭建的,而是以@ohif/mode-basic为基座进行扩展:它复用了 Basic 模式的modeFactoryonModeEnteronModeExitisValidModelayoutTemplateextensionDependencies,再叠加自己特有的布局、工具组、工具栏与生命周期逻辑(见 modes/segmentation/src/index.tsx)。

模式实例:布局、路由与生命周期

模式核心定义

在 modes/segmentation/src/index.tsx 中,modeInstance是模式的“自描述”数据对象,包含以下关键字段:

export const modeInstance = { id, routeName: 'segmentation', displayName: 'Segmentation', toolbarButtons: [ { $reference: 'cornerstone.toolbarButtons' }, { $reference: 'cornerstone.segmentationToolbarButtons' }, ], toolbarSections: [ { $reference: 'cornerstone.segmentationModeToolbarSections' }, { $reference: 'cornerstone.segmentationToolbarSections' }, ], toolGroupAdditions: { default: [], mpr: [], volume3d: [] }, initToolGroups, modeCustomizations: 'segmentationModeCustomizations', activatePanelTriggers: [], onModeEnter, onModeExit, isValidMode, nonModeModalities: ['SM', 'ECG', 'OT', 'DOC'], routes: [segmentationRoute], extensions: extensionDependencies, hangingProtocol: ['@ohif/mnGrid'], sopClassHandlers: [ohif.sopClassHandler, segmentation.sopClassHandler, dicomRT.sopClassHandler], };

要点解读:

  • routeNamesegmentation,路由路径为template,挂载在segmentationRoute上;
  • toolbarButtons / toolbarSections使用$reference引用语法,属于“组合式”声明:它们指向 cornerstone 扩展中注册的通用工具栏按钮与分割专用按钮、分区(见下文工具栏小节),而不是在模式内硬编码按钮列表;
  • nonModeModalities声明['SM', 'ECG', 'OT', 'DOC']为“非模式模态”——当研究(Study)只包含这些模态时模式判定为无效,即无法对纯超声(SM)、心电(ECG)、其他(OT)、文档(DOC)影像进行分割;
  • hangingProtocol显式优先使用网格布局协议@ohif/mnGrid,便于多视口同步分割;
  • sopClassHandlers只注册三个处理器:默认栈处理器(普通影像)、SEG 处理器(分割)与 RT 处理器(放疗结构),这正是该模式的分割职能在数据层的体现。

布局:左右面板与三种视口

segmentationLayout继承自 Basic 模式的布局 id(ohif.layout),但在右面板上做了关键替换:

export const segmentationLayout = { id: ohif.layout, props: { leftPanels: [ohif.thumbnailList], leftPanelResizable: true, rightPanels: [ cornerstone.labelMapSegmentationPanel, // Labelmap 分割面板(含编辑工具) cornerstone.contourSegmentationPanel, // Contour 分割面板(含编辑工具) ], rightPanelResizable: true, viewports: [ { namespace: cornerstone.viewport, displaySetsToDisplay: [ohif.sopClassHandler] }, { namespace: segmentation.viewport, displaySetsToDisplay: [segmentation.sopClassHandler] }, { namespace: dicomRT.viewport, displaySetsToDisplay: [dicomRT.sopClassHandler] }, ], }, };

对照 modes/basic/src/index.tsx 中 Basic 模式的basicLayout,两者的差异一目了然:

维度Basic 模式Segmentation 模式
右面板cornerstone.segmentation+cornerstone.measurements(测量优先)Labelmap 分割面板 + Contour 分割面板(分割优先)
视口命名空间6 类视口(含 SR、PDF、ECG 等)3 类视口:常规影像、SEG 分割、RT 结构
右面板默认状态rightPanelClosed: true无该字段(右面板展开)

布局中注释明确指出:面板列表以“字面量”形式在模式路由进入时注入到标准的leftPanels/rightPanels定制项中,因此模式阶段的定制块与全局定制都可以在布局解析前覆盖它们——这是 OHIF 定制化体系(Customization)在该模式上的直接体现。

生命周期钩子:在 Basic 之上叠加自动切页

模式定义了自己的onModeEnter,其实现先调用 Basic 模式的onModeEnter(负责初始化工具组、注册工具栏、应用toolGroupAdditions、清理测量等),随后额外挂载分割面板自动切换处理器:

export function onModeEnter(ctx: withAppTypes) { basicOnModeEnter.call(this, ctx); const { segmentationService, viewportGridService, panelService } = ctx.servicesManager.services; const { unsubscribeAutoTabSwitchEvents } = setUpAutoTabSwitchHandler({ segmentationService, viewportGridService, panelService, }); this._unsubscriptions.push(...unsubscribeAutoTabSwitchEvents); }

onModeExit直接复用 Basic 模式的实现(见 modes/basic/src/index.tsx),它会统一执行this._unsubscriptions中的全部取消订阅函数,并销毁 toolGroupService、syncGroupService、segmentationService 与 cornerstoneViewportService——因此 Segmentation 模式在onModeEnter中 push 的订阅能被正确清理,不会泄漏。

分割面板自动切换:Labelmap 与 Contour 的智能联动

setUpAutoTabSwitchHandler(见 modes/segmentation/src/utils/setUpAutoTabSwitchHandler.ts)是该模式区别于 Basic 模式的核心增强逻辑。它解决的实际问题是:当用户首次添加分割时,右面板应自动切换到与分割类型匹配的标签页

实现要点:

const autoTabSwitchEvents = [ segmentationService.EVENTS.SEGMENTATION_MODIFIED, segmentationService.EVENTS.SEGMENTATION_REPRESENTATION_MODIFIED, ]; let shouldSwitchTab = true; const unsubscribeAutoTabSwitchEvents = autoTabSwitchEvents .map(eventName => segmentationService.subscribe(eventName, () => { const segmentations = segmentationService.getSegmentations(); if (!segmentations.length) { shouldSwitchTab = true; // 全部分割被移除后,下次添加时重新触发切换 return; } const activeViewportId = viewportGridService.getActiveViewportId(); const activeRepresentation = segmentationService .getSegmentationRepresentations(activeViewportId) ?.find(representation => representation.active); if (activeRepresentation && shouldSwitchTab) { shouldSwitchTab = false; switch (activeRepresentation.type) { case 'Labelmap': panelService.activatePanel( '@ohif/extension-cornerstone.panelModule.panelSegmentationWithToolsLabelMap', true ); break; case 'Contour': panelService.activatePanel( '@ohif/extension-cornerstone.panelModule.panelSegmentationWithToolsContour', true ); break; } } }) ) .map(subscription => subscription.unsubscribe);

逻辑拆解:

  1. 事件源:订阅SEGMENTATION_MODIFIEDSEGMENTATION_REPRESENTATION_MODIFIED两个事件,覆盖分割数据被修改或分割表示(表示类型)被修改的场景;
  2. 状态复位:当getSegmentations()返回空数组(全部分割被删除)时,将shouldSwitchTab置回true,确保下一次新增分割时再次触发自动切换;
  3. 按类型分派:通过viewportGridService.getActiveViewportId()找到当前活动视口,再在其分割表示中查找active标记的表示,依据type字段在LabelmapContour之间分派到不同的面板 ID(panelSegmentationWithToolsLabelMap/panelSegmentationWithToolsContour);
  4. 一次性语义shouldSwitchTab标志位保证整个会话中只在首次添加分割时切换一次,之后用户手动切换的标签页状态不被干扰。

返回值{ unsubscribeAutoTabSwitchEvents }是取消订阅函数数组,正好被onModeEnter通过this._unsubscriptions.push(...)纳入统一清理。

工具组初始化:default / mpr / volume3d 三套配置

Segmentation 模式的工具组初始化在 modes/segmentation/src/initToolGroups.ts,入口函数签名与 Basic 模式完全一致:

function initToolGroups({ extensionManager, toolGroupService, commandsManager }) { initDefaultToolGroup(extensionManager, toolGroupService, commandsManager, 'default'); initMPRToolGroup(extensionManager, toolGroupService, commandsManager); initVolume3DToolGroup(extensionManager, toolGroupService); }
  • default:2D 轴位工作流的主工具组;
  • mpr:多平面重建(MPR)工具组,额外绑定 Crosshairs(十字线)并禁用 ReferenceLines;
  • volume3d:3D 体渲染工具组,仅含旋转、缩放、平移三件套。

toolGroupAdditions字段({ default: [], mpr: [], volume3d: [] })为后续定制预留了空数组钩子——扩展模式或?customization=模块可以通过mode阶段的$push命令向这三个工具组追加额外工具。

活跃工具与鼠标绑定

三套工具组的活跃(active)工具保持一致,即分割模式下的基础导航能力:

const tools = { active: [ { toolName: toolNames.WindowLevel, bindings: [{ mouseButton: Enums.MouseBindings.Primary }] }, { toolName: toolNames.Pan, bindings: [{ mouseButton: Enums.MouseBindings.Auxiliary }] }, { toolName: toolNames.Zoom, bindings: [{ mouseButton: Enums.MouseBindings.Secondary }, { numTouchPoints: 2 }], }, { toolName: toolNames.StackScroll, bindings: [{ mouseButton: Enums.MouseBindings.Wheel }, { numTouchPoints: 3 }], }, ], ... };

对应的按键/触控约定为:Primary=WindowLevel(窗宽窗位)、Auxiliary=Pan(平移)、Secondary=Zoom(缩放)、滚轮=StackScroll(翻页),双指/三指触摸分别映射缩放与翻页。volume3d 工具组则把 Primary 换成了TrackballRotateTool(旋转)。

被动工具:完整的分割编辑工具清单

Segmentation 模式在passive列表中注册了远超 Basic 模式的分割编辑工具矩阵,这是本模式与 Basic 模式在工具层面最显著的区别。全部工具在创建后经由commandsManager.run('initializeSegmentLabelTool', { tools })统一初始化分割标签工具。

按功能可归类如下:

画笔与擦除器(Brush / Eraser 家族)——均以Brush为父工具,通过activeStrategy指定策略,并统一受MIN_SEGMENTATION_DRAWING_RADIUS/MAX_SEGMENTATION_DRAWING_RADIUS约束(见 modes/segmentation/src/constants.ts:0.599.5):

工具名activeStrategy用途
CircularBrushFILL_INSIDE_CIRCLE圆形画笔填充
CircularEraserERASE_INSIDE_CIRCLE圆形擦除
SphereBrushFILL_INSIDE_SPHERE球状画笔(体数据)
SphereEraserERASE_INSIDE_SPHERE球状擦除
ThresholdCircularBrushTHRESHOLD_INSIDE_CIRCLE阈值圆形画笔
ThresholdSphereBrushTHRESHOLD_INSIDE_SPHERE阈值球状画笔
ThresholdCircularBrushDynamicTHRESHOLD_INSIDE_CIRCLE+threshold: { isDynamic: true, dynamicRadius: 3 }动态阈值圆形画笔
ThresholdSphereBrushDynamicTHRESHOLD_INSIDE_SPHERE+threshold: { isDynamic: true, dynamicRadius: 3 }动态阈值球状画笔

剪刀工具(Scissors)CircleScissors(圆形)、RectangleScissors(矩形)、SphereScissors(球状),用于快速框选区域生成分割。

分割辅助工具LabelmapSlicePropagation(切片传播)、MarkerLabelmap(标记)、ClickSegment(点击选中分割段)、SegmentBidirectional(分割内双向测量)、SegmentSelect(分割段选择)、LabelMapEditWithContourTool(用轮廓编辑标签图)。

轮廓分割(Contour Segmentation)工具PlanarFreehandContourSegmentation(自由手绘轮廓)、LivewireContourSegmentation(活线/磁吸轮廓)、SculptorTool(雕刻工具)。

样条 ROI 工具——以SplineContourSegmentation为父工具,全部开启enableTwoPointPreview两点预览:

  • CatmullRomSplineROItype: 'CATMULLROM'
  • LinearSplineROItype: 'LINEAR'
  • BSplineROItype: 'BSPLINE'

测量与浏览工具PlanarFreehandROIWindowLevelRegionMagnifyStackScrollUltrasoundDirectional

禁用工具ReferenceLinesAdvancedMagnify在 default 组中被显式禁用(disabled列表)。

MPR 工具组的 Crosshairs 细节

MPR 工具组除复用上述工具外,还以特殊方式注册Crosshairs(见 modes/segmentation/src/initToolGroups.ts 中initMPRToolGroup):

  • 将 Crosshairs 绑定到Primary + ShiftmouseButton: Primary, modifierKey: Shift),使其独占一个鼠标组合键——注释解释了原因:若不绑定独立按键,Crosshairs 会在 Primary 上激活,而它是disableOnPassive的,一旦工具栏激活其他 Primary 工具(画笔/缩放/平移)就会被立即禁用,从而与它们互斥;绑定独立按键后可与之共存;
  • 开启viewportIndicators(视口指示器),配置circleRadius: 5xOffset: 0.95yOffset: 0.05
  • 通过getReferenceLineColor根据视口 id 或方位返回参考线颜色:viewport-0rgb(200, 0, 0)viewport-1rgb(200, 200, 0)viewport-2rgb(0, 200, 0),按方位(axial/sagittal/coronal)同样有对应色,兜底色为#0c0
  • autoPan默认关闭(enabled: false, panSize: 10)。

MPR 工具组同时把ReferenceLines加入disabled,避免与 Crosshairs 的参考线能力冲突。

工具栏定制:分割按钮包与分区

模式实例通过$reference引用了 cornerstone 扩展注册的三个定制项,其定义位于 extensions/cornerstone/src/customizations/segmentationToolbarCustomization.ts:

  • cornerstone.segmentationToolbarButtons:分割编辑工具栏按钮包,与通用按钮包cornerstone.toolbarButtons一起构成该模式的默认按钮集合;
  • cornerstone.segmentationModeToolbarSectionscornerstone.segmentationToolbarSections:工具栏分区容器定义。

在按钮包内部,结构非常清晰(以BrushToolsLabelMapUtilitiesContourUtilitiesLabelMapToolsContourTools为容器):

  • BrushTools/LabelMapTools/ContourTools使用ohif.toolBoxButtonGroup(嵌套工具盒子按钮组);
  • LabelMapUtilities/ContourUtilities使用ohif.Toolbar(内嵌工具条);
  • 具体工具按钮(如PlanarFreehandContourSegmentationTool)配置了evaluate评估函数(如evaluate.cornerstone.segmentation在无分割时显示"Create new segmentation to enable this tool."禁用提示、evaluate.cornerstone.hasSegmentationOfType校验 Contour 类型分割是否存在)以及commands命令序列(激活工具、激活对应类型的分割表示等)。

值得注意的是,该定制文件也导出了与模式常量一致的MIN_SEGMENTATION_DRAWING_RADIUS = 0.5/MAX_SEGMENTATION_DRAWING_RADIUS = 99.5,印证画笔半径上下限是跨模式与工具栏统一约定的。工具栏注释还提示:这些按钮包可被segmentationEditing.jsonc等定制拉入 Basic / Longitudinal 模式,即分割编辑能力可以被其他模式按需复用

模式有效性校验与自定义定制块

数据驱动的有效性判断

Segmentation 模式复用了 Basic 模式导出的isValidMode(实现见 modes/basic/src/index.tsx),其判定逻辑为数据驱动:

  • 若存在excludedStudies:研究中每条属性均命中任一排除项则无效;
  • 若存在excludedModalities:研究包含其中任一模态则无效;
  • 若存在modeModalities:研究至少包含一个列出的模态才有效(数组条目要求全部模态都存在);
  • 否则依据nonModeModalities:只要研究包含任意一个不在该列表中的模态即有效。

Segmentation 模式设置nonModeModalities: ['SM', 'ECG', 'OT', 'DOC'],因此:只有当研究完全由这些模态构成时,模式才不可用;只要存在任何可分割的影像模态(如 CT、MR、PT),模式即为有效。

定制块:可编辑的分割面板

模式注册了segmentationModeCustomizations定制块(见 modes/segmentation/src/index.tsx):

export const customizations = { segmentationModeCustomizations: {}, };

注释明确说明:与 Basic 模式注册的basicModeCustomizations'panelSegmentation.disableEditing': true(Basic 模式默认禁用分割面板编辑)不同,Segmentation 模式的定制块是空的——分割面板在该模式下是可编辑的。该块虽为空,但仍被注册,以便 bootstrap 或?customization=模块以模式作用域追加值。这正是该模式“默认可编辑”语义的定制化实现。

基于 modeFactory 派生自定义分割模式

模式的最终导出对象包含modeFactory(来自 Basic 模式):

const mode = { id, modeFactory, modeInstance, extensionDependencies, customizations, };

modeFactory使用immutability-helperupdatemodeConfiguration应用到modeInstance上(见 modes/basic/src/index.tsx 中modeFactory实现)。这意味着站点可以定义一个mySegmentation模式,通过 modeConfiguration 以不可变更新的方式扩展本模式,例如:

  • 替换initToolGroups为自定义工具组初始化函数(initToolGroups已被模式显式导出:export { initToolGroups });
  • 通过$pushtoolbarButtons/toolGroupAdditions追加按钮与工具;
  • 覆盖modeCustomizations以调整面板行为;
  • 修改nonModeModalitieshangingProtocolsopClassHandlers以适配特定临床工作流。

与相关扩展的协同

Segmentation 模式的运转依赖若干扩展的能力注入(均在 modes/basic/src/index.tsx 的extensionDependencies中声明):

  • @ohif/extension-cornerstone-dicom-seg:提供 SEG 的 SOP Class Handler 与专用视口(见 extensions/cornerstone-dicom-seg/src/index.tsx,视口以React.lazy懒加载OHIFCornerstoneSEGViewport),承担分割数据的解析、显示与渲染;
  • @ohif/extension-cornerstone-dicom-rt:提供 RTSTRUCT 的读取与渲染,使放疗结构可与分割同屏对照;
  • @ohif/extension-cornerstone:提供全部影像交互工具(utilityModule.tools)、分割面板(Labelmap/Contour)与分割工具栏按钮包;
  • @ohif/mode-basic:提供模式工厂、生命周期基座与有效性校验逻辑。

小结

Segmentation 模式以极简的包描述(README 仅一句话:启用 Labelmap 分割的读/编辑/导出)承载了一套完整的分割工作流实现:它复用 Basic 模式的生命周期与组合机制,替换为双分割面板布局与三视口路由,注册了覆盖画笔、擦除器、剪刀、阈值、动态阈值、样条与轮廓分割的完整工具矩阵,并通过setUpAutoTabSwitchHandler在 Labelmap 与 Contour 表示之间智能切换面板。对于需要搭建专用分割应用的开发者,该模式既可直接使用,也可通过modeFactory与定制化体系低成本地派生定制版本——其完整实现均可从 modes/segmentation/src/index.tsx、modes/segmentation/src/initToolGroups.ts 与 extensions/cornerstone/src/customizations/segmentationToolbarCustomization.ts 中继续深入研读。

【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询