OHIF Study Browser 深度定制指南:StudyMode、缩略图细节与排序函数的全套配置方案
【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers
导读
Study Browser(研究浏览器)是 OHIF 查看器中用于浏览与管理影像研究(Study)的核心面板组件,用户在加载检查后通过它查看系列(Series)缩略图、切换列表/缩略图视图、对系列进行排序,并通过右键菜单执行打开 DICOM 标签浏览器、删除系列等操作。本文以官方文档 StudyBrowser.md 为骨架,结合sampleCustomizations.tsx中的studyBrowserCustomizations完整清单与 ui-next 源码实现,系统讲解 Study Browser 的 10 个可定制项:研究模式、视图预设、排序函数、缩略图右键菜单、缩略图详情行、命名取值源、命名测试、研究级右键菜单、双击回调与实例排序标准。读者学完后可独立完成从"仅看当前研究"到"自定义缩略图详情行并配 JSONC 数据文件"的完整定制。
一、Study Browser 是什么
官方文档对 Study Browser 的定义非常简洁:"The Study Browser is a component that allows users to browse and manage studies."——它是一个让用户浏览并管理研究的组件。
从源码看,该组件位于 platform/ui-next/src/components/StudyBrowser/StudyBrowser.tsx,其职责包括:
- 以 Tab(标签页)形式组织研究列表(
tabs与activeTabName),每个 Tab 下渲染多个StudyItem; - 提供视图切换(列表
list/ 缩略图thumbnails),由viewPresets中selected: true的项决定当前视图(StudyBrowser.tsx); - 顶部的设置栏渲染
<StudyBrowserViewOptions>(Tab 切换)与<StudyBrowserSort>(排序下拉与升降序切换); - 每个系列项支持单击展开、双击缩略图、缩略图右键菜单(
ThumbnailMenuItems)与研究级右键菜单(StudyMenuItems); - 缩略图下方有"详情行"(detail line),用于显示系列号、实例数等信息。
在 OHIF 中,Study Browser 通常作为模式(Mode)工作区的左侧/右侧面板出现,是工作流导航的关键入口。由于它承载了大量交互,OHIF 通过customizationService暴露了 10 个studyBrowser.*定制点,全部定义在 platform/docs/docs/platform/services/customization-service/sampleCustomizations.tsx 的studyBrowserCustomizations数组中。
二、定制机制基础:customizationService 与 immutability-helper 命令
所有 Study Browser 定制都通过window.config中的customizationService数组下发,核心语法基于 immutability-helper 的命令式更新。官方文档 customizationService.md 定义了六种命令,写配置前必须先掌握:
| 命令 | 作用 | 典型场景 |
|---|---|---|
$set | 整体替换某个值 | 替换整个数组/对象 |
$push | 向数组末尾追加元素 | 追加一个排序函数或详情行项 |
$unshift | 向数组头部插入元素 | 让自定义项排在最前 |
$splice | 在指定下标插入/替换/删除 | 精确调整数组顺序 |
$merge | 合并对象的部分字段 | 往命名源注册表里追加一个 key |
$apply | 用函数动态计算新值 | 基于现有数组做复杂变换 |
$filter | 递归查找匹配项并施加$merge/$set | 修改某个已有子项 |
配置统一放在 HTML 中的window.config里,结构为:
window.config = { // 其余 window config customizationService: [ { 'studyBrowser.xxx': { $set: /* 或 $push / $merge / $splice / $apply / $filter */, }, }, ], };$push会保留默认项并追加,$set会整体替换——在需要保留默认行为时优先用$push/$merge,在需要完全自定义时用$set。
三、研究模式:studyBrowser.studyMode
定制点:studyBrowser.studyMode默认值:'all'
该定制控制 Study Browser 显示哪些研究:是显示全部研究(含既往研究 prior studies),还是只显示当前研究。
| 取值 | 含义 |
|---|---|
'all'(默认) | 显示全部研究,包括既往研究 |
'primary' | 仅显示当前(主)研究 |
'recent' | 显示最近的研究 |
配置示例(sampleCustomizations.tsx):
window.config = { // rest of window config customizationService: [ { 'studyBrowser.studyMode': { $set: 'primary', // or recent }, }, ], };实际使用中,'primary'模式常用于需要屏蔽既往研究、聚焦当前检查的工作流(例如放疗或随访场景),避免医生被多个历史研究干扰。
四、视图预设:studyBrowser.viewPresets
定制点:studyBrowser.viewPresets默认值:
[ { id: 'list', iconName: 'ListView', selected: false }, { id: 'thumbnails', iconName: 'ThumbnailView', selected: true }, ]该定制定义 Study Browser 可用的视图模式(列表视图 / 缩略图视图)。selected: true的项为默认视图。从 StudyBrowser.tsx 的源码可以看到视图选择的真实逻辑:
const viewPreset = viewPresets ? viewPresets.filter(preset => preset.selected)[0]?.id : 'thumbnails';即:selected: true的预设的id会被当作当前视图传入每个StudyItem;若viewPresets为空则回退到'thumbnails'。
若想让列表视图成为默认视图,配置如下(sampleCustomizations.tsx):
window.config = { // rest of window config customizationService: [ { 'studyBrowser.viewPresets': { $set: [ { id: 'list', iconName: 'ListView', selected: true, // Makes the list view the default selected option }, { id: 'thumbnails', iconName: 'ThumbnailView', selected: false, }, ], }, }, ], };五、排序函数:studyBrowser.sortFunctions
定制点:studyBrowser.sortFunctions默认值:两个排序项——按SeriesNumber(系列号)与按SeriesDate(系列日期,新日期在前):
[ { label: 'Series Number', sortFunction: (a, b) => a?.SeriesNumber - b?.SeriesNumber, }, { label: 'Series Date', sortFunction: (a, b) => { const dateA = new Date(formatDate(a?.SeriesDate)); const dateB = new Date(formatDate(b?.SeriesDate)); return dateB.getTime() - dateA.getTime(); }, }, ]工作方式:Study Browser 顶部的<StudyBrowserSort>组件(platform/ui-next/src/components/StudyBrowserSort/StudyBrowserSort.tsx)通过customizationService.getCustomization('studyBrowser.sortFunctions')读取该数组,将其渲染为一个下拉菜单(显示label),并提供升降序切换按钮。切换后调用displaySetService.sortDisplaySets(selectedSort.sortFunction, sortDirection)对显示集排序,并订阅DISPLAY_SETS_CHANGED与DISPLAY_SET_SERIES_METADATA_INVALIDATED事件在数据变化时重排。
追加自定义排序函数(示例为 sampleCustomizations.tsx 中示意性写法,实际请替换为真实比较逻辑):
window.config = { // rest of window config customizationService: [ { 'studyBrowser.sortFunctions': { $push: [ { label: 'Series Stuff', sortFunction: (a, b) => Stuff, // 替换为 (a, b) => a?.x - b?.x 之类 }, ], }, }, ], };每个排序项对象至少包含label(下拉菜单显示名)与sortFunction(接收两个显示集并返回数值)。排序函数中可使用formatDate等 formatter 对 DICOM 日期字符串做格式化后再比较,与默认的 Series Date 实现一致。
六、缩略图右键菜单:studyBrowser.thumbnailMenuItems
定制点:studyBrowser.thumbnailMenuItems默认值:仅一个"Tag Browser"(打开 DICOM 标签浏览器):
[ { id: 'tagBrowser', label: 'Tag Browser', iconName: 'DicomTagBrowser', commands: 'openDICOMTagViewer', }, ]每个菜单项包含:
id:唯一标识;label:菜单显示文本;iconName:图标名(对应 ui-next Icons 集合中的图标);commands:点击后执行的命令,可以是命令名字符串、命令对象或函数。
示例:用$set整体替换为"标签浏览器 + 删除 + 收藏"三个菜单项(sampleCustomizations.tsx):
window.config = { // rest of window config customizationService: [ { 'studyBrowser.thumbnailMenuItems': { $set: [ { id: 'tagBrowser', label: 'Tag Browser', iconName: 'DicomTagBrowser', commands: 'openDICOMTagViewer', }, { id: 'deleteThumbnail', label: 'Delete', iconName: 'Delete', commands: 'deleteThumbnail', }, { id: 'markAsFavorite', label: 'Mark as Favorite', commands: 'markAsFavorite', }, ], }, }, ], };菜单项作为ThumbnailMenuItems属性传入 StudyBrowser.tsx,最终被StudyItem→Thumbnail渲染为右键弹出菜单。
七、缩略图详情行:studyBrowser.thumbnailDetails
定制点:studyBrowser.thumbnailDetails默认值:
[ { id: 'SeriesNumber', label: 'S:', source: 'seriesNumber' }, { id: 'InstanceCount', source: 'numInstances', iconName: ({ displaySet }) => displaySet?.countIcon || 'InfoSeries', }, ]详情行是缩略图上位于模态(modality)与系列描述下方的那一行文字,默认显示"系列号"与"实例数"。其声明方式与 viewport overlay 项一致,每个项可包含:
id:唯一标识;label:值的前缀文本(如'S:');title:鼠标悬停提示(tooltip);iconName:值前的图标,可以是字符串或函数;condition:是否显示该行的条件,可以是函数或命名测试;value的来源三选一:contentF:一个函数,返回要显示的值;source:引用studyBrowser.thumbnailDetailSources中的命名取值源;attribute:直接取实例(instance)上的 DICOM 属性名。
无值的项会被自动省略;若所有项都被省略(或整条定制解析为空数组),缩略图会回退到默认详情行而不是显示空行——这一行为在 Thumbnail.tsx 中有明确注释:??(而非||)保证了"未解析"与"解析为空"两种状态的区分。测试 Thumbnail.test.ts 也验证了"自定义详情行渲染S:5、3、19-Aug-2026 14:30"以及"空 details 渲染空行"两种情形。
用$push追加两个详情项(示例来自 sampleCustomizations.tsx):
window.config = { // rest of window config customizationService: [ { 'studyBrowser.thumbnailDetails': { $push: [ { id: 'InstanceDateTime', // Named source and test, so this can also be written in a // ?customization= JSONC file, which is data and never executed. source: 'instanceDateTime', condition: 'isDerivedDisplaySet', title: 'Created', }, { // Or supply the functions directly. id: 'BodyPart', label: 'Part:', attribute: 'BodyPartExamined', condition: ({ displaySet }) => displaySet.Modality === 'CT', }, ], }, }, ], };注意第二个项condition是函数(仅 CT 显示),第一个项source/condition都是名字——这种"全数据化"写法可以直接放进 JSONC 配置文件而不会被执行。
7.1 命名取值源:studyBrowser.thumbnailDetailSources
定制点:studyBrowser.thumbnailDetailSources默认值(sampleCustomizations.tsx):
{ seriesNumber: ({ displaySet }) => displaySet?.SeriesNumber, numInstances: ({ displaySet }) => (displaySet?.numImageFrames ?? displaySet?.instances?.length) || 1, seriesDate: ({ displaySet, formatters }) => formatters.formatDate(displaySet?.SeriesDate), instanceDateTime: '(the creation date/time, see getLatestInstanceDateTime)', }每个命名源接收{ displaySet, instance, formatters }并返回详情行的值。追加新源时必须使用$merge——因为$set会整体替换注册表,导致默认项引用的seriesNumber、numInstances等源全部消失:
window.config = { // rest of window config customizationService: [ { 'studyBrowser.thumbnailDetailSources': { $merge: { seriesDescription: ({ displaySet }) => displaySet.SeriesDescription, }, }, }, ], };7.2 命名测试:studyBrowser.thumbnailDetailTests
定制点:studyBrowser.thumbnailDetailTests默认值:
{ isDerivedDisplaySet: ({ displaySet }) => !!displaySet?.isDerivedDisplaySet, }命名测试让详情项的condition也能以字符串形式写在 JSONC 数据文件中,与命名源一样接收{ displaySet, instance, formatters }并返回布尔值:
window.config = { // rest of window config customizationService: [ { 'studyBrowser.thumbnailDetailTests': { $merge: { isMultiframe: ({ displaySet }) => displaySet.isMultiFrame, }, }, }, ], };7.3 实战:用 JSONC 文件为派生系列加"创建时间"
仓库中提供了一个可直接通过 URL 加载的完整范例 platform/app/public/customizations/studyBrowser/derivedDateTime.jsonc。它解决了一个真实痛点:SR、SEG、RTSTRUCT、PMAP 等派生系列在列表中按实例创建时间倒序排列,但默认详情行只显示系列号和实例数,同一天保存的多份报告看不出区别。通过?customization=studyBrowser/derivedDateTime加载后,每个派生系列缩略图会增加一行"Created"(创建时间):
{ "global": { "studyBrowser.thumbnailDetails": { "$push": [ { "id": "InstanceDateTime", "source": "instanceDateTime", "condition": "isDerivedDisplaySet", "title": "Created" } ] } } }关键点:source与condition都是名字而非函数,整个文件是纯数据、永不执行,可安全用于运行时加载的定制配置;$push保留默认的系列号与实例数两项。加载方式为在应用 URL 后追加?customization=studyBrowser/derivedDateTime。
八、研究级右键菜单:studyBrowser.studyMenuItems
定制点:studyBrowser.studyMenuItems默认值:[](空)
与缩略图菜单不同,这是研究(Study)级别的右键菜单,作用于整个研究条目。默认为空,可用$set定义:
window.config = { // rest of window config customizationService: [ { 'studyBrowser.studyMenuItems': { $set: [ { id: 'downloadStudy', label: 'Download Study', iconName: 'Download', commands: () => { console.debug('downloadStudy'); }, }, ], }, }, ], };菜单项结构与缩略图菜单一致(id/label/iconName/commands),commands也可替换为实际命令名或commandsManager.run可接受的命令描述对象。
九、双击回调:studyBrowser.thumbnailDoubleClickCallback
定制点:studyBrowser.thumbnailDoubleClickCallback默认值:内置的"将显示集放入视口"回调,完整实现见 sampleCustomizations.tsx。其核心逻辑为:
callback: ({ activeViewportId, servicesManager, isHangingProtocolLayout }) => async displaySetInstanceUID => { const { hangingProtocolService, viewportGridService, uiNotificationService } = servicesManager.services; let updatedViewports = []; const viewportId = activeViewportId; try { updatedViewports = hangingProtocolService.getViewportsRequireUpdate( viewportId, displaySetInstanceUID, isHangingProtocolLayout ); } catch (error) { console.warn(error); uiNotificationService.show({ title: 'Thumbnail Double Click', message: 'The selected display sets could not be added to the viewport.', type: 'error', duration: 3000, }); } commandsManager.run({ commandName: 'setDisplaySetsForViewports', commandOptions: { viewportsToUpdate: updatedViewports }, }); },流程为:先通过hangingProtocolService.getViewportsRequireUpdate计算需要更新的视口集合,失败时用uiNotificationService弹出错误通知,最后运行setDisplaySetsForViewports命令把显示集放入对应视口。
自定义示例:在回调中先做黑名单模态过滤再走默认流程(sampleCustomizations.tsx):
window.config = { // rest of window config customizationService: [ { 'studyBrowser.thumbnailDoubleClickCallback': { callback: ({ activeViewportId, commandsManager, servicesManager, isHangingProtocolLayout }) => async displaySetInstanceUID => { const { hangingProtocolService, viewportGridService, uiNotificationService } = servicesManager.services; let updatedViewports = []; const viewportId = activeViewportId; // Changing original function here: if (isBlacklistedModality(displaySetInstanceUID)) { return; } try { updatedViewports = hangingProtocolService.getViewportsRequireUpdate( viewportId, displaySetInstanceUID, isHangingProtocolLayout ); } catch (error) { console.warn(error); uiNotificationService.show({ title: 'Thumbnail Double Click', message: 'The selected display sets could not be added to the viewport.', type: 'error', duration: 3000, }); } commandsManager.run({ commandName: 'setDisplaySetsForViewports', commandOptions: { viewportsToUpdate: updatedViewports }, }); }, }, }, ], };注意自定义回调的入参中显式解构了commandsManager,可用于在回调内直接运行命令。
十、实例排序标准:instanceSortingCriteria
定制点:instanceSortingCriteria默认值:
{ sortFunctions: {}, defaultSortFunctionName: '', }该定制定义图像实例(instance)层面的排序标准,与studyBrowser.sortFunctions(显示集层面)不同,它作用于单个实例的播放/浏览顺序:
window.config = { // rest of window config customizationService: [ { 'instanceSortingCriteria': { $set: { sortFunctions: { sort: (a, b) => {}, }, defaultSortFunctionName: 'sort', }, }, }, ], };结构为:sortFunctions是一个命名函数映射,defaultSortFunctionName指定默认使用的那个排序函数名。
十一、十个定制点速查表
| 定制点 ID | 默认值 | 作用 | 推荐操作 |
|---|---|---|---|
studyBrowser.studyMode | 'all' | 显示全部/仅当前/最近研究 | $set |
studyBrowser.viewPresets | list + thumbnails(默认 thumbnails) | 定义视图预设与默认视图 | $set |
studyBrowser.sortFunctions | 按系列号、按系列日期 | 显示集排序选项 | $push追加 |
studyBrowser.thumbnailMenuItems | Tag Browser | 缩略图右键菜单 | $set替换 |
studyBrowser.thumbnailDetails | 系列号 S: + 实例数 | 缩略图详情行 | $push追加 |
studyBrowser.thumbnailDetailSources | seriesNumber / numInstances / seriesDate / instanceDateTime | 详情行命名取值源 | $merge追加 |
studyBrowser.thumbnailDetailTests | isDerivedDisplaySet | 详情行命名条件测试 | $merge追加 |
studyBrowser.studyMenuItems | [] | 研究级右键菜单 | $set |
studyBrowser.thumbnailDoubleClickCallback | 默认放图回调 | 双击缩略图行为 | $set整体替换 |
instanceSortingCriteria | 空 | 实例层排序标准 | $set |
十二、实践要点与常见陷阱
$push与$set的选择:对sortFunctions、thumbnailDetails这类"默认项要保留"的数组,用$push(或$unshift)追加;对viewPresets、thumbnailMenuItems、studyMenuItems这类"整体重定义"的数组,用$set。thumbnailDetailSources/thumbnailDetailTests千万别用$set:它们是对象注册表,$set会清掉默认项引用的所有源/测试,导致默认详情行解析失败。- 函数与数据分离:只要详情项只用命名
source和命名condition,整条定制就能写成 derivedDateTime.jsonc 这种纯数据 JSONC 文件,通过?customization=在运行时加载且不会被执行;一旦混入contentF/condition函数,就必须回到window.config的 JS 环境。 - 未注册的源/测试会静默跳过:详情项引用了一个未注册的命名源或测试时,该项会被略过并产生警告;若最终一个项都不剩,缩略图会保留默认详情行而非显示空行。
- 默认值语义:所有默认值均可在 sampleCustomizations.tsx 的
studyBrowserCustomizations中直接查看,它是权威参考;StudyBrowser.tsx、StudyBrowserSort.tsx、Thumbnail.tsx则分别印证了视图预设解析、排序函数调用链与详情行渲染逻辑。
十三、参考资料
- 官方定制文档:StudyBrowser.md、customizationService.md
- 全部定制项定义与配置示例:sampleCustomizations.tsx
- 组件实现:StudyBrowser.tsx、StudyBrowserSort.tsx、Thumbnail.tsx、Thumbnail.test.ts
- 运行时 JSONC 定制范例:derivedDateTime.jsonc
- 历史版本(3.11)对照:version-3.11 StudyBrowser.md
【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考