读取与显示工作流)
OHIF cornerstone-dicom-rt 扩展解析DICOM RT 结构集RTSTRUCT读取与显示工作流【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers导读ohif/extension-cornerstone-dicom-rt是 OHIFOpen Health Imaging FoundationViewers 中负责DICOM RT 读取DICOM RT read workflow的扩展包它允许用户加载一个 DICOM RTSSRT Structure Set放射治疗结构集图像并在 OHIF 中显示。本文将围绕该扩展的 README 与源码完整梳理其从 SOP 注册、显示集构建、RT 结构解析到 contour 轮廓渲染与按需水合hydrate的完整链路并给出在 OHIF 中配置、安装与调试该扩展的实战方法。读完本文你将掌握 RTSTRUCT 数据在 OHIF 中“如何被识别、如何被加载、如何被绘制、如何与基础影像联动”的全部技术细节。一、扩展概览README 说了什么扩展的官方 README 内容非常精简核心信息只有三条功能定位DICOM RT 读取工作流DICOM RT read workflow核心能力允许加载一个 DICOM RTSS 图像并在 OHIF 中显示元数据作者为 OHIF许可证为 MIT。package.json中扩展名为ohif/extension-cornerstone-dicom-rt版本 3.14.0-beta.29描述字段与 README 一致并声明了完整的 peerDependenciesohif/core、ohif/extension-cornerstone、ohif/extension-default、ohif/i18n、React 18.3.1 等说明它必须运行在已安装 cornerstone 扩展的 OHIF 环境中。README 虽短但“DICOM RT 读取工作流”这个定位背后承载着完整的实现一个扩展要能在 OHIF 中显示 RTSS需要同时提供SOP Class Handler把 RTSTRUCT 序列转成显示集、Viewport Module专用的 RT 渲染视口和Commands Module水合与跳转命令。下面逐一从源码展开。二、扩展装配index.tsx 中的模块声明扩展的入口 index.tsx 定义了一个标准的 OHIF 扩展对象注册了以下模块const extension: Types.Extensions.Extension { id, getCommandsModule, getViewportModule({ servicesManager, extensionManager, commandsManager }) { const ExtendedOHIFCornerstoneRTViewport props { return ( OHIFCornerstoneRTViewport servicesManager{servicesManager} extensionManager{extensionManager} commandsManager{commandsManager} {...props} / ); }; return [{ name: dicom-rt, component: ExtendedOHIFCornerstoneRTViewport }]; }, getSopClassHandlerModule, };关键实现细节延迟加载视口RT 视口组件通过React.lazy按需加载带webpackPrefetch: true外层用React.Suspense包裹避免未使用 RT 功能时拉取无关代码。视口模块以{ name: dicom-rt, component: ... }形式注册供各 Mode 消费。SOP 处理模块负责将 RTSTRUCT 序列转化为显示集详见下一节。扩展 ID 取自 id.js 中的包名即ohif/extension-cornerstone-dicom-rt其 SOP Class Handler 唯一 ID 为ohif/extension-cornerstone-dicom-rt.sopClassHandlerModule.dicom-rt。从源码结构看该扩展没有独立的面板Panel模块也没有工具栏模块——它专注于“读 RT 并画轮廓”其余交互能力复用 cornerstone 扩展。三、SOP Class HandlerRTSTRUCT 序列如何变成显示集getSopClassHandlerModule.ts 是 RT 读取链路的起点。它注册了const { sopClassDictionary } utils; const sopClassUids [sopClassDictionary.RTStructureSetStorage];即只处理RT Structure Set Storage这一种 SOP Class其余序列不会被本扩展接管。3.1 显示集构建_getDisplaySetsFromSeries当 OHIF 发现 RTSTRUCT 序列后会调用getDisplaySetsFromSeries(instances)。核心逻辑用utils.sortStudyInstances(instances)排序并选择最后一个实例作为“最近创建”的那个RTSS 保存到既有序列时只有实例级时间能反映保存动作用utils.getLatestInstanceDateTime(instance)取SeriesDate / SeriesTime作为显示集日期时间构造一个isDerivedDisplaySet: true、isOverlayDisplaySet: true、Modality: RTSTRUCT的显示集对象并携带instance、instances、wadoRoot/wadoUri/wadoUriRoot等数据源信息建立引用关系从实例的ReferencedSeriesSequence必要时由ReferencedFrameOfReferenceSequence.RTReferencedStudySequence.RTReferencedSeriesSequence推导见_deriveReferencedSeriesSequenceFromFrameOfReferenceSequence取得referencedSeriesInstanceUID与referencedImages再通过displaySetService.getDisplaySetsForReferences(...)找到被引用的基础影像显示集写入referencedDisplaySetInstanceUID异步补引用如果被引用的显示集尚未加载例如 RT 序列先于影像序列到达会订阅displaySetService.EVENTS.DISPLAY_SETS_ADDED等被引用的显示集加入后再回填引用信息最后为显示集挂上load方法参数为{ headers, createSegmentation true }。3.2 延迟加载与去重_load显示集的load最终落到_load()if ( (rtDisplaySet.loading || rtDisplaySet.isLoaded) loadPromises[SOPInstanceUID] _segmentationExists(rtDisplaySet) ) { return loadPromises[SOPInstanceUID]; }它用一个模块级loadPromises缓存按 SOPInstanceUID 去重的加载 Promise避免同一 RT 显示集被多次请求时重复拉取。加载流程为若structureSet尚未解析调用loadRTStruct(extensionManager, rtDisplaySet, headers)解析 RT 结构若createSegmentation为真默认调用segmentationService.createSegmentationForRTDisplaySet(rtDisplaySet)创建对应分割这里实际是 contour 表示见第四节resolve后置loading false出错则reject并在finally中复位 loading。该文件还体现了两个工程细节引用超过一个显示集时输出警告并只应用第一个_segmentationExists通过cornerstonejs/tools的segmentation.state.getSegmentation(displaySetInstanceUID)判断分割是否已存在防止重复创建。四、loadRTStructRT 结构集的解析与轮廓数据装载loadRTStruct.js 是 RT 解析的核心模块内部依赖dcmjsDicomMessage.readFileDicomMetaDictionary.naturalizeDataset。4.1 两种取数路径loadRTStruct根据数据源配置决定如何获取实例const dataSource extensionManager.getActiveDataSource()[0]; const { bulkDataURI } dataSource.getConfig?.() || {}; if (!bulkDataURI || !bulkDataURI.enabled) { instance await getRTStructInstance({ extensionManager, rtStructDisplaySet, headers }); } else { instance await checkAndLoadContourData({ instance, dataSource, ... }); }路径一常规getRTStructInstance通过 cornerstone 扩展注册的utilityModule.common中的dicomLoaderService.findDicomDataPromise(...)拉取整个 RT 实例的二进制数据再用 dcmjs 解析为自然化数据集naturalized dataset设置_meta元信息。路径二bulk data当数据源配置了bulkDataURI.enabled例如本地数据源绕过、或 DICOMweb 服务返回非内联的 bulk 数据时走checkAndLoadContourData。4.2 checkAndLoadContourData 的三种 ContourData 形态RTSS 的ROIContourSequence中每个Contour的ContourData可能有三种存储形态该函数逐一处理内联数组Array.isArray(contourData)直接Promise.resolveBulkDataURI通过dataSource.retrieve.bulkDataURI({ BulkDataURI, StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID })异步拉取拉回的是字节数组Blob随后用TextDecoder解码、按\分隔并parseFloatInlineBinaryatob解码 Base64按\切分并校验“坐标总数能被 3 整除”每 3 个数一组 x/y/z逐组parseFloat过滤非法值。每个 ROI 的轮廓数据用Promise.allSettled并发等待最后统一写回Contour.ContourData。校验失败如实例无ROIContourSequence、ContourData 非法、数据源缺少retrieve.bulkDataURI等会以 Promise reject 的方式明确报错。4.3 structureSet 结构对象解析完成后loadRTStruct会构建一个结构集对象const structureSet { StructureSetLabel: instance.StructureSetLabel, SeriesInstanceUID: instance.SeriesInstanceUID, ROIContours: [], visible: true, ReferencedSOPInstanceUIDsSet: new Set(), };随后遍历ROIContourSequence对每个 Contour按NumberOfContourPoints * 3步长把ContourData拆成{x, y, z}点数组用supportedContourTypesMap标注轮廓几何类型CLOSED_PLANAR、OPEN_NONPLANAR、OPEN_PLANAR、POINT及isSupported标记当前仅POINT在映射表中标为支持其余默认false收集ContourImageSequence.ReferencedSOPInstanceUID到ReferencedSOPInstanceUIDsSet调用_setROIContourMetadata填充每个 ROI 的元数据。4.4 ROI 元数据与颜色_setROIContourMetadata把三张序列表关联起来用ROINumber ReferencedROINumber从StructureSetROISequence找到结构名ROIName、生成算法ROIGenerationAlgorithm、描述ROIDescription_setROIContourDataColor优先取ROIDisplayColor缺失时用dcmjs.data.Colors.dicomlab2RGB把RecommendedDisplayCIELabValueCIELab转成 RGB写入colorArray若存在RTROIObservationsSequence用ReferencedROINumber匹配观察记录把ObservationNumber、ROIObservationDescription、RTROIInterpretedType、ROIInterpreter深拷贝进ROIContourData.RTROIObservations。这些元数据最终交由 segmentation service 转换为 contour 表示渲染。五、RT 视口渲染、水合提示与工具组5.1 视口组件结构OHIFCornerstoneRTViewport.tsx 是扩展注册的dicom-rt视口特点如下单显示集约束displaySets.length 1时直接抛错RT 视口只承载一个 RT 显示集工具组按视口隔离toolGroupId RTToolGroup-${viewportId}由 initRTToolGroup.ts 通过customizationService.getCustomization(cornerstone.overlayViewportTools)取得工具列表再经ToolGroupService.createToolGroupAndAddTools创建卸载时调用segmentationService.removeRepresentationsFromViewport与toolGroupService.destroyToolGroup清理引用显示集缺失兜底当 RT 序列被单独启动如通过SeriesInstanceUID直启而找不到referencedDisplaySetInstanceUID时读取 customizationmissingReferenceDisplaySetHandler并调用它优雅处理而不是直接崩溃加载进度订阅SEGMENTATION_LOADING_COMPLETE与SEGMENT_LOADING_COMPLETE事件把percentComplete、numSegments传给ui.loadingIndicatorTotalPercent定制组件渲染“Loading RTSTRUCT...”进度界面首段定位当rtDisplaySet.firstSegmentedSliceImageId与viewportOptions.presentationIds存在时通过usePositionPresentationStore的setPositionPresentation把视口位置预设到首个分割切片实现加载完成后自动跳到病灶所在层面渲染底层实际仍复用ohif/extension-cornerstone的OHIFCornerstoneViewport传入[referencedDisplaySet, rtDisplaySet]基础影像 RT 叠加从而保证缩放、平移、窗宽窗位等基础交互行为一致。5.2 水合Hydration提示视口挂载后若 RT 尚未加载完rtIsLoading会进入 promptHydrateRT.ts 的流程return utils.promptHydrationDialog({ servicesManager, viewportId, displaySet: rtDisplaySet, preHydrateCallbacks, hydrateCallback: hydrateRTDisplaySet, type: RTSTRUCT, });它会弹出一个类型为RTSTRUCT的水合确认对话框与 SEG 的水合提示一致用户确认后执行commandsManager.runCommand(hydrateSecondaryDisplaySet, { displaySet: rtDisplaySet, viewportId, });这条命令由 cornerstone 扩展注册最终调用本扩展commandsModule中的hydrateRTSDisplaySet见下节。水合的目的与 RT/SEG 场景一致默认只把轮廓作为 overlay 轻量绘制不重建体积分割避免加载大量标注拖慢首屏当用户真正需要测量、编辑或联动时再按需补齐完整数据。六、Commands Module水合命令与状态恢复getCommandsModule.ts 注册了唯一的动作hydrateRTSDisplaySet默认上下文为cornerstone-dicom-rtconst actions { hydrateRTSDisplaySet: ({ displaySet, viewportId }) { if (displaySet.Modality ! RTSTRUCT) { throw new Error(Display set is not an RTSTRUCT); } const referencedDisplaySet displaySetService.getDisplaySetByUID( displaySet.referencedDisplaySetInstanceUID ); commandsManager.runCommand(updateStoredSegmentationPresentation, { displaySet, type: SegmentationRepresentations.Contour, }); commandsManager.runCommand(updateStoredPositionPresentation, { viewportId, displaySetInstanceUIDs: [referencedDisplaySet.displaySetInstanceUID], }); viewportGridService.setDisplaySetsForViewport({ viewportId, displaySetInstanceUIDs: [referencedDisplaySet.displaySetInstanceUID], }); }, };这条命令做三件事校验非 RTSTRUCT 显示集直接抛错状态恢复分别调用updateStoredSegmentationPresentation记录当前 contour 表示的分割展示状态与updateStoredPositionPresentation记录视口当前的缩放/平移位置确保后续把被引用的显示集放回视口时轮廓与视角都能还原到水合前的状态切回基础影像用viewportGridService.setDisplaySetsForViewport把视口中的显示集替换为被引用的基础影像显示集——完成“RT 轮廓完成水合后回到原始影像继续阅片”的闭环。七、如何接入 OHIF安装、注册与数据准备7.1 安装与注册在 OHIF monorepo 中该扩展位于 extensions/cornerstone-dicom-rt作为 workspace 包参与pnpm install。要在自定义 Mode 中使用在应用的扩展列表中引入import cornerstoneRT from ohif/extension-cornerstone-dicom-rt; // ... extensions: [cornerstone, cornerstoneRT, defaultExtension, ...],在 Mode 的routes中把该扩展提供的视口dicom-rt挂到相应 layout可参考 default 扩展的 Mode 装配 中getViewportModule的注册方式。7.2 RT 数据来源与格式要求SOP Class仅支持RT Structure Set Storage其他 SOP Class 不会被本扩展接管引用完整性RTSTRUCT 实例必须携带ReferencedSeriesSequence或可由ReferencedFrameOfReferenceSequence推导出的引用关系且被引用的影像序列需存在于同一 study 中否则显示集无法建立referencedDisplaySetInstanceUID视口会走missingReferenceDisplaySetHandler兜底流程ContourData 形态内联数组、BulkDataURI需数据源配置bulkDataURI.enabled且实现retrieve.bulkDataURI、InlineBinaryBase64、\分隔三种形态均可解析颜色优先ROIDisplayColor否则使用RecommendedDisplayCIELabValueCIELab经 dcmjs 转换轮廓类型解析层对CLOSED_PLANAR、OPEN_NONPLANAR、OPEN_PLANAR、POINT四种几何类型做了标记POINT在映射表中标记为支持。7.3 与分割SEG工作流的共性该扩展的“overlay 显示 按需水合”模式与 cornerstone-dicom-seg 一致RTSTRUCT 与 SEG 都被视为叠加在基础影像上的辅助显示集加载后默认轻量展示用户确认后才水合为可交互的 contour/labelmap 分割。区别在于 RT 的 contour 数据源是 RTSS 的ROIContourSequence包含三维坐标点列而 SEG 则是逐体素的 labelmap。八、总结ohif/extension-cornerstone-dicom-rt虽然 README 只有寥寥数行但源码实现了一条完整且健壮的 RT 读取链路识别getSopClassHandlerModule注册RT Structure Set Storage把 RTSTRUCT 序列转换为带引用关系的派生显示集解析loadRTStruct用 dcmjs 解析结构集兼容内联、bulk data、inline binary 三种 ContourData并合成 ROI 名称、类型、观察记录与颜色渲染OHIFCornerstoneRTViewport将基础影像与 RT 显示集叠加渲染提供加载进度与首段定位工具组按视口隔离水合promptHydrateRT弹出确认对话框用户确认后由hydrateRTSDisplaySet命令恢复分割展示与视口位置再切回基础影像完成闭环。对开发者而言这意味着只要数据源能正确提供携带引用信息的 RTSS 实例OHIF 即可开箱即用地完成放射治疗结构集的浏览若要深度定制如自定义轮廓类型支持、调整水合确认交互、修改加载进度组件可分别通过 customizationcornerstone.overlayViewportTools、ui.loadingIndicatorTotalPercent、missingReferenceDisplaySetHandler与既有命令链扩展实现。【免费下载链接】ViewersOHIF zero-footprint DICOM viewer and oncology specific Lesion Tracker, plus shared extension packages项目地址: https://gitcode.com/GitHub_Trending/vi/Viewers创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考