
OHIF Viewer 分割与轮廓显示行为规格SEG/RTSTRUCT 的加载、Hydration 与展示全解析【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers导读DICOM SEGlabelmap 分割与 RTSTRUCT轮廓如何在 OHIF Viewer 中上屏、数据何时被拉取、以及从屏幕上移除到底意味着什么是医学影像前端中极易产生歧义的领域。本文以仓库中的行为规格文档 specs/segmentation-and-contour-display.ears.md 为骨架结合 cornerstone 扩展、dicom-seg/dicom-rt 扩展与默认扩展的命令实现系统讲解 overlay 显示集模型、自动/手动展示、hydration 机制、资格判定eligibility规则、移除与删除的语义差异以及 PR #5996 引入的hydration 是显示集层面的陈述这一设计转向帮助开发者理解并能实际配置 OHIF 中分割数据的完整生命周期。一、一个核心事实分割永远是叠加层而不是视口的主图整份行为规格的第一条规则SEG-1也是理解一切后续行为的基石分割永远不是一个视口的画面picture它始终是绘制在其引用序列之上的叠加层overlay。在 OHIF 中SEG 与 RTSTRUCT 的显示集被标记为overlay display set叠加显示集。这意味着它们没有属于自己的主像素primary pixels可供独立显示它们携带一个指向派生来源序列的引用referencedDisplaySetInstanceUID当 OHIF 被要求显示一个分割时它真正做的是显示被引用的序列然后把分割叠加在上面。这一条事实解释了文档中的大部分行为包括把一个 SEG 拖入视口会明显改变该视口正在显示的内容这一现象——因为视口底层已经被切换成了被引用的序列。从源码中可以验证这一模型。在 cornerstone-dicom-seg/src/getSopClassHandlerModule.ts 中SEG 显示集创建时被显式标记const displaySet { Modality: SEG, isReconstructable: false, // 分割自身不构成可重建体积 isDerivedDisplaySet: true, // 派生显示集 isOverlayDisplaySet: true, // 叠加显示集绝不作为视口自身内容渲染 isLoaded: false, isHydrated: false, referencedDisplaySetInstanceUID: null, // 稍后由引用序列解析回填 // ... };而专门的 SEG 预览视口 OHIFCornerstoneSEGViewport.tsx 在组装底层视口时也总是同时传入引用序列与分割本身return ( OHIFCornerstoneViewport {...props} displaySets{[referencedDisplaySet, segDisplaySet]} // 引用序列作背景SEG 作叠加 viewportOptions{{ viewportType, toolGroupId, orientation, viewportId, presentationIds }} / );叠加层的数据模型引用如何被解析在 getSopClassHandlerModule.ts 中SEG 实例通过 DICOM 的ReferencedSeriesSequence解析其引用序列从ReferencedSeriesSequence[0]读取SeriesInstanceUID通过displaySetService.getDisplaySetsForReferences(...)找到对应的引用显示集一旦找到就回填referencedDisplaySetInstanceUID、isReconstructable和FrameOfReferenceUID这三个属性从引用显示集拷贝而来见 isDisplaySetOverlayable.ts 的注释。如果引用显示集当时尚未创建例如 SEG 序列先于其引用的图像序列加载处理器会订阅DISPLAY_SETS_ADDED事件等引用序列出现后再完成关联——这保证了先 SEG 后图像的加载顺序也能正常工作。二、分割数据的加载时机首次进入视口时且仅此一次规格中的 SEG-2、SEG-3、SEG-4 定义了加载时机规则SEG-2在显示集被放入视口之前系统不得获取或解码分割数据SEG-3显示集一旦被放入视口系统应加载它、报告加载进度并将其注册到分割服务segmentation serviceSEG-4一旦加载完成后续所有展示都应复用已解码的数据。换句话说打开一个研究只会根据元数据创建 SEG/RTSTRUCT 显示集——足够生成缩略图和面板条目——但不会下载或解码任何像素数据。真正的数据获取发生在显示集首次到达视口的那一刻并且每个分割只加载一次后续的传播展示、布局变化后的恢复都复用已解码的数据。源码层面的实现印证这一按需加载 幂等去重的行为在 loadDisplaySetData.ts 中体现为两个关键设计加载与展示解耦loadDisplaySetData只负责取数据、解码、注册到分割状态它既不关心也不感知哪个视口会显示这个分割——结果在哪里显示、如何挂载表示representation是另一个视口范围内的独立步骤。正是这种解耦让决定哪些叠加层属于哪些视口可以在视口组装之外完成而无需把加载作为副作用。加载失败被吞掉displaySet.load失败时向用户弹出Error loading displaySet通知然后被捕获消化——一个无法加载的显示集不能阻止请求它的流程完成。而 getSopClassHandlerModule.ts 中的_load实现展示了幂等与并发安全细节function _load(segDisplaySet, servicesManager, extensionManager, headers) { // 若已在加载或已加载直接返回同一个 promise避免重复触发加载 if ( (segDisplaySet.loading || segDisplaySet.isLoaded) loadPromises[SOPInstanceUID] _segmentationExists(segDisplaySet) ) { return loadPromises[SOPInstanceUID]; } segDisplaySet.loading true; // 不触发多次加载等待第一个完成并把同一个 promise 返回给其他调用方 loadPromises[SOPInstanceUID] new Promise(async (resolve, reject) { // ... 先解码 segment 数据再 createSegmentationForSEGDisplaySet }); segDisplaySet.loadingPromise loadPromises[SOPInstanceUID]; // 暴露给等待方 return loadPromises[SOPInstanceUID]; }值得注意的实现细节还有多帧 SEG 以 Part 10 整体预取默认开启SEG 帧又小又多逐帧请求几百次不如一次批量获取划算。由loadMultiframeAsPart10控制数据源配置或cornerstone.segmentation.loadMultiframeAsPart10定制项默认true预取等到完成或失败——失败会快速回退到逐帧加载慢的大文件仍然是到达全部帧的最快方式并发解码上限SEG_FRAME_DECODE_CONCURRENCY 16getSopClassHandlerModule.ts当前硬编码计划后续可配置化加载进度监听SEGMENTATION_LOAD_PROGRESS事件将percentComplete广播为SEGMENT_LOADING_COMPLETE视口据此显示百分比与段数见 OHIFCornerstoneSEGViewport.tsx颜色优先采用 DICOM 的RecommendedDisplayCIELabValue转 RGB缺失时回退到默认颜色 LUT 并给出警告通知。失败与超时行为SEG-22、SEG-23SEG-22若分割没有引用序列系统应委托给missingReferenceDisplaySetHandler定制项否则跳过渲染。仓库中的默认实现位于 extensions/default/src/customizations/missingReferenceDisplaySetHandler.ts其默认行为是Promise.resolve({ handled: false })——即不处理、由调用方跳过渲染。SEG/RT 预览视口正是据此在无引用显示集例如通过 SeriesInstanceUID 直接启动 SEG 序列时优雅跳过渲染避免视口崩溃见 OHIFCornerstoneSEGViewport.tsx。SEG-23若分割加载失败或两分钟内未完成系统应跳过绘制该分割但允许视口完成其余渲染而不是挂起。三、自动展示Hydration水合机制自动展示发生在两种情形中而这两种都不包含打开研究时SEG-5除非悬挂协议显式选中否则研究打开时系统不得显示分割。3.1 情形一用户刚选择查看的分割当 SEG/RTSTRUCT 被放入视口时OHIF 渲染一个专门的 SEG/RT 预览视口OHIFCornerstoneSEGViewport/OHIFCornerstoneRTViewport它显示被引用序列加上叠加的分割。之后数据加载完成后且仅当该视口是活动视口时SEG-8系统询问是否要hydrate水合这个分割SEG-7默认弹出一个对话框但安装方可以设置disableConfirmationPrompts此时水合静默发生SEG-9水合的含义SEG-10把预览一个派生对象切换为把该分割作为引用序列的分割——视口内容切换为引用序列本身分割出现在分割面板中变得可编辑、可导出。3.2 情形二传播与恢复一旦分割在某处被渲染OHIF 会把它传播到显示同一体积或同一 Frame of Reference帧参照系的其他视口——这就是大多数悬挂协议声明的hydrateseg同步组干的活。同时OHIF 会按视口记住正在渲染哪些分割。这个记忆是以底层序列为键的SEG-30视口的展示身份 其非叠加显示集的displaySetInstanceUID连接串因此改变布局、切换悬挂协议阶段、重定向 MPR 视图、或把引用序列拖进另一个窗格都会自动把分割带回来。而视口方向orientation不参与该身份SEG-30。3.3 水合对话框的源码实现promptHydrationDialog.ts 是水合提示的完整实现几个关键点决定是否询问对应 SEG-9// 对于 RT 和 SEG检查 disableConfirmationPrompts // 对于 SR检查 standardMode const shouldPrompt type HydrationType.SR ? standardMode : !appConfig?.disableConfirmationPrompts; const promptResult shouldPrompt ? await _askHydrate(uiViewportDialogService, customizationService, viewportId, type) : RESPONSE.HYDRATE; // 静默水合按类型选择消息与对话框getCustomizationMessageKey与getDialogId分别映射 SEG / RTSTRUCT / SR 三类水合的自定义消息键viewportNotification.hydrateSEGMessage等与对话框 ID。对话框交互提供NoRESPONSE.CANCEL与YesRESPONSE.HYDRATE两个动作支持回车确认、点击外部取消。水合执行确认后先执行preHydrateCallbacks再通过setTimeout(..., 0)异步调用hydrateCallback——SEG 与 RTSTRUCT 的回调参数结构不同segDisplaySetvsrtDisplaySetSR 则走完全独立的结果结构HydrationSRResult。对话框的onOutsideClick会取消水合并 resolve 为CANCEL——这就是规格中被用户 dismiss 的提示的来源也正是 LOAD 徽章见下文 4.3存在的意义。3.4 水合命令的实际行为hydrateSecondaryDisplaySetcommandsModule.ts展示了水合的完整副作用序列// 记录表示类型提示在视口渲染时会被纠正 const segmentationType displaySet.Modality ! SEG ? SegmentationRepresentations.Contour : viewport isVolume3DViewportType(viewport) ? SegmentationRepresentations.Surface : SegmentationRepresentations.Labelmap; commandsManager.runCommand(updateStoredSegmentationPresentation, { displaySet, type: segmentationType }); // isHydrated 的含义是把该显示集作为标准视图的一部分展示 // 该决策在这里做出且不依赖任何视口是否已存在 displaySet.isHydrated true; // SEG / RTSTRUCT把引用序列加载进视口 const results commandsManager.runCommand(loadSegmentationDisplaySetsForViewport, { viewportId, displaySetInstanceUIDs: [referencedDisplaySet.displaySetInstanceUID], derivedDisplaySetInstanceUID: displaySet.displaySetInstanceUID, viewportType: getHydrationViewportTypeForModality(displaySet.Modality), }); // panelSegmentation.disableEditing 配置为 true 时锁定所有段SEG-13 const disableEditing customizationService.getCustomization(panelSegmentation.disableEditing); if (disableEditing) { const segmentationId displaySet.displaySetInstanceUID; const segmentation segmentationService.getSegmentation(segmentationId); Object.keys(segmentation?.segments ?? {}).forEach(segmentIndex { segmentationService.setSegmentLocked(segmentationId, parseInt(segmentIndex), true); }); }其中loadSegmentationDisplaySetsForViewportcommandsModule.ts负责计算哪些视口需要被更新并完成更新const updatedViewports getUpdatedViewportsForSegmentation({ viewportId, servicesManager, displaySetInstanceUIDs, derivedDisplaySetInstanceUID, }); // ...对每个目标视口 setNeedsRender actions.setDisplaySetsForViewports({ viewportsToUpdate: updatedViewports.map(...) });注意其中的渲染模式钉扎逻辑SEG-53viewportTypeRTSTRUCT 在原生视口路径请求stack只应用于水合被调用的那个窗格其背景正在被替换为引用图像其他窗格是因为已经显示合格背景才被匹配到的保留自己的渲染模式——MPR 窗格不会被强制翻转成 stack。3.5 水合目标视口的计算hydrationUtils.ts 中的getUpdatedViewportsForSegmentation定义了水合会触及哪些视口SEG-25/SEG-26/SEG-46/SEG-47首先通过hangingProtocolService.getViewportsRequireUpdate(targetViewportId, displaySetInstanceUIDs[0], isHangingProtocolLayout)让悬挂协议匹配引用显示集所选择的视口然后通过mergeMatchingViewports合并所有网格窗格中已列出该体积displaySetInstanceUIDs精确匹配的窗格以及所有其背景满足isDisplaySetOverlayable即共享帧参照系的窗格关键的合并语义只有精确的displaySetInstanceUID匹配才强制把引用显示集放进窗格仅共享帧参照系的窗格保留它已有的显示集——这正是 SEG-26/SEG-47 保护的场景把不同的 UID 强制塞进体积视口可能导致它空白。// 悬挂协议 pass 已经运行对它点名的窗格具有权威性——它知道引用显示集 // 必须被*加载*进水合目标用窗格现有 UID 覆盖它会丢弃该指令。 if (byId.has(vp.viewportId)) return; const uids vp.displaySetInstanceUIDs || []; // 只有精确 displaySetInstanceUID 匹配才强制把引用体积放到窗格上 // 仅共享帧参照系的窗格保留其已有显示集 if (volumeUid uids.includes(volumeUid)) { add(vp.viewportId, [volumeUid]); return; } if (isEligiblePane?.(vp)) { add(vp.viewportId, uids); }同时makeEligiblePanePredicate组合了两道闸门视口类型必须被isAutoHydrateViewportType允许SEG-50且背景显示集必须满足isDisplaySetOverlayable。目标视口可能已经不存在提示框打开期间布局变了或从未存在过从研究面板驱动水合——此时不 dereference 视口而是回退到资格匹配SEG-49。四、手动展示三种入口手动展示分为两类语义给我看这个分割替换视口内容与把这个分割加到我正在看的东西上保持背景、叠加图层。4.1 缩略图拖放/双击SEG-14、SEG-34双击或把 SEG/RTSTRUCT 缩略图拖入视口会接管该视口进入 3.1 的流程预览 → 水合询问。手动缩略图放置不受 SEG-31~33 的叠加资格约束——它是替换视口内容SEG-6而非叠加。4.2 Add as Layer 与视口叠加菜单SEG-15、SEG-16保持当前背景、把分割叠加上去有两个入口缩略图的Add as Layer菜单项视口的data-overlay 菜单数据叠加菜单列出其他显示集并标记哪些可以合法叠加。叠加资格由 SEG-31~SEG-33 决定SEG-31只有当视口的背景显示集可重建reconstructable且候选未被标记为 unsupported 时才提供手动叠加SEG-32候选声明了FrameOfReferenceUID时必须与背景一致未声明的则不做限制缺失信息是宽松的SEG-33SEG 与 RTSTRUCT豁免于图像叠加的体积形状约束背景必须是有效体积、候选必须是多帧或有效体积。一旦作为图层添加其表示会在视口挂载时立即被添加——没有提示因为用户已经表达了意图。4.3 LOAD 徽章SEG-17如果视口正在显示一个未水合的分割角落会有一个小的SEG/RTSTRUCT徽章带一个LOAD按钮。这个按钮就是对话框提供的水合动作本身服务于关闭了对话框、或从未被展示过对话框的用户。五、资格判定Eligibility不是单一谓词而是四道独立闸门规格明确指出资格不是单一谓词。一个分割要穿过至多四道相互独立的闸门——水合更新哪些视口、传播到哪些视口、恢复到哪些视口、表示最终能否被挂载——而且它们使用的测试各不相同。闸门规则测试对象水合 fan-outSEG-25/SEG-26悬挂协议为引用显示集匹配的视口或已包含引用显示集的网格窗格按精确displaySetInstanceUID匹配传播同步组SEG-27~SEG-29目标与源共享显示集或报告相同FrameOfReferenceUID已持有该分割表示的视口不再添加恢复展示身份SEG-30视口展示身份非叠加显示集 UID 连接串等于记录时的身份方向不参与表示挂载兼容性SEG-35~SEG-39视口状态显示的 imageIds、视口帧参照系、表示类型5.1 传播同步器hydrateseg 同步组createHydrateSegmentationSynchronizer.ts 实现了 SEG-27~29 的传播逻辑。它监听SEGMENTATION_REPRESENTATION_MODIFIED事件回调segmentationRepresentationModifiedCallbackconst sharedDisplaySetExists isAnyDisplaySetCommon(sourceDisplaySetUIDs, targetDisplaySetUIDs); // 不共享显示集且目标无帧参照系 → 不传播SEG-28 if (!sharedDisplaySetExists !targetFrameOfReferenceUID) return; // 不共享显示集且帧参照系不一致 → 不传播SEG-28 if (!sharedDisplaySetExists targetFrameOfReferenceUID ! sourceFrameOfReferenceUID) return; // 目标已持有该分割表示 → 不重复添加SEG-29 if (targetViewportRepresentation.length 0) return; // 表示类型与目标视口类型对齐3D 视口用 Surface否则沿用源类型、兜底 Labelmap const type is3D ? Surface : requestedRepresentation requestedRepresentation ! Surface ? requestedRepresentation : Labelmap; await segmentationService.addSegmentationRepresentation(targetViewportId, { segmentationId, type, config });5.2 表示/视口兼容性单一决策点SEG-35~SEG-39SEG-35 要求在同一个地方决定分割表示能否挂载进视口让所有调用方应用同一条规则。规格给出了该规则的内容SEG-36Contour、Surface 和未类型化的表示没有任何视口兼容性约束——因此 RTSTRUCT 绝不会被这道闸门抑制SEG-37labelmap 只能挂载到 stack 视口且仅当该视口显示了 labelmap 的至少一个源图像SEG-38labelmap 可以挂载到体积视口除非 labelmap 声明的FrameOfReferenceUID与视口的不同由视口未显示的序列派生的 labelmap 仍兼容因为体积视口按几何重采样SEG-39无法确定兼容性时分割未知、源图像或帧参照系尚不可推导、视口未就绪、查询抛异常一律视为兼容——缺失信息是宽松的。规格中给出的源码位置是isSegmentationOverlayCompatible位于 cornerstonejs tools 包的stateManagement/segmentation/helpers/下当前仓库中该 helper 位于第三方依赖内读者可在 node_modules 中按同名文件查找。5.3 资格规则的四个不一致点设计红线规格专门列出了基线中资格规则互相矛盾的观察点对任何想要重构这块逻辑的人至关重要帧参照系既被拒绝又被依赖SEG-26 拒绝基于共享帧参照系推断资格会空白体积视口SEG-27 却恰恰基于该推断传播SEG-38 又基于该依据允许挂载——同一个关系在选择更新哪些视口时不安全在扩散到它们时却足够。手动叠加与渲染时兼容性使用不同测试叠加菜单SEG-31~33测显示集元数据可重建背景、声明的帧参照系、体积形状挂载闸门SEG-36~38测视口状态imageIds、视口帧参照系并按表示类型分支——菜单从不考虑类型。因此一个显示集可能被菜单提供却挂载不上也可能经由从未咨询菜单的路径被挂载。缺失帧参照系扩大而非缩小资格SEG-32 对未声明FrameOfReferenceUID的候选对任何背景都提供SEG-39 对无法确定的视为兼容。缺失信息全程宽松——这是有意为之但也意味着资格不能作为正确性保证。轮廓在挂载时不被检查SEG-36 豁免所有非 labelmap 表示所以只要前面的任何闸门放行RTSTRUCT 就会到达视口。六、移除Remove与删除Delete作用域完全不同6.1 从视口移除SEG-19Remove from Viewport来自分割面板菜单或移除图层只影响一个视口丢弃该视口的表示面板不再为该视口列出它因为移除发生在视口状态被存储之前它在之后的布局变化中保持移除状态不会被恢复。分割本身毫发无损仍然已加载、仍然在拥有它的其他视口中渲染、随时可以被加回来。6.2 删除SEG-20、SEG-21Delete则处处摧毁分割从每个列出它的视口移除如果是客户端创建的分割用户画的 labelmap 或轮廓其显示集也会被删除——从研究面板消失除非先导出过否则真正消失SEG-20从服务器来的分割在一种意义上幸存于自身删除SEG 序列仍在研究面板中可以简单地重新加载——因为删除只丢弃已解码的工作副本SEG-21。6.3 源码印证分割事件处理器setUpSegmentationEventHandlers.ts 实现了删除路径。当分割从分割服务中移除时SEGMENTATION_REMOVED事件// 全局移除路径分割已从状态中消失例如从分割面板删除 // 因此清除全局的显示它到它逻辑上该在的地方状态。 displaySet.isHydrated false; commandsManager.runCommand(updateStoredSegmentationPresentation, { displaySet, hydrated: false }); // 遍历所有携带该分割图层的视口逐个移除图层 for (const [viewportId, viewport] of viewports.entries()) { if (displaySetInstanceUIDs.includes(segmentationId)) { commandsManager.runCommand(removeDisplaySetLayer, { viewportId, displaySetInstanceUID: segmentationId }); } } // 客户端创建的分割才删除显示集服务端分割保留序列可重新加载 if (displaySet.madeInClient) { displaySetService.deleteDisplaySet(segmentationId); }同一个文件还处理客户端创建的分割的显示集生成SEG-18监听SEGMENTATION_ADDED为没有对应显示集的客户端分割构造一个madeInClient: true的 overlay 显示集并注册使其表现得如同已加载的分割一样。七、PR #5996 的转向Hydration 是关于显示集的陈述而非关于视口的陈述文档的后半部分是分支PR #5996相对于基线的变更说明。组织性思想是水合是对一个显示集的陈述把这个分割显示在它逻辑上该在的地方而不是对一个视口的陈述。这把基线中单一的移除/添加概念拆成了每视口动作与全局动作两个层级并让资格问题可以在根本不存在任何视口的情况下被回答。7.1 每视口显示 vs 水合SEG-40~SEG-45SEG-40在视口中添加/移除分割图层只影响该视口不改变显示集的水合状态所以之后重建的视口会重新应用水合所说的状态而不是继承这个每视口动作。这就是视口>isDisplaySetOverlayable(displaySet, backgroundDisplaySet): 1. 任一为空 → false 2. 二者 UID 相同 → false 3. displaySet.unsupported → false 4. 候选声明了 FOR 且与背景不一致 → false 5. 候选是 SEG/RTSTRUCT a. 候选的引用 背景 → true不受可重建约束 b. 双方可重建 且 候选有 FOR → true仅此越出自身引用 6. 其他colormap 叠加 背景可重建 有效体积且候选为多帧或有效体积 → true注释中明确指出了两个易被误解的设计决策派生显示集从其引用的显示集拷贝isReconstructable和FrameOfReferenceUID所以这两者都是关于引用的答案叠加所依据的那个显示集不必可重建——RTSTRUCT 叠加在 stack 上正是水合时getHydrationViewportTypeForModality钉扎到stack的情形可重建闸门不能适用于此。7.4 表示类型的自动纠正hydrateSecondaryDisplaySetcommandsModule.ts在记录类型时明确标注仅作提示SEG 在 3D 视口记录为Surface否则记录为Labelmap非 SEG即 RTSTRUCT记录为Contour。注释指出Recorded as a hint only:_setSegmentationPresentationcorrects Labelmap - Surface against the viewport that actually renders it, so this does not have to be resolved against a viewport here.这与 SEG-51 完全对应。7.5 展示存储与恢复的源码实现store 结构useSegmentationPresentationStore.ts 是基于 zustand 的展示存储以presentationId 视口非叠加显示集 UID 的连接串见_getSegmentationPresentationId为键每个条目是一个SegmentationPresentationItem[]// segmentationPresentationItem: { // segmentationId: string; // type: SegmentationRepresentations; // hydrated: boolean | null; // config?: unknown; // }三个写入动作各有精确语义addSegmentationPresentationItem按segmentationIdupsert同一分割的 hydrate → remove → hydrate 不会累积重复条目否则视口会把同一个表示添加多次setHydrationForSegmentation重述某分割在所有已含其条目的展示中的水合不新建条目——因为客户端分割没有引用显示集就没有自己的键对应 SEG-45syncSegmentationPresentation记录视口当前渲染的表示但保留已记录的水合、只刷新 type/config——水合是显示集全局的显示在它逻辑上该在的地方归水合路径所有被闸门挡住的窗格或用户移除叠加层的窗格不能抹掉其他窗格的记录对应 SEG-44。无条目时调用方传入的hydrated才生效。读取恢复getViewportPresentations.ts 实现了 SEG-52 的关系式读取先做键控查找segmentationPresentationStore[segmentationPresentationId]再扫描其余条目用isDisplaySetOverlayable判断哪些分割也能叠加到本视口背景上键控条目胜出它是分割自身的显示集类型与水合状态具有权威性。该文件配有一份完整的单元测试 getViewportPresentations.test.ts测试数据覆盖了同一帧参照系中的不同序列volume-1/volume-2/volume-3共享for-1、无关的非可重建序列共享帧参照系stack-1/stack-2共享for-s以及 SEG/RT 叠加显示集等场景。syncSegmentationPresentation在 CornerstoneViewportService.ts 中被调用且每项的水合初值由_getInitialHydrationForSync同文件 L498-L504给出// 有引用显示集 → null无陈述留给水合提示/面板/研究浏览器决定 // 无引用显示集客户端绘制或未注册→ true视口是权威记录为已显示 return displaySet?.referencedDisplaySetInstanceUID ? null : true;7.6 渲染模式钉扎SEG-53nextViewportPolicies.ts 集中定义了 legacy 与原生next视口路径之间的行为策略差异其中水合相关的策略是export function getHydrationViewportTypeForModality(modality: string): stack | undefined { return modality RTSTRUCT isNextViewportsEnabled() ? stack : undefined; }注释解释了原因RTSTRUCT 轮廓在原生 stack/vtkImage 视口上渲染正确且滚动快所以水合时引用图像保持 stack 模式而非提升为体积切片性能验收标准禁止。而loadSegmentationDisplaySetsForViewportcommandsModule.ts只在目标视口水合被调用者上应用这个钉扎并特别注明合并条目不携带 viewportOptions若把裸{ viewportType }直接合并到窗格真实选项上会把 MPR 窗格翻成 stack——所以必须按viewport.viewportId targetViewportId精确过滤。八、可配置项速查配置项位置/默认值作用disableConfirmationPrompts应用配置appConfig为true时 SEG/RT 水合不再弹对话框静默执行SEG-9panelSegmentation.disableEditingsegmentationPanelCustomization.tsx默认false水合完成后锁定所有段SEG-13cornerstone.segmentation.autoHydrateViewportTypessegmentationHydrationCustomization.ts默认null列出允许自动显示已水合分割的视口类型如[stack,volume]可将 3D 视口排除在自动水合之外null 所有能渲染的类型SEG-50cornerstone.segmentation.loadMultiframeAsPart10定制项默认true多帧 SEG 是否整实例 Part 10 预取后本地供帧false走逐帧请求cornerstone.modalityOverlayDefaultColorMaps定制项叠加层的默认 colormap 与不透明度默认hsv/ 0.5~0.9missingReferenceDisplaySetHandlerextensions/default/src/customizations/missingReferenceDisplaySetHandler.ts无引用序列时的兜底处理默认{ handled: false }即跳过渲染其中autoHydrateViewportTypes的类型词汇是悬挂协议viewportOptions.viewportType的词汇stack/volume/volume3d/...见 autoHydrateViewportTypes.ts。其注释解释了一个易踩的坑cornerstone 自己的枚举词汇不可用作键——orthographic是volume视口的实际形态而在useNextViewports下所有平面视口stack 与 MPR 一视同仁都会塌缩为planarNext任何配置列表都无法指名它因此planarnext被特意映射为undefined含义真正歧义猜错会误伤站点不想排除的视口。九、已知的未解决问题设计边界规格在Still open一节中如实列出了两个遗留问题isHydrated标志被直接突变、无事件消费者研究浏览器缩略图、LOAD 徽章如果直接渲染该标志可能显示陈旧状态。有进行中的工作要把该标志正式挪到显示集上。展示身份跨方向共享SEG-44 防止共享展示条目被抹掉但身份仍跨方向共享SEG-30方向不参与。真正随窗格不同而不同的状态——2D 窗格的 labelmap vs 3D 窗格的 surface——没有自己的容身之处这正是 SEG-51 在挂载时纠正类型的原因。十、核心实现文件索引关注点位置水合提示与命令promptHydrationDialog.ts、commandsModule.ts 中hydrateSecondaryDisplaySet水合触及哪些视口hydrationUtils.ts手动叠加资格ViewportDataOverlaySettingMenu/utils.ts 中getEnhancedDisplaySets叠加资格统一规则SEG-46~48isDisplaySetOverlayable.ts自动水合视口类型SEG-50autoHydrateViewportTypes.ts、segmentationHydrationCustomization.ts视口展示解析SEG-52getViewportPresentations.ts水合渲染模式钉扎SEG-53nextViewportPolicies.ts、commandsModule.ts 中loadSegmentationDisplaySetsForViewport记录已显示内容useSegmentationPresentationStore.ts、CornerstoneViewportService.ts向其他视口传播createHydrateSegmentationSynchronizer.ts图层添加与移除commandsModule.ts、layerConfigurationUtils.ts加载CornerstoneCacheService.ts、SEG/RT 的 SOP class handlers预览视口OHIFCornerstoneSEGViewport.tsx、OHIFCornerstoneRTViewport.tsx删除与客户端创建的分割setUpSegmentationEventHandlers.ts结语SEG 与 RTSTRUCT 在 OHIF 中的完整生命周期由一条根本事实统领——分割是叠加层永远绘制在被引用序列之上——并由加载一次、展示多次的按需加载、自动/手动两条展示路径、四道相互独立的资格闸门、以及移除≠删除的作用域区分共同支撑。PR #5996 的转向进一步把水合重新定义为对显示集的全局陈述使资格判定不再依赖任何视口的存在同时保留了每视口图层操作的局部性。理解这些规则无论是排查分割为什么没出现/为什么被恢复了还是定制自动水合范围、静默水合或只读面板都能直接定位到上述源码位置做出有依据的决策。【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考