ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

OHIF v3 Toolbar 模块开发指南:组件注册、评估器(Evaluator)与工具箱(Toolbox)实战

OHIF v3 Toolbar 模块开发指南:组件注册、评估器(Evaluator)与工具箱(Toolbox)实战 OHIF v3 Toolbar 模块开发指南组件注册、评估器Evaluator与工具箱Toolbox实战【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers本文以 OHIF 官方文档《Module: Toolbar》为主线结合当前仓库中ohif/extension-default、ohif/extension-cornerstone以及ohif/core的 ToolbarService 源码系统讲解 Toolbar Module 的注册机制、evaluate评估器的返回值契约与组合策略、按钮 Section 的组织方式以及useToolbarHook 与Toolbox组件的二次开发实战。读完本文你将能够为自己的扩展注册自定义按钮组件、编写三态等高级评估器并在 Mode 中把工具栏按钮装配到主工具栏或任意自定义面板中。在 OHIF v3 的扩展体系中Toolbar Module工具栏模块是扩展对外提供按钮外观与按钮状态判定逻辑的标准出口。它与 ToolbarService负责按钮注册、Section 组织与命令执行以及 Mode模式负责消费这些按钮三者配合构成了完整的工具栏闭环。理解 Toolbar Module 是进行 OHIF 二次开发、定制自己医学影像工作流 UI 的第一步。一、Toolbar Module 是什么getToolbarModule导出契约一个扩展通过定义getToolbarModule方法来注册 Toolbar Module。ohif/extension-default默认扩展提供了若干内置按钮uiTypes其中最基础的三种为ohif.radioGroup可点击的简单按钮radio 型按钮ohif.splitButton带下拉菜单的按钮ohif.divider简单的分隔线。在当前仓库中默认扩展的实际实现位于 extensions/default/src/getToolbarModule.tsx它注册的uiTypes已扩充为ohif.toolButton、ohif.toolButtonList、ohif.row、ohif.toolBoxButtonGroup、ohif.toolBoxButton新版按钮系列ohif.layoutSelector布局选择器、ohif.progressDropdown进度下拉、ohif.Toolbar整体工具栏容器以及evaluate.cineCine 播放评估器依据cineService.getState().isCineEnabled返回开关样式。值得注意的是Toolbar Module 返回的每一项既可以是组件定义defaultComponent也可以是评估器定义evaluate。两者混排在同一个数组中由 ToolbarService 统一登记。二、注册按钮组件ComponentsToolbar Module 返回一个objects数组。最简单的注册方式如下export default function getToolbarModule({ commandsManager, servicesManager }) { return [ { name: ohif.radioGroup, defaultComponent: ToolbarButton, clickHandler: () {}, }, { name: ohif.splitButton, defaultComponent: ToolbarSplitButton, clickHandler: () {}, }, { name: ohif.layoutSelector, defaultComponent: ToolbarLayoutSelector, clickHandler: (evt, clickedBtn, btnSectionName) {}, }, { name: ohif.toggle, defaultComponent: ToolbarButton, clickHandler: () {}, }, ]; }自定义组件Custom Components你完全可以创建自己的扩展并注册全新的按钮外观例如将 split tool 从垂直改为水平方向。只需在扩展中加入getToolbarModule并在返回对象中把defaultComponent指向自己的 React 组件import myToolComponent from ./myToolComponent; export default function getToolbarModule({ commandsManager, servicesManager }) { return [ { name: new-tool-type, defaultComponent: myToolComponent, clickHandler: () {}, }, ]; }组装自有组件时可以直接复用ohif/ui提供的IconButton、Icon、Tooltip、ToolbarButton等基础部件。组件被消费的具体流程是在 ToolbarService.ts 的_mapButtonToDisplay中服务会通过_getButtonUITypes()汇总所有扩展的toolbarModule注册项然后根据按钮定义里的uiType找到对应的defaultComponent并挂到按钮上若找不到组件会输出警告Neither button type nor a component found for button: ${id}。这一查找逻辑与文档描述一致Toolbar Module 只负责提供真正把按钮组装到工具栏上是在 Mode 中完成的。三、评估器Evaluators按钮状态的裁判按钮可以配备评估器evaluator——它们是 ToolbarService 调用、用于评估按钮状态的函数必须返回一个包含{ className }的对象并可附带更多字段。评估器的核心价值在于根据当前视口viewport动态决定按钮是否可用。典型的场景包括当 displaySet 不可重建not reconstructable时限制用户点击 MPR 相关按钮或某些按钮与特定 toolGroup 绑定在特定视口下应保持不激活。下面以 cornerstone 扩展中最常用的evaluate.cornerstoneTool为例完整实现见 extensions/cornerstone/src/getToolbarModule.tsx{ name: evaluate.cornerstoneTool, evaluate: ({ viewportId, button, toolNames, disabledText }) { const toolGroup toolGroupService.getToolGroupForViewport(viewportId); if (!toolGroup) { return; } const toolName toolbarService.getToolNameForButton(button); if (!toolGroup || (!toolGroup.hasTool(toolName) !toolNames)) { return getDisabledState(disabledText); // { disabled: true, disabledText } } const isPrimaryActive toolNames ? toolNames.includes(toolGroup.getActivePrimaryMouseButtonTool()) : toolGroup.getActivePrimaryMouseButtonTool() toolName; return { disabled: false, isActive: isPrimaryActive, }; }, },该评估器的工作方式是先通过toolGroupService.getToolGroupForViewport(viewportId)拿到视口对应的工具组再通过toolbarService.getToolNameForButton(button)解析按钮要激活的工具名优先读取命令commandOptions.toolName回退到按钮 id见 ToolbarService.ts 的getToolNameForButton最后检查工具是否存在于工具组、以及是否是主鼠标键当前激活的工具。3.1 评估器可返回的字段评估器可以返回多种信息ToolbarService 在 刷新逻辑refreshToolbarState中会将评估结果合并进按钮 props字段说明disabled布尔值表示按钮是否应被禁用disabledText当disabled为true时显示的文字/提示visible布尔值是否显示按钮是基于自定义逻辑强制隐藏按钮的便捷手段isActive布尔值按钮当前是否处于激活状态className添加到按钮组件上的自定义 CSS 类名3.2 内置评估器清单OHIF 与 cornerstone 扩展合计提供了以下内置评估器前五项出自官方文档evaluate.viewport.supported、evaluate.modality.supported等在 cornerstone 扩展源码中有完整实现evaluate.cornerstoneTool按钮随当前视口的 toolGroup 状态变化例如工具不在工具组中时禁用evaluate.cornerstoneTool.toggle面向具有开关行为的工具如参考线、图像叠加层的显示/隐藏。实现上通过_evaluateToggle检查工具模式是否为Disabled/Passive并借助utils.getToggledClassName生成开关样式源码位置evaluate.cornerstone.synchronizer依据视口的同步器synchronizer状态判断按钮是否处于已同步状态源码位置evaluate.viewportProperties.toggle针对视口可切换属性invert、flip、rotate 等按钮外观随当前视口属性动态变化源码位置evaluate.mprMPR 专用评估器需要检查 displaySet 是否可重建当前仓库中以evaluate.displaySetIsReconstructable形式实现见 extensions/cornerstone/src/getToolbarModule.tsxevaluate.viewport.supported与evaluate.modality.supported见下一节。3.3 视口类型与模态Modality支持评估工具栏系统现在采用更稳健的方式基于视口类型与模态评估按钮状态。视口类型支持evaluate.viewport.supported禁用特定视口类型上的按钮。其实现会读取cornerstoneViewportService.getCornerstoneViewport(viewportId)的viewport.type与unsupportedViewportTypes比对源码位置{ name: evaluate.viewport.supported, unsupportedViewportTypes: [volume3d, video, sm], }模态支持evaluate.modality.supported按模态控制按钮状态。实现会取视口内所有 displaySet 的Modality字段做包含/排除判断源码位置{ name: evaluate.modality.supported, supportedModalities: [CT, MR], // 仅在这些模态下启用 // 或者 unsupportedModalities: [US], // 在这些模态下禁用 }注意unsupportedModalities是排除式匹配displaySet 中存在任一模态即禁用supportedModalities是包含式匹配存在任一支持的模态即启用两者可同时使用默认禁用文案为Buttons:Tool not available for this modality。3.4 组合评估器Composing Evaluators一个按钮可以挂多个评估器这在需要按多种条件联合判定时非常有用。例如防止 Cine 播放器出现在 3D 视口上evaluate: [ evaluate.cine, { name: evaluate.viewport.supported, unsupportedViewportTypes: [volume3d], }, ],当evaluate是数组时ToolbarService 的handleEvaluate会逐个解析每个条目字符串按名字查找对象则取出name并把其余字段作为选项混入评估参数把所有评估结果用 reduce 合并成一个对象只要有一个评估器返回disabled: true最终按钮即为禁用态。还可以组合出更复杂的评估器。tmtv 模式的RectangleROIStartEndThreshold工具使用的就是evaluate: [ evaluate.cornerstone.segmentation, // 必须把 disabledText 放在最后一个评估器因为每个评估器的文本会被合并进最终结果 { name: evaluate.cornerstoneTool, disabledText: Select the PT Axial to enable this tool, }, ],这里第一个评估器evaluate.cornerstone.segmentation确保已经创建了分割segmentation第二个evaluate.cornerstoneTool确保工具在当前视口可用。由于多个评估器的disabledText会合并进最终结果必须把disabledText放在最后一个评估器上。当多个按钮复用一个评估器但参数不同时可以采用对象形式传入额外属性。例如分割剪刀类工具{ name: evaluate.cornerstone.segmentation, toolNames: [CircleBrush, SphereBrush], }3.5 组评估器Group EvaluatorsSplit 按钮如何定义见 ToolbarService 文档可以配备组评估器group evaluator用于决定用户交互后哪个按钮应被提升到 primary 区。官方提供两个也支持自定义evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList检查 cornerstone 工具状态若该工具未在按钮列表中激活则把按钮提升到 primary 区evaluate.group.promoteToPrimary忽略 cornerstone 工具状态无条件把按钮提升到 primary 区。若未指定组评估器则不会有任何动作按钮将停留在 secondary 区。3.6 自定义评估器你可以自由编写自己的评估器。文档给出的典型例子是设计三态按钮——例如视口叠加层Overlay的 Show All / Show Some / Show None 三种状态。实现方式是在自己的getToolbarModule中返回一个{ name, evaluate }对象之后即可像内置评估器一样通过name在按钮定义中引用在 ToolbarService 中若按名字找不到评估函数会抛出Evaluate function not found for name: ...错误并提示可通过扩展的getToolbarModule注册。四、在 Mode 中消费按钮注册与 Section 组织只提供组件是不够的还需要把按钮加入 ToolbarService并决定每个 Section 使用哪些按钮。下面是一个简化版的longitudinalbasic viewer模式示例——通过ToolBarService.addButtons(toolbarButtons)添加按钮toolbarButtons是toolDefinitions数组function modeFactory({ modeConfiguration }) { return { id: viewer, displayName: Basic Viewer, onModeEnter: ({ servicesManager, extensionManager }) { const { toolBarService } servicesManager.services; toolbarService.addButtons([...toolbarButtons, ...moreTools]); toolbarService.createButtonSection(primary, [ MeasurementTools, Zoom, info, WindowLevel, Pan, Capture, Layout, Crosshairs, MoreTools, ]); }, routes: [ { path: longitudinal, layoutTemplate: ({ location, servicesManager }) { return { /* */ }; }, }, ], }; }API 演进提示当前仓库中addButtons与createButtonSection已标记为deprecated会输出控制台警告官方推荐使用register()与updateSection()替代见 ToolbarService.ts 与 ToolbarService.ts。新版register(buttons, replace)还支持buttonSection: true的快捷写法——当按钮 props 中buttonSection为布尔值true时服务会自动把该属性替换为按钮自身的 id源码位置。实际上ohif/extension-default的默认布局extensions/default/src/ViewerLayout/index.tsx在所有 Mode 中都会使用一个 Toolbar 组件来创建primarySection这正是上面示例创建primarySection 的原因。布局同样可定制你可以在自己的扩展中实现getLayoutTemplateModule模块并供 Mode 使用。默认情况下使用ohif/extension-default.layoutTemplateModule.viewerLayout它提供Header左侧 Logo、中间工具栏、右侧用户菜单左侧面板主视口网格区域右侧面板。以当前仓库的 basic 模式为参考其内部通过 modes/basic/src/modeCustomization.ts 中的registerModeToolbar把按钮列表注册进 toolbarService并按toolbarSections配置逐个调用updateSection按钮清单与 Section 配置本身来自 cornerstone 扩展的定制数据cornerstone.toolbarButtons/cornerstone.toolbarSections见 modes/basic/src/index.tsx具体按钮定义在 extensions/cornerstone/src/customizations/toolbarButtonsCustomization.ts。五、替代工具栏 SectionuseToolbarHook 与Toolbox组件除了主工具栏你还可以在自定义 UI 组件如面板中引入一个工具栏 Section 模板之后再往里面添加按钮。只需使用useToolbarHook 即可保证按钮被正确添加、正确响应交互、正确评估状态。该 Hook 暴露onInteraction函数和toolbarButtons数组你可以按需定制 UI完整实现见 platform/core/src/hooks/useToolbar.tsxfunction myCustomPanel({ servicesManager }) { const { onInteraction, toolbarButtons } useToolbar({ servicesManager, buttonSection: myCustomSectionName, }); // 把按钮映射到 UI return ( div {toolbarButtons.map((button, index) { return ( button key{index} onClick{() onInteraction(button)} {button.label} /button ); })} /div ); }useToolbar内部会订阅TOOL_BAR_MODIFIED与TOOL_BAR_STATE_MODIFIED事件刷新按钮列表并监听ACTIVE_VIEWPORT_ID_CHANGED、VIEWPORTS_READY、LAYOUT_CHANGED等视口网格事件在活跃视口变化时调用toolbarService.refreshToolbarState({ viewportId })重估所有按钮onInteraction则最终路由到toolbarService.recordInteraction见 platform/core/src/hooks/useToolbar.tsx。OHIF 还提供了一个通用的Toolbox容器组件用于承载一组工具栏工具。它配合useToolbarHook通过 Context API 管理工具状态、处理用户交互并记忆选项。Toolbox集成非常简单只需传入服务与配置参数buttonSectionId、titlefunction MyApplication({ servicesManager, commandsManager }) { // 工具箱容器配置 const config { servicesManager, commandsManager, buttonSectionId: customButtonSection, title: My Toolbox, }; return Toolbox {...config} /; }随后在 Mode 中即可向该 Section 注入工具onModeEnter: ({ servicesManager, extensionManager }) { const { toolBarService } servicesManager.services; toolbarService.addButtons([...toolbarButtons, ...moreTools]); toolbarService.createButtonSection(customButtonSection, [ MeasurementTools, Zoom, info, ]); },5.1 典型场景点击按钮弹出工具选项 Modal另一个常见需求是点击按钮后弹出一个 Modal 展示工具选项。首先在 Mode 中定义按钮// Mode 中的 ToolbarButton { id: Others, uiType: ohif.radioGroup, props: { icon: info-action, label: Others, commands: showOthersModal, }, },在 Mode factory 中把按钮加入 Section// 把 Others 按钮加入 primary 区 toolbarService.createButtonSection(primary, [ Others, // -------- 这里 ]); // 把 shapes 按钮加入 other 区 toolbarService.createButtonSection(other, [Shapes]);这里使用了showOthersModal命令它定义在扩展的 commandsModule 中// 在扩展的 commandsModule 内 showOthersModal: () { const { uiModalService } servicesManager.services; uiModalService.show({ content: OthersModal, title: Others, customClassName: w-8, movable: true, contentProps: { onClose: uiModalService.hide, servicesManager, commandsManager, }, containerDimensions: h-[125px] w-[300px], contentDimensions: h-[125px] w-[300px], }); },Modal 内容组件内部放置Toolbox从ohif/ui导入并将buttonSectionId指向other// Others modal import { Toolbox } from ohif/ui; function OthersModal({ servicesManager, commandsManager }) { return ( div classNamepx-2 Toolbox buttonSectionId{other} commandsManager{commandsManager} servicesManager{servicesManager} title{other} useCollapsedPanel{false} /Toolbox /div ); }最终效果是点击Others按钮后弹出一个内含工具箱的 Modal且其状态会自动与 ToolbarService 同步运行效果即前文第一张配图。六、Toolbox 带选项Toolbox With Options工具箱中的按钮可以携带选项options这对需要调整参数的进阶工具非常有用——例如笔刷brush需要切换笔刷大小或 2D/3D 模式。注意带选项的 Toolbox 会在组件挂载mount时执行选项命令commands这有助于设置工具箱的初始状态。目前支持三种选项类型。6.1 Radio 选项单选用于分割形状工具让用户在三种裁剪模式间选择{ id: Shapes, uiType: ohif.radioGroup, props: { label: Shapes, evaluate: { name: evaluate.cornerstone.segmentation, toolNames: [CircleScissor, SphereScissor, RectangleScissor], }, icon: icon-tool-shape, options: [ { name: Shape, type: radio, value: CircleScissor, id: shape-mode, values: [ { value: CircleScissor, label: Circle }, { value: SphereScissor, label: Sphere }, { value: RectangleScissor, label: Rectangle }, ], commands: setToolActiveToolbar, }, ], }, },6.2 Range 选项滑块用于笔刷半径调整{ id: Brush, icon: icon-tool-brush, label: Brush, evaluate: { name: evaluate.cornerstone.segmentation, toolNames: [CircularBrush, SphereBrush], disabledText: Create new segmentation to enable this tool., }, options: [ { name: Radius (mm), id: brush-radius, type: range, min: 0.5, max: 99.5, step: 0.5, value: 25, commands: { commandName: setBrushSize, commandOptions: { toolNames: [CircularBrush, SphereBrush] }, }, }, ], },6.3 Custom 选项自定义组件tmtv 模式的RectangleROIThreshold使用了该模式通过字符串引用扩展注册的自定义选项组件{ id: RectangleROIStartEndThreshold, uiType: ohif.radioGroup, props: { icon: tool-create-threshold, label: Rectangle ROI Threshold, commands: setToolActiveToolbar, evaluate: { name: evaluate.cornerstoneTool, disabledText: Select the PT Axial to enable this tool, }, options: tmtv.RectangleROIThresholdOptions, }, },注意你有责任在扩展的getToolbarModule中提供tmtv.RectangleROIThresholdOptions这个选项组件定义。其解析逻辑在 ToolbarService 的handleEvaluate中当options是字符串时服务会到_getButtonUITypes()中按该名字查找defaultComponent并设置为optionComponent。选项变更时服务会调用选项的commands并携带{ ...option, value, options, servicesManager, commandsManager }运行见 ToolbarService.ts 的createEnhancedOptions。七、根据 Hanging Protocol 动态切换工具栏如果你希望工具栏随挂片协议hanging protocol变化可以订阅hangingProtocolService的PROTOCOL_CHANGED事件在回调中重建按钮 Sectionconst { unsubscribe } hangingProtocolService.subscribe( hangingProtocolService.EVENTS.PROTOCOL_CHANGED, () { toolbarService.createButtonSection(primary, [ MeasurementTools, Zoom, WindowLevel, ]); } );这样当协议切换时primary 区会立即按新配置重组。ToolbarService 还提供了updateSection合并追加不重复添加同 id 按钮与clearButtonSection清空 Section等配套 API可组合出更灵活的协议级工具栏策略见 ToolbarService.ts 与 ToolbarService.ts。八、源码级补充ToolbarService 的状态流转机制最后从源码层面总结一下工具栏的状态流转闭环核心代码均在 platform/core/src/services/ToolBarService/ToolbarService.ts注册register(buttons)/addButtons(buttons)将按钮定义写入state.buttons并广播TOOL_BAR_MODIFIED事件组织updateSection(key, buttons)/createButtonSection(key, buttons)将按钮 id 分组写入state.buttonSections交互用户点击按钮 →recordInteraction(interaction, options)运行按钮的commands通过commandsManager.run随后触发refreshToolbarState({ viewportId, itemId })重估所有按钮ToolbarService.ts评估refreshToolbarState遍历按钮将evaluate函数 / 字符串名 / 对象 / 数组统一解析为函数执行合并返回的disabled、disabledText、visible、isActive、className并通过hideWhenDisabled在evaluateProps或 props 层均可设置决定隐藏逻辑ToolbarService.ts渲染getButtonSection(sectionId)经_mapButtonToDisplay把按钮 id 映射为{ id, Component, componentProps }供useToolbar/Toolbox/ 主工具栏渲染。这套机制保证了一个按钮出现在多个 Section 中时状态完全同步得益于统一的评估系统也解释了文档中的一条建议不要忘记正确配置 toolGroups——按钮只是视觉界面交互时执行命令评估器则在交互后判定其状态二者都以 toolGroup 的配置为前提。延伸阅读ToolbarService 服务文档按钮定义basic / nested、listeners 监听与完整示例Mode 文档了解 Mode 如何消费扩展模块默认扩展的 Toolbar Module新版uiTypes与evaluate.cine的实际实现cornerstone 扩展的 Toolbar Module本文涉及的绝大多数内置评估器源码cornerstone 扩展的默认工具栏按钮定制真实按钮定义含buttonSection: true、hideWhenDisabled等用法。【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表