ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OHIF 3.9 分割表示管理迁移指南:从 ToolGroup 中心化到 Viewport 中心化与 Specifier 模式

OHIF 3.9 分割表示管理迁移指南:从 ToolGroup 中心化到 Viewport 中心化与 Specifier 模式 OHIF 3.9 分割表示管理迁移指南从 ToolGroup 中心化到 Viewport 中心化与 Specifier 模式【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers本文是 OHIF 3.8 升级到 3.9对应 Cornerstone3D 2.0迁移指南中关于分割Segmentation表示管理的关键章节。它聚焦于一个核心架构转变分割表示Segmentation Representation的管理从「以 ToolGroup 为中心」全面转向「以 Viewport 为中心」并引入了全新的Specifier描述符模式以提供更灵活、更精确的 API。读完本文你将掌握addSegmentationRepresentation、removeSegmentationRepresentation、getSegmentationRepresentations三组核心 API 的新旧写法对照理解 Specifier 的对象结构与应用场景并能据此完成现有分割代码的平滑迁移。本文同时结合当前仓库 extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts 的源码实现给出仓库内的真实调用证据帮助你从「知道怎么写」进阶到「理解为什么这么写」。背景为什么从 ToolGroup 中心化转向 Viewport 中心化在 Cornerstone3D 2.0OHIF 3.9之前分割表示labelmap、contour、surface的管理依附于ToolGroup。一个 ToolGroup 汇聚了一组共享相同工具配置的 viewport分割表示必须挂在 ToolGroup 上再由 ToolGroup 分发到其内部的所有 viewport。这种设计带来了心智负担开发者必须理解 ToolGroup 与 viewport 的对应关系当需要为不同 viewport 配置不同表示时往往要额外创建 ToolGroup。新的架构将管理粒度下沉到viewport本身分割表示直接与「它被渲染在哪个 viewport」绑定每个 viewport 都可以拥有自己独立唯一的表示配置不再需要为了差异化渲染而创建额外的 ToolGroup 中间层心智模型更简单表示就在它显示的地方。这一变化的核心目的是提供对分割渲染更精细的控制同时简化开发者管理分割表示的心智模型。这一点在仓库源码中也有印证OHIF 的 SegmentationService 中所有表示查询、添加、移除方法的第一个参数都是viewportId而SegmentationRepresentation类型也显式携带了viewportId字段见 SegmentRepresentation 类型定义。核心 API 对照旧接口与新接口迁移的第一步是替换三组核心 API。下表汇总了新旧方法的对应关系操作3.8ToolGroup 中心化3.9Viewport 中心化添加表示addSegmentationRepresentationToToolGroupaddSegmentationRepresentation移除指定表示removeSegmentationRepresentationFromToolGroupremoveSegmentationRepresentation移除全部表示removeSegmentationRepresentationFromToolGroup(toolGroupId)removeSegmentationRepresentations(viewportId)查询表示getSegmentationRepresentationsForToolGroupgetSegmentationRepresentations/getSegmentationRepresentation添加分割表示Adding Segmentation Representations迁移前3.8——基于 ToolGroup// Tool group-based approach await segmentation.addSegmentationRepresentationToToolGroup( toolGroupId, segmentationId, hydrateSegmentation, csToolsEnums.SegmentationRepresentations.Labelmap );迁移后3.9——基于 Viewport// Viewport-centric approach await segmentation.addSegmentationRepresentation( viewportId, { segmentationId: segmentationId, type: csToolsEnums.SegmentationRepresentations.Labelmap, } );注意新接口的变化原来平铺的四个位置参数toolGroupId、segmentationId、hydrateSegmentation、表示类型被收敛为「viewportId 一个 Specifier 对象」type字段也移入了对象内部。从 OHIF 仓库源码看OHIF 的SegmentationService.addSegmentationRepresentation在 SegmentationService.ts 中还支持一些附加字段可作为实际项目中的完整用法参考await segmentationService.addSegmentationRepresentation( viewportId, { segmentationId, predecessorImageId, // 可选前驱图像 ID用于保留原始系列引用 type, // 可选Labelmap / Contour / Surface缺省时自动推断 config: { blendMode, // 可选混合模式如 LABELMAP_EDGE_PROJECTION_BLEND useSliceRendering, // 可选是否启用切片渲染 }, suppressEvents, // 可选为 true 时抑制事件广播 } );源码中有几个值得注意的细节类型自动推断当不传type时默认表示类型 根据 viewport 是否为 3D 体积视口自动选择——3D 视口默认Surface其余默认Labelmap前置校验如果segmentationId尚未进入 cornerstone 状态方法会直接console.warn并提前返回避免后续空引用崩溃见 addSegmentationRepresentation 的守卫逻辑ColorLUT 复用表示添加时会携带为该分割预先注册的colorLUTIndex确保同一分割在不同 viewport 上的多个表示共用同一套颜色查找表见 _addSegmentationRepresentation。仓库中的真实调用示例位于 extensions/cornerstone-dicom-seg/src/commandsModule.tsloadSegmentationsForViewport命令在创建/更新分割后正是通过addSegmentationRepresentation(viewport.viewportId, { segmentationId })将表示挂到目标 viewportawait segmentationService.createLabelmapForDisplaySet(displaySet, { segmentationId, segments, label, }); segmentationService.addOrUpdateSegmentation(segmentation); await segmentationService.addSegmentationRepresentation(viewport.viewportId, { segmentationId, });移除分割表示Removing Segmentation Representations迁移前3.8// Remove specific representations from a tool group segmentation.removeSegmentationRepresentationFromToolGroup( toolGroupId, [segmentationRepresentationUID] ); // Remove all representations from a tool group segmentation.removeSegmentationRepresentationFromToolGroup(toolGroupId);迁移后3.9// Remove specific representation from a viewport segmentation.removeSegmentationRepresentation( viewportId, { segmentationId: segmentationId, type: csToolsEnums.SegmentationRepresentations.Labelmap } ); // Remove all representations from a viewport segmentation.removeSegmentationRepresentations(viewportId);与添加接口对称单条移除改为「viewportId Specifier」批量移除则直接传viewportId。在 OHIF 中对应的服务方法是removeRepresentationsFromViewport(viewportId, specifier)其行为与 Specifier 语义完全一致——不传 Specifier 移除该 viewport 的全部表示传segmentationId或type则按条件精确移除见 removeRepresentationsFromViewport。另一个真实用例是 removeViewportSegmentationRepresentations.ts它通过segmentation.state.getSegmentationRepresentations(viewportId)读取全部表示再逐个调用segmentation.state.removeSegmentationRepresentation(representation.segmentationRepresentationUID)清理视口完整展示了「查询 移除」的组合const representations segmentation.state.getSegmentationRepresentations(viewportId); if (!representations || !representations.length) { return; } representations.forEach(representation { segmentation.state.removeSegmentationRepresentation( representation.segmentationRepresentationUID ); });查询分割表示Getting Segmentation Representations迁移前3.8// Get representations for a tool group const representations segmentation.getSegmentationRepresentationsForToolGroup(toolGroupId);迁移后3.9// Get all representations for a viewport const representations segmentation.getSegmentationRepresentations(viewportId); // Get specific type of representations const labelmapReps segmentation.getSegmentationRepresentations(viewportId, { type: csToolsEnums.SegmentationRepresentations.Labelmap }); // Get representations for specific segmentation const segmentationReps segmentation.getSegmentationRepresentations(viewportId, { segmentationId: segmentationId }); // Get specific representation const representation segmentation.getSegmentationRepresentation(viewportId, { segmentationId: segmentationId, type: csToolsEnums.SegmentationRepresentations.Labelmap });查询 API 是 Specifier 模式发挥最大威力的地方同样的方法签名通过传入不同的 Specifier 即可实现「全量查询」「按类型过滤」「按分割过滤」「精确匹配」四种粒度。OHIF 源码中 getSegmentationRepresentations 的 JSDoc 明确记载了 Specifier 四种组合的过滤语义不传segmentationId或type返回该viewportId下的全部表示只传segmentationId返回具有该segmentationId的所有表示不限定 viewport只传type返回该viewportId下指定类型的所有表示同时传segmentationId和type返回同时满足两个条件的表示不限定 viewport。OHIF 还在返回前通过_toOHIFSegmentationRepresentation把 cornerstone 原生表示包装为携带viewportId、id、label、styles、segments每个 segment 的颜色/透明度/可见性等字段的 OHIF 表示对象见 SegmentationRepresentation 映射。Specifier 模式结构、语义与应用Specifier 是整个新 API 的基石。它是一个用于「精确指定目标」的普通对象type Specifier { segmentationId?: string; // The ID of the segmentation type?: SegmentationRepresentations; // The type of representation (Labelmap, Contour, etc.) }两个字段均为可选这种可选性正是灵活性的来源。Specifier 模式带来的三大能力精确瞄准Precise Targeting直接访问单个分割或按表示类型过滤灵活查询Flexible Querying按分割 ID 查、按表示类型查或将两者组合以满足特定需求细粒度控制Granular Control可以在不同特异度级别管理表示——viewport 级别、分割级别、单个表示类型级别。Specifier 使用示例// Get all labelmap representations in a viewport const labelmaps segmentation.getSegmentationRepresentations(viewportId, { type: csToolsEnums.SegmentationRepresentations.Labelmap }); // Get all representations of a specific segmentation (including contour, labelmap, surface) const segReps segmentation.getSegmentationRepresentations(viewportId, { segmentationId: seg123 }); // Get a specific representation const specificRep segmentation.getSegmentationRepresentation(viewportId, { segmentationId: seg123, type: csToolsEnums.SegmentationRepresentations.Labelmap });需要说明的是SegmentationRepresentations枚举在 Cornerstone3D 2.0 中至少包含Labelmap、Contour、Surface三种类型。OHIF 的 SegmentationService 顶部正是从csToolsEnums.SegmentationRepresentations解构出这三者并分别用于createLabelmapForDisplaySet、createContourForDisplaySet以及 3D 视口默认表示推断isVolume3DViewportType时取SURFACE见 默认表示类型推断。Specifier 模式并不局限于「添加/移除/查询」三个 API——OHIF 的分割样式与可见性配置也全面采用了类似的描述符结构。例如 hasCustomStyles / getStyle / setStyle 均接收{ viewportId, segmentationId, type, segmentIndex? }形态的 SpecifiersetSegmentColor、setSegmentVisibility也以(viewportId, segmentationId, ...)为前提。这说明「viewport 分割 类型」三元组已成为 OHIF 3.9 分割体系的统一寻址方式。事件系统的变化viewportId取代toolGroupId迁移时不要忽略事件监听代码。在新架构下分割相关事件如表示添加、移除、修改的载荷中携带的是viewportId而非toolGroupId。在 OHIF 源码中事件处理函数 将 cornerstone 事件里的{ segmentationId, viewportId }原样透传广播private _onSegmentationRepresentationModifiedFromSource evt { const { segmentationId, viewportId } evt.detail; this._broadcastEvent(this.EVENTS.SEGMENTATION_REPRESENTATION_MODIFIED, { segmentationId, viewportId, }); }; private _onSegmentationRepresentationRemovedFromSource evt { const { segmentationId, viewportId } evt.detail; this._broadcastEvent(this.EVENTS.SEGMENTATION_REPRESENTATION_REMOVED, { segmentationId, viewportId, }); };任何监听SEGMENTATION_REPRESENTATION_MODIFIED/SEGMENTATION_REPRESENTATION_REMOVED等事件的代码都需要把原来基于toolGroupId的过滤逻辑改为基于viewportId。事件驱动视图更新的一个典型例子是 useViewportSegmentations.ts 这个 React Hook。它订阅分割修改、移除、表示修改、表示移除以及ACTIVE_VIEWPORT_ID_CHANGED、GRID_STATE_CHANGED等事件一旦触发便重新调用segmentationService.getSegmentationRepresentations(viewportId)刷新「当前 viewport 的分割与表示」列表驱动分割面板 UI 更新。值得注意的是该 Hook 专门订阅了SEGMENTATION_REPRESENTATION_REMOVED否则「Remove from Viewport」操作后分割会从影像上消失、却仍残留在面板列表中相关说明见该文件第 201-211 行的注释。这提醒我们事件与查询方法必须配套迁移只改查询不改监听会出现 UI 状态与影像状态不一致的隐性 Bug。另一个体现新事件模型的仓库示例是 createHydrateSegmentationSynchronizer.ts它以SEGMENTATION_REPRESENTATION_MODIFIED为同步触发器将表示从源 viewport 同步到共享 FrameOfReference 或共享 DisplaySet 的目标 viewport内部同样以addSegmentationRepresentation(targetViewportId, { segmentationId, type, config })完成挂载。从中可以看到新架构的一个直接收益——同一个分割可以在多个 viewport 间自动同步渲染这正是「每个 viewport 都能以不同方式渲染同一分割」能力的实现基础。新架构的优势总结直接的视口控制Direct Viewport Control每个 viewport 可拥有自己独立的表示配置无需为不同 viewport 的表示差异化而创建额外 ToolGroup更简单的心智模型Simpler Mental Model表示直接与「它被显示在哪里」绑定中间不再有 ToolGroup 这一层需要管理更灵活的渲染More Flexible Rendering同一分割在不同 viewport 上可以渲染出不同效果对同一数据的多视图展示支持更好更好的类型安全Improved Type SafetySpecifier 模式带来更完善的 TypeScript 支持API 意图更明确、更显式。迁移检查清单Migration Tips迁移到新 API 时建议按以下步骤逐项排查代码库替换 ToolGroup 引用Replace Tool Group References在分割相关代码中全局搜索toolGroupId将其替换为恰当的viewportId更新事件处理器Update Event Handlers检查所有监听分割事件的代码事件载荷现在携带的是viewportId而不是toolGroupId对应的过滤与取值逻辑要同步调整重审表示管理逻辑Review Representation Management定位所有管理分割表示的地方添加、移除、查询统一转换为新的 viewport 中心化方法考虑视口上下文Consider Viewport Context始终站在「该表示将在哪个 viewport 显示」的角度思考问题需要精确操作时使用 Specifier 定位到具体分割与类型。在 OHIF 仓库中定位相关实现如果你希望对照源码深入理解本文内容可以在当前仓库中找到以下关键文件分割服务核心实现extensions/cornerstone/src/services/SegmentationService/SegmentationService.tsaddSegmentationRepresentation、getSegmentationRepresentations、removeRepresentationsFromViewport、getRepresentationsForSegmentation、事件广播全部在此分割服务的单元测试extensions/cornerstone/src/services/SegmentationService/SegmentationService.test.ts分割加载命令的真实调用extensions/cornerstone-dicom-seg/src/commandsModule.ts视口表示清理工具extensions/cornerstone/src/utils/removeViewportSegmentationRepresentations.ts基于事件的分割同步器extensions/cornerstone/src/services/SyncGroupService/createHydrateSegmentationSynchronizer.ts驱动分割面板的 React Hookextensions/cornerstone/src/hooks/useViewportSegmentations.ts本文原始文档platform/docs/docs/migration-guide/3p8-to-3p9/1-segmentation/3-segmentationserice-representation.md从 OHIF 3.8 升级到 3.9 时分割表示管理相关的改动主要集中在上述文件所代表的「服务层 命令层 UI 订阅层」三个层面。只要抓住「以viewportId为准绳、用 Specifier 做精确寻址、让事件与查询配套更新」这三个要点迁移过程就会清晰而可控。【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表