OHIF 工具栏实战:从第一笔测量到源码级定制的完整操作路径
【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers
OHIF Viewer 顶部工具栏把测量、窗宽窗位、截图导出与布局切换收进了一条数据驱动的横栏。本篇用 4 个真实场景串起操作流程,并拆出命令与 evaluate 背后的机制。
📏 场景一:打开检查后做第一笔测量
激活 Length 工具,在图像上点两点,线性距离立刻以 mm 显示。工具栏左侧的 Length 按钮是测量组入口,点击激活后在断层图像上点取两个端点,数值以黄色标尺形式呈现,单位为 mm。
用工具旁的下拉箭头切换其他测量工具。列表默认顺序为:Length、Bidirectional、ArrowAnnotate(注释)、EllipticalROI、RectangleROI、CircleROI、PlanarFreehandROI、SplineROI、LivewireContour。常用几项速查:
| 工具 | 用途 | 结果单位 |
|---|---|---|
| Length | 两点直线距离 | mm |
| Bidirectional | 最长径与最长垂直径 | mm(双值) |
| EllipticalROI | 椭圆 ROI 面积与平均 CT 值 | mm² + HU |
| RectangleROI / CircleROI | 区域面积测量 | mm² + HU |
| CalibrationLine | 校正 Pixel Spacing | mm/pixel |
测量值对不上标尺怎么办?多半是 DICOM 的 Pixel Spacing 元数据缺失或错误——通过下拉菜单切换到 Calibration 工具,沿已知长度的结构画一条线并填入真实尺寸即可完成校准,上面所有长度类工具此后都会按校准后的像素间距计算。
源码层面,这些测量按钮的commands都指向 setToolActiveToolbar:这一条命令把激活事件同时广播给default、mpr、SRToolGroup、volume3d四个工具组,无论当前聚焦哪个视口,工具都能生效。下拉列表的排布则由toolbarSections中的MeasurementTools分区决定(toolbarButtonsCustomization.ts)。
🎚️ 场景二:图像对比度不对,窗宽窗位怎么调
手动微调:直接在图像上点击并拖拽。激活 Window/Level 工具后,在图像上水平拖拽调整窗位(window level),垂直拖拽调整窗宽(window width),对比度随拖拽实时变化,拖到感兴趣结构清晰后松手即可。
一键预设:用下拉箭头展开预设菜单。默认预设分 CT 与 PT 两组:CT 提供 Soft tissue(400/40)、Lung(1500/-600)、Liver(150/90)、Bone(2500/480)、Brain(80/40),PT 提供 SUV 3 到 SUV 15 的一列档位,数值即窗宽/窗位,点击即生效。预设数据定义在 defaultWindowLevelPresets.ts,并且可以通过 customization service 覆盖或追加——给本院加一个自定义"骨窗",只是改一份数据的事。
Window/Level 家族其实由多个按钮组成:工具栏主按钮WindowLevel、视口左下角的windowLevelMenu、视口底部中间内嵌的windowLevelMenuEmbedded,以及高级手动菜单voiManualControlMenu,都挂在AdvancedRenderingControls分组下(见 toolbarButtonsCustomization.ts)。注意WindowLevel按钮通过evaluate.viewport.supported排除了wholeSlide与 ECG 类视口,这些视口上会自动禁用。
📸 场景三:导出截图与切换多视口布局
截图导出:点相机图标弹出配置弹窗。Capture 按钮位于工具栏中部,就是相机造型的那个图标。
点击后由showDownloadViewportModal命令弹出下载弹窗(见 commandsModule.ts),弹窗中可配置:
- 文件名(默认
image) - 文件格式:JPG / PNG 等
- 图像宽高:单位 px,上限 10000(受
MAX_TEXTURE_SIZE限制) - Include annotations:是否把已有测量标注画进图里
- Include warning message:是否附带 "Not For Diagnostic Use" 水印
最后点击Save Image下载到本地,或点击Copy to Clipboard直接复制进剪贴板。弹窗组件见 captureViewportModalCustomization.tsx。再看按钮定义(toolbarButtonsCustomization.ts):evaluate.viewport.supported排除了video与wholeSlide,视频视口和 WSI 全切片视口因此拿不到这个导出能力。
布局切换:工具栏上的 Layout Selector。点击网格图标打开布局菜单:common 区提供 1×1、1×2、2×2、2×3 四种常用格(定义在 layoutSelectorCustomization.ts),advanced 区则根据当前检查的悬挂协议与 display set 动态生成可选组合。网格上限由按钮上的rows: 3, columns: 4参数控制(toolbarButtonsCustomization.ts),也就是最多可自由切出 3 行 × 4 列的视口网格。
🧭 场景四:用"更多工具"菜单做视图微调
工具栏最右侧的 More Tools 菜单聚合了一批视图操作工具,选中某项后它会保持激活,直到你点选其他工具替换:
| 工具 | 绑定命令 | 作用 |
|---|---|---|
| Reset View | resetViewport | 位置、缩放、窗宽窗位一次还原 |
| Rotate Right | rotateViewportCW | 图像顺时针转 90° |
| Flip Horizontal | flipViewportHorizontal | 水平镜像翻转 |
| Image Slice Sync | toggleSynchronizer(type:imageSlice) | 多视口位置联动 |
| Stack Scroll | setToolActiveToolbar | 序列滚动工具 |
| Invert | invertViewport | 灰度反色 |
| Cine | toggleCine | 逐帧播放,再点一次关闭 |
| Probe / Angle / Magnify | setToolActiveToolbar | 探针取像素值 / 角度 / 放大镜 |
几个使用细节:
- Magnify:激活后点击图像某区域打开放大镜窗口,放大区域细节实时显示;
- Cine:播放开始后点击播放器上的
x,或再次点击工具按钮即可退出; - Flip Horizontal同时挂了
evaluate.viewportProperties.toggle与evaluate.viewport.supported(排除video、volume3d),在 3D 视口上自动置灰; - Rotate Right只排除
video,2D 堆栈与 MPR 都能用。
这份菜单的完整排布见MoreTools分区(toolbarButtonsCustomization.ts)。
给开发者:按钮即数据
整条工具栏没有写死任何一个按钮,机制分三层。
数据层(按钮定义):cornerstone 扩展注册了两个能力包——cornerstone.toolbarButtons(全部按钮定义)与cornerstone.toolbarSections(分区到按钮 ID 的映射),见 toolbarButtonsCustomization.ts。以flipHorizontal为例,核心只有两个字段:
props: { commands: 'flipViewportHorizontal', evaluate: ['evaluate.viewportProperties.toggle', { name: 'evaluate.viewport.supported', unsupportedViewportTypes: ['video', 'volume3d'] }], }commands声明点击时执行什么命令,evaluate声明按钮该不该用、该不该显示。数组形式表示多个求值器并联,任一判 disabled 则按钮整体禁用,合并逻辑在 ToolbarService.ts 的handleEvaluate里。对比Capture按钮(toolbarButtonsCustomization.ts):commands换成showDownloadViewportModal,evaluate.viewport.supported排除video/wholeSlide——同一套按钮结构,因此可以无差别地投放在 2D 堆栈、MPR、3D 容积、视频、WSI 各类视口上,不需要为每种视口单独写分支。
组合层(模式装配):Basic 模式在 index.tsx 里没有写任何按钮实体,只有toolbarSections: [{ $reference: 'cornerstone.toolbarSections' }]与toolbarButtons: [{ $reference: 'cornerstone.toolbarButtons' }]两条引用。进入模式时,modeCustomization.ts 的registerModeToolbar负责调toolbarService.register()注册按钮、逐个updateSection()装配分区。这也解释了为什么?customization=模块能仅靠$push追加按钮,而不必重建整个模式。
渲染层(UI 组件):Toolbar.tsx 通过useToolbarhook 拿到指定分区(默认primary)的按钮列表,为每个按钮注入isOpen、isLocked、onOpen、onToggleLock等交互状态,再按uiType(ohif.toolButton、ohif.layoutSelector、ohif.toolButtonList等)渲染成组件。ToolbarService的refreshToolbarState(ToolbarService.ts)会在视口切换、状态变化时重算所有 evaluate——modalityLoadBadge、trackingStatus这类按钮随数据出现或消失,靠的就是这条刷新链路。
收尾:一个设计思想贯穿整条工具栏
OHIF 的工具栏本质是"按钮即数据 + 注册进服务"的两段式设计:命令绑定在数据上,可用状态交给 evaluate 计算。你要为特定临床工作流做定制查看器时,改一份toolbarSections、用$reference/$push追加按钮即可,渲染组件一行不用碰。
【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考