OHIF 视口叠加层(Viewport Overlay)定制指南:四个角落的信息自定义实现与源码解析
【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers
本指南讲解 OHIF Viewer 中 Viewport Overlay(视口叠加层)的定制机制——即显示在医学影像视口四个角落(左上、右上、左下、右下)的文字信息,如何通过customizationService在window.config中按需增删改查。读完本文,你将掌握四个定制端点的作用、默认项结构、$set/$push/$splice等配置操作符的用法,以及底层渲染组件和 overlay item 的完整实现原理。
Viewport Overlay 是什么
Viewport Overlay 是渲染在视口(Viewport)之上的信息层,用于在不遮挡影像主体的情况下,向用户展示当前影像的关键元数据,例如检查日期、序列描述、窗宽窗位(W/L)、缩放级别、实例编号等。这些信息以绝对定位的<div>覆盖在影像画布四角,且默认不响应鼠标事件(pointer-events-none),不会干扰影像操作。
根据 官方文档,OHIF 提供4 个视图叠加层定制端点(customization end points):
viewportOverlay.topRightviewportOverlay.topLeftviewportOverlay.bottomLeftviewportOverlay.bottomRight
每个端点对应视口的一个角落,其值是一个overlay item 数组。你可以通过定制服务(CustomizationService)单独替换任意一个角落的内容,也可以只对默认项做局部增删。
四个角落的默认内容
官方 示例定制文档 与 cornerstone 扩展源码 中给出了一致的默认值,汇总如下:
| 定制端点 | 默认内容 | 说明 |
|---|---|---|
viewportOverlay.topLeft | StudyDate(检查日期)、SeriesDescription(序列描述) | 通过contentF从referenceInstance提取并格式化 |
viewportOverlay.topRight | [](空) | 默认不显示任何内容 |
viewportOverlay.bottomLeft | WindowLevel(窗宽窗位)、ZoomLevel(缩放级别) | ZoomLevel仅在当前活动工具为 Zoom 时显示 |
viewportOverlay.bottomRight | InstanceNumber(实例编号) | 显示当前实例在序列中的序号 |
以viewportOverlay.topLeft为例,默认两个 item 的定义如下(来自 viewportOverlayCustomization.tsx):
'viewportOverlay.topLeft': [ { id: 'StudyDate', inheritsFrom: 'ohif.overlayItem', label: '', title: 'Study date', condition: ({ referenceInstance }) => referenceInstance?.StudyDate, contentF: ({ referenceInstance, formatters: { formatDate } }) => formatDate(referenceInstance.StudyDate), }, { id: 'SeriesDescription', inheritsFrom: 'ohif.overlayItem', label: '', title: 'Series description', condition: ({ referenceInstance }) => { return referenceInstance && referenceInstance.SeriesDescription; }, contentF: ({ referenceInstance }) => referenceInstance.SeriesDescription, }, ],可以看到,默认内容由一组 item 组成,每个 item 都基于ohif.overlayItem这一基础定制项派生而来。
Overlay Item 的字段结构与渲染规则
每个 overlay item 本质上是一个“声明式组件描述”,核心渲染逻辑集中在 overlayItemCustomization.tsx 的ohif.overlayItem中。它支持的字段包括:
| 字段 | 类型 | 作用 |
|---|---|---|
id | string | item 的唯一标识,用于定位和后续操作 |
inheritsFrom | string | 继承的基础定制项 ID,如ohif.overlayItem、ohif.overlayItem.windowLevel |
attribute | string | 直接从instance上读取的 DICOM 属性名(如PatientName),有值时优先于contentF |
condition | function | 接收props,返回false时该 item 不渲染(不满足条件则返回null) |
contentF | function | 计算显示内容的函数,接收props并返回字符串 |
label | string | 显示在内容前的标签文本,如'PN:'、'Patient' |
title | string | HTMLtitle属性,鼠标悬停时的提示文字 |
color | string | 文字颜色(CSS 颜色值),如'yellow' |
底层渲染逻辑(见 overlayItemCustomization.tsx)依次执行以下步骤:
- 若定义了
condition且返回假值,直接不渲染该 item; - 若定义了
attribute且instance存在,则取instance[attribute]作为原始值;否则调用contentF(props)计算值; - 用
utils.formatValue格式化最终值,若格式化后为空则返回null; - 渲染为一个
<span>,应用label、title与color。
'ohif.overlayItem': function (props) { if (this.condition && !this.condition(props)) { return null; } const { instance } = props; const value = instance && this.attribute ? instance[this.attribute] : this.contentF && typeof this.contentF === 'function' ? this.contentF(props) : null; const displayValue = utils.formatValue(value); if (!displayValue) { return null; } return ( <span className="overlay-item flex flex-row" style={{ color: this.color || undefined }} title={this.title || ''} > {this.label && <span className="mr-1 shrink-0">{this.label}</span>} <span className="font-light">{displayValue}</span> </span> ); },除基础ohif.overlayItem外,cornerstone 扩展还注册了三种派生 item,用于渲染特殊内容(见 CustomizableViewportOverlay.tsx):
ohif.overlayItem.windowLevel:渲染窗宽窗位,如W: 1892 L: 1048;ohif.overlayItem.zoomLevel:渲染当前缩放级别,通常配合活动工具判断(toolGroupService.getActiveToolForViewport);ohif.overlayItem.instanceNumber:渲染实例编号。
通过 window.config 定制叠加层
叠加层定制与其他 CustomizationService 定制一样,通过应用配置文件中的customizationService数组完成。下面给出四种常见操作。
1. 用$set整体替换(topRight)
viewportOverlay.topRight默认是空数组,用$set可以整体注入自定义 item(参考 sampleCustomizations.tsx):
window.config = { // rest of window config customizationService: [ { 'viewportOverlay.topRight': { $set: [ // Add your overlay items here, e.g.: // { id: 'CustomOverlay', inheritsFrom: 'ohif.overlayItem.custom' }, ], }, }, ], };$set会以传入的新数组整体替换该端点的默认数组。
2. 用$splice删除默认项(topLeft)
如果不希望左上角显示StudyDate,可以在viewportOverlay.topLeft上使用$splice,按索引删除第一个元素(参考 sampleCustomizations.tsx):
window.config = { // rest of window config customizationService: [ { 'viewportOverlay.topLeft': { $splice: [ [0, 1], // Remove 1 item starting at index 0 (removes StudyDate) ], }, }, ], };3. 用$push追加新 item(bottomLeft)
在左下角追加一个自定义的黄色患者姓名叠加项PatientNameOverlay(参考 sampleCustomizations.tsx):
window.config = { // rest of window config customizationService: [ { 'viewportOverlay.bottomLeft': { $push: [ { id: 'PatientNameOverlay', inheritsFrom: 'ohif.overlayItem', attribute: 'PatientName', label: 'PN:', title: 'Patient Name', color: 'yellow', condition: ({ instance }) => instance && instance.PatientName && instance.PatientName.Alphabetic, contentF: ({ instance, formatters: { formatPN } }) => formatPN(instance.PatientName.Alphabetic) + ' ' + (instance.PatientSex ? '(' + instance.PatientSex + ')' : ''), }, ], }, }, ], };这个例子综合演示了多个字段:attribute用于直接取 DICOM 标签PatientName,label提供PN:前缀,color设为黄色,condition在影像没有姓名信息时隐藏该 item,contentF则利用formatPN格式化姓名并附加性别信息。
4. 条件显示的 ZoomLevel 默认项
默认的ZoomLevelitem 演示了“根据上下文决定是否显示”的典型写法(见 viewportOverlayCustomization.tsx):
{ id: 'ZoomLevel', inheritsFrom: 'ohif.overlayItem.zoomLevel', condition: props => { const activeToolName = props.toolGroupService.getActiveToolForViewport(props.viewportId); return activeToolName === 'Zoom'; }, },condition接收的props中包含toolGroupService、viewportId、instance、referenceInstance、formatters等运行上下文,开发者可以据此实现任意的显示逻辑。
源码级原理:叠加层如何被读取与渲染
叠加层的实际渲染由 cornerstone 扩展的 CustomizableViewportOverlay.tsx 组件完成。其核心流程如下:
- 从
servicesManager.services中取出customizationService、toolGroupService、displaySetService、cornerstoneViewportService; - 分别调用
customizationService.getCustomization('viewportOverlay.topLeft')等四个方法读取四个角落的定制数组(对应源码); - 通过
displaySetService与当前imageIndex组装出displaySetProps,其中包含instance(当前实例)与referenceInstance(参考实例),这些正是 overlay item 的condition/contentF所依赖的数据(对应源码); - 监听 Cornerstone 的相机/注释事件(如缩放变化、超声 Pleura B-line 标注修改)以刷新 scale、VOI 与叠加层内容(对应源码);
- 将每个 item 与
OverlayItemComponents中的对应组件(ohif.overlayItem、ohif.overlayItem.windowLevel等)匹配,渲染到对应角落的容器中。
源码注释还特别指出:早期版本将四个角落定义成独立 item,由于当时缺少追加(append)能力才这样做;现在推荐直接向cornerstoneOverlay的默认数组中追加/修改,而不是为单个角落定义独立 item(见 CustomizableViewportOverlay.tsx)。不过四个viewportOverlay.*端点依然保持向后兼容,可继续使用。
此外,微缩显微镜(dicom-microscopy)扩展提供了一套较早的、基于config对象的叠加层生成方式:generateFromConfig接收{ topLeft, topRight, bottomLeft, bottomRight }四组 item 列表,直接渲染到对应角落(见 index.tsx),其 item 字段使用value/contents函数而非contentF,供以config驱动叠加层的扩展参考。
实战案例:兽医影像的运行时叠加层定制
仓库提供了一个完整、可直接对照的实战案例——兽医影像(veterinary)叠加层,文件位于 veterinaryOverlay.jsonc。该文件演示了:
- 用
global顶层结构承载定制; - 用
$set整体替换viewportOverlay.topLeft与viewportOverlay.topRight; - 每个 item 均使用
inheritsFrom: 'ohif.overlayItem',与 cornerstone 扩展默认项写法保持一致; - 通过
attribute直接绑定兽医专用 DICOM 标签(如PatientSpeciesDescription、PatientBreedDescription)。
{ "global": { "viewportOverlay.topLeft": { "$set": [ { "id": "PatientName", "inheritsFrom": "ohif.overlayItem", "attribute": "PatientName", "label": "Patient", "title": "Patient name" }, { "id": "PatientID", "inheritsFrom": "ohif.overlayItem", "attribute": "PatientID", "label": "ID", "title": "Patient ID" }, { "id": "StudyDate", "inheritsFrom": "ohif.overlayItem", "attribute": "StudyDate", "label": "Date", "title": "Study date" } ] }, "viewportOverlay.topRight": { "$set": [ { "id": "PatientSpecies", "inheritsFrom": "ohif.overlayItem", "attribute": "PatientSpeciesDescription", "label": "Species", "title": "Patient species" }, { "id": "PatientBreed", "inheritsFrom": "ohif.overlayItem", "attribute": "PatientBreedDescription", "label": "Breed", "title": "Patient breed" } ] } } }根据文件头注释,该定制支持通过 URL 参数?customization=veterinary/veterinaryOverlay在运行时动态加载(CustomizationService 的 URL 处理机制),这为多院区、多科室按需切换叠加层布局提供了轻量方案。
小结与排查建议
定制 Viewport Overlay 只需记住三条主线:
- 四个端点各管一个角落:
viewportOverlay.topLeft/topRight/bottomLeft/bottomRight,值均为 item 数组; - item 是声明式组件:基于
inheritsFrom: 'ohif.overlayItem'派生,可用attribute、contentF、condition、label、title、color精确控制内容与显隐; - 定制操作符决定增删改方式:
$set整体替换、$push追加、$splice按索引删除、$merge合并属性。
排查叠加层不生效的问题时,可以从 CustomizableViewportOverlay.tsx 的getCustomization读取逻辑入手,确认四个端点定制是否被正确加载;若自定义 item 始终不显示,请检查condition是否返回了假值、contentF/attribute是否能取到非空值,以及formatValue格式化后的结果是否为空——这三处是 ohif.overlayItem 渲染链路上最常见的“静默跳过”原因。
【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考